v2:
  - Update comment above fold_offsetof to document the 'may_fail' parameter.

This patch supersedes my earlier submission:
https://gcc.gnu.org/pipermail/gcc-patches/2025-October/697934.html

It adds the c_parse_component_ref callback that plugins can register
using register_c_parse_component_ref_cb. The callback receives the
parsed COMPONENT_REF tree and may replace it by returning a new tree.
Replacements that are not type-compatible with the original are ignored.

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 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 the callback to alter COMPONENT_REF nodes required minor
adjustments to fold_offsetof, which assumes a specific input format.
The parser paths that cannot guarantee that format after invoking the
callback now use fold_offsetof_maybe(), which attempts to fold normally
but, on failure, casts the unfolded expressions to the desired output
type.

If the callback is not used to alter COMPONENT_REF trees, there is **no
change** in GCC’s behavior.

Since this callback targets fairly niche use cases, I opted to not make
it part of the public plugin API so there is no need for maintenance
guarantees.

**Changes since the first proposal:**
  * Moved callback invocation to a higher level (the parser)
  * The callback is no longer part of the public plugin API

Signed-off-by: York Jasper Niebuhr <[email protected]>

---
 gcc/c-family/c-common.cc | 52 ++++++++++++++++++++++-------
 gcc/c-family/c-common.h  |  3 +-
 gcc/c/c-parser.cc        | 72 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 113 insertions(+), 14 deletions(-)

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 587d76461e9..a1c76642b12 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -7073,46 +7073,53 @@ c_common_to_target_charset (HOST_WIDE_INT c)
 /* Fold an offsetof-like expression.  EXPR is a nested sequence of component
    references with an INDIRECT_REF of a constant at the bottom; much like the
    traditional rendering of offsetof as a macro.  TYPE is the desired type of
-   the whole expression.  Return the folded result.  */
+   the whole expression.  On error, error_mark_node is returned.  Compiler
+   errors are only produced if MAY_FAIL is set to false.  Otherwise, the folded
+   result is returned.  */
 
 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 +7128,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 +7185,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..08cf0d9839b 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -11354,6 +11354,64 @@ get_counted_by_ref (tree array_ref)
   return NULL_TREE;
 }
 
+/* Callback type for notifying plugins when the C parser constructs
+   a COMPONENT_REF expression.
+
+   The callback receives the COMPONENT_REF tree that has just been parsed.
+   It may optionally return a replacement tree, which will be used instead
+   of the original if it is type-compatible.  Returning NULL_TREE leaves
+   the expression unchanged.
+
+   This callback is intended for plugins that wish to observe or transform
+   member-access expressions (such as 'a.b' or 'a->b') or offsetof expressions
+   at parse time.  */
+using c_parse_component_ref_cb_t = tree (*)(tree ref);
+
+/* Plugin-registered callback for COMPONENT_REF parse notifications.
+   Initialized to NULL when no plugin has registered a callback.  */
+static c_parse_component_ref_cb_t c_parse_component_ref_cb = nullptr;
+
+/* Register a plugin callback to be invoked for each parsed COMPONENT_REF.
+
+   Only a single callback is supported; registering a new one replaces
+   any previously registered callback.  */
+__attribute__ ((visibility ("default")))
+void
+register_c_parse_component_ref_cb (c_parse_component_ref_cb_t cb)
+{
+  c_parse_component_ref_cb = cb;
+}
+
+/* Helper to notify the registered plugin callback that a COMPONENT_REF
+   has been parsed.
+
+   If a plugin has registered a callback, this function invokes it with
+   the given COMPONENT_REF tree.  If the callback returns a non-NULL
+   tree whose type is compatible with the original (as determined by
+   comptypes), the COMPONENT_REF is replaced with that tree.
+
+   This preserves parser invariants and prevents type inconsistencies
+   in subsequent compilation stages.  */
+static void
+notify_plugin_parse_component_ref (tree* ref)
+{
+  if (!ref || !c_parse_component_ref_cb)
+    return;
+
+  tree repl = c_parse_component_ref_cb (*ref);
+  if (!repl)
+    return;
+
+  if (comptypes (TREE_TYPE (*ref), TREE_TYPE (repl)))
+  {
+    *ref = repl;
+    return;
+  }
+
+  warning (0, "plugin: tree returned from %<c_parse_component_ref_cb%> "
+          "has incompatible type; ignored");
+}
+
 /* Parse a postfix expression (C90 6.3.1-6.3.2, C99 6.5.1-6.5.2,
    C11 6.5.1-6.5.2).  Compound literals aren't handled here; callers have to
    call c_parser_postfix_expression_after_paren_type on encountering them.
@@ -11766,6 +11824,9 @@ c_parser_postfix_expression (c_parser *parser)
                  = build_component_ref (loc, offsetof_ref, comp_tok->value,
                                         comp_tok->location, UNKNOWN_LOCATION,
                                         false);
+
+               notify_plugin_parse_component_ref (&offsetof_ref);
+
                c_parser_consume_token (parser);
                while (c_parser_next_token_is (parser, CPP_DOT)
                       || c_parser_next_token_is (parser,
@@ -11800,6 +11861,9 @@ c_parser_postfix_expression (c_parser *parser)
                                                 comp_tok->location,
                                                 UNKNOWN_LOCATION,
                                                 false);
+
+                       notify_plugin_parse_component_ref (&offsetof_ref);
+
                        c_parser_consume_token (parser);
                      }
                    else
@@ -11823,7 +11887,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;
@@ -13771,6 +13835,9 @@ c_parser_postfix_expression_after_primary (c_parser 
*parser,
          c_parser_consume_token (parser);
          expr.value = build_component_ref (op_loc, expr.value, ident,
                                            comp_loc, UNKNOWN_LOCATION);
+
+         notify_plugin_parse_component_ref (&expr.value);
+
          set_c_expr_source_range (&expr, start, finish);
          expr.original_code = ERROR_MARK;
          if (TREE_CODE (expr.value) != COMPONENT_REF)
@@ -13813,6 +13880,9 @@ c_parser_postfix_expression_after_primary (c_parser 
*parser,
                                                                RO_ARROW),
                                            ident, comp_loc,
                                            expr.get_location ());
+
+         notify_plugin_parse_component_ref (&expr.value);
+
          set_c_expr_source_range (&expr, start, finish);
          expr.original_code = ERROR_MARK;
          if (TREE_CODE (expr.value) != COMPONENT_REF)
-- 
2.43.0

Reply via email to