This patch adds support for emitting diagnostics about JSON input files
to global_dc, showing both the file/line/columns and the JSON Pointer
for the problematic json::value.  Test coverage is added by the followup
on aarch64.

gcc/ChangeLog:
        * Makefile.in (OBJS-libcommon): Add json-diagnostic.o.
        * diagnostics/client-data-hooks.h
        (class client_data_hooks_decorator): New.
        * diagnostics/context.cc (context::set_client_data_hooks): Return
        the old hooks.
        * diagnostics/context.h (context::set_client_data_hooks): Update
        decl likewise.
        * json-diagnostic.cc: New file.
        * json-diagnostic.h: New file.

Signed-off-by: David Malcolm <[email protected]>
---
 gcc/Makefile.in                     |   2 +-
 gcc/diagnostics/client-data-hooks.h |  53 ++++
 gcc/diagnostics/context.cc          |   5 +-
 gcc/diagnostics/context.h           |   4 +-
 gcc/json-diagnostic.cc              | 374 ++++++++++++++++++++++++++++
 gcc/json-diagnostic.h               |  74 ++++++
 6 files changed, 508 insertions(+), 4 deletions(-)
 create mode 100644 gcc/json-diagnostic.cc
 create mode 100644 gcc/json-diagnostic.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 54865765b6ef6..d4170dd7a3262 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1905,7 +1905,7 @@ OBJS-libcommon = \
        gcc-diagnostic-spec.o \
        graphviz.o pex.o \
        pretty-print.o intl.o \
-       json.o json-parsing.o \
+       json.o json-parsing.o json-diagnostic.o \
        pub-sub.o \
        xml.o \
        sbitmap.o \
diff --git a/gcc/diagnostics/client-data-hooks.h 
b/gcc/diagnostics/client-data-hooks.h
index 6fa31d0d65e00..bd760dcb70bba 100644
--- a/gcc/diagnostics/client-data-hooks.h
+++ b/gcc/diagnostics/client-data-hooks.h
@@ -63,6 +63,59 @@ class client_data_hooks
   add_sarif_invocation_properties (sarif_object &invocation_obj) const = 0;
 };
 
+/* Implementation of client_data_hooks that delegates vfuncs to an
+   optional inner object.  */
+
+class client_data_hooks_decorator : public client_data_hooks
+{
+ public:
+  client_data_hooks_decorator (const client_data_hooks *inner)
+  : m_inner (inner)
+  {
+  }
+
+  const client_version_info *get_any_version_info () const override
+  {
+    if (m_inner)
+      return m_inner->get_any_version_info ();
+    return nullptr;
+  }
+
+  const logical_locations::manager *
+  get_logical_location_manager () const override
+  {
+    if (m_inner)
+      return m_inner->get_logical_location_manager ();
+    return nullptr;
+  }
+
+  logical_locations::key
+  get_current_logical_location () const override
+  {
+    if (m_inner)
+      return m_inner->get_current_logical_location ();
+    return logical_locations::key ();
+  }
+
+  const char *
+  maybe_get_sarif_source_language (const char *filename) const override
+  {
+    if (m_inner)
+      return m_inner->maybe_get_sarif_source_language (filename);
+    return nullptr;
+  }
+
+  void
+  add_sarif_invocation_properties (sarif_object &invocation_obj) const override
+  {
+    if (m_inner)
+      m_inner->add_sarif_invocation_properties (invocation_obj);
+  }
+
+private:
+  const client_data_hooks *m_inner;
+};
+
 class client_plugin_info;
 
 /* Abstract base class for a diagnostics::context to get at
diff --git a/gcc/diagnostics/context.cc b/gcc/diagnostics/context.cc
index 03d570665843d..98af6ecb1b665 100644
--- a/gcc/diagnostics/context.cc
+++ b/gcc/diagnostics/context.cc
@@ -529,12 +529,13 @@ context::set_main_input_filename (const char *filename)
     sink_->set_main_input_filename (filename);
 }
 
-void
+std::unique_ptr<client_data_hooks>
 context::set_client_data_hooks (std::unique_ptr<client_data_hooks> hooks)
 {
-  delete m_client_data_hooks;
+  std::unique_ptr<client_data_hooks> old_hooks (m_client_data_hooks);
   /* Ideally the field would be a std::unique_ptr here.  */
   m_client_data_hooks = hooks.release ();
+  return old_hooks;
 }
 
 void
