diff mbox series

xen/bitops: Fix break with in a for_each_set_bit() loop

Message ID 20241121145000.3107723-1-andrew.cooper3@citrix.com (mailing list archive)
State New
Headers show
Series xen/bitops: Fix break with in a for_each_set_bit() loop | expand

Commit Message

Andrew Cooper Nov. 21, 2024, 2:50 p.m. UTC
for_each_set_bit()'s use of a double for loop had an accidental bug with a
break in the inner loop leading to an infinite outer loop.

Adjust for_each_set_bit() to avoid this behaviour, and add extend
test_for_each_set_bit() with a test case for this.

Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
CC: Jan Beulich <JBeulich@suse.com>
CC: Roger Pau Monné <roger.pau@citrix.com>
CC: Frediano Ziglio <frediano.ziglio@cloud.com>
CC: Stefano Stabellini <sstabellini@kernel.org>
CC: Julien Grall <julien@xen.org>

Both GCC and Clang seem happy with this, even at -O1:

  https://godbolt.org/z/o6ohjrzsY
---
 xen/common/bitops.c      | 16 ++++++++++++++++
 xen/include/xen/bitops.h |  2 +-
 2 files changed, 17 insertions(+), 1 deletion(-)


base-commit: e0058760a0c7935ad0690d8b9babb9050eceedf0

Comments

Frediano Ziglio Nov. 21, 2024, 3:19 p.m. UTC | #1
On Thu, Nov 21, 2024 at 2:50 PM Andrew Cooper <andrew.cooper3@citrix.com> wrote:
>
> for_each_set_bit()'s use of a double for loop had an accidental bug with a
> break in the inner loop leading to an infinite outer loop.
>
> Adjust for_each_set_bit() to avoid this behaviour, and add extend
> test_for_each_set_bit() with a test case for this.
>
> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> ---
> CC: Jan Beulich <JBeulich@suse.com>
> CC: Roger Pau Monné <roger.pau@citrix.com>
> CC: Frediano Ziglio <frediano.ziglio@cloud.com>
> CC: Stefano Stabellini <sstabellini@kernel.org>
> CC: Julien Grall <julien@xen.org>
>
> Both GCC and Clang seem happy with this, even at -O1:
>
>   https://godbolt.org/z/o6ohjrzsY
> ---
>  xen/common/bitops.c      | 16 ++++++++++++++++
>  xen/include/xen/bitops.h |  2 +-
>  2 files changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/xen/common/bitops.c b/xen/common/bitops.c
> index 91ae961440af..0edd62d25c28 100644
> --- a/xen/common/bitops.c
> +++ b/xen/common/bitops.c
> @@ -110,6 +110,22 @@ static void __init test_for_each_set_bit(void)
>
>      if ( ull != ull_res )
>          panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
> +
> +    /* Check that we break from the middle of the loop */
> +    ui = HIDE(0x80001008U);
> +    ui_res = 0;
> +    for_each_set_bit ( i, ui )
> +    {
> +        static __initdata unsigned int count;
> +
> +        if ( count++ > 1 )
> +            break;
> +
> +        ui_res |= 1U << i;
> +    }
> +
> +    if ( ui_res != 0x1008 )
> +        panic("for_each_set_bit(break) expected 0x1008, got %#x\n", ui_res);
>  }
>
>  static void __init test_multiple_bits_set(void)
> diff --git a/xen/include/xen/bitops.h b/xen/include/xen/bitops.h
> index 79615fb89d04..448b2d3e0937 100644
> --- a/xen/include/xen/bitops.h
> +++ b/xen/include/xen/bitops.h
> @@ -299,7 +299,7 @@ static always_inline attr_const unsigned int fls64(uint64_t x)
>   * A copy of @val is taken internally.
>   */
>  #define for_each_set_bit(iter, val)                     \
> -    for ( typeof(val) __v = (val); __v; )               \
> +    for ( typeof(val) __v = (val); __v; __v = 0 )       \
>          for ( unsigned int (iter);                      \
>                __v && ((iter) = ffs_g(__v) - 1, true);   \
>                __v &= __v - 1 )
>
> base-commit: e0058760a0c7935ad0690d8b9babb9050eceedf0

Not a fun of static variables but it's just in the test,

Reviewed-by: Frediano Ziglio <frediano.ziglio@cloud.com>

