From dabee91a708b4307cc4aee24e85d526fb08322bd Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 12 Dec 2025 16:30:21 -0500
Subject: [PATCH 55/98] FIXME: WIP on capturing analyzer graphs in SARIF/HTML
 output

---
 gcc/Makefile.in                       |   1 +
 gcc/analyzer/analyzer-graphs.cc       | 359 ++++++++++++++++++++++++++
 gcc/analyzer/common.h                 |  11 +
 gcc/analyzer/engine.cc                |  13 +
 gcc/doc/invoke.texi                   |  10 +-
 gcc/opts-diagnostic.cc                |   9 +-
 gcc/opts-diagnostic.h                 |   3 +
 gcc/tree-diagnostic-sink-extensions.h |   3 +
 8 files changed, 407 insertions(+), 2 deletions(-)
 create mode 100644 gcc/analyzer/analyzer-graphs.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 92c71cbdba26..270a2d304758 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1337,6 +1337,7 @@ ANALYZER_OBJS = \
 	analyzer/ana-state-to-diagnostic-state.o \
 	analyzer/analysis-plan.o \
 	analyzer/analyzer.o \
+	analyzer/analyzer-graphs.o \
 	analyzer/analyzer-language.o \
 	analyzer/analyzer-logging.o \
 	analyzer/analyzer-pass.o \
