diff mbox series

[RFC,07/15] strbuf.c: placate -fanalyzer in strbuf_grow()

Message ID RFC-patch-07.15-cf1a5f3ed0f-20220603T183608Z-avarab@gmail.com (mailing list archive)
State New, archived
Headers show
Series Fix GCC -fanalyzer warnings & add -fanalyzer DEVOPTS mode | expand

Commit Message

Ævar Arnfjörð Bjarmason June 3, 2022, 6:37 p.m. UTC
Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
yell at us about sb->buf[0] dereferencing NULL, this also makes this
code easier to follow.

This BUG() should be unreachable since the state of our "sb->buf" and
"sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
know that, and adding the BUG() also makes it clearer to human readers
that that's what happens here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 strbuf.c | 2 ++
 1 file changed, 2 insertions(+)

Comments

René Scharfe June 4, 2022, 12:24 p.m. UTC | #1
Am 03.06.22 um 20:37 schrieb Ævar Arnfjörð Bjarmason:
> Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
> yell at us about sb->buf[0] dereferencing NULL, this also makes this
> code easier to follow.
>
> This BUG() should be unreachable since the state of our "sb->buf" and
> "sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
> know that, and adding the BUG() also makes it clearer to human readers
> that that's what happens here.
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  strbuf.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/strbuf.c b/strbuf.c
> index dd9eb85527a..61c4630aeeb 100644
> --- a/strbuf.c
> +++ b/strbuf.c
> @@ -97,6 +97,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>  	if (new_buf)
>  		sb->buf = NULL;
>  	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
> +	if (new_buf && !sb->buf)
> +		BUG("for a new buffer ALLOC_GROW() should always do work!");

new_buf is !sb->alloc.  ALLOC_GROW sets buf if sb->len + extra + 1 is
bigger than sb->alloc, which is always true because the variables in the
sum are unsigned, so we end up with at least 1 > 0.  That's easy enough
to see; I wonder why the compiler doesn't agree.  Am I missing
something?

Does setting the attribute returns_nonnull for xrealloc help?
Specifying it explicitly makes sense to me -- expecting the compiler to
infer it automatically across compilation units is probably a bit too
much to ask for, or at least needlessly expensive.

>  	if (new_buf)
>  		sb->buf[0] = '\0';
>  }
Phillip Wood June 4, 2022, 12:46 p.m. UTC | #2
Hi Ævar

[This is an old address that I only have webmail access to, please use phillip.wood@dunelm.org.uk when cc'ing me]

> On 03 June 2022 at 19:37 Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
> 
> 
> Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
> yell at us about sb->buf[0] dereferencing NULL, this also makes this
> code easier to follow.
> 
> This BUG() should be unreachable since the state of our "sb->buf" and
> "sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
> know that, and adding the BUG() also makes it clearer to human readers
> that that's what happens here.
> 
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  strbuf.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/strbuf.c b/strbuf.c
> index dd9eb85527a..61c4630aeeb 100644
> --- a/strbuf.c
> +++ b/strbuf.c
> @@ -97,6 +97,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>  	if (new_buf)
>  		sb->buf = NULL;
>  	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
> +	if (new_buf && !sb->buf)
> +		BUG("for a new buffer ALLOC_GROW() should always do work!");

This is a bit ugly, have you tried adding
__attribute__((malloc (free), returns_nonnull))
to xmalloc() and xrealloc() ?

Best Wishes

Phillip

>  	if (new_buf)
>  		sb->buf[0] = '\0';
>  }
> -- 
> 2.36.1.1124.g577fa9c2ebd
>
Ævar Arnfjörð Bjarmason June 4, 2022, 4:21 p.m. UTC | #3
On Sat, Jun 04 2022, Phillip Wood wrote:

> Hi Ævar
>
> [This is an old address that I only have webmail access to, please use
> phillip.wood@dunelm.org.uk when cc'ing me]

Hrm, I just grabbed it from an old commit of yours I found. Consider
sending in a patch to update .mailmap :)

