Hi,

I got a - I thought - spurious warning in a development patch. A simplified
reproducer of the warning is [1], which triggers:

<source>: In function 'trigger_warning':
<source>:19:9: warning: array subscript 'struct foo[0]' is partly outside array 
bounds of 'unsigned char[13]' [-Warray-bounds=]
   19 |     foop->len = len;
      |         ^~
<source>:18:12: note: object of size 13 allocated by 'allocme'
   18 |     foop = allocme(offsetof(struct foo, data) + sizeof(char) * len);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 0


This is a pattern that we have in quite a few places.


Note that the warning vanishes if the "flag" element is not present.  The
reason for that difference is that when the flag element is present, sizeof(
struct foo) is 16 on a 64bit platform, but we allocate only 13 bytes, as
offsetof(struct foo, data) is 12.


I asked the gcc folks whether they think the warning is spurious or whether
the code is broken. They think it's the code. Their argument is that it always
has to be legal to assign a struct foo * to a struct foo (i.e. do struct foo a
= *foop), and that that may access the trailing padding bytes.

Unfortunately, after staring at the C99 draft, I think the gcc folks are
right, and this undefined behavior. Although it's really hard to tell with the
C99 formulation.

I also looked at C23, and the examples (e.g. ยง 6.7.3.2, paragraph 28) do lend
some additional credence to the position of the gcc folks.


Which isn't great, given how widely we use offsetof(structtype, flex_member)
as the base allocation size.

The historic reason we do that is support for systems that didn't support real
flexible arrays, where we used one element arrays instead. In that case
sizeof(structtype) would obviously return too big a value.



I don't know how likely doing the wrong thing here is to lead to wrong code
generation, but relying on that not to happen doesn't seem great.


I've attached a list of structs that have a flexible array and padding,
generated with
  pahole -a --padding_ge=1 --with_flexible_array src/backend/postgres 
contrib/*/*.so
which of course is not exhaustive. And will differ between platforms.


One example of where we do this is gtrgm_alloc().


It's not entirely obvious to me what to do here. I think the minimum ought to
to not introduce more uses of offsetof() for flexible-array allocation sizing
in the future.  But just leaving the existing uses as-is seems a bit
scary. Fixign them all seems like a lot of fiddly work with some compatibility
hazards :(


Greetings,

Andres Freund

PS: I'm fairly sure that we also are entering UB territory when allocating
space for just the smallest member of a union, as we do a *lot* in numeric.c,
due to all the allocations using NUMERIC_HDRSZ_SHORT. But there's a lot more
standardese to parse to figure out the requirements.


[1]

See also https://godbolt.org/z/98jchG4Ex

/* compiled with gcc -O2 -Warray-bounds */
#include <stddef.h>

struct foo
{
    size_t len;
    /* warning is only emitted if the "flag" struct member is present */
    int flag;
    char data[];
};

extern void *allocme(size_t sz) __attribute__((alloc_size(1)));

struct foo* trigger_warning()
{
    size_t len = 1;
    struct foo *foop;

    foop = allocme(offsetof(struct foo, data) + sizeof(char) * len);
    foop->len = len;

    return foop;
}
struct HeapTupleHeaderData {
        union {
                HeapTupleFields    t_heap;               /*     0    12 */
                DatumTupleFields   t_datum;              /*     0    12 */
        } t_choice;                                      /*     0    12 */
        ItemPointerData            t_ctid __attribute__((__aligned__(2))); /*   
 12     6 */
        uint16                     t_infomask2;          /*    18     2 */
        uint16                     t_infomask;           /*    20     2 */
        uint8                      t_hoff;               /*    22     1 */
        bits8                      t_bits[];             /*    23     0 */

        /* size: 24, cachelines: 1, members: 6 */
        /* padding: 1 */
        /* forced alignments: 1 */
        /* last cacheline: 24 bytes */
} __attribute__((__aligned__(4)));
struct MinimalTupleData {
        uint32                     t_len;                /*     0     4 */
        char                       mt_padding[6];        /*     4     6 */
        uint16                     t_infomask2;          /*    10     2 */
        uint16                     t_infomask;           /*    12     2 */
        uint8                      t_hoff;               /*    14     1 */
        bits8                      t_bits[];             /*    15     0 */

        /* size: 16, cachelines: 1, members: 6 */
        /* padding: 1 */
        /* last cacheline: 16 bytes */
};
struct BTVacuumPostingData {
        IndexTuple                 itup;                 /*     0     8 */
        OffsetNumber               updatedoffset;        /*     8     2 */
        uint16                     ndeletedtids;         /*    10     2 */
        uint16                     deletetids[];         /*    12     0 */

        /* size: 16, cachelines: 1, members: 4 */
        /* padding: 4 */
        /* last cacheline: 16 bytes */
};
struct xl_multixact_create {
        MultiXactId                mid;                  /*     0     4 */

