diff mbox series

[06/19] block/stream: fix -Werror=maybe-uninitialized false-positives

Message ID 20240328102052.3499331-7-marcandre.lureau@redhat.com
State New
Headers show
Series -Werror=maybe-uninitialized fixes | expand

Commit Message

Marc-André Lureau March 28, 2024, 10:20 a.m. UTC
From: Marc-André Lureau <marcandre.lureau@redhat.com>

../block/stream.c:193:19: error: ‘unfiltered_bs’ may be used uninitialized [-Werror=maybe-uninitialized]
../block/stream.c:176:5: error: ‘len’ may be used uninitialized [-Werror=maybe-uninitialized]
trace/trace-block.h:906:9: error: ‘ret’ may be used uninitialized [-Werror=maybe-uninitialized]

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 block/stream.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

Comments

Vladimir Sementsov-Ogievskiy March 29, 2024, 8:34 a.m. UTC | #1
On 28.03.24 13:20, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> ../block/stream.c:193:19: error: ‘unfiltered_bs’ may be used uninitialized [-Werror=maybe-uninitialized]
> ../block/stream.c:176:5: error: ‘len’ may be used uninitialized [-Werror=maybe-uninitialized]
> trace/trace-block.h:906:9: error: ‘ret’ may be used uninitialized [-Werror=maybe-uninitialized]
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..

Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?

Actually, "unused variable initialization" is bad thing too.

Anyway, if no better solution for now:
Acked-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>

