From 6f029cd04046e89dfea8bc3cac72c4a740830690 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 3 Feb 2025 01:11:54 +0100 Subject: [PATCH] gh-129603: Don't segfault if sqlite3.Row description is None --- Lib/test/test_sqlite3/test_dbapi.py | 65 +++++++++++++++++++ ...-02-03-01-43-16.gh-issue-129603.xge9Tx.rst | 3 + Modules/_sqlite/row.c | 25 ++++--- 3 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index f5ffe2427430e2..c3aa3bf2d7bd9f 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -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() diff --git a/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst b/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst new file mode 100644 index 00000000000000..0d0ec21bddd28f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-03-01-43-16.gh-issue-129603.xge9Tx.rst @@ -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. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 79660008b180dc..2a85716aaf1448 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -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)) { @@ -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); @@ -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; }