diff --git a/gcc/analyzer/analyzer-graphs.cc b/gcc/analyzer/analyzer-graphs.cc
new file mode 100644
index 000000000000..4d2534fa654d
--- /dev/null
+++ b/gcc/analyzer/analyzer-graphs.cc
@@ -0,0 +1,359 @@
+/* Implementation of "analyzer-graphs" diagnostic sink option.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "analyzer/common.h"
+
+#include "diagnostics/digraphs.h"
+#include "diagnostics/dumping.h"
+#include "diagnostic.h"
+#include "tree-diagnostic-sink-extensions.h"
+#include "tree-logical-location.h"
+#include "context.h"
+#include "channels.h"
+
+#include "analyzer/supergraph.h"
+
+#if 0
+namespace {
+  namespace graph_properties = custom_sarif_properties::cfg::graph;
+  namespace node_properties = custom_sarif_properties::cfg::node;
+  namespace edge_properties = custom_sarif_properties::cfg::edge;
+}
+#endif
+
+/* Disable warnings about quoting issues in the pp_xxx calls below
+   that (intentionally) don't follow GCC diagnostic conventions.  */
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+namespace diagnostics_digraphs {
+
+class digraph_for_supergraph;
+
+class node_for_function : public diagnostics::digraphs::node
+{
+public:
+  node_for_function (digraph_for_supergraph &g,
+		     const function &n);
+};
+
+class node_for_loop : public diagnostics::digraphs::node
+{
+};
+
+class node_for_basic_block : public diagnostics::digraphs::node
+{
+public:
+  node_for_basic_block (digraph_for_supergraph &g,
+			const function &fun,
+			basic_block bb);
+};
+
+class node_for_supernode : public diagnostics::digraphs::node
+{
+public:
+  node_for_supernode (digraph_for_supergraph &g,
+		      const supernode &n);
+};
+
+class edge_for_superedge : public diagnostics::digraphs::edge
+{
+public:
+  edge_for_superedge (digraph_for_supergraph &g,
+		      const superedge &e);
+};
+
+class digraph_for_supergraph : public diagnostics::digraphs::digraph
+{
+public:
+  digraph_for_supergraph (const supergraph &sg)
+    : m_sg (sg)
+  {
+    for (auto n : m_sg.m_nodes)
+      add_node_for_supernode (*n);
+    for (auto e : m_sg.m_edges)
+      add_edge_for_superedge (*e);
+  }
+
+  void
+  add_node_for_supernode (const supernode &n)
+  {
+    // FIXME:
+    /* Subdivide:
+       - by function
+       - by loop
+       - by basic block.  */
+    auto node_for_snode = std::make_unique<node_for_supernode> (*this, n);
+    m_node_for_snode[&n] = node_for_snode.get ();
+
+    diagnostics::digraphs::node *parent = nullptr;
+    gcc_assert (n.m_fun);
+    if (n.m_bb)
+      parent = &get_or_create_node_for_basic_block (*n.m_fun, n.m_bb);
+    else
+      parent = &get_or_create_node_for_function (*n.m_fun);
+    parent->add_child (std::move (node_for_snode));
+  }
+
+  node_for_function &
+  get_or_create_node_for_function (function &fun)
+  {
+    auto iter = m_node_for_function.find (&fun);
+    if (iter != m_node_for_function.end ())
+      return *iter->second;
+    auto node_for_fun = std::make_unique<node_for_function> (*this, fun);
+    m_node_for_function[&fun] = node_for_fun.get ();
+    auto &result = *node_for_fun;
+    add_node (std::move (node_for_fun));
+    return result;
+  }
+
+  node_for_basic_block &
+  get_or_create_node_for_basic_block (function &fun, basic_block bb)
+  {
+    auto iter = m_node_for_basic_block.find (bb);
+    if (iter != m_node_for_basic_block.end ())
+      return *iter->second;
+
+    auto node_for_bb = std::make_unique<node_for_basic_block> (*this, fun, bb);
+    m_node_for_basic_block[bb] = node_for_bb.get ();
+
+    auto &result = *node_for_bb;
+
+    auto &node_for_fun = get_or_create_node_for_function (fun);
+    node_for_fun.add_child (std::move (node_for_bb));
+
+    return result;
+  }
+
+  void
+  add_edge_for_superedge (const superedge &e)
+  {
+    auto edge_for_sedge = std::make_unique<edge_for_superedge> (*this, e);
+    add_edge (std::move (edge_for_sedge));
+  }
+
+  std::string
+  get_node_id_for_snode (const supernode &n) const
+  {
+    return std::string ("sn_") + std::to_string (n.m_id);
+  }
+
+  node_for_supernode &
+  get_node_for_snode (const supernode &n) const
+  {
+    auto iter = m_node_for_snode.find (&n);
+    gcc_assert (iter != m_node_for_snode.end ());
+    return *iter->second;
+  }
+
+private:
+  const supergraph &m_sg;
+  std::map<const function *, node_for_function *> m_node_for_function;
+  std::map<basic_block, node_for_basic_block *> m_node_for_basic_block;
+  std::map<const supernode *, node_for_supernode *> m_node_for_snode;
+};
+
+// class node_for_function : public diagnostics::digraphs::node
+
+node_for_function::node_for_function (digraph_for_supergraph &g,
+				      const function &fun)
+: diagnostics::digraphs::node (g,
+			       (std::string ("fun_")
+				+ std::to_string (fun.funcdef_no)))
+{
+  set_logical_loc (tree_logical_location_manager::key_from_tree (fun.decl));
+#if 0
+  set_property (node_properties::kind, "function");
+#endif
+
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_printf (&pp, "%qD", fun.decl);
+  set_label (pp_formatted_text (&pp));
+}
+
+// class node_for_basic_block : public diagnostics::digraphs::node
+
+node_for_basic_block::node_for_basic_block (digraph_for_supergraph &g,
+					    const function &fun,
+					    basic_block bb)
+: diagnostics::digraphs::node (g,
+			       (std::string ("fun_")
+				+ std::to_string (fun.funcdef_no)
+				+ std::string ("_bb_")
+				+ std::to_string (bb->index)))
+{
+  pretty_printer pp;
+  pp_printf (&pp, "bb %i", bb->index);
+  set_label (pp_formatted_text (&pp));
+}
+
+// class node_for_supernode : public diagnostics::digraphs::node
+
+node_for_supernode::node_for_supernode (digraph_for_supergraph &g,
+					const supernode &n)
+: diagnostics::digraphs::node (g, g.get_node_id_for_snode (n))
+{
+  set_physical_loc (n.m_loc);
+
+  pretty_printer pp;
+  pp_printf (&pp, "SN %i", n.m_id);
+  set_label (pp_formatted_text (&pp));
+}
+
+// class edge_for_superedge : public diagnostics::digraphs::edge
+
+edge_for_superedge::edge_for_superedge (digraph_for_supergraph &g,
+					const superedge &e)
+: diagnostics::digraphs::edge (g, nullptr,
+			       g.get_node_for_snode (*e.m_src),
+			       g.get_node_for_snode (*e.m_dest))
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  e.dump_label_to_pp (&pp, false);
+  set_label (pp_formatted_text (&pp));
+}
+
+} // namespace diagnostics_digraphs
+
+class lazy_digraph_for_supergraph
+  : public lazily_created<diagnostics::digraphs::digraph>
+{
+public:
+  lazy_digraph_for_supergraph (const supergraph &sg,
+			       const char *title)
+  : m_sg (sg),
+    m_title (title)
+  {}
+
+  std::unique_ptr<diagnostics::digraphs::digraph>
+  create_object () const final override
+  {
+    auto g
+      = std::make_unique<diagnostics_digraphs::digraph_for_supergraph> (m_sg);
+    g->set_graph_kind ("ana-supergraph");
+    g->set_description (m_title);
+    return g;
+  }
+
+private:
+  const supergraph &m_sg;
+  const char *m_title;
+};
+
+class lazy_digraph_for_exploded_graph
+  : public lazily_created<diagnostics::digraphs::digraph>
+{
+public:
+  lazy_digraph_for_exploded_graph (const exploded_graph &eg,
+				   const char *title)
+  : m_eg (eg),
+    m_title (title)
+  {}
+
+  std::unique_ptr<diagnostics::digraphs::digraph>
+  create_object () const final override
+  {
+    auto g = std::make_unique<diagnostics::digraphs::digraph> ();
+    g->set_graph_kind ("ana-exploded-graph");
+    g->set_description (m_title);
+    return g;
+  }
+
+private:
+  const exploded_graph &m_eg;
+  const char *m_title;
+};
+
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic pop
+#endif
+
+namespace analyzer_events = gcc::topics::analyzer_events;
+
+/* A diagnostics::sink::extension which subscribes to analyzer_events
+   and responds to FIXME events by adding a diagnostics digraph
+   for FIXME.  */
+
+class compiler_capture_analyzer_graphs : public diagnostics::sink::extension
+{
+public:
+  compiler_capture_analyzer_graphs (diagnostics::sink &sink)
+  : extension (sink),
+    m_event_subscriber (sink)
+  {
+    g->get_channels ().analyzer_events_channel.add_subscriber
+      (m_event_subscriber);
+  }
+
+  void
+  dump (FILE *out, int indent) const
+  {
+    diagnostics::dumping::emit_heading (out, indent,
+					"compiler_capture_analyzer_graphs");
+  }
+
+private:
+  class event_subscriber : public analyzer_events::subscriber
+  {
+  public:
+    event_subscriber (diagnostics::sink &sink) : m_sink (sink) {}
+
+    void
+    on_message (const analyzer_events::on_graph<supergraph> &msg)
+      final override
+    {
+      m_sink.report_global_digraph
+	(lazy_digraph_for_supergraph (msg.m_graph,
+				      msg.m_title));
+    }
+
+    void
+    on_message (const analyzer_events::on_graph<exploded_graph> &msg)
+      final override
+    {
+      m_sink.report_global_digraph
+	(lazy_digraph_for_exploded_graph (msg.m_graph,
+					  msg.m_title));
+    }
+
+  private:
+    diagnostics::sink &m_sink;
+  } m_event_subscriber;
+};
+
+} // namespace ana
+
+#endif /* #if !ENABLE_ANALYZER */
+
+std::unique_ptr<diagnostics::sink::extension>
+compiler_extension_factory::make_analyzer_graphs_extension (diagnostics::sink &sink) const
+{
+#if ENABLE_ANALYZER
+  return std::make_unique<ana::compiler_capture_analyzer_graphs> (sink);
+#endif /* #if !ENABLE_ANALYZER */
+}
diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h
index 29bd0633f139..97b31e31a8bc 100644
--- a/gcc/analyzer/common.h
+++ b/gcc/analyzer/common.h
@@ -695,6 +695,15 @@ struct on_frame_popped
   ana::region_model_context *m_ctxt;
 };
 
