Skip to content

Commit 24ade8a

Browse files
committed
gh-150449: Fix sqlite3.Blob crash with negative-step slices
Reading or writing a sqlite3.Blob with a negative-step slice such as blob[9:0:-2] caused a crash (SystemError on older versions, ValueError on current main) because the C code computed stop - start as the read length, which is negative for negative steps. Fix subscript_slice() and ass_subscript_slice() in Modules/_sqlite/blob.c to handle both positive and negative steps correctly: - For step > 1: keep reading [start, stop) and indexing forward as before. - For step < -1: read the contiguous range [start+(len-1)*step, start] and index backward with stride -step.
1 parent 629da5c commit 24ade8a

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,12 @@ def test_blob_get_slice_negative_index(self):
13901390
def test_blob_get_slice_with_skip(self):
13911391
self.assertEqual(self.blob[0:10:2], b"ti lb")
13921392

1393+
def test_blob_get_slice_with_negative_step(self):
1394+
# gh-150449: negative-step slices must not crash
1395+
self.assertEqual(self.blob[9:0:-2], self.data[9:0:-2])
1396+
self.assertEqual(self.blob[9::-2], self.data[9::-2])
1397+
self.assertEqual(self.blob[::-1], self.data[::-1])
1398+
13931399
def test_blob_set_slice(self):
13941400
self.blob[0:5] = b"12345"
13951401
expected = b"12345" + self.data[5:]
@@ -1406,6 +1412,21 @@ def test_blob_set_slice_with_skip(self):
14061412
expected = b"1h2s3b4o5 " + self.data[10:]
14071413
self.assertEqual(actual, expected)
14081414

1415+
def test_blob_set_slice_with_negative_step(self):
1416+
# gh-150449: negative-step slice assignment must not crash
1417+
expected = bytearray(self.data)
1418+
expected[9:0:-2] = b"12345"
1419+
self.blob[9:0:-2] = b"12345"
1420+
actual = self.cx.execute("select b from test").fetchone()[0]
1421+
self.assertEqual(actual, bytes(expected))
1422+
1423+
# Also verify a slice that includes index 0
1424+
expected2 = bytearray(self.data)
1425+
expected2[9::-2] = b"12345"
1426+
self.blob[9::-2] = b"12345"
1427+
actual2 = self.cx.execute("select b from test").fetchone()[0]
1428+
self.assertEqual(actual2, bytes(expected2))
1429+
14091430
def test_blob_mapping_invalid_index_type(self):
14101431
msg = "indices must be integers"
14111432
with self.assertRaisesRegex(TypeError, msg):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`sqlite3.Blob` raising :exc:`SystemError` (or :exc:`ValueError`
2+
on recent versions) when reading or writing with a negative-step slice such
3+
as ``blob[9:0:-2]``.

Modules/_sqlite/blob.c

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,20 @@ subscript_slice(pysqlite_Blob *self, PyObject *item)
445445
return read_multiple(self, len, start);
446446
}
447447

448-
PyObject *blob = read_multiple(self, stop - start, start);
448+
// For positive step: read the contiguous range [start, stop) and pick
449+
// every step-th byte. For negative step: read the contiguous range
450+
// [start+(len-1)*step, start] and pick bytes in reverse stride order.
451+
Py_ssize_t read_offset, read_length;
452+
if (step > 0) {
453+
read_offset = start;
454+
read_length = stop - start;
455+
}
456+
else {
457+
read_offset = start + (len - 1) * step;
458+
read_length = (len - 1) * (-step) + 1;
459+
}
460+
461+
PyObject *blob = read_multiple(self, read_length, read_offset);
449462
if (blob == NULL) {
450463
return NULL;
451464
}
@@ -456,10 +469,18 @@ subscript_slice(pysqlite_Blob *self, PyObject *item)
456469
return NULL;
457470
}
458471
char *res_buf = PyBytesWriter_GetData(writer);
459-
460472
char *blob_buf = PyBytes_AS_STRING(blob);
461-
for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {
462-
res_buf[i] = blob_buf[j];
473+
474+
if (step > 0) {
475+
for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {
476+
res_buf[i] = blob_buf[j];
477+
}
478+
}
479+
else {
480+
Py_ssize_t neg_step = -step;
481+
for (Py_ssize_t i = 0, j = (len - 1) * neg_step; i < len; i++, j -= neg_step) {
482+
res_buf[i] = blob_buf[j];
483+
}
463484
}
464485
Py_DECREF(blob);
465486
return PyBytesWriter_Finish(writer);
@@ -549,13 +570,34 @@ ass_subscript_slice(pysqlite_Blob *self, PyObject *item, PyObject *value)
549570
rc = inner_write(self, vbuf.buf, len, start);
550571
}
551572
else {
552-
PyObject *blob_bytes = read_multiple(self, stop - start, start);
573+
// For positive step: read the contiguous range [start, stop) and
574+
// update every step-th byte. For negative step: read the contiguous
575+
// range [start+(len-1)*step, start] and update bytes in reverse
576+
// stride order.
577+
Py_ssize_t read_offset, read_length;
578+
if (step > 0) {
579+
read_offset = start;
580+
read_length = stop - start;
581+
}
582+
else {
583+
read_offset = start + (len - 1) * step;
584+
read_length = (len - 1) * (-step) + 1;
585+
}
586+
PyObject *blob_bytes = read_multiple(self, read_length, read_offset);
553587
if (blob_bytes != NULL) {
554588
char *blob_buf = PyBytes_AS_STRING(blob_bytes);
555-
for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {
556-
blob_buf[j] = ((char *)vbuf.buf)[i];
589+
if (step > 0) {
590+
for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {
591+
blob_buf[j] = ((char *)vbuf.buf)[i];
592+
}
593+
}
594+
else {
595+
Py_ssize_t neg_step = -step;
596+
for (Py_ssize_t i = 0, j = (len - 1) * neg_step; i < len; i++, j -= neg_step) {
597+
blob_buf[j] = ((char *)vbuf.buf)[i];
598+
}
557599
}
558-
rc = inner_write(self, blob_buf, stop - start, start);
600+
rc = inner_write(self, blob_buf, read_length, read_offset);
559601
Py_DECREF(blob_bytes);
560602
}
561603
}

0 commit comments

Comments
 (0)