Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Lib/test/test_sqlite3/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,35 @@ def test_delete_connection_text_factory(self):
with self.assertRaises(AttributeError):
del self.con.text_factory

def test_uninitialized_connection_factories(self):
# gh-152817: skipping __init__() should still result in initialized factories (None not Null)
con = sqlite.Connection.__new__(sqlite.Connection)
self.assertIsNone(con.row_factory)
self.assertIs(con.text_factory, str)

def test_uninitialized_cursor_row_factory(self):
# gh-152817: skipping __init__() should still result in initialized factories (None not Null)
# __init__ must not crash.
cur = sqlite.Cursor.__new__(sqlite.Cursor)
self.assertIsNone(cur.row_factory)

def test_subclass_skipping_super_init(self):
# gh-152817: forgetting to call super().__init__() shouldn't leave a NULL {row,text}_factory
class Connection(sqlite.Connection):
def __init__(self, *args, **kwargs):
pass

class Cursor(sqlite.Cursor):
def __init__(self, *args, **kwargs):
pass

con = Connection(":memory:")
self.assertIsNone(con.row_factory)
self.assertIs(con.text_factory, str)

cur = Cursor(self.con)
self.assertIsNone(cur.row_factory)

def test_sqlite_row_index_unicode(self):
row = self.con.execute("select 1 as \xff").fetchone()
self.assertEqual(row["\xff"], 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:mod:`sqlite3`: Prevent ``Cursor`` or ``Connection`` objects from having
uninitialized ``row_factory`` or ``text_factory`` attributes before
``__init__`` is called.
28 changes: 23 additions & 5 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
self->thread_ident = PyThread_get_thread_ident();
self->statement_cache = statement_cache;
self->blobs = blobs;
self->row_factory = Py_NewRef(Py_None);
self->text_factory = Py_NewRef(&PyUnicode_Type);
// re-initialize the factory members here, as tp_clear() is called above in some cases
Py_XSETREF(self->row_factory, Py_NewRef(Py_None));
Py_XSETREF(self->text_factory, Py_NewRef((PyObject *)&PyUnicode_Type));
self->trace_ctx = NULL;
self->progress_ctx = NULL;
self->authorizer_ctx = NULL;
Expand Down Expand Up @@ -339,6 +340,19 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database,
return -1;
}

static PyObject *
pysqlite_connection_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
pysqlite_Connection *self = (pysqlite_Connection *)type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
// row_factory and text_factory should never be uninitialized, even if tp_init is bypassed.
self->row_factory = Py_NewRef(Py_None);
self->text_factory = Py_NewRef((PyObject *)&PyUnicode_Type);
return (PyObject *)self;
}

/*[clinic input]
# Create a new destination 'connect' for the docstring and methoddef only.
# This makes it possible to keep the signatures for Connection.__init__ and
Expand Down Expand Up @@ -549,7 +563,7 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory)
return NULL;
}

if (cursor && self->row_factory != Py_None) {
if (cursor && self->row_factory != NULL && !Py_IsNone(self->row_factory)) {
Py_INCREF(self->row_factory);
Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory);
}
Expand All @@ -561,7 +575,8 @@ static PyObject *
connection_get_row_factory(PyObject *op, void *closure)
{
pysqlite_Connection *self = (pysqlite_Connection *)op;
return Py_NewRef(self->row_factory);
PyObject *row_factory = self->row_factory;
return Py_NewRef(row_factory != NULL ? row_factory : Py_None);
}

static int
Expand All @@ -581,7 +596,9 @@ static PyObject *
connection_get_text_factory(PyObject *op, void *closure)
{
pysqlite_Connection *self = (pysqlite_Connection *)op;
return Py_NewRef(self->text_factory);
PyObject *text_factory = self->text_factory;
return Py_NewRef(text_factory != NULL ? text_factory
: (PyObject *)&PyUnicode_Type);
}

static int
Expand Down Expand Up @@ -2722,6 +2739,7 @@ static PyType_Slot connection_slots[] = {
{Py_tp_methods, connection_methods},
{Py_tp_members, connection_members},
{Py_tp_getset, connection_getset},
{Py_tp_new, pysqlite_connection_new},
{Py_tp_init, pysqlite_connection_init},
{Py_tp_call, pysqlite_connection_call},
{Py_tp_traverse, connection_traverse},
Expand Down
25 changes: 20 additions & 5 deletions Modules/_sqlite/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self,
return 0;
}

static PyObject *
pysqlite_cursor_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
pysqlite_Cursor *self = (pysqlite_Cursor *)type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
// row_factory should never be uninitialized, even if tp_init is bypassed.
self->row_factory = Py_NewRef(Py_None);
return (PyObject *)self;
}

static inline int
stmt_reset(pysqlite_Statement *self)
{
Expand Down Expand Up @@ -413,7 +425,9 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self)
}

nbytes = sqlite3_column_bytes(self->statement->st, i);
if (self->connection->text_factory == (PyObject*)&PyUnicode_Type) {
PyObject *text_factory = self->connection->text_factory;
if (text_factory == NULL ||
text_factory == (PyObject*)&PyUnicode_Type) {
converted = PyUnicode_FromStringAndSize(text, nbytes);
if (!converted && PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) {
PyErr_Clear();
Expand All @@ -434,12 +448,12 @@ _pysqlite_fetch_one_row(pysqlite_Cursor* self)
Py_DECREF(error_msg);
}
}
} else if (self->connection->text_factory == (PyObject*)&PyBytes_Type) {
} else if (text_factory == (PyObject*)&PyBytes_Type) {
converted = PyBytes_FromStringAndSize(text, nbytes);
} else if (self->connection->text_factory == (PyObject*)&PyByteArray_Type) {
} else if (text_factory == (PyObject*)&PyByteArray_Type) {
converted = PyByteArray_FromStringAndSize(text, nbytes);
} else {
converted = PyObject_CallFunction(self->connection->text_factory, "y#", text, nbytes);
converted = PyObject_CallFunction(text_factory, "y#", text, nbytes);
}
} else {
/* coltype == SQLITE_BLOB */
Expand Down Expand Up @@ -1176,7 +1190,7 @@ pysqlite_cursor_iternext(PyObject *op)
}
return NULL;
}
if (!Py_IsNone(self->row_factory)) {
if (self->row_factory != NULL && !Py_IsNone(self->row_factory)) {
PyObject *factory = self->row_factory;
PyObject *args[] = { op, row, };
PyObject *new_row = PyObject_Vectorcall(factory, args, 2, NULL);
Expand Down Expand Up @@ -1421,6 +1435,7 @@ static PyType_Slot cursor_slots[] = {
{Py_tp_methods, cursor_methods},
{Py_tp_members, cursor_members},
{Py_tp_getset, cursor_getsets},
{Py_tp_new, pysqlite_cursor_new},
{Py_tp_init, pysqlite_cursor_init},
{Py_tp_traverse, cursor_traverse},
{Py_tp_clear, cursor_clear},
Expand Down
Loading