Skip to content

Commit

Permalink
Fixing immutability bug (finally). Bug 176.
Browse files Browse the repository at this point in the history
  • Loading branch information
scott-griffiths committed Jul 7, 2019
1 parent be95461 commit 24d2bef
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 31 deletions.
39 changes: 19 additions & 20 deletions bitstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@ def _prependstore(self, store):
bit_offset = self.offset % 8
if bit_offset:
# first do the byte with the join.
store.setbyte(-1, (store.getbyte(-1) & (255 ^ (255 >> bit_offset)) | \
(self._rawarray[self.byteoffset] & (255 >> bit_offset))))
joinval = (store.getbyte(-1) & (255 ^ (255 >> bit_offset)) |
(self._rawarray[self.byteoffset] & (255 >> bit_offset)))
store._rawarray[-1] = joinval
store._rawarray.extend(self._rawarray[self.byteoffset + 1: self.byteoffset + self.bytelength])
else:
store._rawarray.extend(self._rawarray[self.byteoffset: self.byteoffset + self.bytelength])
Expand Down Expand Up @@ -270,7 +271,7 @@ def offsetcopy(s, newoffset):
return copy.copy(s)
else:
if newoffset == s.offset % 8:
return ByteStore(s.getbyteslice(s.byteoffset, s.byteoffset + s.bytelength), s.bitlength, newoffset)
return type(s)(s.getbyteslice(s.byteoffset, s.byteoffset + s.bytelength), s.bitlength, newoffset)
newdata = []
d = s._rawarray
assert newoffset != s.offset % 8
Expand All @@ -297,7 +298,7 @@ def offsetcopy(s, newoffset):
bits_in_last_byte = 8
if bits_in_last_byte + shiftright > 8:
newdata.append((d[s.byteoffset + s.bytelength - 1] << (8 - shiftright)) & 0xff)
new_s = ByteStore(bytearray(newdata), s.bitlength, newoffset)
new_s = type(s)(bytearray(newdata), s.bitlength, newoffset)
assert new_s.offset == newoffset
return new_s

Expand Down Expand Up @@ -807,6 +808,7 @@ def __new__(cls, auto=None, length=None, offset=None, _cache={}, **kwargs):
except TypeError:
pass
x = super(Bits, cls).__new__(cls)
x._datastore = ConstByteStore(b'')
x._initialise(auto, length, offset, **kwargs)
return x

Expand Down Expand Up @@ -1348,7 +1350,7 @@ def _setbytes_safe(self, data, length=None, offset=0):

def _setbytes_unsafe(self, data, length, offset):
"""Unchecked version of _setbytes_safe."""
self._datastore = ByteStore(data[:], length, offset)
self._datastore = type(self._datastore)(data[:], length, offset)
assert self._assertsanity()

def _readbytes(self, length, start):
Expand Down Expand Up @@ -1440,14 +1442,8 @@ def _setint(self, int_, length=None):
if int_ >= 0:
self._setuint(int_, length)
return
# TODO: We should decide whether to just use the _setuint, or to do the bit flipping,
# based upon which will be quicker. If the -ive number is less than half the maximum
# possible then it's probably quicker to do the bit flipping...

# Do the 2's complement thing. Add one, set to minus number, then flip bits.
int_ += 1
self._setuint(-int_, length)
self._invert_all()
self._setuint((-int_ - 1) ^ ((1 << length) - 1), length)

def _readint(self, length, start):
"""Read bits and interpret as a signed int"""
Expand Down Expand Up @@ -1504,7 +1500,7 @@ def _setuintle(self, uintle, length=None):
raise CreationError("Little-endian integers must be whole-byte. "
"Length = {0} bits.", length)
self._setuint(uintle, length)
self._reversebytes(0, self.len)
self._datastore._rawarray = self._datastore._rawarray[::-1]

def _readuintle(self, length, start):
"""Read bits and interpret as a little-endian unsigned int."""
Expand Down Expand Up @@ -1542,7 +1538,7 @@ def _setintle(self, intle, length=None):
raise CreationError("Little-endian integers must be whole-byte. "
"Length = {0} bits.", length)
self._setint(intle, length)
self._reversebytes(0, self.len)
self._datastore._rawarray = self._datastore._rawarray[::-1]

