Skip to content

Commit 6c54d0b

Browse files
committed
deque: add bounds check for __getitem__ / __setitem__ / __delitem__
1 parent 44eee4a commit 6c54d0b

File tree

3 files changed

+45
-123
lines changed

3 files changed

+45
-123
lines changed

graalpython/com.oracle.graal.python.test/src/graalpytest.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,23 @@ def assertSequenceEqual(self, expected, actual, msg=None):
154154
class assertRaises():
155155

156156
def __init__(self, exc_type, function=None, *args, **kwargs):
157-
if function is None:
157+
self.function = function
158+
if self.function is None:
158159
self.exc_type = exc_type
159160
else:
160161
try:
161-
function(*args, **kwargs)
162+
self.function(*args, **kwargs)
162163
except exc_type:
163164
pass
164165
else:
165-
assert False, "expected '%r' to raise '%r'" % (function, exc_type)
166+
assert False, "expected '%r' to raise '%r'" % (self.function, exc_type)
166167

167168
def __enter__(self):
168169
return self
169170

170171
def __exit__(self, exc_type, exc, traceback):
171172
if not exc_type:
172-
assert False, "expected '%r' to raise '%r'" % (function, exc_type)
173+
assert False, "expected '%r' to raise '%r'" % (self.function, exc_type)
173174
elif self.exc_type in exc_type.mro():
174175
self.exception = exc
175176
return True

graalpython/com.oracle.graal.python.test/src/tests/test_deque.py

Lines changed: 3 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939
import copy
40-
import pickle
4140
import random
4241
import unittest
4342
from collections import deque
@@ -290,7 +289,6 @@ def test_index(self):
290289

291290
# Test start and stop arguments behavior matches list.index()
292291
elements = 'ABCDEFGHI'
293-
nonelement = 'Z'
294292
d = deque(elements * 2)
295293
s = list(elements * 2)
296294
for start in range(-5 - len(s)*2, 5 + len(s) * 2):
@@ -299,8 +297,7 @@ def test_index(self):
299297
try:
300298
target = s.index(element, start, stop)
301299
except ValueError:
302-
with self.assertRaises(ValueError):
303-
d.index(element, start, stop)
300+
self.assertRaises(ValueError, lambda: d.index(element, start, stop))
304301
else:
305302
self.assertEqual(d.index(element, start, stop), target)
306303

@@ -309,31 +306,6 @@ def test_index_bug_24913(self):
309306
with self.assertRaises(ValueError):
310307
i = d.index("Hello world", 0, 4)
311308

312-
def test_insert(self):
313-
# Test to make sure insert behaves like lists
314-
elements = 'ABCDEFGHI'
315-
for i in range(-5 - len(elements)*2, 5 + len(elements) * 2):
316-
d = deque('ABCDEFGHI')
317-
s = list('ABCDEFGHI')
318-
d.insert(i, 'Z')
319-
s.insert(i, 'Z')
320-
self.assertEqual(list(d), s)
321-
322-
def test_insert_bug_26194(self):
323-
data = 'ABC'
324-
d = deque(data, maxlen=len(data))
325-
with self.assertRaises(IndexError):
326-
d.insert(2, None)
327-
328-
elements = 'ABCDEFGHI'
329-
for i in range(-len(elements), len(elements)):
330-
d = deque(elements, maxlen=len(elements)+1)
331-
d.insert(i, 'Z')
332-
if i >= 0:
333-
self.assertEqual(d[i], 'Z')
334-
else:
335-
self.assertEqual(d[i-1], 'Z')
336-
337309
def test_imul(self):
338310
for n in (-10, -1, 0, 1, 2, 10, 1000):
339311
d = deque()
@@ -411,9 +383,9 @@ def test_delitem(self):
411383
self.assertEqual(len(d), n-i)
412384
j = random.randrange(-len(d), len(d))
413385
val = d[j]
414-
self.assertIn(val, d)
386+
self.assertTrue(val in d)
415387
del d[j]
416-
self.assertNotIn(val, d)
388+
self.assertTrue(val not in d)
417389
self.assertEqual(len(d), 0)
418390

419391
def test_reverse(self):
@@ -614,74 +586,6 @@ def test_roundtrip_iter_init(self):
614586
self.assertNotEqual(id(d), id(e))
615587
self.assertEqual(list(d), list(e))
616588

