On Wed, Jul 2, 2025 at 11:32 PM Siddhesh Poyarekar <siddh...@gotplt.org> wrote:
>
> 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.

This variant works for me.

> 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>
> ---
> Changes from v2:
> * Skip over sub-byte offsets
>
> Changes from v1:
> * Use byte_position to get byte position of a field
>
> Testing:
> - x86_64 bootstrap and test
> - i686 build and test
> - config=ubsan bootstrap
>
>  .../builtin-dynamic-object-size-pr120780.c    | 233 ++++++++++++++++++
>  gcc/tree-object-size.cc                       |  90 ++++++-
>  2 files changed, 322 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..2d13ab79a84 100644
> --- a/gcc/tree-object-size.cc
> +++ b/gcc/tree-object-size.cc
> @@ -464,6 +464,93 @@ 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;
> +
> +      /* Skip over fields at bit offsets that are not BITS_PER_UNIT aligned
> +        to avoid an accidental truncated match with BYTE_POSITION below since
> +        the address of such fields cannot be taken.  */
> +      if (wi::bit_and (wi::to_offset (DECL_FIELD_BIT_OFFSET (t)),
> +                      BITS_PER_UNIT - 1) != 0)
> +       continue;
> +
> +      tree byte_offset = byte_position (t);
> +      if (TREE_CODE (byte_offset) != INTEGER_CST
> +         || tree_int_cst_lt (offset, byte_offset))
> +       return false;
> +
> +      /* 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 +623,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 +635,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.50.0
>

Reply via email to