Hello David,

On Fri, Jul 11 2025, David Malcolm wrote:
> In r16-1631-g2334d30cd8feac I added support for capturing state
> information from -fanalyzer in XML form, and adding a way to visualize
> these states in HTML output.  The data was optionally captured in SARIF
> output (with "xml-state=yes"), stashing the XML in string form in
> a property bag.
>
> This worked, but there was no way to round-trip the stored data back
> from SARIF without adding an XML parser to GCC, which I don't want to
> do.
>
> SARIF supports capturing directed graphs, so this patch:
>
> (a) adds a new namespace diagnostics::digraphs, with classes digraph,
> node, and edge, representing directed graphs in a form similar to
> what SARIF can serialize
>
> (b) adds support to GCC's diagnostic subsystem for reporting graphs,
> either "globally" or as part of a diagnostic.  An example in a testsuite
> plugin emits an error that has a couple of dummy graphs associated with
> it, and captures the optimization passes as a digraph "globally".
> Graphs are ignored by text sinks, but are captured by sarif sinks,
> and the "experimental-html" sink gains SVG-based rendering of any graphs
> using dot.  This HTML output is rather crude; an example can be seen
> here:
>   
> https://dmalcolm.fedorapeople.org/gcc/2025-07-10/diagnostic-test-graphs-html.c.html
>
> (c) adds support to libgdiagnostics for the above
>
> (d) adds support to sarif-replay for the above (round-tripping any
> graph information)
>
> (e) replaces the XML representation of state with a representation
> based on the above directed graphs, using property bags to stash
> additional information (e.g. "this is an on-stack buffer")
>
> (f) implements round-tripping of this information in sarif-replay
>

[...]

