Implement equivalent patch to upstream python/cpython#14268

This commit is contained in:
Charles Leifer 2019-07-22 08:46:59 -05:00
parent 4754ff5cdc
commit e113883eab
3 changed files with 63 additions and 101 deletions

View file

@ -187,10 +187,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) {
@ -250,13 +249,13 @@ 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);
@ -343,9 +342,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);
@ -906,6 +903,12 @@ 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};
@ -941,17 +944,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,
}
rc = sqlite3_create_function(self->db,
name, name,
narg, narg,
flags, flags,
(void*)func, (void*)func,
_pysqlite_func_callback, _pysqlite_func_callback,
NULL, NULL,
NULL); NULL,
&_destructor);
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 */
@ -979,10 +981,17 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje
return NULL; return NULL;
} }
if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) { Py_INCREF(aggregate_class);
return NULL; rc = sqlite3_create_function_v2(self->db,
} name,
rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); n_arg,
SQLITE_UTF8,
(void*)aggregate_class,
0,
&_pysqlite_step_callback,
&_pysqlite_final_callback,
&_destructor);
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");
@ -1011,10 +1020,7 @@ PyObject* pysqlite_connection_create_window_function(pysqlite_Connection* self,
return NULL; return NULL;
} }
if (PyDict_SetItem(self->function_pinboard, window_function_class, Py_None) == -1) { Py_INCREF(window_function_class);
return NULL;
}
rc = sqlite3_create_window_function( rc = sqlite3_create_window_function(
self->db, self->db,
name, name,
@ -1025,7 +1031,7 @@ PyObject* pysqlite_connection_create_window_function(pysqlite_Connection* self,
&_pysqlite_final_callback, &_pysqlite_final_callback,
&_pysqlite_value_callback, &_pysqlite_value_callback,
&_pysqlite_inverse_callback, &_pysqlite_inverse_callback,
NULL); &_destructor);
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 */
@ -1147,13 +1153,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;
} }
@ -1177,10 +1184,11 @@ 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;
@ -1204,10 +1212,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;

View file

@ -85,11 +85,10 @@ typedef struct
*/ */
PyObject* text_factory; PyObject* text_factory;
/* remember references to functions/classes used in /* remember references to functions/classes used in trace/progress/auth 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;

View file

@ -22,8 +22,9 @@
# 3. This notice may not be removed or altered from any source distribution. # 3. This notice may not be removed or altered from any source distribution.
import datetime import datetime
import functools
import unittest import unittest
from sqlcipher3 import dbapi2 as sqlite from pysqlite3 import dbapi2 as sqlite
import weakref import weakref
#from test import support #from test import support
@ -383,72 +384,25 @@ 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]:
def __init__(self, return_value=None): printer_instance = Printer()
self.calls = 0 method(printer_instance.log)
self.return_value = return_value method(printer_instance.log) # Register twice, incref twice.
self.con.execute('select 1') # Triggers segfault.
def __call__(self, *args, **kwargs): method(None)
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():