scott-griffiths / bitstring

A Python module to help you manage your bits
https://bitstring.readthedocs.io/en/stable/index.html
MIT License
401 stars 67 forks source link

Binary assignment operator with a slice are not saved in the backing BitArray #297

Closed clayton13 closed 10 months ago

clayton13 commented 10 months ago

Hi,

Running latest release from pip, bitstring==4.1.2 and Python 3.11.5.

I have been playing around with slices and the binary operations not being saved caught me a little off guard, as they work if performed directly on the BitArray, is this expected behavior?

I see there is a bit of an overhaul in progress on main, if this release is potentially deprecated and this is not expected, would it be possible to add a test case for this in a future version?


Imports:

from bitstring import Bits, BitArray
import bitstring
bitstring.lsb0 = True

Working example:

a = BitArray('0b11101')
assert a.b == '11101'

# basic masks read
assert a.u & 0b01000 == 0b01000
assert a.u ^ 0b01000 == 0b10101
assert a.u | 0b00010 == 0b11111

# fields read
assert a[0:4].u & 0b01000 == 0b1000
assert a[0:4].u ^ 0b01000 == 0b0101
assert a[0:4].u | 0b00010 == 0b1111

Failing:

# basic write, set bit 1 (or)
a = BitArray('0b11101')
assert a[1] == False
a.u |= 0b00010
assert a[1] == True

# field write, set bit 1 (or)
a = BitArray('0b11101')
assert a[1] == False
a[0:4].u |= 0b0010
assert a[1] == True # fails, a.b = '11101'

I traced it through to here, where _uint2bitstore(uint, length) does return the expected value (15) yet the next operation on a returns the original value (13).

https://github.com/scott-griffiths/bitstring/blob/2dd3568e0ea90e90b39d2a46dd783051309b93cf/bitstring/classes.py#L1103

scott-griffiths commented 10 months ago

Hi, thanks for the bug report. The 4.1.2 release should be (almost) bug free, but the lsb0 code is relatively new so I wouldn't be surprised if there are still some edge cases.

The problem here though is a bit subtle, and it wasn't obvious to me, but I don't think it's a bug.

If you look at the line a[0:4].u |= 0b0010 what's happening is:

1) a slice of a is created. This is a new BitArray object with a copy of the data (BitArray is mutable so it can't be a view on the data). 2) This new BitArray gets converted to an unsigned integer. 3) That integer gets 'or'ed with the integer 0b0010 4) The new BitArray's unsigned integer property is set to the new integer value. 5) The new BitArray is thrown away as it's not referred to again!

To look at it another way, because of the conversion to an integer the |= operator is acting on that integer, rather than the slice of a.

The line is equivalent to this line, which doesn't change the value of a:

>>> a[0:4].u = a[0:4].u | 0b0010

whereas what I think you need is this line, which does change a (and does work).

>>> a[0:4] = a[0:4].u | 0b0010

Incidentally, another way of doing this would be just a[0:4] |= '0b0010'

Hopefully that makes sense?