> diff --git a/gcc/diagnostic-state-to-dot.cc b/gcc/diagnostic-state-to-dot.cc
> index ddae83b85cd2..8195c1148fe0 100644
> --- a/gcc/diagnostic-state-to-dot.cc
> +++ b/gcc/diagnostic-state-to-dot.cc
> @@ -1,4 +1,4 @@
> -/* Creating GraphViz .dot files from XML state documents.
> +/* Creating GraphViz .dot files from diagnostic state graphs.
>     Copyright (C) 2025 Free Software Foundation, Inc.
>     Contributed by David Malcolm <dmalc...@redhat.com>.
>

[...]

> @@ -237,13 +244,13 @@ private:
>    enum class style { h1, h2 };
>  
>    void
> -  add_title_tr (const dot::id &id_of_node,
> +  add_title_tr (const dot::id &id_of_dot_node,
>               xml::printer &xp,
>               int num_columns,
> -             const xml::element &input_element,
> +             state_node_ref state_node,
>               std::string heading,
>               enum style styl,
> -             enum dynalloc_state dynalloc_state)
> +             enum node_dynalloc_state dynalloc_state)
>    {
>      xp.push_tag ("tr", true);
>      xp.push_tag ("td", false);
> @@ -282,183 +289,194 @@ private:
>      xp.add_text (std::move (heading));
>      xp.pop_tag ("font");
>  
> -    maybe_add_dst_port (id_of_node, xp, input_element);
> +    maybe_add_dst_port (id_of_dot_node, xp, state_node);
>  
>      xp.pop_tag ("td");
>      xp.pop_tag ("tr");
>    }
>  
> -  /* Recursively add <TR> to XP for INPUT_NODE and its descendents.  */
> +  /* Recursively add <TR> to XP for STATE_NODE and its descendents.  */
>    void
> -  on_xml_node (const dot::id &id_of_node,
> -            xml::printer &xp,
> -            xml::node &input_node,
> -            int max_depth,
> -            int depth,
> -            int num_columns)
> +  on_node_in_table (const dot::id &id_of_dot_node,
> +                 xml::printer &xp,
> +                 state_node_ref state_node,
> +                 int max_depth,
> +                 int depth,
> +                 int num_columns)
>    {
>      bool recurse = true;
> +    auto input_node_kind = state_node.get_node_kind ();
>  
> -    xml::element *input_element = input_node.dyn_cast_element ();
> -    if (!input_element)
> -      return;
> -
> -    if (input_element->m_kind == "concrete-bindings")
> -      return;
> -    if (input_element->m_kind == "padding")
> -      return;
> -
> -    if (input_element->m_kind == "stack")
> -      {
> -     add_title_tr (id_of_node, xp, num_columns, *input_element, "Stack",
> -                   style::h1, dynalloc_state::unknown);
> -      }
> -    else if (input_element->m_kind == "stack-frame")
> -      {
> -     if (const char *function = input_element->get_attr ("function"))
> -       add_title_tr (id_of_node, xp, num_columns, *input_element,
> -                     std::string ("Frame: ") + function,
> -                     style::h2, dynalloc_state::unknown);
> -      }
> -    else if (input_element->m_kind == "heap-buffer")
> +    switch (input_node_kind)
>        {
> -     const char *extents = input_element->get_attr ("dynamic-extents");
> -     enum dynalloc_state dynalloc_st = get_dynalloc_state (*input_element);
> -     if (auto region_id = input_element->get_attr ("region_id"))
> -         m_region_id_to_dynalloc_state[region_id] = dynalloc_st;
> -     const char *type = input_element->get_attr ("type");
> -     pretty_printer pp;
> -     switch (dynalloc_st)
> -       {
> -       default:
> -         gcc_unreachable ();
> -
> -       case dynalloc_state::unknown:
> -       case dynalloc_state::nonnull:
> -         if (type)
> -           {
> +      case node_kind::padding:
> +      case node_kind::other:
> +     return;
> +
> +      case node_kind::stack:
> +     add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
> +                   style::h1,
> +                   node_dynalloc_state::unknown);
> +     break;
> +      case node_kind::stack_frame:
> +     if (auto logical_loc = state_node.get_logical_loc ())
> +       if (const char *function
> +             = m_logical_loc_mgr.get_short_name (logical_loc))
> +         add_title_tr (id_of_dot_node, xp, num_columns, state_node,
> +                       std::string ("Frame: ") + function,
> +                       style::h2,
> +                       node_dynalloc_state::unknown);
> +     break;
> +      case node_kind::dynalloc_buffer:
> +     {
> +       enum node_dynalloc_state dynalloc_st
> +         = state_node.get_dynalloc_state ();
> +       const char *extents = state_node.get_dynamic_extents ();
> +       const char *type = state_node.get_type ();
> +       pretty_printer pp;
> +       switch (dynalloc_st)
> +         {
> +         default:
> +           gcc_unreachable ();
> +
> +         case node_dynalloc_state::unknown:
> +         case node_dynalloc_state::nonnull:
> +           if (type)
> +             {
>               if (extents)
>                 pp_printf (&pp, "%s (%s byte allocation)",
>                            type, extents);
>               else
>                 pp_printf (&pp, "%s", type);
> -           }
> -         else
> +             }
> +           else
> +             {
> +               if (extents)
> +                 pp_printf (&pp, "%s byte allocation",
> +                            extents);
> +             }
> +           break;
> +
> +         case node_dynalloc_state::unchecked:
> +           if (type)
> +             {
> +               if (extents)
> +                 pp_printf (&pp, "%s (unchecked %s byte allocation)",
> +                            type, extents);
> +             }
> +           else
> +             {
> +               if (extents)
> +                 pp_printf (&pp, "Unchecked %s byte allocation",
> +                            extents);
> +             }
> +           break;
> +
> +         case node_dynalloc_state::freed:
> +           // TODO: show deallocator
> +           // TODO: show deallocation event
> +           pp_printf (&pp, "Freed buffer");
> +           break;
> +         }
> +       maybe_add_dst_port (id_of_dot_node, xp, state_node);
> +       add_title_tr (id_of_dot_node, xp, num_columns, state_node,
> +                     pp_formatted_text (&pp),
> +                     style::h2,
> +                     dynalloc_st);
> +     }
> +     break;
> +
> +      default:
> +     {
> +       xp.push_tag ("tr", true);
> +
> +       maybe_add_dst_port (id_of_dot_node, xp, state_node);
> +
> +       if (depth > 0)
> +         {
> +           /* Indent, by create a <td> spanning "depth" columns.  */
> +           xp.push_tag ("td", false);
> +           xp.set_attr ("colspan", std::to_string (depth));
> +           xp.add_text (" "); // graphviz doesn't like <td/>
> +           xp.pop_tag ("td");
> +         }
> +
> +       switch (input_node_kind)
> +         {
> +         default:
> +           break;
> +         case node_kind::variable:
>             {
> -             if (extents)
> -               pp_printf (&pp, "%s byte allocation",
> -                          extents);
> +             const char *name = state_node.get_name ();
> +             gcc_assert (name);
> +             xp.push_tag ("td", false);
> +             maybe_add_dst_port (id_of_dot_node, xp, state_node);
> +             push_src_text (xp);
> +             xp.add_text (name);
> +             pop_src_text (xp);
> +             xp.pop_tag ("td");
>             }
> -         break;
> -
> -       case dynalloc_state::unchecked:
> -         if (type)
> +           break;
> +         case node_kind::element:
>             {
> -             if (extents)
> -               pp_printf (&pp, "%s (unchecked %s byte allocation)",
> -                          type, extents);
> +             const char *index = state_node.get_index ();
> +             gcc_assert (index);
> +             xp.push_tag ("td", false);
> +             maybe_add_dst_port (id_of_dot_node, xp, state_node);
> +             push_src_text (xp);
> +             xp.add_text ("[");
> +             xp.add_text (index);
> +             xp.add_text ("]");
> +             pop_src_text (xp);
> +             xp.pop_tag ("td");
>             }
> -         else
> +           break;
> +         case node_kind::field:
>             {
> -             if (extents)
> -               pp_printf (&pp, "Unchecked %s byte allocation",
> -                          extents);
> +             const char *name = state_node.get_name ();
> +             gcc_assert (name);
> +             xp.push_tag ("td", false);
> +             maybe_add_dst_port (id_of_dot_node, xp, state_node);
> +             push_src_text (xp);
> +             xp.add_text (".");
> +             xp.add_text (name);
> +             pop_src_text (xp);
> +             xp.pop_tag ("td");
>             }
> -         break;
> -
> -       case dynalloc_state::freed:
> -         // TODO: show deallocator
> -         // TODO: show deallocation event
> -         pp_printf (&pp, "Freed buffer");
> -         break;
> -       }
> -     add_title_tr (id_of_node, xp, num_columns, *input_element,
> -                   pp_formatted_text (&pp),
> -                   style::h2,
> -                   dynalloc_st);
> -      }
> -    else
> -      {
> -     xp.push_tag ("tr", true);
> -     if (depth > 0)
> -       {
> -         /* Indent, by create a <td> spanning "depth" columns.  */
> -         xp.push_tag ("td", false);
> -         xp.set_attr ("colspan", std::to_string (depth));
> -         xp.add_text (" "); // graphviz doesn't like <td/>
> -         xp.pop_tag ("td");
> -       }
> -     if (m_show_tags)
> -       {
> -         // Debug: show XML tag
> -         xp.push_tag ("td", false);
> -         xp.add_text ("<");
> -         xp.add_text (input_element->m_kind);
> -         xp.add_text (">");
> -         xp.pop_tag ("td");
> -       }

This seems to have removed the only use, of...

[...]
>  
>  private:
> -  int m_next_id;
> -  std::vector<pending_edge> m_pending_edges;
> -  std::map<std::string, dot::node_id> m_region_id_to_dst_node_id;
> -  std::map<std::string, enum dynalloc_state> m_region_id_to_dynalloc_state;
> +  const logical_location_manager &m_logical_loc_mgr;
> +
> +  /* All nodes involved in edges (and thus will need a port).  */
> +  std::set<diagnostics::digraphs::node *> m_src_nodes;
> +  std::set<diagnostics::digraphs::node *> m_dst_nodes;
> +
> +  std::map<diagnostics::digraphs::node *, dot::node_id> 
> m_src_node_to_port_id;
> +  std::map<diagnostics::digraphs::node *, dot::node_id> 
> m_dst_node_to_port_id;
> +
>    bool m_show_tags;
>  };

this private member, at least clang says so (but it looks only
initialized and never read).  Should it have been removed as a part of
your patch?

More generally speaking, is catching such unused private member fields
useful ? (There is another, m_wanted_type, in c-family/c-format.cc.)  I
can just filter them out if we feel that the warnings simply get in the
way of building/changing things in stages.

Thanks,

Martin

Reply via email to