https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127899
Bug ID: 127899
Summary: [14/15/16/17 Regression] ICE in
gimplify_size_expressions for __builtin_dynamic_objec
t_size type 2 at a negative offset
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: kees at outflux dot net
Target Milestone: ---
A minimum __builtin_dynamic_object_size (type 2 or 3) ICEs at -O1 and
above when the pointer is at a constant negative offset from one whose
size isn't a constant. The offsets below stay inside the object, so
these are valid programs. Both run clean at -O0 with
-fsanitize=address,undefined,bounds-strict, and with Clang at -O2 with
-fsanitize=address,undefined,bounds.
container_of() on a counted_by flexible array member, as the Linux
kernel's netdev_from_priv() does (see PR127875):
$ cat t1.c
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) - offsetof (type, member)))
struct S
{
int count;
char data[] __attribute__ ((counted_by (count)));
};
__attribute__ ((noinline)) size_t
min_size (struct S *s)
{
/* Back from the flexible array member to the structure holding it. */
void *data = s->data;
struct S *again = container_of (data, struct S, data);
return __builtin_dynamic_object_size (again, 2);
}
int
main (void)
{
struct S *s = malloc (sizeof (*s) + 10);
if (!s)
return 1;
s->count = 10;
printf ("%zu\n", min_size (s));
free (s);
return 0;
}
$ gcc -O2 t1.c
during GIMPLE pass: objsz
t1.c: In function 'min_size':
t1.c:15:1: internal compiler error: tree check: expected ssa_name, have
integer_cst in gimplify_size_expressions, at tree-object-size.cc:1204
15 | min_size (struct S *s)
| ^~~~~~~~
Without counted_by, a pool allocator with alloc_size handing out pieces
of one array, where the pointer before one piece is the previous piece:
$ cat t2.c
#include <stdio.h>
static char pool[64];
static size_t used;
/* Hand out consecutive pieces of POOL. */
__attribute__ ((alloc_size (1), noinline)) char *
pool_alloc (size_t n)
{
char *p = pool + used;
used += n;
return p;
}
int
main (int argc, char **argv)
{
(void) argv;
size_t n = argc + 7;
char *a = pool_alloc (8);
char *b = pool_alloc (n);
/* A again: B - 8 is &pool[0]. */
char *before = b - 8;
printf ("%d %zu\n", before == a, __builtin_dynamic_object_size (before, 2));
return 0;
}
$ gcc -O2 t2.c
during GIMPLE pass: objsz
t2.c: In function 'main':
t2.c:17:1: internal compiler error: tree check: expected ssa_name, have
integer_cst in gimplify_size_expressions, at tree-object-size.cc:1204
17 | main (int argc, char **argv)
| ^~~~
0x2acd61f internal_error(char const*, ...)
../../gcc/diagnostic-global-context.cc:787
0x906c53 tree_check_failed(tree_node const*, char const*, int, char const*,
...)
../../gcc/tree.cc:9246
0x8a884c tree_check(tree_node*, char const*, int, char const*, tree_code)
../../gcc/tree.h:3837
0x8a884c gimplify_size_expressions
../../gcc/tree-object-size.cc:1204
0x8a884c compute_builtin_object_size(tree_node*, int, tree_node**)
../../gcc/tree-object-size.cc:1369
That's trunk (r17-5175) with --enable-checking=yes. A release compiler
fails too: GCC 16.2.0 (Debian 16.2.0-3) gives "internal compiler error:
in force_gimple_operand_1, at gimplify-me.cc:79" for both, and a
segmentation fault in release_ssa_name_fn for the reductions below.
Types 0 and 1 don't crash.
The smallest reductions move the pointer before its object, which would
be undefined if the program ran it, but the compiler shouldn't crash on
them either:
__SIZE_TYPE__ h (int n)
{
char *q = __builtin_malloc (n);
return __builtin_dynamic_object_size (q - 8, 2);
}
extern char a[10], b[20];
__SIZE_TYPE__ k (int c)
{
char *q = c ? a : b;
return __builtin_dynamic_object_size (q - 8, 2);
}
and likewise for a parameter with an access attribute.
The pointer's minimum size is the constant 0, the unknown minimum size,
but the pointer is still queued for reexamination with the object it
was computed from. Step 1 of gimplify_size_expressions then finds its
size unknown and takes SSA_NAME_VERSION of its size, assuming an SSA
name. The pointer's whole size isn't its own either: it's the SSA name
of the object's size, so releasing it would break that object's size.
This started with r14-6730-g576c1fc4401a9d ("tree-object-size: Always
set computed bit for bdos [PR113012]"), which queues a dynamic size for
reexamination whenever the size it was computed from is queued, even
when it is already unknown. Its parent, fa1158c50a9, compiles the
reductions. I bisected with checking builds over the commits touching
tree-object-size.cc and then tested both of those commits directly. It
was backported for GCC 13.3 (r13-8210), so 13.3 through 13.5 likely fail
too, but I haven't tested them.