+// FIXME
+
+template <typename AnaGraph>
+struct on_graph
+{
+  const AnaGraph &m_graph;
+  const char *m_title;
+};
+
 struct subscriber {
 
   virtual ~subscriber () = default;
@@ -702,6 +711,8 @@ struct subscriber {
   virtual void on_message (const on_tu_finished &) {}
   virtual void on_message (const on_ana_init &) {}
   virtual void on_message (const on_frame_popped &) {}
+  virtual void on_message (const on_graph<ana::supergraph> &) {}
+  virtual void on_message (const on_graph<ana::exploded_graph> &) {}
 };
 
 } // namespace gcc::topics::analyzer_events
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index cbfc95676ed3..9c47c8d62e1b 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -4762,6 +4762,13 @@ maybe_dump_supergraph (const supergraph &sg, const char *name,
 		       const exploded_graph *eg = nullptr)
 {
   static int dump_idx = 0;
+
+  if (auto chan = g->get_channels ().analyzer_events_channel.get_if_active ())
+    {
+      gcc::topics::analyzer_events::on_graph<supergraph> msg {sg, name};
+      chan->publish (msg);
+    }
+
   if (!flag_dump_analyzer_supergraph)
     return;
 
@@ -4897,6 +4904,12 @@ impl_run_checkers (logger *logger)
       free (filename);
     }
 
