From 98414a9c77dd8fe4250aa3ec11d0937d08722228 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 10 Feb 2025 00:27:28 +0100 Subject: [PATCH 1/2] [3.12] gh-129603: Don't segfault if sqlite3.Row description is None (#129604) (cherry picked from commit 7e6ee50b6b8c760bcefb92ab4ddbc3d85d37a834) --- 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 f0b99b13f6806f..272415b8ab9bb1 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -1955,5 +1955,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 1a1943285ce008..6179e27967894a 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -128,7 +128,6 @@ static PyObject * pysqlite_row_subscript(pysqlite_Row *self, PyObject *idx) { Py_ssize_t _idx; - Py_ssize_t nitems, i; if (PyLong_Check(idx)) { _idx = PyNumber_AsSsize_t(idx, PyExc_IndexError); @@ -140,9 +139,13 @@ pysqlite_row_subscript(pysqlite_Row *self, 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_GET_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); @@ -185,17 +188,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_GET_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; } From 3d08e4177ea3ff2432e11df13c0356cb4621de87 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 10 Feb 2025 00:58:18 +0100 Subject: [PATCH 2/2] Update Lib/test/test_sqlite3/test_dbapi.py --- Lib/test/test_sqlite3/test_dbapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index 272415b8ab9bb1..9d3856a226dba1 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -2016,8 +2016,8 @@ def test_row_is_a_sequence(self): cu = self.cx.execute("SELECT 1") row = cu.fetchone() - self.assertIsSubclass(sqlite.Row, Sequence) - self.assertIsInstance(row, Sequence) + self.assertTrue(issubclass(sqlite.Row, Sequence)) + self.assertTrue(isinstance(row, Sequence)) if __name__ == "__main__":