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 >