        /* XXX 4 bytes hole, try to pack */

        MultiXactOffset            moff;                 /*     8     8 */
        int32                      nmembers;             /*    16     4 */
        MultiXactMember            members[];            /*    20     0 */

        /* size: 24, cachelines: 1, members: 4 */
        /* sum members: 16, holes: 1, sum holes: 4 */
        /* padding: 4 */
        /* last cacheline: 24 bytes */
};
struct spgxlogVacuumRedirect {
        uint16                     nToPlaceholder;       /*     0     2 */
        OffsetNumber               firstPlaceholder;     /*     2     2 */
        TransactionId              snapshotConflictHorizon; /*     4     4 */
        _Bool                      isCatalogRel;         /*     8     1 */

        /* XXX 1 byte hole, try to pack */

        OffsetNumber               offsets[];            /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* sum members: 9, holes: 1, sum holes: 1 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
};
struct TimeZoneAbbrevTable {
        Size                       tblsize;              /*     0     8 */
        int                        numabbrevs;           /*     8     4 */
        datetkn                    abbrevs[];            /*    12     0 */

        /* size: 16, cachelines: 1, members: 3 */
        /* padding: 4 */
        /* last cacheline: 16 bytes */
};
typedef struct {
        char                       nuls[2];              /*     0     2 */
        uint16                     len;                  /*     2     2 */
        int32                      pid;                  /*     4     4 */
        bits8                      flags;                /*     8     1 */
        char                       data[];               /*     9     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 3 */
        /* last cacheline: 12 bytes */
} PipeProtoHeader;
struct MVDependency {
        double                     degree;               /*     0     8 */
        AttrNumber                 nattributes;          /*     8     2 */
        AttrNumber                 attributes[];         /*    10     0 */

        /* size: 16, cachelines: 1, members: 3 */
        /* padding: 6 */
        /* last cacheline: 16 bytes */
};
struct ReadStream {
        int16                      max_ios;              /*     0     2 */
        int16                      io_combine_limit;     /*     2     2 */
        int16                      ios_in_progress;      /*     4     2 */
        int16                      queue_size;           /*     6     2 */
        int16                      max_pinned_buffers;   /*     8     2 */
        int16                      forwarded_buffers;    /*    10     2 */
        int16                      pinned_buffers;       /*    12     2 */
        int16                      distance;             /*    14     2 */
        int16                      initialized_buffers;  /*    16     2 */

        /* XXX 2 bytes hole, try to pack */

        int                        read_buffers_flags;   /*    20     4 */
        _Bool                      sync_mode;            /*    24     1 */
        _Bool                      batch_mode;           /*    25     1 */
        _Bool                      advice_enabled;       /*    26     1 */
        _Bool                      temporary;            /*    27     1 */
        BlockNumber                buffered_blocknum;    /*    28     4 */
        ReadStreamBlockNumberCB    callback;             /*    32     8 */
        void *                     callback_private_data; /*    40     8 */
        BlockNumber                seq_blocknum;         /*    48     4 */
        BlockNumber                seq_until_processed;  /*    52     4 */
        BlockNumber                pending_read_blocknum; /*    56     4 */
        int16                      pending_read_nblocks; /*    60     2 */

        /* XXX 2 bytes hole, try to pack */

        /* --- cacheline 1 boundary (64 bytes) --- */
        size_t                     per_buffer_data_size; /*    64     8 */
        void *                     per_buffer_data;      /*    72     8 */
        InProgressIO *             ios;                  /*    80     8 */
        int16                      oldest_io_index;      /*    88     2 */
        int16                      next_io_index;        /*    90     2 */
        _Bool                      fast_path;            /*    92     1 */

        /* XXX 1 byte hole, try to pack */

        int16                      oldest_buffer_index;  /*    94     2 */
        int16                      next_buffer_index;    /*    96     2 */

        /* XXX 2 bytes hole, try to pack */

        Buffer                     buffers[];            /*   100     0 */

        /* size: 104, cachelines: 2, members: 30 */
        /* sum members: 93, holes: 4, sum holes: 7 */
        /* padding: 4 */
        /* last cacheline: 40 bytes */
};
struct shm_mq {
        slock_t                    mq_mutex;             /*     0     1 */

        /* XXX 7 bytes hole, try to pack */

        PGPROC *                   mq_receiver;          /*     8     8 */
        PGPROC *                   mq_sender;            /*    16     8 */
        pg_atomic_uint64           mq_bytes_read;        /*    24     8 */
        pg_atomic_uint64           mq_bytes_written;     /*    32     8 */
        Size                       mq_ring_size;         /*    40     8 */
        _Bool                      mq_detached;          /*    48     1 */
        uint8                      mq_ring_offset;       /*    49     1 */
        char                       mq_ring[];            /*    50     0 */

