https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86259

--- Comment #24 from Martin Sebor <msebor at gcc dot gnu.org> ---
The code in example #21 has the same bug:

    union U u;
    u.s = (struct S){0, 0, 0};

    char *bp = u.s.b;   // <<< bp points to u.s.b

    uintptr_t sp_ip = (uintptr_t)bp - offsetof(struct S,b);   // sp_ip has
u.s.b's provenance

    strcpy(u.xx, "abcdefghijk");
    size_t len = strlen((char *)(union U *)sp_ip + 4);   // still the same
provenance

    puts(len == 7 ? "YES" : "NO");

The strlen call is undefined because (char*)sp_ip is known to point just past
the last element of u.s.b.  It wouldn't matter if there happened to be a valid
string at that address -- there isn't in this case because what's there is a
char[4] with no terminating NUL.  The pointer wasn't derived from that address.
 The pointer was derived from u.s.b and points to u.s.b + sizeof u.s.b, and
there can never be anything valid beyond the end of an object. 

Compile the test case with -fdump-tree-fre1=/dev/stdout to see what GCC sees:

  bp.0_1 = (long unsigned int) &u.s.b;
  sp_ip_9 = bp.0_1 + 18446744073709551612;
  MEM[(char * {ref-all})&u] = MEM[(char * {ref-all})"abcdefghijk"];
  _4 = __builtin_strlen (&u.s.b);

The rule to keep in mind is that pointer arithmetic is only valid within the
boundaries of the smallest subobject it points to.  This applies to structs as
much as arrays.  Just like it's not valid to increment a pointer from a[0][1]
to a[1][0] and dereference the latter in 'char a[2][2]; it's not valid to
increment a pointer to one struct member to point to another and dereference
it.

Reply via email to