617-
def test_pickle(self):
618-
for d in deque(range(200)), deque(range(200), 100):
619-
for i in range(pickle.HIGHEST_PROTOCOL + 1):
620-
s = pickle.dumps(d, i)
621-
e = pickle.loads(s)
622-
self.assertNotEqual(id(e), id(d))
623-
self.assertEqual(list(e), list(d))
624-
self.assertEqual(e.maxlen, d.maxlen)
625-
626-
def test_pickle_recursive(self):
627-
for d in deque('abc'), deque('abc', 3):
628-
d.append(d)
629-
for i in range(pickle.HIGHEST_PROTOCOL + 1):
630-
e = pickle.loads(pickle.dumps(d, i))
631-
self.assertNotEqual(id(e), id(d))
632-
self.assertEqual(id(e[-1]), id(e))
633-
self.assertEqual(e.maxlen, d.maxlen)
634-
635-
def test_iterator_pickle(self):
636-
orig = deque(range(200))
637-
data = [i*1.01 for i in orig]
638-
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
639-
# initial iterator
640-
itorg = iter(orig)
641-
dump = pickle.dumps((itorg, orig), proto)
642-
it, d = pickle.loads(dump)
643-
for i, x in enumerate(data):
644-
d[i] = x
645-
self.assertEqual(type(it), type(itorg))
646-
self.assertEqual(list(it), data)
647-
648-
# running iterator
649-
next(itorg)
650-
dump = pickle.dumps((itorg, orig), proto)
651-
it, d = pickle.loads(dump)
652-
for i, x in enumerate(data):
653-
d[i] = x
654-
self.assertEqual(type(it), type(itorg))
655-
self.assertEqual(list(it), data[1:])
656-
657-
# empty iterator
658-
for i in range(1, len(data)):
659-
next(itorg)
660-
dump = pickle.dumps((itorg, orig), proto)
661-
it, d = pickle.loads(dump)
662-
for i, x in enumerate(data):
663-
d[i] = x
664-
self.assertEqual(type(it), type(itorg))
665-
self.assertEqual(list(it), [])
666-
667-
# exhausted iterator
668-
self.assertRaises(StopIteration, next, itorg)
669-
dump = pickle.dumps((itorg, orig), proto)
670-
it, d = pickle.loads(dump)
671-
for i, x in enumerate(data):
672-
d[i] = x
673-
self.assertEqual(type(it), type(itorg))
674-
self.assertEqual(list(it), [])
675-
676-
def test_deepcopy(self):
677-
mut = [10]
678-
d = deque([mut])
679-
e = copy.deepcopy(d)
680-
self.assertEqual(list(d), list(e))
681-
mut[0] = 11
682-
self.assertNotEqual(id(d), id(e))
683-
self.assertNotEqual(list(d), list(e))
684-
685589
def test_copy(self):
686590
mut = [10]
687591
d = deque([mut])

graalpython/lib-graalpython/_collections.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,14 @@ def remove(self, x):
279279
"""Remove first occurrence of value."""
280280
block = self.leftblock
281281
index = self.leftindex
282-
lock = self._getlock()
283-
for i in range(self.len):
282+
n = self.len
283+
for i in range(n):
284284
item = block.data[index]
285-
self._checklock(lock)
285+
if self.len != n:
286+
raise IndexError("deque mutated during remove().")
287+
286288
if item == x:
287-
self._delitem(i)
289+
self.delitem(i)
288290
return
289291
# Advance the block/index pair
290292
index += 1
@@ -395,7 +397,7 @@ def __contains__(self, v):
395397
index = self.leftindex
396398
b = self.leftblock
397399

398-
while n >= 0:
400+
while (n - 1) >= 0:
399401
n -= 1
400402
assert b is not None
401403
item = b.data[index]
@@ -411,36 +413,45 @@ def __contains__(self, v):
411413

412414
return False
413415

414-
def index(self, v, start=0, stop=-1):
415-
lock = self._getlock()
416-
if start < 0:
417-
start += self.len
418-
if start < 0:
419-
start = 0
416+
def _norm_index(self, idx, force_index_to_zero=True):
417+
if idx < 0:
418+
idx += self.len
419+
if idx < 0 and force_index_to_zero:
420+
idx = 0
421+
return idx
420422

421-
if stop < 0:
422-
stop += self.len
423-
if stop < 0:
424-
stop = 0
423+
def _check_index(self, idx):
424+
if idx < 0 or idx >= self.len:
425+
raise IndexError("deque index out of range")
426+
427+
def index(self, v, start=0, stop=None):
428+
if stop is None:
429+
stop = self.len
430+
431+
lock = self._getlock()
432+
start = self._norm_index(start)
433+
stop = self._norm_index(stop)
425434

426435
if stop > self.len:
427436
stop = self.len
428437

429438
if start > stop:
430439
start = stop
431440

441+
assert 0 <= start <= stop <= self.len
442+
432443
index = self.leftindex
433444
b = self.leftblock
434-
435445
i = 0
446+
436447
for i in range(start):
437448
index += 1
438449
if index == BLOCKLEN:
439450
b = b.rightlink
440451
index = 0
441452

442-
n = stop - i
443-
while n >= 0:
453+
n = stop - start
454+
while (n - 1) >= 0:
444455
n -= 1
445456
assert b is not None
446457
item = b.data[index]
@@ -491,23 +502,29 @@ def _locate(self, i):
491502
assert i >= 0
492503
return b, i
493504

494-
def _delitem(self, i):
505+
def delitem(self, i):
495506
# delitem() implemented in terms of rotate for simplicity and
496507
# reasonable performance near the end points.
497508
self.rotate(-i)
498509
self.popleft()
499510
self.rotate(i)
500511

501512
def __getitem__(self, idx):
513+
idx = self._norm_index(idx)
514+
self._check_index(idx)
502515
b, i = self._locate(idx)
503516
return b.data[i]
504517

505518
def __setitem__(self, idx, value):
519+
idx = self._norm_index(idx, force_index_to_zero=False)
520+
self._check_index(idx)
506521
b, i = self._locate(idx)
507522
b.data[i] = value
508523

509524
def __delitem__(self, idx):
510-
self._delitem(idx)
525+
idx = self._norm_index(idx, force_index_to_zero=False)
526+
self._check_index(idx)
527+
self.delitem(idx)
511528

512529
def copy(self):
513530
"""Return a shallow copy of a deque."""

0 commit comments

Comments
 (0)