Issue #27861: Fixed a crash in sqlite3.Connection.cursor() when a factory
creates not a cursor. Patch by Xiang Zhang.
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index 2b730e8..6b13172 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -256,11 +256,11 @@
:ref:`sqlite3-controlling-transactions` for a more detailed explanation.
- .. method:: cursor([cursorClass])
+ .. method:: cursor(factory=Cursor)
- The cursor method accepts a single optional parameter *cursorClass*. If
- supplied, this must be a custom cursor class that extends
- :class:`sqlite3.Cursor`.
+ The cursor method accepts a single optional parameter *factory*. If
+ supplied, this must be a callable returning an instance of :class:`Cursor`
+ or its subclasses.
.. method:: commit()
diff --git a/Lib/sqlite3/test/factory.py b/Lib/sqlite3/test/factory.py
index 0e0196a..b9e9cd7 100644
--- a/Lib/sqlite3/test/factory.py
+++ b/Lib/sqlite3/test/factory.py
@@ -58,8 +58,20 @@
self.con.close()
def CheckIsInstance(self):
- cur = self.con.cursor(factory=MyCursor)
+ cur = self.con.cursor()
+ self.assertIsInstance(cur, sqlite.Cursor)
+ cur = self.con.cursor(MyCursor)
self.assertIsInstance(cur, MyCursor)
+ cur = self.con.cursor(factory=lambda con: MyCursor(con))
+ self.assertIsInstance(cur, MyCursor)
+
+ def CheckInvalidFactory(self):
+ # not a callable at all
+ self.assertRaises(TypeError, self.con.cursor, None)
+ # invalid callable with not exact one argument
+ self.assertRaises(TypeError, self.con.cursor, lambda: None)
+ # invalid callable returning non-cursor
+ self.assertRaises(TypeError, self.con.cursor, lambda con: None)
class RowFactoryTestsBackwardsCompat(unittest.TestCase):
def setUp(self):
@@ -173,10 +185,12 @@
def CheckFakeCursorClass(self):
# Issue #24257: Incorrect use of PyObject_IsInstance() caused
# segmentation fault.
+ # Issue #27861: Also applies for cursor factory.
class FakeCursor(str):
__class__ = sqlite.Cursor
- cur = self.con.cursor(factory=FakeCursor)
- self.assertRaises(TypeError, sqlite.Row, cur, ())
+ self.con.row_factory = sqlite.Row
+ self.assertRaises(TypeError, self.con.cursor, FakeCursor)
+ self.assertRaises(TypeError, sqlite.Row, FakeCursor(), ())
def tearDown(self):
self.con.close()
diff --git a/Misc/NEWS b/Misc/NEWS
index e19cab4..1fd3d85 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -33,6 +33,9 @@
Library
-------
+- Issue #27861: Fixed a crash in sqlite3.Connection.cursor() when a factory
+ creates not a cursor. Patch by Xiang Zhang.
+
- Issue #19884: Avoid spurious output on OS X with Gnu Readline.
- Issue #10513: Fix a regression in Connection.commit(). Statements should
diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c
index 78cc9be..24b39c1 100644
--- a/Modules/_sqlite/connection.c
+++ b/Modules/_sqlite/connection.c
@@ -324,7 +324,7 @@
PyObject* pysqlite_connection_cursor(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
{
- static char *kwlist[] = {"factory", NULL, NULL};
+ static char *kwlist[] = {"factory", NULL};
PyObject* factory = NULL;
PyObject* cursor;
@@ -341,7 +341,16 @@
factory = (PyObject*)&pysqlite_CursorType;
}
- cursor = PyObject_CallFunction(factory, "O", self);
+ cursor = PyObject_CallFunctionObjArgs(factory, (PyObject *)self, NULL);
+ if (cursor == NULL)
+ return NULL;
+ if (!PyObject_TypeCheck(cursor, &pysqlite_CursorType)) {
+ PyErr_Format(PyExc_TypeError,
+ "factory must return a cursor, not %.100s",
+ Py_TYPE(cursor)->tp_name);
+ Py_DECREF(cursor);
+ return NULL;
+ }
_pysqlite_drop_unused_cursor_references(self);