On Wed, 2025-07-16 at 15:58 +0200, Martin Jambor wrote:
> 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?

Yes; as it happens Filip Kastl already reported this to me off-list,
and I have a fix queued up.

> 
> More generally speaking, is catching such unused private member
> fields
> useful ? 

The above was a genuine mistake on my part, so I'd say "yes".

> (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.

If you're using -Werror with these, that might be overkill, but
otherwise the warnings seem useful to me (and maybe GCC should
implement this?)

Thanks
Dave

Reply via email to