diff --git a/gcc/diagnostics/context.h b/gcc/diagnostics/context.h
index 742319fbd204f..514ff1e68f9b8 100644
--- a/gcc/diagnostics/context.h
+++ b/gcc/diagnostics/context.h
@@ -388,7 +388,9 @@ public:
   /* Various setters for use by option-handling logic.  */
   void set_sink (std::unique_ptr<sink> sink_);
   void set_text_art_charset (enum diagnostic_text_art_charset charset);
-  void set_client_data_hooks (std::unique_ptr<client_data_hooks> hooks);
+
+  std::unique_ptr<client_data_hooks>
+  set_client_data_hooks (std::unique_ptr<client_data_hooks> hooks);
 
   void push_owned_urlifier (std::unique_ptr<urlifier>);
   void push_borrowed_urlifier (const urlifier &);
diff --git a/gcc/json-diagnostic.cc b/gcc/json-diagnostic.cc
new file mode 100644
index 0000000000000..bf61cd0007ecf
--- /dev/null
+++ b/gcc/json-diagnostic.cc
@@ -0,0 +1,374 @@
+/* Diagnostics relating to JSON values.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+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/>.  */
+
+#define INCLUDE_MAP
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "json-diagnostic.h"
+#include "diagnostics/dumping.h"
+#include "diagnostics/logging.h"
+#include "diagnostics/logical-locations.h"
+#include "diagnostics/client-data-hooks.h"
+#include "diagnostics/text-sink.h"
+#include "diagnostics/physical-location-maker.h"
+#include "pretty-print-markup.h"
+
+static bool
+emit_json_diagnostic (gcc_json_context &ctxt,
+                     enum diagnostics::kind kind,
+                     const json::value &js_val,
+                     diagnostics::option_id option_id,
+                     const char *gmsgid, va_list *ap)
+  ATTRIBUTE_GCC_DIAG(5,0);
+
+using log_function_params = diagnostics::logging::log_function_params;
+using auto_inc_log_depth = diagnostics::logging::auto_inc_depth;
+
+class json_logical_location_manager
+  : public diagnostics::logical_locations::manager
+{
+public:
+  using key = diagnostics::logical_locations::key;
+  using kind = diagnostics::logical_locations::kind;
+
+  void
+  dump (FILE *outfile, int indent) const final override
+  {
+    diagnostics::dumping::emit_heading (outfile, indent,
+                                       "json_logical_location_manager");
+  }
+
+  label_text
+  get_short_name (key k) const final override
+  {
+    auto *js_val = js_from_key (k);
+    const json::pointer::token &pointer_token = js_val->get_pointer_token ();
+    pretty_printer pp;
+    pointer_token.print (&pp);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  label_text
+  get_name_with_scope (key k) const final override
+  {
+    auto *js_val = js_from_key (k);
+    pretty_printer pp;
+    js_val->print_pointer (&pp);
+    return label_text::take (xstrdup (pp_formatted_text (&pp)));
+  }
+
+  label_text
+  get_internal_name (key) const final override
+  {
+    return label_text ();
+  }
+
+  kind
+  get_kind (key k) const final override
+  {
+    auto *js_val = js_from_key (k);
+
+    switch (js_val->get_kind ())
+      {
+      default:
+       gcc_unreachable ();
+
+      case json::JSON_OBJECT:
+       return kind::object;
+
+      case json::JSON_ARRAY:
+       return kind::array;
+
+      case json::JSON_INTEGER:
+      case json::JSON_FLOAT:
+      case json::JSON_STRING:
+      case json::JSON_TRUE:
+      case json::JSON_FALSE:
+      case json::JSON_NULL:
+       return kind::property;
+      }
+  }
+
+  label_text
+  get_name_for_path_output (key k) const final override
+  {
+    return get_name_with_scope (k);
+  }
+
+  key
+  get_parent (key k) const final override
+  {
+    auto *js_val = js_from_key (k);
+    auto &pointer_token = js_val->get_pointer_token ();
+    return key_from_js (pointer_token.m_parent);
+  }
+
+  static const json::value *
+  js_from_key (key k)
+  {
+    return k.cast_to<const json::value *> ();
+  }
+
+  static key
+  key_from_js (const json::value *js_val)
+  {
+    return key::from_ptr (js_val);
+  }
+
+private:
+  static void
+    print_json_pointer_token (pretty_printer *);
+};
+
+/* Implementation of diagnostics::client_data_hooks
+   for reporting a diagnostic at a particular json::value
+   in a JSON input file.
+
+   It wraps another hooks instance, but uses a
+   json_logical_location_manager, has a specific
+   json::value for the current logical location,
+   and treats the SARIF source lang as "json".  */
+
+class json_client_data_hooks : public diagnostics::client_data_hooks_decorator
+{
+public:
+  json_client_data_hooks (const json::value &js_val,
+                         const client_data_hooks *inner)
+  : diagnostics::client_data_hooks_decorator (inner),
+    m_js_val (js_val)
+  {}
+
+  const diagnostics::logical_locations::manager *
+  get_logical_location_manager () const override
+  {
+    return &m_logical_loc_mgr;
+  }
+
+  diagnostics::logical_locations::key
+  get_current_logical_location () const override
+  {
+    /* Use the json value's pointer as the key.  */
+    return json_logical_location_manager::key_from_js (&m_js_val);
+  }
+
+  const char *
+  maybe_get_sarif_source_language (const char *) const override
+  {
+    return "json";
+  }
+
+private:
+  json_logical_location_manager m_logical_loc_mgr;
+  const json::value &m_js_val;
+};
+
+namespace pp_markup {
+
+/* Print the JSON Pointer of a given json::value in quotes.  */
+
+class quoted_json_pointer : public pp_element
+{
+public:
+  quoted_json_pointer (const json::value &js_val)
+  : m_js_val (js_val)
+  {
+  }
+
+  void
+  add_to_phase_2 (context &ctxt) final override
+  {
+    ctxt.begin_quote ();
+    m_js_val.print_pointer (&ctxt.m_pp);
+    ctxt.end_quote ();
+  }
+
+private:
+  const json::value &m_js_val;
+};
+
+} // namespace pp_markup
+
+/* text_sink starter for diagnostics relating to JSON.  */
+
+static void
+json_text_starter (diagnostics::text_sink &sink,
+                  const diagnostics::diagnostic_info *diagnostic)
+{
+  pretty_printer *pp = sink.get_printer ();
+
+  /* If this isn't the root value, report its json pointer.  */
+  diagnostics::logical_locations::key k;
+  if (auto data_hooks = sink.get_context ().get_client_data_hooks ())
+    k = data_hooks->get_current_logical_location ();
+  const json::value *js_val = json_logical_location_manager::js_from_key (k);
+  if (js_val && js_val->get_pointer_token ().m_parent)
+    {
+      const char *file = LOCATION_FILE (diagnostic_location (diagnostic));
+      char *new_prefix = file ?  sink.file_name_as_prefix (file) : nullptr;
+      pp_set_prefix (pp, new_prefix);
+
+      pp_markup::quoted_json_pointer e (*js_val);
+      switch (js_val->get_kind ())
+       {
+       default:
+         pp_printf (pp, _("In JSON value %e"), &e);
+         break;
+       case json::JSON_OBJECT:
+         pp_printf (pp, _("In JSON object %e"), &e);
+         break;
+       case json::JSON_ARRAY:
+         pp_printf (pp, _("In JSON array %e"), &e);
+         break;
+       }
+      pp_newline (pp);
+    }
+
+  pp_set_prefix (pp, sink.build_prefix (*diagnostic));
+}
+
+
+/* class gcc_json_context : public json::simple_location_map.  */
+
+location_t
+gcc_json_context::make_location_for_point (const json::location_map::point &p)
+{
+  diagnostics::physical_location_maker m (line_table);
+  return m.new_location_from_file_line_column (m_filename,
+                                              p.m_line,
+                                              p.m_column + 1);
+}
+
+location_t
+gcc_json_context::make_location_for_range (const json::location_map::range &r)
+{
+  location_t start_loc = make_location_for_point (r.m_start);
+  location_t end_loc = make_location_for_point (r.m_end);
+  return make_location (start_loc, start_loc, end_loc);
+}
+
+/* Emit a diagnostic on global_dc of the relevant KIND relating to JS_VAL,
+   using CTXT to get at file/line/column info.
+
+   Temporarily override global_dc's logical location to refer to JS_VAL
+   and the text_sink starter and finalizer to be suitable for handling
+   reporting within a JSON file.  */
+
+static bool
+emit_json_diagnostic (gcc_json_context &ctxt,
+                     enum diagnostics::kind kind,
+                     const json::value &js_val,
+                     diagnostics::option_id option_id,
+                     const char *gmsgid, va_list *ap)
+{
+  auto logger = global_dc->get_logger ();
+  log_function_params (logger, __func__)
+    .log_param_option_id ("option_id", option_id)
+    .log_param_string ("gmsgid", gmsgid);
+  auto_inc_log_depth depth_sentinel (logger);
+
+  auto_diagnostic_group d;
+
+  // Get physical location for JS_VAL.
+  location_t phys_loc
+    = ctxt.make_location_for_range (ctxt.get_range_for_value (js_val));
+  rich_location richloc (line_table, phys_loc);
+
+  // Set logical location by overriding client data hooks
+  auto tmp_client_data_hooks
+    = std::make_unique<json_client_data_hooks>
+       (js_val, global_dc->get_client_data_hooks ());
+  auto old_client_data_hooks
+    = global_dc->set_client_data_hooks (std::move (tmp_client_data_hooks));
+
+  // Override text hooks
+  auto old_text_starter = text_starter (global_dc);
+  auto old_text_finalizer = text_finalizer (global_dc);
+  text_starter (global_dc) = json_text_starter;
+  text_finalizer (global_dc) = diagnostics::default_text_finalizer;
+
+  bool ret = global_dc->diagnostic_impl (&richloc, nullptr, option_id,
+                                        gmsgid, ap, kind);
+
+  // Restore old text and client data hooks:
+  text_starter (global_dc) = old_text_starter;
+  text_finalizer (global_dc) = old_text_finalizer;
+  global_dc->set_client_data_hooks (std::move (old_client_data_hooks));
+
+  if (logger)
+    logger->log_bool_return ("emit_diagnostic", ret);
+
+  return ret;
+}
+
+
+/* Emit an error on gcc's global_dc relating to JS_VAL.  */
+
+void
+json_error (gcc_json_context &ctxt,
+           const json::value &js_val,
+           const char *gmsgid, ...)
+{
+  va_list ap;
+  va_start (ap, gmsgid);
+  emit_json_diagnostic (ctxt,
+                       diagnostics::kind::error,
+                       js_val, -1,
+                       gmsgid, &ap);
+  va_end (ap);
+}
+
+/* Emit a warning on gcc's global_dc relating to JS_VAL.  */
+
+bool
+json_warning (gcc_json_context &ctxt,
+             const json::value &js_val,
+             diagnostics::option_id option_id,
+             const char *gmsgid, ...)
+{
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool ret = emit_json_diagnostic (ctxt,
+                                  diagnostics::kind::warning,
+                                  js_val, option_id,
+                                  gmsgid, &ap);
+  va_end (ap);
+  return ret;
+}
+
+/* Emit a note on gcc's global_dc relating to JS_VAL.  */
+
+void
+json_note (gcc_json_context &ctxt,
+          const json::value &js_val,
+          const char *gmsgid, ...)
+{
+  va_list ap;
+  va_start (ap, gmsgid);
+  emit_json_diagnostic (ctxt,
+                       diagnostics::kind::note,
+                       js_val, -1,
+                       gmsgid, &ap);
+  va_end (ap);
+}
diff --git a/gcc/json-diagnostic.h b/gcc/json-diagnostic.h
new file mode 100644
index 0000000000000..1773ee0a0fd50
--- /dev/null
+++ b/gcc/json-diagnostic.h
@@ -0,0 +1,74 @@
+/* Diagnostics relating to JSON values.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_JSON_DIAGNOSTIC_H
+#define GCC_JSON_DIAGNOSTIC_H
+
+#include "json-parsing.h"
+
+/* Implementation of json::location_map for use with
+   GCC diagnostics.
+   Stores location information for json::value * from parsing, and
+   can generate location_t values for the.  */
+
+class gcc_json_context : public json::simple_location_map
+{
+public:
+  gcc_json_context (const char *filename)
+  : m_filename (filename)
+  {
+  }
+
+  location_t
+  make_location_for_point (const json::location_map::point &);
+
+  location_t
+  make_location_for_range (const json::location_map::range &);
+
+private:
+  const char *m_filename;
+};
+
+/* Emit an error on gcc's global_dc relating to JS_VAL.  */
+
+extern void
+json_error (gcc_json_context &ctxt,
+           const json::value &js_val,
+           const char *gmsgid, ...)
+  ATTRIBUTE_GCC_DIAG(3,4);
+
+/* Emit a warning on gcc's global_dc relating to JS_VAL.  */
+
+extern bool
+json_warning (gcc_json_context &ctxt,
+             const json::value &js_val,
+             diagnostics::option_id option_id,
+             const char *gmsgid, ...)
+  ATTRIBUTE_GCC_DIAG(4,5);
+
+/* Emit a note on gcc's global_dc relating to JS_VAL.  */
+
+extern void
+json_note (gcc_json_context &ctxt,
+          const json::value &js_val,
+          const char *gmsgid, ...)
+  ATTRIBUTE_GCC_DIAG(3,4);
+
+#endif  /* GCC_JSON_DIAGNOSTIC_H  */
-- 
2.26.3

Reply via email to