Frediano
Andrew Cooper Nov. 21, 2024, 3:59 p.m. UTC | #2
On 21/11/2024 3:19 pm, Frediano Ziglio wrote:
> On Thu, Nov 21, 2024 at 2:50 PM Andrew Cooper <andrew.cooper3@citrix.com> wrote:
>> for_each_set_bit()'s use of a double for loop had an accidental bug with a
>> break in the inner loop leading to an infinite outer loop.
>>
>> Adjust for_each_set_bit() to avoid this behaviour, and add extend
>> test_for_each_set_bit() with a test case for this.
>>
>> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
>> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
>> ---
>> CC: Jan Beulich <JBeulich@suse.com>
>> CC: Roger Pau Monné <roger.pau@citrix.com>
>> CC: Frediano Ziglio <frediano.ziglio@cloud.com>
>> CC: Stefano Stabellini <sstabellini@kernel.org>
>> CC: Julien Grall <julien@xen.org>
>>
>> Both GCC and Clang seem happy with this, even at -O1:
>>
>>   https://godbolt.org/z/o6ohjrzsY
>> ---
>>  xen/common/bitops.c      | 16 ++++++++++++++++
>>  xen/include/xen/bitops.h |  2 +-
>>  2 files changed, 17 insertions(+), 1 deletion(-)
>>
>> diff --git a/xen/common/bitops.c b/xen/common/bitops.c
>> index 91ae961440af..0edd62d25c28 100644
>> --- a/xen/common/bitops.c
>> +++ b/xen/common/bitops.c
>> @@ -110,6 +110,22 @@ static void __init test_for_each_set_bit(void)
>>
>>      if ( ull != ull_res )
>>          panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
>> +
>> +    /* Check that we break from the middle of the loop */
>> +    ui = HIDE(0x80001008U);
>> +    ui_res = 0;
>> +    for_each_set_bit ( i, ui )
>> +    {
>> +        static __initdata unsigned int count;
>> +
>> +        if ( count++ > 1 )
>> +            break;
>> +
>> +        ui_res |= 1U << i;
>> +    }
>> +
>> +    if ( ui_res != 0x1008 )
>> +        panic("for_each_set_bit(break) expected 0x1008, got %#x\n", ui_res);
>>  }
>>
>>  static void __init test_multiple_bits_set(void)
>> diff --git a/xen/include/xen/bitops.h b/xen/include/xen/bitops.h
>> index 79615fb89d04..448b2d3e0937 100644
>> --- a/xen/include/xen/bitops.h
>> +++ b/xen/include/xen/bitops.h
>> @@ -299,7 +299,7 @@ static always_inline attr_const unsigned int fls64(uint64_t x)
>>   * A copy of @val is taken internally.
>>   */
>>  #define for_each_set_bit(iter, val)                     \
>> -    for ( typeof(val) __v = (val); __v; )               \
>> +    for ( typeof(val) __v = (val); __v; __v = 0 )       \
>>          for ( unsigned int (iter);                      \
>>                __v && ((iter) = ffs_g(__v) - 1, true);   \
>>                __v &= __v - 1 )
>>
>> base-commit: e0058760a0c7935ad0690d8b9babb9050eceedf0
> Not a fun of static variables but it's just in the test,

Oh, I guess I can do it with just a local variable.  Incremental diff is:

