I'm not aware of any mechanisms, or a foundation for introducing a new
mechanism that allows associating original trees with folded constant
expressions, without the risk of those being lost during further
folding.

That said, I see your point about a new global plugin event being
heavy-weight in terms of API surface and maintenance. I could make
this a front-end-local callback. For example, the C front-end could
expose an internal registration function, implemented in c-common.cc
and declared only in c-common.h.

Since c-common.h is not included in the public plugin headers, this
wouldn't become part of the documented plugin API. Plugins that really
need this would have to declare the function prototype themselves and
link against the C front end. In other words, this would be a private,
opt-in mechanism rather than a public API commitment, so it shouldn’t
constrain future refactoring.

If that seems reasonable, I can prepare a v2 implementing it this way.

On Mon, Oct 20, 2025 at 9:22 AM Richard Biener
<[email protected]> wrote:
>
> On Sat, Oct 18, 2025 at 3:49 PM York Jasper Niebuhr
> <[email protected]> wrote:
> >
> > This patch adds the PLUGIN_BUILD_COMPONENT_REF callback, which is invoked
> > by the C front end when a COMPONENT_REF node is built. The callback
> > receives a pointer to the COMPONENT_REF tree (of type 'tree *'). Plugins
> > may replace the node by assigning through the pointer, but any
> > replacement must be type-compatible with the original node.
> >
> > The callback allows plugins to observe or instrument struct member
> > accesses that would otherwise be lost due to folding before the earliest
> > possible plugin pass or hook. In particular, the fold_offsetof
> > functionality removes all traces of type and member information in
> > offsetof-like trees, leaving only an integer constant for plugins to
> > inspect.
> >
> > A considered alternative was to disable fold_offsetof altogether.
> > However, that prevents offsetof expressions from qualifying as
> > constant-expressions; for example, static assertions can no longer be
> > evaluated if they contain non-folded offsetof expressions. The callback
> > provides fine-grained control over individual COMPONENT_REFs instead of
> > universally changing folding behavior.
>
> I think a hook on COMPONENT_REF building is quite heavy-weight.  IMO
> folding required for constant-expression-ness diagnosing might be better
> of exposing both the folding result and the original tree somehow?
>
> > A typical use case would be to replace a select set of COMPONENT_REF
> > nodes with type-compatible expressions calling a placeholder function,
> > e.g. __deferred_offsetof(type, member). These calls cannot be folded
> > away and thus remain available for plugin analysis in later passes.
> > Offsets not of interest can be left untouched, preserving their const
> > qualification and use in static assertions.
> >
> > Allowing PLUGIN_BUILD_COMPONENT_REF to alter COMPONENT_REF nodes required
> > minor adjustments to fold_offsetof, which assumes a specific input
> > format. Code paths that cannot guarantee that format should now use
> > fold_offsetof_maybe(), which attempts to fold normally but, on failure,
> > casts the unfolded expression to the desired output type.
> >
> > If the callback is not used to alter COMPONENT_REF trees, there is **no
> > change** in GCC’s behavior.
> >
> > Signed-off-by: York Jasper Niebuhr <[email protected]>
> >
> > ---
> >  gcc/c-family/c-common.cc | 48 +++++++++++++++++++++++++++++++---------
> >  gcc/c-family/c-common.h  |  3 ++-
> >  gcc/c/c-parser.cc        |  2 +-
> >  gcc/c/c-typeck.cc        | 12 ++++++++++
> >  gcc/doc/plugins.texi     |  6 +++++
> >  gcc/plugin.cc            |  2 ++
> >  gcc/plugin.def           |  6 +++++
> >  7 files changed, 66 insertions(+), 13 deletions(-)
> >
> > diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> > index 587d76461e9..d34edfaa688 100644
> > --- a/gcc/c-family/c-common.cc
> > +++ b/gcc/c-family/c-common.cc
> > @@ -7076,43 +7076,48 @@ c_common_to_target_charset (HOST_WIDE_INT c)
> >     the whole expression.  Return the folded result.  */
> >
> >  tree
> > -fold_offsetof (tree expr, tree type, enum tree_code ctx)
> > +fold_offsetof (tree expr, tree type, enum tree_code ctx, bool may_fail)
> >  {
> >    tree base, off, t;
> >    tree_code code = TREE_CODE (expr);
> > +
> >    switch (code)
> >      {
> >      case ERROR_MARK:
> >        return expr;
> >
> >      case VAR_DECL:
> > -      error ("cannot apply %<offsetof%> to static data member %qD", expr);
> > +      if (!may_fail)
> > +       error ("cannot apply %<offsetof%> to static data member %qD", expr);
> >        return error_mark_node;
> >
> >      case CALL_EXPR:
> >      case TARGET_EXPR:
> > -      error ("cannot apply %<offsetof%> when %<operator[]%> is 
> > overloaded");
> > +      if (!may_fail)
> > +       error ("cannot apply %<offsetof%> when %<operator[]%> is 
> > overloaded");
> >        return error_mark_node;
> >
> >      case NOP_EXPR:
> >      case INDIRECT_REF:
> >        if (!TREE_CONSTANT (TREE_OPERAND (expr, 0)))
> >         {
> > -         error ("cannot apply %<offsetof%> to a non constant address");
> > +         if (!may_fail)
> > +           error ("cannot apply %<offsetof%> to a non constant address");
> >           return error_mark_node;
> >         }
> >        return convert (type, TREE_OPERAND (expr, 0));
> >
> >      case COMPONENT_REF:
> > -      base = fold_offsetof (TREE_OPERAND (expr, 0), type, code);
> > +      base = fold_offsetof (TREE_OPERAND (expr, 0), type, code, may_fail);
> >        if (base == error_mark_node)
> >         return base;
> >
> >        t = TREE_OPERAND (expr, 1);
> >        if (DECL_C_BIT_FIELD (t))
> >         {
> > -         error ("attempt to take address of bit-field structure "
> > -                "member %qD", t);
> > +         if (!may_fail)
> > +           error ("attempt to take address of bit-field structure "
> > +                  "member %qD", t);
> >           return error_mark_node;
> >         }
> >        off = size_binop_loc (input_location, PLUS_EXPR, DECL_FIELD_OFFSET 
> > (t),
> > @@ -7121,7 +7126,7 @@ fold_offsetof (tree expr, tree type, enum tree_code 
> > ctx)
> >        break;
> >
> >      case ARRAY_REF:
> > -      base = fold_offsetof (TREE_OPERAND (expr, 0), type, code);
> > +      base = fold_offsetof (TREE_OPERAND (expr, 0), type, code, may_fail);
> >        if (base == error_mark_node)
> >         return base;
> >
> > @@ -7178,17 +7183,38 @@ fold_offsetof (tree expr, tree type, enum tree_code 
> > ctx)
> >      case COMPOUND_EXPR:
> >        /* Handle static members of volatile structs.  */
> >        t = TREE_OPERAND (expr, 1);
> > -      gcc_checking_assert (VAR_P (get_base_address (t)));
> > -      return fold_offsetof (t, type);
> > +      if (!VAR_P (get_base_address (t)))
> > +       return error_mark_node;
> > +      return fold_offsetof (t, type, ERROR_MARK, may_fail);
> >
> >      default:
> > -      gcc_unreachable ();
> > +      return error_mark_node;
> >      }
> >
> >    if (!POINTER_TYPE_P (type))
> >      return size_binop (PLUS_EXPR, base, convert (type, off));
> >    return fold_build_pointer_plus (base, off);
> >  }
> > +
> > +/* Tries folding expr using fold_offsetof.  On success, the folded offsetof
> > +   is returned.  On failure, the original expr is wrapped in an ADDR_EXPR
> > +   and converted to the desired expression type.  The resulting expression
> > +   may or may not be constant!  */
> > +
> > +tree
> > +fold_offsetof_maybe (tree expr, tree type)
> > +{
> > +  /* expr might not have the correct structure, thus folding may fail.  */
> > +  tree maybe_folded = fold_offsetof (expr, type, ERROR_MARK, true);
> > +  if (maybe_folded != error_mark_node)
> > +    return maybe_folded;
> > +
> > +  tree ptr_type = build_pointer_type (TREE_TYPE (expr));
> > +  tree ptr = build1 (ADDR_EXPR, ptr_type, expr);
> > +
> > +  return fold_convert (type, ptr);
> > +}
> > +
> >
> >  /* *PTYPE is an incomplete array.  Complete it with a domain based on
> >     INITIAL_VALUE.  If INITIAL_VALUE is not present, use 1 if DO_DEFAULT
> > diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> > index ea6c2975056..70fcfeb6661 100644
> > --- a/gcc/c-family/c-common.h
> > +++ b/gcc/c-family/c-common.h
> > @@ -1174,7 +1174,8 @@ extern bool c_dump_tree (void *, tree);
> >  extern void verify_sequence_points (tree);
> >
> >  extern tree fold_offsetof (tree, tree = size_type_node,
> > -                          tree_code ctx = ERROR_MARK);
> > +                          tree_code ctx = ERROR_MARK, bool may_fail = 
> > false);
> > +extern tree fold_offsetof_maybe (tree, tree = size_type_node);
> >
> >  extern int complete_array_type (tree *, tree, bool);
> >  extern void complete_flexible_array_elts (tree);
> > diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> > index 22ec0f849b7..6a8a5d58e6d 100644
> > --- a/gcc/c/c-parser.cc
> > +++ b/gcc/c/c-parser.cc
> > @@ -11823,7 +11823,7 @@ c_parser_postfix_expression (c_parser *parser)
> >             location_t end_loc = c_parser_peek_token (parser)->get_finish 
> > ();
> >             c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
> >                                        "expected %<)%>");
> > -           expr.value = fold_offsetof (offsetof_ref);
> > +           expr.value = fold_offsetof_maybe (offsetof_ref);
> >             set_c_expr_source_range (&expr, loc, end_loc);
> >           }
> >           break;
> > diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> > index 55d896e02df..aff6dce36fb 100644
> > --- a/gcc/c/c-typeck.cc
> > +++ b/gcc/c/c-typeck.cc
> > @@ -55,6 +55,7 @@ along with GCC; see the file COPYING3.  If not see
> >  #include "realmpfr.h"
> >  #include "tree-pretty-print-markup.h"
> >  #include "gcc-urlifier.h"
> > +#include "plugin.h"
> >
> >  /* Possible cases of implicit conversions.  Used to select diagnostic 
> > messages
> >     and control folding initializers in convert_for_assignment.  */
> > @@ -133,6 +134,7 @@ static int lvalue_or_else (location_t, const_tree, enum 
> > lvalue_use);
> >  static void record_maybe_used_decl (tree);
> >  static bool comptypes_internal (const_tree, const_tree,
> >                                 struct comptypes_data *data);
> > +
> >
> >  /* Return true if EXP is a null pointer constant, false otherwise.  */
> >
> > @@ -3174,6 +3176,16 @@ build_component_ref (location_t loc, tree datum, 
> > tree component,
> >           else if (TREE_DEPRECATED (subdatum))
> >             warn_deprecated_use (subdatum, NULL_TREE);
> >
> > +      tree pre_cb_type = TREE_TYPE (ref);
> > +      if (invoke_plugin_callbacks (PLUGIN_BUILD_COMPONENT_REF, &ref)
> > +             == PLUGEVT_SUCCESS
> > +             && !comptypes (TREE_TYPE (ref), pre_cb_type))
> > +       {
> > +         error_at (EXPR_LOCATION (ref),
> > +                   "PLUGIN_BUILD_COMPONENT_REF callback returned"
> > +                   " expression of incompatible type");
> > +       }
> > +
> >           datum = ref;
> >
> >           field = TREE_CHAIN (field);
> > diff --git a/gcc/doc/plugins.texi b/gcc/doc/plugins.texi
> > index c11167a34ef..312f178fab4 100644
> > --- a/gcc/doc/plugins.texi
> > +++ b/gcc/doc/plugins.texi
> > @@ -222,6 +222,12 @@ enum plugin_event
> >       ana::plugin_analyzer_init_iface *.  */
> >    PLUGIN_ANALYZER_INIT,
> >
> > +  /* Called by the C front end when a COMPONENT_REF node is built.  The
> > +     callback receives a pointer to the COMPONENT_REF tree (of type 'tree 
> > *').
> > +     Plugins may replace the node by assigning through the pointer, but any
> > +     replacement must be type-compatible with the original node.  */
> > +  PLUGIN_BUILD_COMPONENT_REF,
> > +
> >    PLUGIN_EVENT_FIRST_DYNAMIC    /* Dummy event used for indexing callback
> >                                     array.  */
> >  @};
> > diff --git a/gcc/plugin.cc b/gcc/plugin.cc
> > index 0de2cc2dd2c..975e8c4e291 100644
> > --- a/gcc/plugin.cc
> > +++ b/gcc/plugin.cc
> > @@ -500,6 +500,7 @@ register_callback (const char *plugin_name,
> >        case PLUGIN_NEW_PASS:
> >        case PLUGIN_INCLUDE_FILE:
> >        case PLUGIN_ANALYZER_INIT:
> > +      case PLUGIN_BUILD_COMPONENT_REF:
> >          {
> >            struct callback_info *new_callback;
> >            if (!callback)
> > @@ -581,6 +582,7 @@ invoke_plugin_callbacks_full (int event, void *gcc_data)
> >        case PLUGIN_NEW_PASS:
> >        case PLUGIN_INCLUDE_FILE:
> >        case PLUGIN_ANALYZER_INIT:
> > +      case PLUGIN_BUILD_COMPONENT_REF:
> >          {
> >            /* Iterate over every callback registered with this event and
> >               call it.  */
> > diff --git a/gcc/plugin.def b/gcc/plugin.def
> > index 94e012a1e00..b0335178762 100644
> > --- a/gcc/plugin.def
> > +++ b/gcc/plugin.def
> > @@ -103,6 +103,12 @@ DEFEVENT (PLUGIN_INCLUDE_FILE)
> >     ana::plugin_analyzer_init_iface *.  */
> >  DEFEVENT (PLUGIN_ANALYZER_INIT)
> >
> > +/* Called by the C front end when a COMPONENT_REF node is built.
> > +   The callback receives a pointer to the COMPONENT_REF tree (of type 
> > 'tree *').
> > +   Plugins may replace the node by assigning through the pointer, but any
> > +   replacement must be type-compatible with the original node.  */
> > +DEFEVENT (PLUGIN_BUILD_COMPONENT_REF)
> > +
> >  /* When adding a new hard-coded plugin event, don't forget to edit in
> >     file plugin.cc the functions register_callback and
> >     invoke_plugin_callbacks_full accordingly!  */
> > --
> > 2.43.0
> >

Reply via email to