> Am 27.06.2025 um 13:40 schrieb Siddhesh Poyarekar <siddh...@gotplt.org>:
>
> MEM_REF cast of a subobject to its containing object has negative
> offsets, which objsz sees as an invalid access. Support this use case
> by peeking into the structure to validate that the containing object
> indeed contains a type of the subobject at that offset and if present,
> adjust the wholesize for the object to allow the negative offset.
>
> gcc/ChangeLog:
>
> PR tree-optimization/120780
> * tree-object-size.cc (inner_at_offset,
> get_wholesize_for_memref): New functions.
> (addr_object_size): Call GET_WHOLESIZE_FOR_MEMREF.
>
> gcc/testsuite/ChangeLog:
>
> PR tree-optimization/120780
> * gcc.dg/builtin-dynamic-object-size-pr120780.c: New test case.
>
> Signed-off-by: Siddhesh Poyarekar <siddh...@gotplt.org>
> ---
> .../builtin-dynamic-object-size-pr120780.c | 233 ++++++++++++++++++
> gcc/tree-object-size.cc | 87 ++++++-
> 2 files changed, 319 insertions(+), 1 deletion(-)
> create mode 100644 gcc/testsuite/gcc.dg/builtin-dynamic-object-size-pr120780.c
>
> diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-pr120780.c
> b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-pr120780.c
> new file mode 100644
> index 00000000000..0d6593ec828
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-pr120780.c
> @@ -0,0 +1,233 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2" } */
> +
> +#include "builtin-object-size-common.h"
> +typedef __SIZE_TYPE__ size_t;
> +#define NUM_MCAST_RATE 6
> +
> +#define MIN(a,b) ((a) < (b) ? (a) : (b))
> +#define MAX(a,b) ((a) > (b) ? (a) : (b))
> +
> +struct inner
> +{
> + int dummy[4];
> +};
> +
> +struct container
> +{
> + int mcast_rate[NUM_MCAST_RATE];
> + struct inner mesh;
> +};
> +
> +static void
> +test1_child (struct inner *ifmsh, size_t expected)
> +{
> + struct container *sdata =
> + (struct container *) ((void *) ifmsh
> + - __builtin_offsetof (struct container, mesh));
> +
> + if (__builtin_dynamic_object_size (sdata->mcast_rate, 1)
> + != sizeof (sdata->mcast_rate))
> + FAIL ();
> +
> + if (__builtin_dynamic_object_size (&sdata->mesh, 1) != expected)
> + FAIL ();
> +}
> +
> +void
> +__attribute__((noinline))
> +test1 (size_t sz)
> +{
> + struct container *sdata = __builtin_malloc (sz);
> + struct inner *ifmsh = &sdata->mesh;
> +
> + test1_child (ifmsh,
> + (sz > sizeof (sdata->mcast_rate)
> + ? sz - sizeof (sdata->mcast_rate) : 0));
> +
> + __builtin_free (sdata);
> +}
> +
> +struct container2
> +{
> + int mcast_rate[NUM_MCAST_RATE];
> + union
> + {
> + int dummy;
> + double dbl;
> + struct inner mesh;
> + } u;
> +};
> +
> +static void
> +test2_child (struct inner *ifmsh, size_t sz)
> +{
> + struct container2 *sdata =
> + (struct container2 *) ((void *) ifmsh
> + - __builtin_offsetof (struct container2, u.mesh));
> +
> + if (__builtin_dynamic_object_size (sdata->mcast_rate, 1)
> + != sizeof (sdata->mcast_rate))
> + FAIL ();
> +
> + size_t diff = sizeof (*sdata) - sz;
> + size_t expected = MIN(sizeof (double), MAX (sizeof (sdata->u), diff) -
> diff);
> +
> + if (__builtin_dynamic_object_size (&sdata->u.dbl, 1) != expected)
> + FAIL ();
> +
> + expected = MAX (sizeof (sdata->u.mesh), diff) - diff;
> + if (__builtin_dynamic_object_size (&sdata->u.mesh, 1) != expected)
> + FAIL ();
> +}
> +
> +void
> +__attribute__((noinline))
> +test2 (size_t sz)
> +{
> + struct container2 *sdata = __builtin_malloc (sz);
> + struct inner *ifmsh = &sdata->u.mesh;
> +
> + test2_child (ifmsh, sz);;
> +
> + __builtin_free (sdata);
> +}
> +
> +struct container3
> +{
> + int mcast_rate[NUM_MCAST_RATE];
> + char mesh[8];
> +};
> +
> +static void
> +test3_child (char ifmsh[], size_t expected)
> +{
> + struct container3 *sdata =
> + (struct container3 *) ((void *) ifmsh
> + - __builtin_offsetof (struct container3, mesh));
> +
> + if (__builtin_dynamic_object_size (sdata->mcast_rate, 1)
> + != sizeof (sdata->mcast_rate))
> + FAIL ();
> +
> + if (__builtin_dynamic_object_size (sdata->mesh, 1) != expected)
> + FAIL ();
> +}
> +
> +void
> +__attribute__((noinline))
> +test3 (size_t sz)
> +{
> + struct container3 *sdata = __builtin_malloc (sz);
> + char *ifmsh = sdata->mesh;
> + size_t diff = sizeof (*sdata) - sz;
> +
> + test3_child (ifmsh, MAX(sizeof (sdata->mesh), diff) - diff);
> +
> + __builtin_free (sdata);
> +}
> +
> +
> +struct container4
> +{
> + int mcast_rate[NUM_MCAST_RATE];
> + struct
> + {
> + int dummy;
> + struct inner mesh;
> + } s;
> +};
> +
> +static void
> +test4_child (struct inner *ifmsh, size_t expected)
> +{
> + struct container4 *sdata =
> + (struct container4 *) ((void *) ifmsh
> + - __builtin_offsetof (struct container4, s.mesh));
> +
> +
> + if (__builtin_dynamic_object_size (sdata->mcast_rate, 1)
> + != sizeof (sdata->mcast_rate))
> + FAIL ();
> +
> + if (__builtin_dynamic_object_size (&sdata->s.mesh, 1) != expected)
> + FAIL ();
> +}
> +
> +void
> +__attribute__((noinline))
> +test4 (size_t sz)
> +{
> + struct container4 *sdata = __builtin_malloc (sz);
> + struct inner *ifmsh = &sdata->s.mesh;
> + size_t diff = sizeof (*sdata) - sz;
> +
> + test4_child (ifmsh, MAX(sizeof (sdata->s.mesh), diff) - diff);
> +
> + __builtin_free (sdata);
> +}
> +
> +struct container5
> +{
> + int mcast_rate[NUM_MCAST_RATE];
> + struct
> + {
> + int dummy;
> + struct inner *mesh;
> + } s;
> +};
> +
> +static void
> +test5_child (struct inner **ifmsh, size_t expected)
> +{
> + struct container5 *sdata =
> + (struct container5 *) ((void *) ifmsh
> + - __builtin_offsetof (struct container5, s.mesh));
> +
> +
> + if (__builtin_dynamic_object_size (sdata->mcast_rate, 1)
> + != sizeof (sdata->mcast_rate))
> + FAIL ();
> +
> + if (__builtin_dynamic_object_size (&sdata->s.mesh, 1) != expected)
> + FAIL ();
> +}
> +
> +void
> +__attribute__((noinline))
> +test5 (size_t sz)
> +{
> + struct container5 *sdata = __builtin_malloc (sz);
> + struct inner **ifmsh = &sdata->s.mesh;
> + size_t diff = sizeof (*sdata) - sz;
> +
> + test5_child (ifmsh, MAX(sizeof (sdata->s.mesh), diff) - diff);
> +
> + __builtin_free (sdata);
> +}
> +
> +int
> +main (size_t sz)
> +{
> + test1 (sizeof (struct container));
> + test1 (sizeof (struct container) - sizeof (int));
> + test1 (sizeof (int) * NUM_MCAST_RATE - 1);
> +
> + test2 (sizeof (struct container2));
> + test2 (sizeof (struct container2) - sizeof (int));
> + test2 (sizeof (int) * NUM_MCAST_RATE - 1);
> +
> + test3 (sizeof (struct container3));
> + test3 (sizeof (struct container3) - sizeof (int));
> + test3 (sizeof (int) * NUM_MCAST_RATE - 1);
> +
> + test4 (sizeof (struct container4));
> + test4 (sizeof (struct container4) - sizeof (int));
> + test4 (sizeof (int) * NUM_MCAST_RATE - 1);
> +
> + test5 (sizeof (struct container5));
> + test5 (sizeof (struct container5) - sizeof (int));
> + test5 (sizeof (int) * NUM_MCAST_RATE - 1);
> +
> + DONE ();
> +}
> diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc
> index f348673ae75..16095a482ec 100644
> --- a/gcc/tree-object-size.cc
> +++ b/gcc/tree-object-size.cc
> @@ -464,6 +464,90 @@ compute_object_offset (tree expr, const_tree var)
> return size_binop (code, base, off);
> }
>
> +/* Return true if CONTAINER has a field of type INNER at OFFSET. */
> +
> +static bool
> +inner_at_offset (tree container, tree inner, tree offset)
> +{
> + gcc_assert (RECORD_OR_UNION_TYPE_P (container));
> +
> + for (tree t = TYPE_FIELDS (container); t; t = DECL_CHAIN (t))
> + {
> + if (TREE_CODE (t) != FIELD_DECL)
> + continue;
> +
> + tree byte_offset = DECL_FIELD_OFFSET (t);
> + if (TREE_CODE (byte_offset) != INTEGER_CST
> + || tree_int_cst_lt (offset, byte_offset))
> + return false;
> +
> + tree bit_offset = size_int (tree_to_uhwi (DECL_FIELD_BIT_OFFSET (t))
> + / BITS_PER_UNIT);
That math seems to be wrong for actual bit offsets. I’d suggest to skip the
field if it isn’t a multiple of BITS_PER_UNIT
> + byte_offset = size_binop (PLUS_EXPR, byte_offset, bit_offset);
it would be nice to avoid tree arithmetic here as well by using wide_int.
> +
> + /* For an array, check the element type, otherwise the actual type.
> This
> + deliberately does not support the case of jumping from a pointer to
> + the middle of an array to its containing struct. */
> + tree t_type = TREE_TYPE (t);
> + if (((TREE_CODE (t_type) == ARRAY_TYPE && TREE_TYPE (t_type) == inner)
> + || t_type == inner)
> + && tree_int_cst_equal (byte_offset, offset))
> + return true;
> +
> + /* Nested structure or union, adjust the expected offset and dive in.
> */
> + if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (t))
> + && inner_at_offset (TREE_TYPE (t), inner,
> + fold_build2 (MINUS_EXPR, sizetype, offset,
> + byte_offset)))
> + return true;
> + }
> +
> + return false;
> +}
> +
> +/* For the input MEMREF of type MEMREF_TYPE, look for the presence of a field
> + of BASE_TYPE at OFFSET and return an adjusted WHOLESIZE if found. */
> +
> +static tree
> +get_wholesize_for_memref (tree memref, tree wholesize)
> +{
> + tree base = TREE_OPERAND (memref, 0);
> + tree offset = fold_convert (sizetype, TREE_OPERAND (memref, 1));
> + tree memref_type = TREE_TYPE (memref);
> + tree base_type = TREE_TYPE (base);
> +
> + if (POINTER_TYPE_P (base_type))
> + base_type = TREE_TYPE ((base_type));
> +
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + {
> + fprintf (dump_file, "wholesize_for_memref: ");
> + print_generic_expr (dump_file, wholesize, dump_flags);
> + fprintf (dump_file, ", offset: ");
> + print_generic_expr (dump_file, offset, dump_flags);
> + fprintf (dump_file, "\n");
> + }
> +
> + if (TREE_CODE (offset) != INTEGER_CST
> + || compare_tree_int (offset, offset_limit) < 0
> + || !RECORD_OR_UNION_TYPE_P (memref_type))
> + return wholesize;
> +
> + offset = fold_build1 (NEGATE_EXPR, sizetype, offset);
> +
> + if (inner_at_offset (memref_type, base_type, offset))
> + wholesize = size_binop (PLUS_EXPR, wholesize, offset);
> +
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + {
> + fprintf (dump_file, " new wholesize: ");
> + print_generic_expr (dump_file, wholesize, dump_flags);
> + fprintf (dump_file, "\n");
> + }
> +
> + return wholesize;
> +}
> +
> /* Returns the size of the object designated by DECL considering its
> initializer if it either has one or if it would not affect its size,
> otherwise the size of the object without the initializer when MIN
> @@ -536,7 +620,7 @@ addr_object_size (struct object_size_info *osi,
> const_tree ptr,
> {
> compute_builtin_object_size (TREE_OPERAND (pt_var, 0),
> object_size_type & ~OST_SUBOBJECT, &sz);
> - wholesize = sz;
> + wholesize = get_wholesize_for_memref (pt_var, sz);
> }
> else
> {
> @@ -548,6 +632,7 @@ addr_object_size (struct object_size_info *osi,
> const_tree ptr,
> {
> sz = object_sizes_get (osi, SSA_NAME_VERSION (var));
> wholesize = object_sizes_get (osi, SSA_NAME_VERSION (var), true);
> + wholesize = get_wholesize_for_memref (pt_var, wholesize);
> }
> else
> sz = wholesize = size_unknown (object_size_type);
> --
> 2.49.0
>