@@ -86,7 +86,7 @@ static void __init test_fls(void)
 
 static void __init test_for_each_set_bit(void)
 {
-    unsigned int  ui,  ui_res = 0;
+    unsigned int  ui,  ui_res = 0, tmp;
     unsigned long ul,  ul_res = 0;
     uint64_t      ull, ull_res = 0;
 
@@ -113,12 +113,11 @@ static void __init test_for_each_set_bit(void)
 
     /* Check that we break from the middle of the loop */
     ui = HIDE(0x80001008U);
+    tmp = 0;
     ui_res = 0;
     for_each_set_bit ( i, ui )
     {
-        static __initdata unsigned int count;
-
-        if ( count++ > 1 )
+        if ( tmp++ > 1 )
             break;
 
         ui_res |= 1U << i;


> Reviewed-by: Frediano Ziglio <frediano.ziglio@cloud.com>

Thanks.  This turns out to be a whole lot easier than I was fearing.

~Andrew
Jan Beulich Nov. 21, 2024, 4:07 p.m. UTC | #3
On 21.11.2024 15:50, Andrew Cooper wrote:
> for_each_set_bit()'s use of a double for loop had an accidental bug with a
> break in the inner loop leading to an infinite outer loop.
> 
> Adjust for_each_set_bit() to avoid this behaviour, and add extend
> test_for_each_set_bit() with a test case for this.
> 
> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>

Reviewed-by: Jan Beulich <jbeulich@suse.com>

Nit: In the title, isn't it supposed to be "within"? And in the 2md paragraph
there looks to be a stray "add".

Jan
Andrew Cooper Nov. 21, 2024, 4:29 p.m. UTC | #4
On 21/11/2024 4:07 pm, Jan Beulich wrote:
> On 21.11.2024 15:50, Andrew Cooper wrote:
>> for_each_set_bit()'s use of a double for loop had an accidental bug with a
>> break in the inner loop leading to an infinite outer loop.
>>
>> Adjust for_each_set_bit() to avoid this behaviour, and add extend
>> test_for_each_set_bit() with a test case for this.
>>
>> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
>> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> Reviewed-by: Jan Beulich <jbeulich@suse.com>

Thanks.

>
> Nit: In the title, isn't it supposed to be "within"?

Yes, already noticed and fixed.

>  And in the 2md paragraph
> there looks to be a stray "add".

Will fix.

~Andrew
Roger Pau Monné Nov. 21, 2024, 4:32 p.m. UTC | #5
On Thu, Nov 21, 2024 at 02:50:00PM +0000, Andrew Cooper wrote:
> for_each_set_bit()'s use of a double for loop had an accidental bug with a
> break in the inner loop leading to an infinite outer loop.
> 
> Adjust for_each_set_bit() to avoid this behaviour, and add extend
> test_for_each_set_bit() with a test case for this.
> 
> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>

Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>

I have to admit it took me a while to understand what was going on.

Subject should likely be "Fix break usage in for_each_set_bit() loop"

Or similar?

> ---
> CC: Jan Beulich <JBeulich@suse.com>
> CC: Roger Pau Monné <roger.pau@citrix.com>
> CC: Frediano Ziglio <frediano.ziglio@cloud.com>
> CC: Stefano Stabellini <sstabellini@kernel.org>
> CC: Julien Grall <julien@xen.org>
> 
> Both GCC and Clang seem happy with this, even at -O1:
> 
>   https://godbolt.org/z/o6ohjrzsY
> ---
>  xen/common/bitops.c      | 16 ++++++++++++++++
>  xen/include/xen/bitops.h |  2 +-
>  2 files changed, 17 insertions(+), 1 deletion(-)
> 
> diff --git a/xen/common/bitops.c b/xen/common/bitops.c
> index 91ae961440af..0edd62d25c28 100644
> --- a/xen/common/bitops.c
> +++ b/xen/common/bitops.c
> @@ -110,6 +110,22 @@ static void __init test_for_each_set_bit(void)
>  
>      if ( ull != ull_res )
>          panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
> +
> +    /* Check that we break from the middle of the loop */
> +    ui = HIDE(0x80001008U);
> +    ui_res = 0;
> +    for_each_set_bit ( i, ui )
> +    {
> +        static __initdata unsigned int count;

Preferably as you suggested without the static variable, I may suggest
that you use ui_tmp instead of plain tmp as the variable name?

Thanks, Roger.
Andrew Cooper Nov. 21, 2024, 5:35 p.m. UTC | #6
On 21/11/2024 4:32 pm, Roger Pau Monné wrote:
> On Thu, Nov 21, 2024 at 02:50:00PM +0000, Andrew Cooper wrote:
>> for_each_set_bit()'s use of a double for loop had an accidental bug with a
>> break in the inner loop leading to an infinite outer loop.
>>
>> Adjust for_each_set_bit() to avoid this behaviour, and add extend
>> test_for_each_set_bit() with a test case for this.
>>
>> Fixes: ed26376f20bf ("xen/bitops: Introduce for_each_set_bit()")
>> Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
> Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>

Thanks.

>
> I have to admit it took me a while to understand what was going on.
>
> Subject should likely be "Fix break usage in for_each_set_bit() loop"
>
> Or similar?

I'll adjust.

>
>> ---
>> CC: Jan Beulich <JBeulich@suse.com>
>> CC: Roger Pau Monné <roger.pau@citrix.com>
>> CC: Frediano Ziglio <frediano.ziglio@cloud.com>
>> CC: Stefano Stabellini <sstabellini@kernel.org>
>> CC: Julien Grall <julien@xen.org>
>>
>> Both GCC and Clang seem happy with this, even at -O1:
>>
>>   https://godbolt.org/z/o6ohjrzsY
>> ---
>>  xen/common/bitops.c      | 16 ++++++++++++++++
>>  xen/include/xen/bitops.h |  2 +-
>>  2 files changed, 17 insertions(+), 1 deletion(-)
>>
>> diff --git a/xen/common/bitops.c b/xen/common/bitops.c
>> index 91ae961440af..0edd62d25c28 100644
>> --- a/xen/common/bitops.c
>> +++ b/xen/common/bitops.c
>> @@ -110,6 +110,22 @@ static void __init test_for_each_set_bit(void)
>>  
>>      if ( ull != ull_res )
>>          panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
>> +
>> +    /* Check that we break from the middle of the loop */
>> +    ui = HIDE(0x80001008U);
>> +    ui_res = 0;
>> +    for_each_set_bit ( i, ui )
>> +    {
>> +        static __initdata unsigned int count;
> Preferably as you suggested without the static variable, I may suggest
> that you use ui_tmp instead of plain tmp as the variable name?

For this, I'd prefer not to.

For ui, ul and ull, there are a pair of variables with precise usage.

This is one random number that gets as far as 2.  And it's test code.

~Andrew
Roger Pau Monné Nov. 22, 2024, 7:54 a.m. UTC | #7
On Thu, Nov 21, 2024 at 05:35:16PM +0000, Andrew Cooper wrote:
> On 21/11/2024 4:32 pm, Roger Pau Monné wrote:
> > On Thu, Nov 21, 2024 at 02:50:00PM +0000, Andrew Cooper wrote:
> >> diff --git a/xen/common/bitops.c b/xen/common/bitops.c
> >> index 91ae961440af..0edd62d25c28 100644
> >> --- a/xen/common/bitops.c
> >> +++ b/xen/common/bitops.c
> >> @@ -110,6 +110,22 @@ static void __init test_for_each_set_bit(void)
> >>  
> >>      if ( ull != ull_res )
> >>          panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
> >> +
> >> +    /* Check that we break from the middle of the loop */
> >> +    ui = HIDE(0x80001008U);
> >> +    ui_res = 0;
> >> +    for_each_set_bit ( i, ui )
> >> +    {
> >> +        static __initdata unsigned int count;
> > Preferably as you suggested without the static variable, I may suggest
> > that you use ui_tmp instead of plain tmp as the variable name?
> 
> For this, I'd prefer not to.
> 
> For ui, ul and ull, there are a pair of variables with precise usage.
> 
> This is one random number that gets as far as 2.  And it's test code.

My suggestion was on the basis that we might need to add more 'tmp'
variables of different types in the future maybe?  No strong opinion
anyway, I'm fine if you prefer to leave as plain 'tmp'.

Thanks, Roger.
diff mbox series

Patch

diff --git a/xen/common/bitops.c b/xen/common/bitops.c
index 91ae961440af..0edd62d25c28 100644
--- a/xen/common/bitops.c
+++ b/xen/common/bitops.c
@@ -110,6 +110,22 @@  static void __init test_for_each_set_bit(void)
 
     if ( ull != ull_res )
         panic("for_each_set_bit(uint64) expected %#"PRIx64", got %#"PRIx64"\n", ull, ull_res);
+
+    /* Check that we break from the middle of the loop */
+    ui = HIDE(0x80001008U);
+    ui_res = 0;
+    for_each_set_bit ( i, ui )
+    {
+        static __initdata unsigned int count;
+
+        if ( count++ > 1 )
+            break;
+
+        ui_res |= 1U << i;
+    }
+
+    if ( ui_res != 0x1008 )
+        panic("for_each_set_bit(break) expected 0x1008, got %#x\n", ui_res);
 }
 
 static void __init test_multiple_bits_set(void)
diff --git a/xen/include/xen/bitops.h b/xen/include/xen/bitops.h
index 79615fb89d04..448b2d3e0937 100644
--- a/xen/include/xen/bitops.h
+++ b/xen/include/xen/bitops.h
@@ -299,7 +299,7 @@  static always_inline attr_const unsigned int fls64(uint64_t x)
  * A copy of @val is taken internally.
  */
 #define for_each_set_bit(iter, val)                     \
-    for ( typeof(val) __v = (val); __v; )               \
+    for ( typeof(val) __v = (val); __v; __v = 0 )       \
         for ( unsigned int (iter);                      \
               __v && ((iter) = ffs_g(__v) - 1, true);   \
               __v &= __v - 1 )