def _readintle(self, length, start):
"""Read bits and interpret as a little-endian signed int."""
Expand Down Expand Up @@ -2185,10 +2181,8 @@ def _invert(self, pos):

def _invert_all(self):
"""Invert every bit."""
set = self._datastore.setbyte
get = self._datastore.getbyte
for p in xrange(self._datastore.byteoffset, self._datastore.byteoffset + self._datastore.bytelength):
set(p, 256 + ~get(p))
self._datastore._rawarray[p] = 256 + ~self._datastore._rawarray[p]

def _ilshift(self, n):
"""Shift bits by n to the left in place. Return self."""
Expand Down Expand Up @@ -3063,7 +3057,9 @@ def __init__(self, auto=None, length=None, offset=None, **kwargs):
def __new__(cls, auto=None, length=None, offset=None, **kwargs):
x = super(BitArray, cls).__new__(cls)
y = Bits.__new__(BitArray, auto, length, offset, **kwargs)
x._datastore = y._datastore
x._datastore = ByteStore(y._datastore._rawarray[:],
y._datastore.bitlength,
y._datastore.offset)
return x

def __iadd__(self, bs):
Expand Down Expand Up @@ -4135,12 +4131,15 @@ def __init__(self, auto=None, length=None, offset=None, **kwargs):
"""
self._pos = 0
# For mutable BitStreams we always read in files to memory:
if not isinstance(self._datastore, ByteStore):
if not isinstance(self._datastore, (ByteStore, ConstByteStore)):
self._ensureinmemory()

def __new__(cls, auto=None, length=None, offset=None, **kwargs):
x = super(BitStream, cls).__new__(cls)
x._initialise(auto, length, offset, **kwargs)
y = ConstBitStream.__new__(BitStream, auto, length, offset, **kwargs)
x._datastore = ByteStore(y._datastore._rawarray[:],
y._datastore.bitlength,
y._datastore.offset)
return x

def __copy__(self):
Expand Down
32 changes: 32 additions & 0 deletions test/test_bits.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,35 @@ def testContains(self):
self.assertTrue('0b1' in Bits('0xf'))
self.assertFalse('0b0' in Bits('0xf'))


class ByteStoreImmutablity(unittest.TestCase):

def testBitsDataStoreType(self):
a = Bits('0b1')
b = Bits('0b111')
c = a + b
self.assertEqual(type(a._datastore), ConstByteStore)
self.assertEqual(type(b._datastore), ConstByteStore)
self.assertEqual(type(c._datastore), ConstByteStore)

def testImmutabilityBugAppend(self):
a = Bits('0b111')
b = a + '0b000'
c = BitArray(b)
c[1] = 0
self.assertEqual(c.bin, '101000')
self.assertEqual(a.bin, '111')
self.assertEqual(b.bin, '111000')
self.assertEqual(type(b._datastore), ConstByteStore)

def testImmutabilityBugPrepend(self):
a = Bits('0b111')
b = '0b000' + a
c = BitArray(b)
c[1] = 1
self.assertEqual(b.bin, '000111')
self.assertEqual(c.bin, '010111')

def testImmutabilityBugCreation(self):
a = Bits()
self.assertEqual(type(a._datastore), ConstByteStore)
2 changes: 1 addition & 1 deletion test/test_bitstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ def testOneSingleByte(self):
s = ByteStore(bytearray([1, 0]), 2, 7)
t = ByteStore(bytearray([64]), 2, 1)
self.assertTrue(equal(s, t))
self.assertTrue(equal(t, s))
self.assertTrue(equal(t, s))
10 changes: 0 additions & 10 deletions test/test_bitstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -3849,16 +3849,6 @@ def testInteger(self):
self.assertRaises(bitstring.InterpretError, a.read, 'uint:0')
self.assertRaises(bitstring.InterpretError, a.read, 'float:0')

#class EfficientBitsCopies(unittest.TestCase):
#
# def testBitsCopy(self):
# a = ConstBitStream('0xff')
# b = ConstBitStream(a)
# c = a[:]
# d = copy.copy(a)
# self.assertTrue(a._datastore is b._datastore)
# self.assertTrue(a._datastore is c._datastore)
# self.assertTrue(a._datastore is d._datastore)

class InitialiseFromBytes(unittest.TestCase):
def testBytesBehaviour(self):
Expand Down

0 comments on commit 24d2bef

Please sign in to comment.