> ---
>   block/stream.c | 6 +++---
>   1 file changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/block/stream.c b/block/stream.c
> index 7031eef12b..9076203193 100644
> --- a/block/stream.c
> +++ b/block/stream.c
> @@ -155,8 +155,8 @@ static void stream_clean(Job *job)
>   static int coroutine_fn stream_run(Job *job, Error **errp)
>   {
>       StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
> -    BlockDriverState *unfiltered_bs;
> -    int64_t len;
> +    BlockDriverState *unfiltered_bs = NULL;
> +    int64_t len = -1;
>       int64_t offset = 0;
>       int error = 0;
>       int64_t n = 0; /* bytes */
> @@ -177,7 +177,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
>   
>       for ( ; offset < len; offset += n) {
>           bool copy;
> -        int ret;
> +        int ret = -1;
>   
>           /* Note that even when no rate limit is applied we need to yield
>            * with no pending I/O here so that bdrv_drain_all() returns.
Marc-André Lureau April 2, 2024, 9:12 a.m. UTC | #2
Hi

On Fri, Mar 29, 2024 at 12:35 PM Vladimir Sementsov-Ogievskiy
<vsementsov@yandex-team.ru> wrote:
>
> On 28.03.24 13:20, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > ../block/stream.c:193:19: error: ‘unfiltered_bs’ may be used uninitialized [-Werror=maybe-uninitialized]
> > ../block/stream.c:176:5: error: ‘len’ may be used uninitialized [-Werror=maybe-uninitialized]
> > trace/trace-block.h:906:9: error: ‘ret’ may be used uninitialized [-Werror=maybe-uninitialized]
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
>
> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
>


#define WITH_QEMU_LOCK_GUARD_(x, var) \
    for (g_autoptr(QemuLockable) var = \
                qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
         var; \
         qemu_lockable_auto_unlock(var), var = NULL)

I can't think of a clever way to rewrite this. The compiler probably
thinks the loop may not run, due to the "var" condition. But how to
convince it otherwise? it's hard to introduce another variable too..

> Actually, "unused variable initialization" is bad thing too.
>
> Anyway, if no better solution for now:
> Acked-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
>
> > ---
> >   block/stream.c | 6 +++---
> >   1 file changed, 3 insertions(+), 3 deletions(-)
> >
> > diff --git a/block/stream.c b/block/stream.c
> > index 7031eef12b..9076203193 100644
> > --- a/block/stream.c
> > +++ b/block/stream.c
> > @@ -155,8 +155,8 @@ static void stream_clean(Job *job)
> >   static int coroutine_fn stream_run(Job *job, Error **errp)
> >   {
> >       StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
> > -    BlockDriverState *unfiltered_bs;
> > -    int64_t len;
> > +    BlockDriverState *unfiltered_bs = NULL;
> > +    int64_t len = -1;
> >       int64_t offset = 0;
> >       int error = 0;
> >       int64_t n = 0; /* bytes */
> > @@ -177,7 +177,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
> >
> >       for ( ; offset < len; offset += n) {
> >           bool copy;
> > -        int ret;
> > +        int ret = -1;
> >
> >           /* Note that even when no rate limit is applied we need to yield
> >            * with no pending I/O here so that bdrv_drain_all() returns.
>
> --
> Best regards,
> Vladimir
>
>
Vladimir Sementsov-Ogievskiy April 2, 2024, 9:58 a.m. UTC | #3
On 02.04.24 12:12, Marc-André Lureau wrote:
> Hi
> 
> On Fri, Mar 29, 2024 at 12:35 PM Vladimir Sementsov-Ogievskiy
> <vsementsov@yandex-team.ru> wrote:
>>
>> On 28.03.24 13:20, marcandre.lureau@redhat.com wrote:
>>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>>
>>> ../block/stream.c:193:19: error: ‘unfiltered_bs’ may be used uninitialized [-Werror=maybe-uninitialized]
>>> ../block/stream.c:176:5: error: ‘len’ may be used uninitialized [-Werror=maybe-uninitialized]
>>> trace/trace-block.h:906:9: error: ‘ret’ may be used uninitialized [-Werror=maybe-uninitialized]
>>>
>>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>>
>> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
>>
>> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
>>
> 
> 
> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>      for (g_autoptr(QemuLockable) var = \
>                  qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
>           var; \
>           qemu_lockable_auto_unlock(var), var = NULL)
> 
> I can't think of a clever way to rewrite this. The compiler probably
> thinks the loop may not run, due to the "var" condition. But how to
> convince it otherwise? it's hard to introduce another variable too..


hmm. maybe like this?

#define WITH_QEMU_LOCK_GUARD_(x, var) \
     for (g_autoptr(QemuLockable) var = \
                 qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
          var2 = (void *)(true); \
          var2; \
          qemu_lockable_auto_unlock(var), var2 = NULL)


probably, it would be simpler for compiler to understand the logic this way. Could you check?


(actually, will also need to construct var2 name same way as for var)

> 
>> Actually, "unused variable initialization" is bad thing too.
>>
>> Anyway, if no better solution for now:
>> Acked-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
>>
>>> ---
>>>    block/stream.c | 6 +++---
>>>    1 file changed, 3 insertions(+), 3 deletions(-)
>>>
>>> diff --git a/block/stream.c b/block/stream.c
>>> index 7031eef12b..9076203193 100644
>>> --- a/block/stream.c
>>> +++ b/block/stream.c
>>> @@ -155,8 +155,8 @@ static void stream_clean(Job *job)
>>>    static int coroutine_fn stream_run(Job *job, Error **errp)
>>>    {
>>>        StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
>>> -    BlockDriverState *unfiltered_bs;
>>> -    int64_t len;
>>> +    BlockDriverState *unfiltered_bs = NULL;
>>> +    int64_t len = -1;
>>>        int64_t offset = 0;
>>>        int error = 0;
>>>        int64_t n = 0; /* bytes */
>>> @@ -177,7 +177,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
>>>
>>>        for ( ; offset < len; offset += n) {
>>>            bool copy;
>>> -        int ret;
>>> +        int ret = -1;
>>>
>>>            /* Note that even when no rate limit is applied we need to yield
>>>             * with no pending I/O here so that bdrv_drain_all() returns.
>>
>> --
>> Best regards,
>> Vladimir
>>
>>
> 
>
Eric Blake April 2, 2024, 3:34 p.m. UTC | #4
On Tue, Apr 02, 2024 at 12:58:43PM +0300, Vladimir Sementsov-Ogievskiy wrote:
> > > Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
> > > 
> > > Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
> > > 
> > 
> > 
> > #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >      for (g_autoptr(QemuLockable) var = \
> >                  qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
> >           var; \
> >           qemu_lockable_auto_unlock(var), var = NULL)
> > 
> > I can't think of a clever way to rewrite this. The compiler probably
> > thinks the loop may not run, due to the "var" condition. But how to
> > convince it otherwise? it's hard to introduce another variable too..
> 
> 
> hmm. maybe like this?
> 
> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>     for (g_autoptr(QemuLockable) var = \
>                 qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
>          var2 = (void *)(true); \
>          var2; \
>          qemu_lockable_auto_unlock(var), var2 = NULL)
> 
> 
> probably, it would be simpler for compiler to understand the logic this way. Could you check?

Wouldn't that attach __attribute__((cleanup(xxx))) to var2, at which
point we could cause the compiler to call xxx((void*)(true)) if the
user does an early return inside the lock guard, with disastrous
consequences?  Or is the __attribute__ applied only to the first out
of two declarations in a list?
Vladimir Sementsov-Ogievskiy April 2, 2024, 7:24 p.m. UTC | #5
On 02.04.24 18:34, Eric Blake wrote:
> On Tue, Apr 02, 2024 at 12:58:43PM +0300, Vladimir Sementsov-Ogievskiy wrote:
>>>> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
>>>>
>>>> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
>>>>
>>>
>>>
>>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>>>       for (g_autoptr(QemuLockable) var = \
>>>                   qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
>>>            var; \
>>>            qemu_lockable_auto_unlock(var), var = NULL)
>>>
>>> I can't think of a clever way to rewrite this. The compiler probably
>>> thinks the loop may not run, due to the "var" condition. But how to
>>> convince it otherwise? it's hard to introduce another variable too..
>>
>>
>> hmm. maybe like this?
>>
>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>>      for (g_autoptr(QemuLockable) var = \
>>                  qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
>>           var2 = (void *)(true); \
>>           var2; \
>>           qemu_lockable_auto_unlock(var), var2 = NULL)
>>
>>
>> probably, it would be simpler for compiler to understand the logic this way. Could you check?
> 
> Wouldn't that attach __attribute__((cleanup(xxx))) to var2, at which
> point we could cause the compiler to call xxx((void*)(true)) if the
> user does an early return inside the lock guard, with disastrous
> consequences?  Or is the __attribute__ applied only to the first out
> of two declarations in a list?
> 

Oh, most probably you are right, seems g_autoptr apply it to both variables. Also, we don't need qemu_lockable_auto_unlock(var) separate call, if we zero-out another variable. So, me fixing:

#define WITH_QEMU_LOCK_GUARD_(x, var) \
     for (QemuLockable *var __attribute__((cleanup(qemu_lockable_auto_unlock))) = qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
          *var2 = (void *)(true); \
          var2; \
          var2 = NULL)

(and we'll need to modify qemu_lockable_auto_unlock() to take "QemuLockable **x" argument)
Marc-André Lureau April 3, 2024, 8:11 a.m. UTC | #6
Hi

On Tue, Apr 2, 2024 at 11:24 PM Vladimir Sementsov-Ogievskiy
<vsementsov@yandex-team.ru> wrote:
>
> On 02.04.24 18:34, Eric Blake wrote:
> > On Tue, Apr 02, 2024 at 12:58:43PM +0300, Vladimir Sementsov-Ogievskiy wrote:
> >>>> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
> >>>>
> >>>> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
> >>>>
> >>>
> >>>
> >>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >>>       for (g_autoptr(QemuLockable) var = \
> >>>                   qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
> >>>            var; \
> >>>            qemu_lockable_auto_unlock(var), var = NULL)
> >>>
> >>> I can't think of a clever way to rewrite this. The compiler probably
> >>> thinks the loop may not run, due to the "var" condition. But how to
> >>> convince it otherwise? it's hard to introduce another variable too..
> >>
> >>
> >> hmm. maybe like this?
> >>
> >> #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >>      for (g_autoptr(QemuLockable) var = \
> >>                  qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
> >>           var2 = (void *)(true); \
> >>           var2; \
> >>           qemu_lockable_auto_unlock(var), var2 = NULL)
> >>
> >>
> >> probably, it would be simpler for compiler to understand the logic this way. Could you check?
> >
> > Wouldn't that attach __attribute__((cleanup(xxx))) to var2, at which
> > point we could cause the compiler to call xxx((void*)(true)) if the
> > user does an early return inside the lock guard, with disastrous
> > consequences?  Or is the __attribute__ applied only to the first out
> > of two declarations in a list?
> >
>
> Oh, most probably you are right, seems g_autoptr apply it to both variables. Also, we don't need qemu_lockable_auto_unlock(var) separate call, if we zero-out another variable. So, me fixing:
>
> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>      for (QemuLockable *var __attribute__((cleanup(qemu_lockable_auto_unlock))) = qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
>           *var2 = (void *)(true); \
>           var2; \
>           var2 = NULL)
>
> (and we'll need to modify qemu_lockable_auto_unlock() to take "QemuLockable **x" argument)
>

That's almost good enough. I fixed a few things to generate var2.

I applied a similar approach to WITH_GRAPH_RDLOCK_GUARD macro:

--- a/include/block/graph-lock.h
+++ b/include/block/graph-lock.h
@@ -224,13 +224,22 @@ graph_lockable_auto_unlock(GraphLockable *x)

 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GraphLockable, graph_lockable_auto_unlock)

-#define WITH_GRAPH_RDLOCK_GUARD_(var)                                         \
-    for (g_autoptr(GraphLockable) var = graph_lockable_auto_lock(GML_OBJ_()); \
-         var;                                                                 \
-         graph_lockable_auto_unlock(var), var = NULL)
+static inline void TSA_NO_TSA coroutine_fn
+graph_lockable_auto_cleanup(GraphLockable **x)
+{
+    graph_lockable_auto_unlock(*x);
+}
+
+#define WITH_GRAPH_RDLOCK_GUARD__(var) \
+    GraphLockable *var \
+        __attribute__((cleanup(graph_lockable_auto_cleanup))) G_GNUC_UNUSED = \
+       graph_lockable_auto_lock(GML_OBJ_())
+
+#define WITH_GRAPH_RDLOCK_GUARD_(var, var2)                             \
+    for (WITH_GRAPH_RDLOCK_GUARD__(var), *var2 = (void *)true; var2;
var2 = NULL)

 #define WITH_GRAPH_RDLOCK_GUARD() \
-    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__))
+    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__),
glue(graph_lockable_auto, __COUNTER__))

Unfortunately, it doesn't work in all cases. It seems to have issues
with some guards:
../block/stream.c: In function ‘stream_run’:
../block/stream.c:216:12: error: ‘ret’ may be used uninitialized
[-Werror=maybe-uninitialized]
  216 |         if (ret < 0) {


What should we do? change the macros + cherry-pick the missing
false-positives, or keep this series as is?
Vladimir Sementsov-Ogievskiy April 3, 2024, 8:31 a.m. UTC | #7
On 03.04.24 11:11, Marc-André Lureau wrote:
> Hi
> 
> On Tue, Apr 2, 2024 at 11:24 PM Vladimir Sementsov-Ogievskiy
> <vsementsov@yandex-team.ru> wrote:
>>
>> On 02.04.24 18:34, Eric Blake wrote:
>>> On Tue, Apr 02, 2024 at 12:58:43PM +0300, Vladimir Sementsov-Ogievskiy wrote:
>>>>>> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
>>>>>>
>>>>>> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
>>>>>>
>>>>>
>>>>>
>>>>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>>>>>        for (g_autoptr(QemuLockable) var = \
>>>>>                    qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
>>>>>             var; \
>>>>>             qemu_lockable_auto_unlock(var), var = NULL)
>>>>>
>>>>> I can't think of a clever way to rewrite this. The compiler probably
>>>>> thinks the loop may not run, due to the "var" condition. But how to
>>>>> convince it otherwise? it's hard to introduce another variable too..
>>>>
>>>>
>>>> hmm. maybe like this?
>>>>
>>>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>>>>       for (g_autoptr(QemuLockable) var = \
>>>>                   qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
>>>>            var2 = (void *)(true); \
>>>>            var2; \
>>>>            qemu_lockable_auto_unlock(var), var2 = NULL)
>>>>
>>>>
>>>> probably, it would be simpler for compiler to understand the logic this way. Could you check?
>>>
>>> Wouldn't that attach __attribute__((cleanup(xxx))) to var2, at which
>>> point we could cause the compiler to call xxx((void*)(true)) if the
>>> user does an early return inside the lock guard, with disastrous
>>> consequences?  Or is the __attribute__ applied only to the first out
>>> of two declarations in a list?
>>>
>>
>> Oh, most probably you are right, seems g_autoptr apply it to both variables. Also, we don't need qemu_lockable_auto_unlock(var) separate call, if we zero-out another variable. So, me fixing:
>>
>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
>>       for (QemuLockable *var __attribute__((cleanup(qemu_lockable_auto_unlock))) = qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
>>            *var2 = (void *)(true); \
>>            var2; \
>>            var2 = NULL)
>>
>> (and we'll need to modify qemu_lockable_auto_unlock() to take "QemuLockable **x" argument)
>>
> 
> That's almost good enough. I fixed a few things to generate var2.
> 
> I applied a similar approach to WITH_GRAPH_RDLOCK_GUARD macro:
> 
> --- a/include/block/graph-lock.h
> +++ b/include/block/graph-lock.h
> @@ -224,13 +224,22 @@ graph_lockable_auto_unlock(GraphLockable *x)
> 
>   G_DEFINE_AUTOPTR_CLEANUP_FUNC(GraphLockable, graph_lockable_auto_unlock)
> 
> -#define WITH_GRAPH_RDLOCK_GUARD_(var)                                         \
> -    for (g_autoptr(GraphLockable) var = graph_lockable_auto_lock(GML_OBJ_()); \
> -         var;                                                                 \
> -         graph_lockable_auto_unlock(var), var = NULL)
> +static inline void TSA_NO_TSA coroutine_fn
> +graph_lockable_auto_cleanup(GraphLockable **x)
> +{
> +    graph_lockable_auto_unlock(*x);
> +}
> +
> +#define WITH_GRAPH_RDLOCK_GUARD__(var) \
> +    GraphLockable *var \
> +        __attribute__((cleanup(graph_lockable_auto_cleanup))) G_GNUC_UNUSED = \
> +       graph_lockable_auto_lock(GML_OBJ_())
> +
> +#define WITH_GRAPH_RDLOCK_GUARD_(var, var2)                             \
> +    for (WITH_GRAPH_RDLOCK_GUARD__(var), *var2 = (void *)true; var2;
> var2 = NULL)
> 
>   #define WITH_GRAPH_RDLOCK_GUARD() \
> -    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__))
> +    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__),
> glue(graph_lockable_auto, __COUNTER__))
> 
> Unfortunately, it doesn't work in all cases. It seems to have issues
> with some guards:
> ../block/stream.c: In function ‘stream_run’:
> ../block/stream.c:216:12: error: ‘ret’ may be used uninitialized
> [-Werror=maybe-uninitialized]
>    216 |         if (ret < 0) {
> 
> 

So, updated macro helps in some cases, but doesn't help here? Intersting, why.

> What should we do? change the macros + cherry-pick the missing
> false-positives, or keep this series as is?
> 
> 

I think marco + missing is better. No reason to add dead-initializations in cases where new macros helps.
Still, would be good to understand, what's the difference, why it help on some cases and not help in another.
Marc-André Lureau April 3, 2024, 9:24 a.m. UTC | #8
Hi

On Wed, Apr 3, 2024 at 12:31 PM Vladimir Sementsov-Ogievskiy
<vsementsov@yandex-team.ru> wrote:
>
> On 03.04.24 11:11, Marc-André Lureau wrote:
> > Hi
> >
> > On Tue, Apr 2, 2024 at 11:24 PM Vladimir Sementsov-Ogievskiy
> > <vsementsov@yandex-team.ru> wrote:
> >>
> >> On 02.04.24 18:34, Eric Blake wrote:
> >>> On Tue, Apr 02, 2024 at 12:58:43PM +0300, Vladimir Sementsov-Ogievskiy wrote:
> >>>>>> Again, same false-positives, because of WITH_GRAPH_RDLOCK_GUARD()..
> >>>>>>
> >>>>>> Didn't you try to change WITH_ macros somehow, so that compiler believe in our good intentions?
> >>>>>>
> >>>>>
> >>>>>
> >>>>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >>>>>        for (g_autoptr(QemuLockable) var = \
> >>>>>                    qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))); \
> >>>>>             var; \
> >>>>>             qemu_lockable_auto_unlock(var), var = NULL)
> >>>>>
> >>>>> I can't think of a clever way to rewrite this. The compiler probably
> >>>>> thinks the loop may not run, due to the "var" condition. But how to
> >>>>> convince it otherwise? it's hard to introduce another variable too..
> >>>>
> >>>>
> >>>> hmm. maybe like this?
> >>>>
> >>>> #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >>>>       for (g_autoptr(QemuLockable) var = \
> >>>>                   qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
> >>>>            var2 = (void *)(true); \
> >>>>            var2; \
> >>>>            qemu_lockable_auto_unlock(var), var2 = NULL)
> >>>>
> >>>>
> >>>> probably, it would be simpler for compiler to understand the logic this way. Could you check?
> >>>
> >>> Wouldn't that attach __attribute__((cleanup(xxx))) to var2, at which
> >>> point we could cause the compiler to call xxx((void*)(true)) if the
> >>> user does an early return inside the lock guard, with disastrous
> >>> consequences?  Or is the __attribute__ applied only to the first out
> >>> of two declarations in a list?
> >>>
> >>
> >> Oh, most probably you are right, seems g_autoptr apply it to both variables. Also, we don't need qemu_lockable_auto_unlock(var) separate call, if we zero-out another variable. So, me fixing:
> >>
> >> #define WITH_QEMU_LOCK_GUARD_(x, var) \
> >>       for (QemuLockable *var __attribute__((cleanup(qemu_lockable_auto_unlock))) = qemu_lockable_auto_lock(QEMU_MAKE_LOCKABLE_NONNULL((x))), \
> >>            *var2 = (void *)(true); \
> >>            var2; \
> >>            var2 = NULL)
> >>
> >> (and we'll need to modify qemu_lockable_auto_unlock() to take "QemuLockable **x" argument)
> >>
> >
> > That's almost good enough. I fixed a few things to generate var2.
> >
> > I applied a similar approach to WITH_GRAPH_RDLOCK_GUARD macro:
> >
> > --- a/include/block/graph-lock.h
> > +++ b/include/block/graph-lock.h
> > @@ -224,13 +224,22 @@ graph_lockable_auto_unlock(GraphLockable *x)
> >
> >   G_DEFINE_AUTOPTR_CLEANUP_FUNC(GraphLockable, graph_lockable_auto_unlock)
> >
> > -#define WITH_GRAPH_RDLOCK_GUARD_(var)                                         \
> > -    for (g_autoptr(GraphLockable) var = graph_lockable_auto_lock(GML_OBJ_()); \
> > -         var;                                                                 \
> > -         graph_lockable_auto_unlock(var), var = NULL)
> > +static inline void TSA_NO_TSA coroutine_fn
> > +graph_lockable_auto_cleanup(GraphLockable **x)
> > +{
> > +    graph_lockable_auto_unlock(*x);
> > +}
> > +
> > +#define WITH_GRAPH_RDLOCK_GUARD__(var) \
> > +    GraphLockable *var \
> > +        __attribute__((cleanup(graph_lockable_auto_cleanup))) G_GNUC_UNUSED = \
> > +       graph_lockable_auto_lock(GML_OBJ_())
> > +
> > +#define WITH_GRAPH_RDLOCK_GUARD_(var, var2)                             \
> > +    for (WITH_GRAPH_RDLOCK_GUARD__(var), *var2 = (void *)true; var2;
> > var2 = NULL)
> >
> >   #define WITH_GRAPH_RDLOCK_GUARD() \
> > -    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__))
> > +    WITH_GRAPH_RDLOCK_GUARD_(glue(graph_lockable_auto, __COUNTER__),
> > glue(graph_lockable_auto, __COUNTER__))
> >
> > Unfortunately, it doesn't work in all cases. It seems to have issues
> > with some guards:
> > ../block/stream.c: In function ‘stream_run’:
> > ../block/stream.c:216:12: error: ‘ret’ may be used uninitialized
> > [-Werror=maybe-uninitialized]
> >    216 |         if (ret < 0) {
> >
> >
>
> So, updated macro helps in some cases, but doesn't help here? Intersting, why.
>
> > What should we do? change the macros + cherry-pick the missing
> > false-positives, or keep this series as is?
> >
> >
>
> I think marco + missing is better. No reason to add dead-initializations in cases where new macros helps.

Ok

> Still, would be good to understand, what's the difference, why it help on some cases and not help in another.

I don't know, it's like if the analyzer was lazy for this particular
case, although there is nothing much different from other usages.

If I replace:
for (... *var2 = (void *)true; var2;
with:
for (... *var2 = (void *)true; var2 || true;

then it doesn't warn..

Interestingly as well, if I change:
    for (... *var2 = (void *)true; var2; var2 = NULL)
for:
    for (... *var2 = GML_OBJ_(); var2; var2 = NULL)

GML_OBJ_() simply being &(GraphLockable) { }), an empty compound
literal, then it doesn't work, in all usages.

All in all, I am not sure the trick of using var2 is really reliable either.
Eric Blake April 3, 2024, 5:50 p.m. UTC | #9
On Wed, Apr 03, 2024 at 01:24:11PM +0400, Marc-André Lureau wrote:
> > > Unfortunately, it doesn't work in all cases. It seems to have issues
> > > with some guards:
> > > ../block/stream.c: In function ‘stream_run’:
> > > ../block/stream.c:216:12: error: ‘ret’ may be used uninitialized
> > > [-Werror=maybe-uninitialized]
> > >    216 |         if (ret < 0) {
> > >

That one looks like:

int ret;
WITH_GRAPH_RDLOCK_GUARD() {
  ret = ...;
}
if (copy) {
  ret = ...;
}
if (ret < 0)

I suspect the compiler is seeing the uncertainty possible from the
second conditional, and letting it take priority over the certainty
that the tweaked macro provided for the first conditional.

> > >
> >
> > So, updated macro helps in some cases, but doesn't help here? Intersting, why.
> >
> > > What should we do? change the macros + cherry-pick the missing
> > > false-positives, or keep this series as is?

An uglier macro, with sufficient comments as to why it is ugly (in
order to let us have fewer false positives where we have to add
initializers) may be less churn in the code base, but I'm not
necessarily sold on the ugly macro.  Let's see if anyone else
expresses an opinion.


> > >
> > >
> >
> > I think marco + missing is better. No reason to add dead-initializations in cases where new macros helps.
> 
> Ok
> 
> > Still, would be good to understand, what's the difference, why it help on some cases and not help in another.
> 
> I don't know, it's like if the analyzer was lazy for this particular
> case, although there is nothing much different from other usages.
> 
> If I replace:
> for (... *var2 = (void *)true; var2;
> with:
> for (... *var2 = (void *)true; var2 || true;
> 
> then it doesn't warn..

but it also doesn't work.  We want the body to execute exactly once,
not infloop.


> 
> Interestingly as well, if I change:
>     for (... *var2 = (void *)true; var2; var2 = NULL)
> for:
>     for (... *var2 = GML_OBJ_(); var2; var2 = NULL)
> 
> GML_OBJ_() simply being &(GraphLockable) { }), an empty compound
> literal, then it doesn't work, in all usages.

So the compiler is not figuring out that the compound literal is
sufficient for an unconditional one time through the for loop body.

What's worse, different compiler versions will change behavior over
time.  Making the code ugly to pacify a particular compiler, when that
compiler might improve in the future, is a form of chasing windmills.

> 
> All in all, I am not sure the trick of using var2 is really reliable either.

And that's my biggest argument for not making the macro not more
complex than it already is.
Vladimir Sementsov-Ogievskiy April 3, 2024, 9:28 p.m. UTC | #10
On 03.04.24 20:50, Eric Blake wrote:
> On Wed, Apr 03, 2024 at 01:24:11PM +0400, Marc-André Lureau wrote:
>>>> Unfortunately, it doesn't work in all cases. It seems to have issues
>>>> with some guards:
>>>> ../block/stream.c: In function ‘stream_run’:
>>>> ../block/stream.c:216:12: error: ‘ret’ may be used uninitialized
>>>> [-Werror=maybe-uninitialized]
>>>>     216 |         if (ret < 0) {
>>>>
> 
> That one looks like:
> 
> int ret;
> WITH_GRAPH_RDLOCK_GUARD() {
>    ret = ...;
> }
> if (copy) {
>    ret = ...;
> }
> if (ret < 0)
> 
> I suspect the compiler is seeing the uncertainty possible from the
> second conditional, and letting it take priority over the certainty
> that the tweaked macro provided for the first conditional.
> 
>>>>
>>>
>>> So, updated macro helps in some cases, but doesn't help here? Intersting, why.
>>>
>>>> What should we do? change the macros + cherry-pick the missing
>>>> false-positives, or keep this series as is?
> 
> An uglier macro, with sufficient comments as to why it is ugly (in
> order to let us have fewer false positives where we have to add
> initializers) may be less churn in the code base, but I'm not
> necessarily sold on the ugly macro.  Let's see if anyone else
> expresses an opinion.
> 
> 
>>>>
>>>>
>>>
>>> I think marco + missing is better. No reason to add dead-initializations in cases where new macros helps.
>>
>> Ok
>>
>>> Still, would be good to understand, what's the difference, why it help on some cases and not help in another.
>>
>> I don't know, it's like if the analyzer was lazy for this particular
>> case, although there is nothing much different from other usages.
>>
>> If I replace:
>> for (... *var2 = (void *)true; var2;
>> with:
>> for (... *var2 = (void *)true; var2 || true;
>>
>> then it doesn't warn..
> 
> but it also doesn't work.  We want the body to execute exactly once,
> not infloop.
> 
> 
>>
>> Interestingly as well, if I change:
>>      for (... *var2 = (void *)true; var2; var2 = NULL)
>> for:
>>      for (... *var2 = GML_OBJ_(); var2; var2 = NULL)
>>
>> GML_OBJ_() simply being &(GraphLockable) { }), an empty compound
>> literal, then it doesn't work, in all usages.
> 
> So the compiler is not figuring out that the compound literal is
> sufficient for an unconditional one time through the for loop body.
> 
> What's worse, different compiler versions will change behavior over
> time.  Making the code ugly to pacify a particular compiler, when that
> compiler might improve in the future, is a form of chasing windmills.
> 
>>
>> All in all, I am not sure the trick of using var2 is really reliable either.
> 
> And that's my biggest argument for not making the macro not more
> complex than it already is.
> 

All sounds reasonable, I'm not sure now.

I still don't like an idea to satisfy compiler false-positive warnings by extra initializations.. Interesting that older versions do have unitialized-use warnings, but don't fail here (or nobody check?). Is it necessary to fix them at all? Older versions of compiler don't produce these warnings?  Is it possible that some optimizations in new GCC version somehow breaks our WITH_ hack, so that it really lead to uninitialized behavior? And we just mask real problem with these patches?

Wouldn't it more correct to just drop WITH_ hack, and move to a bit uglier but more gcc-native and fair

{
    QEMU_LOCK_GUARD(lock);
    ...
}

?
diff mbox series

Patch

diff --git a/block/stream.c b/block/stream.c
index 7031eef12b..9076203193 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -155,8 +155,8 @@  static void stream_clean(Job *job)
 static int coroutine_fn stream_run(Job *job, Error **errp)
 {
     StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
-    BlockDriverState *unfiltered_bs;
-    int64_t len;
+    BlockDriverState *unfiltered_bs = NULL;
+    int64_t len = -1;
     int64_t offset = 0;
     int error = 0;
     int64_t n = 0; /* bytes */
@@ -177,7 +177,7 @@  static int coroutine_fn stream_run(Job *job, Error **errp)
 
     for ( ; offset < len; offset += n) {
         bool copy;
-        int ret;
+        int ret = -1;
 
         /* Note that even when no rate limit is applied we need to yield
          * with no pending I/O here so that bdrv_drain_all() returns.