        /* size: 56, cachelines: 1, members: 9 */
        /* sum members: 43, holes: 1, sum holes: 7 */
        /* padding: 6 */
        /* last cacheline: 56 bytes */
};
struct StatEntry {
        uint32                     ndoc;                 /*     0     4 */
        uint32                     nentry;               /*     4     4 */
        struct StatEntry *         left;                 /*     8     8 */
        struct StatEntry *         right;                /*    16     8 */
        uint32                     lenlexeme;            /*    24     4 */
        char                       lexeme[];             /*    28     0 */

        /* size: 32, cachelines: 1, members: 6 */
        /* padding: 4 */
        /* last cacheline: 32 bytes */
};
struct TypeCacheEnumData {
        Oid                        bitmap_base;          /*     0     4 */

        /* XXX 4 bytes hole, try to pack */

        Bitmapset *                sorted_values;        /*     8     8 */
        int                        num_values;           /*    16     4 */
        EnumItem                   enum_values[];        /*    20     0 */

        /* size: 24, cachelines: 1, members: 4 */
        /* sum members: 16, holes: 1, sum holes: 4 */
        /* padding: 4 */
        /* last cacheline: 24 bytes */
};
typedef struct {
        char                       nuls[2];              /*     0     2 */
        uint16                     len;                  /*     2     2 */
        int32                      pid;                  /*     4     4 */
        bits8                      flags;                /*     8     1 */
        char                       data[];               /*     9     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 3 */
        /* last cacheline: 12 bytes */
} PipeProtoHeader;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      val;                  /*     0     4 */
        uint16                     len;                  /*     4     2 */
        uint8                      flag;                 /*     6     1 */
        char                       name[];               /*     7     0 */

        /* size: 8, cachelines: 1, members: 4 */
        /* padding: 1 */
        /* last cacheline: 8 bytes */
} lquery_variant;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        uint16                     firstgood;            /*     6     2 */
        uint16                     flag;                 /*     8     2 */
        char                       data[];               /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
} lquery;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        uint16                     firstgood;            /*     6     2 */
        uint16                     flag;                 /*     8     2 */
        char                       data[];               /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
} lquery;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      val;                  /*     0     4 */
        uint16                     len;                  /*     4     2 */
        uint8                      flag;                 /*     6     1 */
        char                       name[];               /*     7     0 */

        /* size: 8, cachelines: 1, members: 4 */
        /* padding: 1 */
        /* last cacheline: 8 bytes */
} lquery_variant;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        uint16                     firstgood;            /*     6     2 */
        uint16                     flag;                 /*     8     2 */
        char                       data[];               /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
} lquery;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      val;                  /*     0     4 */
        uint16                     len;                  /*     4     2 */
        uint8                      flag;                 /*     6     1 */
        char                       name[];               /*     7     0 */

        /* size: 8, cachelines: 1, members: 4 */
        /* padding: 1 */
        /* last cacheline: 8 bytes */
} lquery_variant;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        uint16                     firstgood;            /*     6     2 */
        uint16                     flag;                 /*     8     2 */
        char                       data[];               /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
} lquery;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      val;                  /*     0     4 */
        uint16                     len;                  /*     4     2 */
        uint8                      flag;                 /*     6     1 */
        char                       name[];               /*     7     0 */

        /* size: 8, cachelines: 1, members: 4 */
        /* padding: 1 */
        /* last cacheline: 8 bytes */
} lquery_variant;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        uint16                     firstgood;            /*     6     2 */
        uint16                     flag;                 /*     8     2 */
        char                       data[];               /*    10     0 */

        /* size: 12, cachelines: 1, members: 5 */
        /* padding: 2 */
        /* last cacheline: 12 bytes */
} lquery;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint16                     numlevel;             /*     4     2 */
        char                       data[];               /*     6     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 2 */
        /* last cacheline: 8 bytes */
} ltree;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint8                      flag;                 /*     4     1 */
        char                       data[];               /*     5     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 3 */
        /* last cacheline: 8 bytes */
} TRGM;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint8                      flag;                 /*     4     1 */
        char                       data[];               /*     5     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 3 */
        /* last cacheline: 8 bytes */
} TRGM;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint8                      flag;                 /*     4     1 */
        char                       data[];               /*     5     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 3 */
        /* last cacheline: 8 bytes */
} TRGM;
typedef struct {
        int32                      vl_len_;              /*     0     4 */
        uint8                      flag;                 /*     4     1 */
        char                       data[];               /*     5     0 */

        /* size: 8, cachelines: 1, members: 3 */
        /* padding: 3 */
        /* last cacheline: 8 bytes */
} TRGM;

Reply via email to