https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127234
Bug ID: 127234
Summary: __bdos(&p->inner, 0) misses counted_by when subobject
inner ends in a FAM
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: gustavo at embeddedor dot com
Target Milestone: ---
For &p->inner below, where inner's type ends in a flexible-array member
annotated with counted_by, __builtin_dynamic_object_size(&p->inner, 0) is
derived from the allocation instead of from counted_by, hence it returns the
number of bytes from &p->inner to the end of the surrounding allocation when
the allocation is visible, and (size_t)-1 when the allocation is hidden.
This is a bug. The size should be determined based on the information provided
by counted_by, that is, __builtin_dynamic_object_size(&p->inner, 0) should
return sizeof(p->inner) + p->inner.count, in this particular case, regardless
of whether the allocation is visible or not. So, counted_by must be
authoritative.
#include <stdlib.h>
#define noinline __attribute__((noinline))
#define __counted_by(member) __attribute__((counted_by(member)))
#define expect(p, _v) do { \
size_t v = _v; \
if (p == v) \
__builtin_printf("ok: %s == %zd\n", #p, p); \
else {\
__builtin_printf("WAT: %s == %zd (expected %zd)\n", #p, p, v); \
} \
} while (0);
struct flex { /* sizeof 8 */
size_t count;
char fam[] __counted_by(count);
};
struct outer { /* sizeof 16 */
int hdr;
struct flex inner;
};
struct outer noinline *noinline_alloc(size_t n)
{
struct outer *q = __builtin_malloc(sizeof(*q) + n);
q->inner.count = n + 1; /* intentionally make count > alloc size */
__builtin_printf("\nvisible allocation (count > alloc):\n");
/* counted_by must take precedence over the allocation size. */
expect(__builtin_dynamic_object_size(&q->inner, 0), sizeof(q->inner) +
q->inner.count);
return q;
}
int main(void)
{
struct outer *p = __builtin_malloc(sizeof(*p) + 100);
struct outer *q = noinline_alloc(10); /* hidden base */
p->inner.count = 10; /* intentionally make count < alloc size */
__builtin_printf("\nvisible allocation (count < alloc):\n");
/* counted_by (count == 10) makes the object 18 bytes, not 108. */
expect(__builtin_dynamic_object_size(&p->inner, 0), sizeof(p->inner) +
p->inner.count);
__builtin_printf("\nhidden allocation (count > alloc):\n");
/* counted_by (count == 11) makes the object 19 bytes, not -1. */
expect(__builtin_dynamic_object_size(&q->inner, 0), sizeof(q->inner) +
q->inner.count);
free(p);
free(q);
return 0;
}
Output (with GCC trunk, -O2 -Wall):
visible allocation (count > alloc):
WAT: __builtin_dynamic_object_size(&q->inner, 0) == 18 (expected 19)
visible allocation (count < alloc):
WAT: __builtin_dynamic_object_size(&p->inner, 0) == 108 (expected 18)
hidden allocation (count > alloc):
WAT: __builtin_dynamic_object_size(&q->inner, 0) == -1 (expected 19)
(Reproducer: https://godbolt.org/z/a58qf3s3h)
Call GCC Expected
__bdos(&q->inner, 0) 18 19
(visible alloc and count > alloc)
__bdos(&p->inner, 0) 108 18
(visible alloc and count < alloc)
__bdos(&q->inner, 0) -1 19
(hidden alloc and count > alloc)
I reported a similar bug to Clang, and they fixed it up already. So with Clang
we have the following expected output (with Clang trunk, -O2 -Wall):
visible allocation (count > alloc):
ok: __builtin_dynamic_object_size(&q->inner, 0) == 19
visible allocation (count < alloc):
ok: __builtin_dynamic_object_size(&p->inner, 0) == 18
hidden allocation (count > alloc):
ok: __builtin_dynamic_object_size(&q->inner, 0) == 19
(Reproducer: https://godbolt.org/z/a58qf3s3h)
LLVM issue: https://github.com/llvm/llvm-project/issues/205652
Fixed by: https://github.com/llvm/llvm-project/pull/205903
(I'll submit a patch for this, shortly.)