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

Reply via email to