> On Dec 6, 2022, at 9:22 AM, Alejandro Colomar via Gcc wrote:
>
> Hi!
>
> In the following function, past_end is a pointer to one-past-the-end of the
> array. Holding such a pointer is legal in C. I use it as a sentinel value
> that helps (1) avoid overrunning the buffer, and (2) detect truncation. I
> mark it as having a size of [0], which clearly states that it can't be
> dereferenced (and as you can see, I don't).
>
> /*
> * This function copies an unterminated string into a string.
> * - It never overruns the dest buffer.
> * - It can be chained, to concatenate strings.
> * - It detects truncation.
> * - Truncation only needs to be tested once after all concatenations.
> * - The name is self-documenting, compared to its alternative: strncat(3).
> */
> char *
> ustr2stpe(char *dst, const char *restrict src, size_t n, char past_end[0])
> {
>bool trunc;
>char *end;
>ptrdiff_t len;
>
>if (dst == past_end)
>return past_end;
>
>trunc = false;
>len = strnlen(src, n);
>if (len > past_end - dst - 1) {
>len = past_end - dst - 1;
>trunc = true;
>}
>
>end = mempcpy(dst, src, len);
>*end = '\0';
>
>return trunc ? past_end : end;
> }
>
>
> If I compile the code above, GCC considers the function definition to be
> fine. However, at call site, it always warns:
>
>
> #define nitems(arr) (sizeof((arr)) / sizeof((arr)[0]))
>
> int
> main(void)
> {
>char pre[4] = "pre.";
>char *post = ".post";
>char *src = "some-long-body.post";
>char dest[100];
>char *p, *past_end;
>
>past_end = dest + nitems(dest);
>p = dest;
>p = ustr2stpe(p, pre, nitems(pre), past_end);
>p = ustr2stpe(p, src, strlen(src) - strlen(post), past_end);
>p = ustr2stpe(p, "", 0, past_end);
>if (p == past_end)
>fprintf(stderr, "truncation\n");
>
>puts(dest); // "pre.some-long-body"
> }
>
>
>
> $ cc -Wall -Wextra ustr2stpe.c
> ustr2stpe.c: In function ‘main’:
> ustr2stpe.c:43:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 43 | p = ustr2stpe(p, pre, nitems(pre), past_end);
> | ^~~~
> ustr2stpe.c:43:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
> ustr2stpe.c:44:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 44 | p = ustr2stpe(p, src, strlen(src) - strlen(post), past_end);
> | ^~~
> ustr2stpe.c:44:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
> ustr2stpe.c:45:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 45 | p = ustr2stpe(p, "", 0, past_end);
> | ^
> ustr2stpe.c:45:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
> ustr2stpe.c:43:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 43 | p = ustr2stpe(p, pre, nitems(pre), past_end);
> | ^~~~
> ustr2stpe.c:43:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
> ustr2stpe.c:44:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 44 | p = ustr2stpe(p, src, strlen(src) - strlen(post), past_end);
> | ^~~
> ustr2stpe.c:44:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
> ustr2stpe.c:45:13: warning: ‘ustr2stpe’ accessing 1 byte in a region of size
> 0 [-Wstringop-overflow=]
> 45 | p = ustr2stpe(p, "", 0, past_end);
> | ^
> ustr2stpe.c:45:13: note: referencing argument 4 of type ‘char[0]’
> ustr2stpe.c:10:1: note: in a call to function ‘ustr2stpe’
> 10 | ustr2stpe(char *dst, const char *restrict src, size_t n, char
> past_end[0])
> | ^
>
>
> The warnings are invalid. While it's true that I'm referencing a pointer of
> size 0, it's false that I'm "accessing 1 byte" in that region. I guess this
> is all