>> On 03 June 2022 at 19:37 Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>> 
>> 
>> Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
>> yell at us about sb->buf[0] dereferencing NULL, this also makes this
>> code easier to follow.
>> 
>> This BUG() should be unreachable since the state of our "sb->buf" and
>> "sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
>> know that, and adding the BUG() also makes it clearer to human readers
>> that that's what happens here.
>> 
>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>> ---
>>  strbuf.c | 2 ++
>>  1 file changed, 2 insertions(+)
>> 
>> diff --git a/strbuf.c b/strbuf.c
>> index dd9eb85527a..61c4630aeeb 100644
>> --- a/strbuf.c
>> +++ b/strbuf.c
>> @@ -97,6 +97,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>>  	if (new_buf)
>>  		sb->buf = NULL;
>>  	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
>> +	if (new_buf && !sb->buf)
>> +		BUG("for a new buffer ALLOC_GROW() should always do work!");
>
> This is a bit ugly, have you tried adding
> __attribute__((malloc (free), returns_nonnull))
> to xmalloc() and xrealloc() ?
>

Will try to experiment with that, perhaps GCC can be massaged to grok
this somehow.

I do vaguely remember (but couldn't track down where it was) that we
have some config for coverity for this function, due to it also having
trouble with it.
René Scharfe June 4, 2022, 8:37 p.m. UTC | #4
Am 04.06.22 um 18:21 schrieb Ævar Arnfjörð Bjarmason:
>
> On Sat, Jun 04 2022, Phillip Wood wrote:
>
>> Hi Ævar
>>
>> [This is an old address that I only have webmail access to, please use
>> phillip.wood@dunelm.org.uk when cc'ing me]
>
> Hrm, I just grabbed it from an old commit of yours I found. Consider
> sending in a patch to update .mailmap :)
>
>>> On 03 June 2022 at 19:37 Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>>>
>>>
>>> Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
>>> yell at us about sb->buf[0] dereferencing NULL, this also makes this
>>> code easier to follow.
>>>
>>> This BUG() should be unreachable since the state of our "sb->buf" and
>>> "sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
>>> know that, and adding the BUG() also makes it clearer to human readers
>>> that that's what happens here.
>>>
>>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>>> ---
>>>  strbuf.c | 2 ++
>>>  1 file changed, 2 insertions(+)
>>>
>>> diff --git a/strbuf.c b/strbuf.c
>>> index dd9eb85527a..61c4630aeeb 100644
>>> --- a/strbuf.c
>>> +++ b/strbuf.c
>>> @@ -97,6 +97,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>>>  	if (new_buf)
>>>  		sb->buf = NULL;
>>>  	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
>>> +	if (new_buf && !sb->buf)
>>> +		BUG("for a new buffer ALLOC_GROW() should always do work!");
>>
>> This is a bit ugly, have you tried adding
>> __attribute__((malloc (free), returns_nonnull))
>> to xmalloc() and xrealloc() ?
>>
>
> Will try to experiment with that, perhaps GCC can be massaged to grok
> this somehow.
>
> I do vaguely remember (but couldn't track down where it was) that we
> have some config for coverity for this function, due to it also having
> trouble with it.

Good idea, but this attribute doesn't seem to help here.

The following helps, but I don't know why it would be needed -- if alloc
is 0 then any strbuf_grow() call will give a nr of at least 1 (for the
NUL character):


diff --git a/cache.h b/cache.h
index 595582becc..fe10789959 100644
--- a/cache.h
+++ b/cache.h
@@ -707,7 +707,7 @@ int daemonize(void);
  */
 #define ALLOC_GROW(x, nr, alloc) \
 	do { \
-		if ((nr) > alloc) { \
+		if (!alloc || (nr) > alloc) { \
 			if (alloc_nr(alloc) < (nr)) \
 				alloc = (nr); \
 			else \
Phillip Wood June 5, 2022, 10:20 a.m. UTC | #5
On 04/06/2022 21:37, René Scharfe wrote:
> Am 04.06.22 um 18:21 schrieb Ævar Arnfjörð Bjarmason:
>>
>> On Sat, Jun 04 2022, Phillip Wood wrote:
>>
>>> Hi Ævar
>>>
>>> [This is an old address that I only have webmail access to, please use
>>> phillip.wood@dunelm.org.uk when cc'ing me]
>>
>> Hrm, I just grabbed it from an old commit of yours I found. Consider
>> sending in a patch to update .mailmap :)
>>
>>>> On 03 June 2022 at 19:37 Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>>>>
>>>>
>>>> Change the strbuf_grow() function so that GCC v12's -fanalyze doesn't
>>>> yell at us about sb->buf[0] dereferencing NULL, this also makes this
>>>> code easier to follow.
>>>>
>>>> This BUG() should be unreachable since the state of our "sb->buf" and
>>>> "sb->alloc" goes hand-in-hand, but -fanalyzer isn't smart enough to
>>>> know that, and adding the BUG() also makes it clearer to human readers
>>>> that that's what happens here.
>>>>
>>>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>>>> ---
>>>>   strbuf.c | 2 ++
>>>>   1 file changed, 2 insertions(+)
>>>>
>>>> diff --git a/strbuf.c b/strbuf.c
>>>> index dd9eb85527a..61c4630aeeb 100644
>>>> --- a/strbuf.c
>>>> +++ b/strbuf.c
>>>> @@ -97,6 +97,8 @@ void strbuf_grow(struct strbuf *sb, size_t extra)
>>>>   	if (new_buf)
>>>>   		sb->buf = NULL;
>>>>   	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
>>>> +	if (new_buf && !sb->buf)
>>>> +		BUG("for a new buffer ALLOC_GROW() should always do work!");
>>>
>>> This is a bit ugly, have you tried adding
>>> __attribute__((malloc (free), returns_nonnull))
>>> to xmalloc() and xrealloc() ?
>>>
>>
>> Will try to experiment with that, perhaps GCC can be massaged to grok
>> this somehow.
>>
>> I do vaguely remember (but couldn't track down where it was) that we
>> have some config for coverity for this function, due to it also having
>> trouble with it.
> 
> Good idea, but this attribute doesn't seem to help here.

Indeed, I was tricked by the misleading BUG message, the analyzer is not 
complaining that xrealloc() may return NULL, it is taking a path where 
it does not reallocate the buffer. I've copied the full output at the 
end of the message if anyone wants to see the whole thing but the 
relevant part is

            |cache.h:727:20:
            |  727 |                 if ((nr) > alloc) { \
            |      |                    ^
            |      |                    |
            |      |                    (10) following ‘false’ branch...
strbuf.c:99:9: note: in expansion of macro ‘ALLOC_GROW’
            |   99 |         ALLOC_GROW(sb->buf, sb->len + extra + 1, 
sb->alloc);
            |      |         ^~~~~~~~~~

> The following helps, but I don't know why it would be needed -- if alloc
> is 0 then any strbuf_grow() call will give a nr of at least 1 (for the
> NUL character):

Yes it seems to be ignoring the result of our overflow checks and 
assuming that sb->len + extra + 1 can overflow to zero. Having seen the 
number of false positives I'm inclined to think we should take the bug 
fixes and punt the rest of these patches. Maybe after a couple more gcc 
releases the false positive rate will be more manageable. (I tired 
running gcc 11 with -fanalyzer a few months ago and gave up looking at 
the output before I found a single real bug so it has certainly improved 
in gcc 12)

Best Wishes

Phillip

strbuf.c: In function ‘strbuf_grow’:
strbuf.c:101:28: warning: dereference of NULL ‘0’ [CWE-476] 
[-Wanalyzer-null-dereference]
   101 |                 sb->buf[0] = '\0';
       |                 ~~~~~~~~~~~^~~~~~
   ‘strbuf_normalize_path’: events 1-2
     |
     | 1156 | int strbuf_normalize_path(struct strbuf *src)
     |      |     ^~~~~~~~~~~~~~~~~~~~~
     |      |     |
     |      |     (1) entry to ‘strbuf_normalize_path’
     |......
     | 1160 |         strbuf_grow(&dst, src->len);
     |      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
     |      |         |
     |      |         (2) calling ‘strbuf_grow’ from ‘strbuf_normalize_path’
     |
     +--> ‘strbuf_grow’: events 3-4
            |
            |   91 | void strbuf_grow(struct strbuf *sb, size_t extra)
            |      |      ^~~~~~~~~~~
            |      |      |
            |      |      (3) entry to ‘strbuf_grow’
            |......
            |   94 |         if (unsigned_add_overflows(extra, 1) ||
            |      |            ~
            |      |            |
            |      |            (4) following ‘false’ branch (when 
‘extra != 18446744073709551615’)...
            |
          ‘strbuf_grow’: event 5
            |
            |   95 |             unsigned_add_overflows(sb->len, extra + 1))
git-compat-util.h:128:7: note: in definition of macro 
‘unsigned_add_overflows’
            |  128 |     ((b) > maximum_unsigned_value_of_type(a) - (a))
            |      |       ^
            |
          ‘strbuf_grow’: events 6-9
            |
            |strbuf.c:94:46:
            |   94 |         if (unsigned_add_overflows(extra, 1) ||
            |......
            |   97 |         if (new_buf)
            |      |         ~~ ~
            |      |         |  |
            |      |         |  (8) following ‘true’ branch...
            |      |         (7) ...to here
            |   98 |                 sb->buf = NULL;
            |      |                 ~~
            |      |                 |
            |      |                 (9) ...to here
            |
          ‘strbuf_grow’: event 10
            |
            |cache.h:727:20:
            |  727 |                 if ((nr) > alloc) { \
            |      |                    ^
            |      |                    |
            |      |                    (10) following ‘false’ branch...
strbuf.c:99:9: note: in expansion of macro ‘ALLOC_GROW’
            |   99 |         ALLOC_GROW(sb->buf, sb->len + extra + 1, 
sb->alloc);
            |      |         ^~~~~~~~~~
            |
          ‘strbuf_grow’: event 11
            |
            |cache.h:726:12:
            |  726 |         do { \
            |      |            ^
            |      |            |
            |      |            (11) ...to here
strbuf.c:99:9: note: in expansion of macro ‘ALLOC_GROW’
            |   99 |         ALLOC_GROW(sb->buf, sb->len + extra + 1, 
sb->alloc);
            |      |         ^~~~~~~~~~
            |
          ‘strbuf_grow’: events 12-15
            |
            |  100 |         if (new_buf)
            |      |            ^
            |      |            |
            |      |            (12) following ‘true’ branch...
            |  101 |                 sb->buf[0] = '\0';
            |      |                 ~~~~~~~~~~~~~~~~~
            |      |                 | |        |
            |      |                 | |        (15) dereference of NULL 
‘*sb.buf’
            |      |                 | (14) ‘dst.buf’ is NULL
            |      |                 (13) ...to here
diff mbox series

Patch

diff --git a/strbuf.c b/strbuf.c
index dd9eb85527a..61c4630aeeb 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -97,6 +97,8 @@  void strbuf_grow(struct strbuf *sb, size_t extra)
 	if (new_buf)
 		sb->buf = NULL;
 	ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+	if (new_buf && !sb->buf)
+		BUG("for a new buffer ALLOC_GROW() should always do work!");
 	if (new_buf)
 		sb->buf[0] = '\0';
 }