+  if (auto chan = g->get_channels ().analyzer_events_channel.get_if_active ())
+    {
+      gcc::topics::analyzer_events::on_graph<exploded_graph> msg {eg, "eg"};
+      chan->publish (msg);
+    }
+
   /* Now emit any saved diagnostics.  */
   eg.get_diagnostic_manager ().emit_saved_diagnostics (eg);
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 13e42cf140e7..42040d462368 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6491,11 +6491,19 @@ also show a SARIF representation of the state.
 
 @end table
 
-As well as scheme-specific keys, the following GCC-related key is usable
+As well as scheme-specific keys, the following GCC-related keys are usable
 on sinks of any scheme:
 
 @table @gcctabopt
 
+@item analyzer-graphs=@r{[}yes@r{|}no@r{]}
+If @code{analyzer-graphs=yes} for a sink, then GCC will attempt to send
+information to that sink about the directed graphs used by @code{-fanalyzer}
+for internal state.
+Text sinks ignore the information.  SARIF sinks will add the graphs within
+@code{theRun.graphs}.  HTML sinks will generate SVG displaying the graphs.
+The precise form of the information is subject to change without notice.
+
 @item cfgs=@r{[}yes@r{|}no@r{]}
 If @code{cfgs=yes} for a sink, then GCC will attempt to send information
 to that sink about the control flow graphs for the functions it is compiling.
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
index 594f735a8c67..8d4bc1438b2d 100644
--- a/gcc/opts-diagnostic.cc
+++ b/gcc/opts-diagnostic.cc
@@ -49,7 +49,8 @@ class gcc_extra_keys : public diagnostics::output_spec::key_handler
 {
 public:
   gcc_extra_keys ()
-  : m_cfgs (false)
+  : m_analyzer_graphs (false),
+    m_cfgs (false)
   {
   }
   enum result
@@ -57,6 +58,8 @@ public:
 		   const std::string &key,
 		   const std::string &value) final override
   {
+    if (key == "analyzer-graphs")
+      return parse_bool_value (ctxt, key, value, m_analyzer_graphs);
     if (key == "cfgs")
       return parse_bool_value (ctxt, key, value, m_cfgs);
     return result::unrecognized;
@@ -65,9 +68,11 @@ public:
   void
   get_keys (auto_vec<const char *> &out_known_keys) const final override
   {
+    out_known_keys.safe_push ("analyzer-graphs");
     out_known_keys.safe_push ("cfgs");
   }
 
+  bool m_analyzer_graphs;
   bool m_cfgs;
 };
 
@@ -105,6 +110,8 @@ handle_gcc_specific_keys (const gcc_extra_keys &gcc_keys,
 			  diagnostics::sink &sink,
 			  const gcc_extension_factory &ext_factory)
 {
+  if (gcc_keys.m_analyzer_graphs)
+    sink.add_extension (ext_factory.make_analyzer_graphs_extension (sink));
   if (gcc_keys.m_cfgs)
     sink.add_extension (ext_factory.make_cfg_extension (sink));
 }
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 6c2b25d63428..6b24ee3f9469 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -70,6 +70,9 @@ class gcc_extension_factory
 public:
   virtual ~gcc_extension_factory () {}
 
+  virtual std::unique_ptr<diagnostics::sink::extension>
+  make_analyzer_graphs_extension (diagnostics::sink &sink) const = 0;
+
   virtual std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const = 0;
 
diff --git a/gcc/tree-diagnostic-sink-extensions.h b/gcc/tree-diagnostic-sink-extensions.h
index b921eaa6c7f5..8653fc7e78a2 100644
--- a/gcc/tree-diagnostic-sink-extensions.h
+++ b/gcc/tree-diagnostic-sink-extensions.h
@@ -25,6 +25,9 @@ along with GCC; see the file COPYING3.  If not see
 class compiler_extension_factory : public gcc_extension_factory
 {
 public:
+  std::unique_ptr<diagnostics::sink::extension>
+  make_analyzer_graphs_extension (diagnostics::sink &sink) const final override;
+
   std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const final override;
 };
-- 
2.49.0

