Skip to content

Commit

Permalink
gh-129603: Don't segfault if sqlite3.Row description is None
Browse files Browse the repository at this point in the history
  • Loading branch information
erlend-aasland committed Feb 3, 2025
1 parent e6c76b9 commit 6f029cd
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 10 deletions.
65 changes: 65 additions & 0 deletions Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1924,5 +1924,70 @@ def wait():
self.assertEqual(proc.returncode, 0)


class RowTests(unittest.TestCase):

def setUp(self):
self.cx = sqlite.connect(":memory:")
self.cx.row_factory = sqlite.Row

def tearDown(self):
self.cx.close()

def test_row_keys(self):
cu = self.cx.execute("SELECT 1 as first, 2 as second")
row = cu.fetchone()
self.assertEqual(row.keys(), ["first", "second"])

def test_row_length(self):
cu = self.cx.execute("SELECT 1, 2, 3")
row = cu.fetchone()
self.assertEqual(len(row), 3)

def test_row_getitem(self):
cu = self.cx.execute("SELECT 1 as a, 2 as b")
row = cu.fetchone()
self.assertEqual(row[0], 1)
self.assertEqual(row[1], 2)
self.assertEqual(row["a"], 1)
self.assertEqual(row["b"], 2)
for key in "nokey", 4, 1.2:
with self.subTest(key=key):
with self.assertRaises(IndexError):
row[key]

def test_row_equality(self):
c1 = self.cx.execute("SELECT 1 as a")
r1 = c1.fetchone()

c2 = self.cx.execute("SELECT 1 as a")
r2 = c2.fetchone()

self.assertIsNot(r1, r2)
self.assertEqual(r1, r2)

c3 = self.cx.execute("SELECT 1 as b")
r3 = c3.fetchone()

self.assertNotEqual(r1, r3)

def test_row_no_description(self):
cu = self.cx.cursor()
self.assertIsNone(cu.description)

row = sqlite.Row(cu, ())
self.assertEqual(row.keys(), [])
with self.assertRaisesRegex(IndexError, "nokey"):
row["nokey"]

def test_row_is_a_sequence(self):
from collections.abc import Sequence

cu = self.cx.execute("SELECT 1")
row = cu.fetchone()

self.assertIsSubclass(sqlite.Row, Sequence)
self.assertIsInstance(row, Sequence)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix bugs where :class:`sqlite3.Row` objects could segfault if their
inherited :attr:`~sqlite3.Cursor.description` was set to ``None``. Patch by
Erlend Aasland.
25 changes: 15 additions & 10 deletions Modules/_sqlite/row.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ static PyObject *
pysqlite_row_subscript(PyObject *op, PyObject *idx)
{
Py_ssize_t _idx;
Py_ssize_t nitems, i;
pysqlite_Row *self = _pysqlite_Row_CAST(op);

if (PyLong_Check(idx)) {
Expand All @@ -151,9 +150,13 @@ pysqlite_row_subscript(PyObject *op, PyObject *idx)
PyObject *item = PyTuple_GetItem(self->data, _idx);
return Py_XNewRef(item);
} else if (PyUnicode_Check(idx)) {
nitems = PyTuple_Size(self->description);
if (Py_IsNone(self->description)) {
PyErr_Format(PyExc_IndexError, "No item with key %R", idx);
return NULL;
}
Py_ssize_t nitems = PyTuple_Size(self->description);

for (i = 0; i < nitems; i++) {
for (Py_ssize_t i = 0; i < nitems; i++) {
PyObject *obj;
obj = PyTuple_GET_ITEM(self->description, i);
obj = PyTuple_GET_ITEM(obj, 0);
Expand Down Expand Up @@ -197,17 +200,19 @@ static PyObject *
pysqlite_row_keys_impl(pysqlite_Row *self)
/*[clinic end generated code: output=efe3dfb3af6edc07 input=7549a122827c5563]*/
{
PyObject* list;
Py_ssize_t nitems, i;

list = PyList_New(0);
PyObject *list = PyList_New(0);
if (!list) {
return NULL;
}
nitems = PyTuple_Size(self->description);
if (Py_IsNone(self->description)) {
return list;
}

for (i = 0; i < nitems; i++) {
if (PyList_Append(list, PyTuple_GET_ITEM(PyTuple_GET_ITEM(self->description, i), 0)) != 0) {
Py_ssize_t nitems = PyTuple_Size(self->description);
for (Py_ssize_t i = 0; i < nitems; i++) {
PyObject *descr = PyTuple_GET_ITEM(self->description, i);
PyObject *name = PyTuple_GET_ITEM(descr, 0);
if (PyList_Append(list, name) < 0) {
Py_DECREF(list);
return NULL;
}
Expand Down

0 comments on commit 6f029cd

Please sign in to comment.