Re: [PATCH ps/stash-in-c] strbuf_vinsertf: provide the correct buffer size to vsnprintf
- Date: Mon, 4 Feb 2019 11:54:20 +0100 (STD)
- From: Johannes Schindelin <Johannes.Schindelin@xxxxxx>
- Subject: Re: [PATCH ps/stash-in-c] strbuf_vinsertf: provide the correct buffer size to vsnprintf
On Sun, 3 Feb 2019, Johannes Sixt wrote:
> strbuf_vinsertf inserts a formatted string in the middle of an existing
> strbuf value. It makes room in the strbuf by moving existing string to
> the back, then formats the string to insert directly into the hole.
> It uses vsnprintf to format the string. The buffer size provided in the
> invocation is the number of characters available in the allocated space
> behind the final string. This does not make any sense at all.
> Fix it to pass the length of the inserted string plus one for the NUL.
> (The functions saves and restores the character that the NUL occupies.)
> Signed-off-by: Johannes Sixt <j6t@xxxxxxxx>
> I found this, because in my environment I have to compile with
> SNPRINTF_RETURNS_BOGUS. Our implementation of vsnprintf in
> compat/snprintf.c writes into the end of the buffer unconditionally,
> at a spot that is unrelated to the formatted string, and this leads to
> "BUG: a NUL byte in commit log message not allowed" in some "stash"
> strbuf.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
> diff --git a/strbuf.c b/strbuf.c
> index bfbbdadbf3..87ecf7f975 100644
> --- a/strbuf.c
> +++ b/strbuf.c
> @@ -270,7 +270,7 @@ void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap)
> memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos);
> /* vsnprintf() will append a NUL, overwriting one of our characters */
> save = sb->buf[pos + len];
> - len2 = vsnprintf(sb->buf + pos, sb->alloc - sb->len, fmt, ap);
> + len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap);
It is really unfortunate that we use a non-dynamic code review system
where it is pretty impossible to increase the amount of context lines
And in this instance, a single line before the shown context would
Which is in line with moving from `pos` to `pos + len`, and it is also in
line with saving the byte at `pos + len`. And since we must consider that
byte as part of the buffer, and since `vsnprintf()` wants that size of the
buffer (including trailing NUL), `len + 1` is correct.
And since `strbuf_grow(sb, len)` would not in general grow the buffer by
exactly `len` bytes, you indeed fixed a bug.
For historical context, when I first implemented `strbuf_vinsertf()`, I
first grew the buffer, then let `vsnprintf()` write to the end, and then
would rotate the bytes into their correct location. This required the
implementation of an in-place rotation scheme, which was a lot of fun, and
totally unnecessary. That `sb->alloc - sb->len` parameter you fixed was a
remainder of that fun side project.
Thanks for fixing it,
P.S.: Side note: I just realized that we could also write
save = sb->buf[pos];
memmove(sb->buf + pos + len + 1, sb->buf + pos + 1, sb->len - pos - 1);
instead, i.e. not move the first byte just to have it overwritten by
vsnprintf() immediately, saving on moving one byte. But quite frankly, in
this case I do agree that readability is more important than trying to
squeeze out the last bit of performance.
> sb->buf[pos + len] = save;
> if (len2 != len)
> BUG("your vsnprintf is broken (returns inconsistent lengths)");