closes bpo-37347: Fix refcount problem in sqlite3. (GH-14268)
This commit is contained in:
parent
0827064c95
commit
b9a0376b0d
|
@ -25,6 +25,7 @@ import datetime
|
||||||
import unittest
|
import unittest
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
import weakref
|
import weakref
|
||||||
|
import functools
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
class RegressionTests(unittest.TestCase):
|
class RegressionTests(unittest.TestCase):
|
||||||
|
@ -383,72 +384,26 @@ class RegressionTests(unittest.TestCase):
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
del self.con.isolation_level
|
del self.con.isolation_level
|
||||||
|
|
||||||
|
def CheckBpo37347(self):
|
||||||
|
class Printer:
|
||||||
|
def log(self, *args):
|
||||||
|
return sqlite.SQLITE_OK
|
||||||
|
|
||||||
class UnhashableFunc:
|
for method in [self.con.set_trace_callback,
|
||||||
__hash__ = None
|
functools.partial(self.con.set_progress_handler, n=1),
|
||||||
|
self.con.set_authorizer]:
|
||||||
|
printer_instance = Printer()
|
||||||
|
method(printer_instance.log)
|
||||||
|
method(printer_instance.log)
|
||||||
|
self.con.execute("select 1") # trigger seg fault
|
||||||
|
method(None)
|
||||||
|
|
||||||
def __init__(self, return_value=None):
|
|
||||||
self.calls = 0
|
|
||||||
self.return_value = return_value
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
self.calls += 1
|
|
||||||
return self.return_value
|
|
||||||
|
|
||||||
|
|
||||||
class UnhashableCallbacksTestCase(unittest.TestCase):
|
|
||||||
"""
|
|
||||||
https://bugs.python.org/issue34052
|
|
||||||
|
|
||||||
Registering unhashable callbacks raises TypeError, callbacks are not
|
|
||||||
registered in SQLite after such registration attempt.
|
|
||||||
"""
|
|
||||||
def setUp(self):
|
|
||||||
self.con = sqlite.connect(':memory:')
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.con.close()
|
|
||||||
|
|
||||||
def test_progress_handler(self):
|
|
||||||
f = UnhashableFunc(return_value=0)
|
|
||||||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
|
||||||
self.con.set_progress_handler(f, 1)
|
|
||||||
self.con.execute('SELECT 1')
|
|
||||||
self.assertFalse(f.calls)
|
|
||||||
|
|
||||||
def test_func(self):
|
|
||||||
func_name = 'func_name'
|
|
||||||
f = UnhashableFunc()
|
|
||||||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
|
||||||
self.con.create_function(func_name, 0, f)
|
|
||||||
msg = 'no such function: %s' % func_name
|
|
||||||
with self.assertRaisesRegex(sqlite.OperationalError, msg):
|
|
||||||
self.con.execute('SELECT %s()' % func_name)
|
|
||||||
self.assertFalse(f.calls)
|
|
||||||
|
|
||||||
def test_authorizer(self):
|
|
||||||
f = UnhashableFunc(return_value=sqlite.SQLITE_DENY)
|
|
||||||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
|
||||||
self.con.set_authorizer(f)
|
|
||||||
self.con.execute('SELECT 1')
|
|
||||||
self.assertFalse(f.calls)
|
|
||||||
|
|
||||||
def test_aggr(self):
|
|
||||||
class UnhashableType(type):
|
|
||||||
__hash__ = None
|
|
||||||
aggr_name = 'aggr_name'
|
|
||||||
with self.assertRaisesRegex(TypeError, 'unhashable type'):
|
|
||||||
self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {}))
|
|
||||||
msg = 'no such function: %s' % aggr_name
|
|
||||||
with self.assertRaisesRegex(sqlite.OperationalError, msg):
|
|
||||||
self.con.execute('SELECT %s()' % aggr_name)
|
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
regression_suite = unittest.makeSuite(RegressionTests, "Check")
|
regression_suite = unittest.makeSuite(RegressionTests, "Check")
|
||||||
return unittest.TestSuite((
|
return unittest.TestSuite((
|
||||||
regression_suite,
|
regression_suite,
|
||||||
unittest.makeSuite(UnhashableCallbacksTestCase),
|
|
||||||
))
|
))
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
|
|
|
@ -1870,3 +1870,4 @@ Diego Rojas
|
||||||
Edison Abahurire
|
Edison Abahurire
|
||||||
Geoff Shannon
|
Geoff Shannon
|
||||||
Batuhan Taskaya
|
Batuhan Taskaya
|
||||||
|
Aleksandr Balezin
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
:meth:`sqlite3.Connection.create_aggregate`,
|
||||||
|
:meth:`sqlite3.Connection.create_function`,
|
||||||
|
:meth:`sqlite3.Connection.set_authorizer`,
|
||||||
|
:meth:`sqlite3.Connection.set_progress_handler`
|
||||||
|
:meth:`sqlite3.Connection.set_trace_callback`
|
||||||
|
methods lead to segfaults if some of these methods are called twice with an equal object but not the same. Now callbacks are stored more carefully. Patch by Aleksandr Balezin.
|
|
@ -186,10 +186,9 @@ int pysqlite_connection_init(pysqlite_Connection* self, PyObject* args, PyObject
|
||||||
}
|
}
|
||||||
self->check_same_thread = check_same_thread;
|
self->check_same_thread = check_same_thread;
|
||||||
|
|
||||||
Py_XSETREF(self->function_pinboard, PyDict_New());
|
self->function_pinboard_trace_callback = NULL;
|
||||||
if (!self->function_pinboard) {
|
self->function_pinboard_progress_handler = NULL;
|
||||||
return -1;
|
self->function_pinboard_authorizer_cb = NULL;
|
||||||
}
|
|
||||||
|
|
||||||
Py_XSETREF(self->collations, PyDict_New());
|
Py_XSETREF(self->collations, PyDict_New());
|
||||||
if (!self->collations) {
|
if (!self->collations) {
|
||||||
|
@ -249,19 +248,18 @@ void pysqlite_connection_dealloc(pysqlite_Connection* self)
|
||||||
|
|
||||||
/* Clean up if user has not called .close() explicitly. */
|
/* Clean up if user has not called .close() explicitly. */
|
||||||
if (self->db) {
|
if (self->db) {
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
SQLITE3_CLOSE(self->db);
|
SQLITE3_CLOSE(self->db);
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_XDECREF(self->isolation_level);
|
Py_XDECREF(self->isolation_level);
|
||||||
Py_XDECREF(self->function_pinboard);
|
Py_XDECREF(self->function_pinboard_trace_callback);
|
||||||
|
Py_XDECREF(self->function_pinboard_progress_handler);
|
||||||
|
Py_XDECREF(self->function_pinboard_authorizer_cb);
|
||||||
Py_XDECREF(self->row_factory);
|
Py_XDECREF(self->row_factory);
|
||||||
Py_XDECREF(self->text_factory);
|
Py_XDECREF(self->text_factory);
|
||||||
Py_XDECREF(self->collations);
|
Py_XDECREF(self->collations);
|
||||||
Py_XDECREF(self->statements);
|
Py_XDECREF(self->statements);
|
||||||
Py_XDECREF(self->cursors);
|
Py_XDECREF(self->cursors);
|
||||||
|
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,9 +340,7 @@ PyObject* pysqlite_connection_close(pysqlite_Connection* self, PyObject* args)
|
||||||
pysqlite_do_all_statements(self, ACTION_FINALIZE, 1);
|
pysqlite_do_all_statements(self, ACTION_FINALIZE, 1);
|
||||||
|
|
||||||
if (self->db) {
|
if (self->db) {
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
rc = SQLITE3_CLOSE(self->db);
|
rc = SQLITE3_CLOSE(self->db);
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
_pysqlite_seterror(self->db, NULL);
|
_pysqlite_seterror(self->db, NULL);
|
||||||
|
@ -808,6 +804,11 @@ static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self)
|
||||||
Py_SETREF(self->cursors, new_list);
|
Py_SETREF(self->cursors, new_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _destructor(void* args)
|
||||||
|
{
|
||||||
|
Py_DECREF((PyObject*)args);
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
|
PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
|
||||||
{
|
{
|
||||||
static char *kwlist[] = {"name", "narg", "func", "deterministic", NULL};
|
static char *kwlist[] = {"name", "narg", "func", "deterministic", NULL};
|
||||||
|
@ -843,17 +844,16 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec
|
||||||
flags |= SQLITE_DETERMINISTIC;
|
flags |= SQLITE_DETERMINISTIC;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) {
|
Py_INCREF(func);
|
||||||
return NULL;
|
rc = sqlite3_create_function_v2(self->db,
|
||||||
}
|
name,
|
||||||
rc = sqlite3_create_function(self->db,
|
narg,
|
||||||
name,
|
flags,
|
||||||
narg,
|
(void*)func,
|
||||||
flags,
|
_pysqlite_func_callback,
|
||||||
(void*)func,
|
NULL,
|
||||||
_pysqlite_func_callback,
|
NULL,
|
||||||
NULL,
|
&_destructor); // will decref func
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
/* Workaround for SQLite bug: no error code or string is available here */
|
/* Workaround for SQLite bug: no error code or string is available here */
|
||||||
|
@ -880,11 +880,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje
|
||||||
kwlist, &name, &n_arg, &aggregate_class)) {
|
kwlist, &name, &n_arg, &aggregate_class)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
Py_INCREF(aggregate_class);
|
||||||
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) {
|
rc = sqlite3_create_function_v2(self->db,
|
||||||
return NULL;
|
name,
|
||||||
}
|
n_arg,
|
||||||
rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback);
|
SQLITE_UTF8,
|
||||||
|
(void*)aggregate_class,
|
||||||
|
0,
|
||||||
|
&_pysqlite_step_callback,
|
||||||
|
&_pysqlite_final_callback,
|
||||||
|
&_destructor); // will decref func
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
/* Workaround for SQLite bug: no error code or string is available here */
|
/* Workaround for SQLite bug: no error code or string is available here */
|
||||||
PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
|
PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate");
|
||||||
|
@ -1003,13 +1008,14 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
|
rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
|
PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback");
|
||||||
|
Py_XSETREF(self->function_pinboard_authorizer_cb, NULL);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
} else {
|
||||||
|
Py_INCREF(authorizer_cb);
|
||||||
|
Py_XSETREF(self->function_pinboard_authorizer_cb, authorizer_cb);
|
||||||
}
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
@ -1033,12 +1039,12 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s
|
||||||
if (progress_handler == Py_None) {
|
if (progress_handler == Py_None) {
|
||||||
/* None clears the progress handler previously set */
|
/* None clears the progress handler previously set */
|
||||||
sqlite3_progress_handler(self->db, 0, 0, (void*)0);
|
sqlite3_progress_handler(self->db, 0, 0, (void*)0);
|
||||||
|
Py_XSETREF(self->function_pinboard_progress_handler, NULL);
|
||||||
} else {
|
} else {
|
||||||
if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1)
|
|
||||||
return NULL;
|
|
||||||
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
|
sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler);
|
||||||
|
Py_INCREF(progress_handler);
|
||||||
|
Py_XSETREF(self->function_pinboard_progress_handler, progress_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1060,10 +1066,11 @@ static PyObject* pysqlite_connection_set_trace_callback(pysqlite_Connection* sel
|
||||||
if (trace_callback == Py_None) {
|
if (trace_callback == Py_None) {
|
||||||
/* None clears the trace callback previously set */
|
/* None clears the trace callback previously set */
|
||||||
sqlite3_trace(self->db, 0, (void*)0);
|
sqlite3_trace(self->db, 0, (void*)0);
|
||||||
|
Py_XSETREF(self->function_pinboard_trace_callback, NULL);
|
||||||
} else {
|
} else {
|
||||||
if (PyDict_SetItem(self->function_pinboard, trace_callback, Py_None) == -1)
|
|
||||||
return NULL;
|
|
||||||
sqlite3_trace(self->db, _trace_callback, trace_callback);
|
sqlite3_trace(self->db, _trace_callback, trace_callback);
|
||||||
|
Py_INCREF(trace_callback);
|
||||||
|
Py_XSETREF(self->function_pinboard_trace_callback, trace_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|
|
@ -85,11 +85,10 @@ typedef struct
|
||||||
*/
|
*/
|
||||||
PyObject* text_factory;
|
PyObject* text_factory;
|
||||||
|
|
||||||
/* remember references to functions/classes used in
|
/* remember references to object used in trace_callback/progress_handler/authorizer_cb */
|
||||||
* create_function/create/aggregate, use these as dictionary keys, so we
|
PyObject* function_pinboard_trace_callback;
|
||||||
* can keep the total system refcount constant by clearing that dictionary
|
PyObject* function_pinboard_progress_handler;
|
||||||
* in connection_dealloc */
|
PyObject* function_pinboard_authorizer_cb;
|
||||||
PyObject* function_pinboard;
|
|
||||||
|
|
||||||
/* a dictionary of registered collation name => collation callable mappings */
|
/* a dictionary of registered collation name => collation callable mappings */
|
||||||
PyObject* collations;
|
PyObject* collations;
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1357,7 +1357,7 @@ class PyBuildExt(build_ext):
|
||||||
]
|
]
|
||||||
if CROSS_COMPILING:
|
if CROSS_COMPILING:
|
||||||
sqlite_inc_paths = []
|
sqlite_inc_paths = []
|
||||||
MIN_SQLITE_VERSION_NUMBER = (3, 3, 9)
|
MIN_SQLITE_VERSION_NUMBER = (3, 7, 2)
|
||||||
MIN_SQLITE_VERSION = ".".join([str(x)
|
MIN_SQLITE_VERSION = ".".join([str(x)
|
||||||
for x in MIN_SQLITE_VERSION_NUMBER])
|
for x in MIN_SQLITE_VERSION_NUMBER])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue