From 83a05d5ee71eaca8feb2782ef989faec0d7d66f6 Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Thu, 17 Apr 2025 13:31:44 +1200 Subject: [PATCH 1/2] gh-127081: add critical sections to dbm objects The dbm_* functions are not thread-safe, naturally. Add critical sections to protect their use. --- ...-04-21-01-05-14.gh-issue-127081.Egrpq7.rst | 2 + Modules/_dbmmodule.c | 102 +++++++++++++----- Modules/_gdbmmodule.c | 99 ++++++++++++----- Modules/clinic/_dbmmodule.c.h | 37 +++++-- Modules/clinic/_gdbmmodule.c.h | 70 +++++++++--- 5 files changed, 234 insertions(+), 76 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst b/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst new file mode 100644 index 00000000000000..30643673bf9a3f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-21-01-05-14.gh-issue-127081.Egrpq7.rst @@ -0,0 +1,2 @@ +Fix libc thread safety issues with :mod:`dbm` by performing stateful +operations in critical sections. diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c index cc65cbd98d71dc..b293b9ee0b9f6a 100644 --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -68,6 +68,7 @@ typedef struct { #include "clinic/_dbmmodule.c.h" +/* NOTE: Must be used within a critical section! */ #define check_dbmobject_open(v, err) \ if ((v)->di_dbm == NULL) { \ PyErr_SetString(err, "DBM object has already been closed"); \ @@ -118,12 +119,15 @@ dbm_dealloc(PyObject *self) static Py_ssize_t dbm_length(PyObject *self) { + Py_ssize_t result = -1; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); + + Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - return -1; + goto done; } if ( dp->di_size < 0 ) { datum key; @@ -135,39 +139,51 @@ dbm_length(PyObject *self) size++; dp->di_size = size; } - return dp->di_size; + result = dp->di_size; +done:; + Py_END_CRITICAL_SECTION(); + return result; } static int dbm_bool(PyObject *self) { + int result; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); + Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - return -1; + result = -1; + goto done; } if (dp->di_size > 0) { /* Known non-zero size. */ - return 1; + result = 1; + goto done; } if (dp->di_size == 0) { /* Known zero size. */ - return 0; + result = 0; + goto done; } /* Unknown size. Ensure DBM object has an entry. */ datum key = dbm_firstkey(dp->di_dbm); if (key.dptr == NULL) { /* Empty. Cache this fact. */ - dp->di_size = 0; - return 0; + result = dp->di_size = 0; + goto done; } + /* Non-empty. Don't cache the length since we don't know. */ - return 1; + result = 1; +done:; + Py_END_CRITICAL_SECTION(); + return result; } static PyObject * @@ -175,26 +191,35 @@ dbm_subscript(PyObject *self, PyObject *key) { datum drec, krec; Py_ssize_t tmp_size; + PyObject *result = NULL; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); if (!PyArg_Parse(key, "s#", &krec.dptr, &tmp_size)) { return NULL; } - krec.dsize = tmp_size; - check_dbmobject_open(dp, state->dbm_error); + + Py_BEGIN_CRITICAL_SECTION(self); + /* Can't use the macro here as it returns. */ + if (dp->di_dbm == NULL) { + PyErr_SetString(state->dbm_error, "DBM object has already been closed"); + goto done; + } drec = dbm_fetch(dp->di_dbm, krec); if ( drec.dptr == 0 ) { PyErr_SetObject(PyExc_KeyError, key); - return NULL; + goto done; } if ( dbm_error(dp->di_dbm) ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, ""); - return NULL; + goto done; } - return PyBytes_FromStringAndSize(drec.dptr, drec.dsize); + result = PyBytes_FromStringAndSize(drec.dptr, drec.dsize); +done:; + Py_END_CRITICAL_SECTION(); + return result; } static int @@ -202,6 +227,7 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) { datum krec, drec; Py_ssize_t tmp_size; + int result = -1; dbmobject *dp = dbmobject_CAST(self); if ( !PyArg_Parse(v, "s#", &krec.dptr, &tmp_size) ) { @@ -212,10 +238,13 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); krec.dsize = tmp_size; + + Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { - PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - return -1; + PyErr_SetString(state->dbm_error, "DBM object has already been closed"); + goto done; } + dp->di_size = -1; if (w == NULL) { if ( dbm_delete(dp->di_dbm, krec) < 0 ) { @@ -228,31 +257,36 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) else { PyErr_SetString(state->dbm_error, "cannot delete item from database"); } - return -1; + goto done; } } else { if ( !PyArg_Parse(w, "s#", &drec.dptr, &tmp_size) ) { PyErr_SetString(PyExc_TypeError, "dbm mappings have bytes or string elements only"); - return -1; + goto done; } drec.dsize = tmp_size; if ( dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, "cannot add item to database"); - return -1; + goto done; } } if ( dbm_error(dp->di_dbm) ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, ""); - return -1; + goto done; } - return 0; + + result = 0; +done:; + Py_END_CRITICAL_SECTION(); + return result; } /*[clinic input] +@critical_section _dbm.dbm.close Close the database. @@ -260,7 +294,7 @@ Close the database. static PyObject * _dbm_dbm_close_impl(dbmobject *self) -/*[clinic end generated code: output=c8dc5b6709600b86 input=046db72377d51be8]*/ +/*[clinic end generated code: output=c8dc5b6709600b86 input=4a94f79facbc28ca]*/ { if (self->di_dbm) { dbm_close(self->di_dbm); @@ -270,6 +304,7 @@ _dbm_dbm_close_impl(dbmobject *self) } /*[clinic input] +@critical_section _dbm.dbm.keys cls: defining_class @@ -279,7 +314,7 @@ Return a list of all keys in the database. static PyObject * _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=f2a593b3038e5996 input=d3706a28fc051097]*/ +/*[clinic end generated code: output=f2a593b3038e5996 input=6ddefeadf2a80156]*/ { PyObject *v, *item; datum key; @@ -315,35 +350,42 @@ dbm_contains(PyObject *self, PyObject *arg) dbmobject *dp = dbmobject_CAST(self); datum key, val; Py_ssize_t size; + int result = -1; _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); + + Py_BEGIN_CRITICAL_SECTION(self); if ((dp)->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - return -1; + goto done; } if (PyUnicode_Check(arg)) { key.dptr = (char *)PyUnicode_AsUTF8AndSize(arg, &size); key.dsize = size; if (key.dptr == NULL) - return -1; + goto done; } else if (!PyBytes_Check(arg)) { PyErr_Format(PyExc_TypeError, "dbm key must be bytes or string, not %.100s", Py_TYPE(arg)->tp_name); - return -1; + goto done; } else { key.dptr = PyBytes_AS_STRING(arg); key.dsize = PyBytes_GET_SIZE(arg); } val = dbm_fetch(dp->di_dbm, key); - return val.dptr != NULL; + result = val.dptr != NULL; +done:; + Py_END_CRITICAL_SECTION(); + return result; } /*[clinic input] +@critical_section _dbm.dbm.get cls: defining_class key: str(accept={str, robuffer}, zeroes=True) @@ -356,7 +398,7 @@ Return the value for key if present, otherwise default. static PyObject * _dbm_dbm_get_impl(dbmobject *self, PyTypeObject *cls, const char *key, Py_ssize_t key_length, PyObject *default_value) -/*[clinic end generated code: output=b4e55f8b6d482bc4 input=66b993b8349fa8c1]*/ +/*[clinic end generated code: output=b4e55f8b6d482bc4 input=1d88a22bb5e55202]*/ { datum dbm_key, val; _dbm_state *state = PyType_GetModuleState(cls); @@ -373,6 +415,7 @@ _dbm_dbm_get_impl(dbmobject *self, PyTypeObject *cls, const char *key, } /*[clinic input] +@critical_section _dbm.dbm.setdefault cls: defining_class key: str(accept={str, robuffer}, zeroes=True) @@ -387,7 +430,7 @@ If key is not in the database, it is inserted with default as the value. static PyObject * _dbm_dbm_setdefault_impl(dbmobject *self, PyTypeObject *cls, const char *key, Py_ssize_t key_length, PyObject *default_value) -/*[clinic end generated code: output=9c2f6ea6d0fb576c input=126a3ff15c5f8232]*/ +/*[clinic end generated code: output=9c2f6ea6d0fb576c input=c01510ef7571e13b]*/ { datum dbm_key, val; Py_ssize_t tmp_size; @@ -427,6 +470,7 @@ _dbm_dbm_setdefault_impl(dbmobject *self, PyTypeObject *cls, const char *key, } /*[clinic input] +@critical_section _dbm.dbm.clear cls: defining_class / @@ -436,7 +480,7 @@ Remove all items from the database. static PyObject * _dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=8d126b9e1d01a434 input=43aa6ca1acb7f5f5]*/ +/*[clinic end generated code: output=8d126b9e1d01a434 input=a1aa5d99adfb9656]*/ { _dbm_state *state = PyType_GetModuleState(cls); assert(state != NULL); diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index ab2ebdba9249bf..7f9afabf652b88 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -80,6 +80,7 @@ typedef struct { #include "clinic/_gdbmmodule.c.h" +/* NOTE: Must be used within a critical section! */ #define check_gdbmobject_open(v, err) \ if ((v)->di_dbm == NULL) { \ PyErr_SetString(err, "GDBM object has already been closed"); \ @@ -144,8 +145,10 @@ gdbm_dealloc(PyObject *op) static Py_ssize_t gdbm_length(PyObject *op) { + Py_ssize_t result = -1; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); + Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); return -1; @@ -161,11 +164,11 @@ gdbm_length(PyObject *op) else { set_gdbm_error(state, "gdbm_count() error"); } - return -1; + goto done; } if (count > PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, "count exceeds PY_SSIZE_T_MAX"); - return -1; + goto done; } dp->di_size = count; #else @@ -185,37 +188,50 @@ gdbm_length(PyObject *op) dp->di_size = size; #endif } - return dp->di_size; + result = dp->di_size; +done:; + Py_END_CRITICAL_SECTION(); + return result; } static int gdbm_bool(PyObject *op) { + int result; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); + + Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - return -1; + result = -1; + goto done; } if (dp->di_size > 0) { /* Known non-zero size. */ - return 1; + result = 1; + goto done; } if (dp->di_size == 0) { /* Known zero size. */ - return 0; + result = 0; + goto done; } /* Unknown size. Ensure DBM object has an entry. */ datum key = gdbm_firstkey(dp->di_dbm); if (key.dptr == NULL) { /* Empty. Cache this fact. */ - dp->di_size = 0; - return 0; + result = dp->di_size = 0; + goto done; } /* Non-empty. Don't cache the length since we don't know. */ free(key.dptr); - return 1; + result = 1; + +done:; + Py_END_CRITICAL_SECTION(); + return result; } // Wrapper function for PyArg_Parse(o, "s#", &d.dptr, &d.size). @@ -242,7 +258,7 @@ parse_datum(PyObject *o, datum *d, const char *failmsg) static PyObject * gdbm_subscript(PyObject *op, PyObject *key) { - PyObject *v; + PyObject *v = NULL; datum drec, krec; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); @@ -250,18 +266,22 @@ gdbm_subscript(PyObject *op, PyObject *key) if (!parse_datum(key, &krec, NULL)) { return NULL; } + + Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - return NULL; + goto done; } drec = gdbm_fetch(dp->di_dbm, krec); if (drec.dptr == 0) { PyErr_SetObject(PyExc_KeyError, key); - return NULL; + goto done; } v = PyBytes_FromStringAndSize(drec.dptr, drec.dsize); free(drec.dptr); +done:; + Py_END_CRITICAL_SECTION(); return v; } @@ -294,16 +314,19 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) { datum krec, drec; const char *failmsg = "gdbm mappings have bytes or string indices only"; + int result = -1; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); if (!parse_datum(v, &krec, failmsg)) { return -1; } + + Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - return -1; + goto done; } dp->di_size = -1; if (w == NULL) { @@ -314,12 +337,12 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) else { set_gdbm_error(state, "gdbm_delete() error"); } - return -1; + goto done; } } else { if (!parse_datum(w, &drec, failmsg)) { - return -1; + goto done; } errno = 0; if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0) { @@ -329,13 +352,18 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) else { set_gdbm_error(state, "gdbm_store() error"); } - return -1; + goto done; } } - return 0; + result = 0; + +done:; + Py_END_CRITICAL_SECTION(); + return result; } /*[clinic input] +@critical_section _gdbm.gdbm.setdefault key: object @@ -348,7 +376,7 @@ Get value for key, or set it to default and return default if not present. static PyObject * _gdbm_gdbm_setdefault_impl(gdbmobject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=f3246e880509f142 input=0db46b69e9680171]*/ +/*[clinic end generated code: output=f3246e880509f142 input=854374cd81ab51b6]*/ { PyObject *res; @@ -363,6 +391,7 @@ _gdbm_gdbm_setdefault_impl(gdbmobject *self, PyObject *key, } /*[clinic input] +@critical_section _gdbm.gdbm.close Close the database. @@ -370,7 +399,7 @@ Close the database. static PyObject * _gdbm_gdbm_close_impl(gdbmobject *self) -/*[clinic end generated code: output=f5abb4d6bb9e52d5 input=0a203447379b45fd]*/ +/*[clinic end generated code: output=f5abb4d6bb9e52d5 input=56b604f4e77f533d]*/ { if (self->di_dbm) { gdbm_close(self->di_dbm); @@ -381,6 +410,7 @@ _gdbm_gdbm_close_impl(gdbmobject *self) /* XXX Should return a set or a set view */ /*[clinic input] +@critical_section _gdbm.gdbm.keys cls: defining_class @@ -390,7 +420,7 @@ Get a list of all keys in the database. static PyObject * _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=c24b824e81404755 input=1428b7c79703d7d5]*/ +/*[clinic end generated code: output=c24b824e81404755 input=785988b1ea8f77e0]*/ { PyObject *v, *item; datum key, nextkey; @@ -437,33 +467,40 @@ gdbm_contains(PyObject *self, PyObject *arg) gdbmobject *dp = (gdbmobject *)self; datum key; Py_ssize_t size; + int result = -1; _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); + Py_BEGIN_CRITICAL_SECTION(self); if ((dp)->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - return -1; + goto done; } if (PyUnicode_Check(arg)) { key.dptr = (char *)PyUnicode_AsUTF8AndSize(arg, &size); key.dsize = size; if (key.dptr == NULL) - return -1; + goto done; } else if (!PyBytes_Check(arg)) { PyErr_Format(PyExc_TypeError, "gdbm key must be bytes or string, not %.100s", Py_TYPE(arg)->tp_name); - return -1; + goto done; } else { key.dptr = PyBytes_AS_STRING(arg); key.dsize = PyBytes_GET_SIZE(arg); } - return gdbm_exists(dp->di_dbm, key); + result = gdbm_exists(dp->di_dbm, key); + +done:; + Py_END_CRITICAL_SECTION(); + return result; } /*[clinic input] +@critical_section _gdbm.gdbm.firstkey cls: defining_class @@ -477,7 +514,7 @@ hash values, and won't be sorted by the key values. static PyObject * _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=139275e9c8b60827 input=ed8782a029a5d299]*/ +/*[clinic end generated code: output=139275e9c8b60827 input=aad5a7c886c542f5]*/ { PyObject *v; datum key; @@ -497,6 +534,7 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls) } /*[clinic input] +@critical_section _gdbm.gdbm.nextkey cls: defining_class @@ -517,7 +555,7 @@ to create a list in memory that contains them all: static PyObject * _gdbm_gdbm_nextkey_impl(gdbmobject *self, PyTypeObject *cls, const char *key, Py_ssize_t key_length) -/*[clinic end generated code: output=c81a69300ef41766 input=365e297bc0b3db48]*/ +/*[clinic end generated code: output=c81a69300ef41766 input=181f1130d5bfeb1e]*/ { PyObject *v; datum dbm_key, nextkey; @@ -539,6 +577,7 @@ _gdbm_gdbm_nextkey_impl(gdbmobject *self, PyTypeObject *cls, const char *key, } /*[clinic input] +@critical_section _gdbm.gdbm.reorganize cls: defining_class @@ -554,7 +593,7 @@ kept and reused as new (key,value) pairs are added. static PyObject * _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=d77c69e8e3dd644a input=e1359faeef844e46]*/ +/*[clinic end generated code: output=d77c69e8e3dd644a input=3e3ca0d2ea787861]*/ { _gdbm_state *state = PyType_GetModuleState(cls); assert(state != NULL); @@ -573,6 +612,7 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls) } /*[clinic input] +@critical_section _gdbm.gdbm.sync cls: defining_class @@ -585,7 +625,7 @@ any unwritten data to be written to the disk. static PyObject * _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=bb680a2035c3f592 input=3d749235f79b6f2a]*/ +/*[clinic end generated code: output=bb680a2035c3f592 input=6054385b071d238a]*/ { _gdbm_state *state = PyType_GetModuleState(cls); assert(state != NULL); @@ -595,6 +635,7 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls) } /*[clinic input] +@critical_section _gdbm.gdbm.clear cls: defining_class / @@ -604,7 +645,7 @@ Remove all items from the database. static PyObject * _gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=673577c573318661 input=34136d52fcdd4210]*/ +/*[clinic end generated code: output=673577c573318661 input=b17467adfe62f23d]*/ { _gdbm_state *state = PyType_GetModuleState(cls); assert(state != NULL); diff --git a/Modules/clinic/_dbmmodule.c.h b/Modules/clinic/_dbmmodule.c.h index 5e503194408776..091ce9edc43d4b 100644 --- a/Modules/clinic/_dbmmodule.c.h +++ b/Modules/clinic/_dbmmodule.c.h @@ -5,6 +5,7 @@ preserve #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) # include "pycore_runtime.h" // _Py_SINGLETON() #endif +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() PyDoc_STRVAR(_dbm_dbm_close__doc__, @@ -22,7 +23,13 @@ _dbm_dbm_close_impl(dbmobject *self); static PyObject * _dbm_dbm_close(PyObject *self, PyObject *Py_UNUSED(ignored)) { - return _dbm_dbm_close_impl((dbmobject *)self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _dbm_dbm_close_impl((dbmobject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(_dbm_dbm_keys__doc__, @@ -40,11 +47,18 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_keys(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); - return NULL; + goto exit; } - return _dbm_dbm_keys_impl((dbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _dbm_dbm_keys_impl((dbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_dbm_dbm_get__doc__, @@ -85,7 +99,9 @@ _dbm_dbm_get(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_ &key, &key_length, &default_value)) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = _dbm_dbm_get_impl((dbmobject *)self, cls, key, key_length, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -131,7 +147,9 @@ _dbm_dbm_setdefault(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py &key, &key_length, &default_value)) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = _dbm_dbm_setdefault_impl((dbmobject *)self, cls, key, key_length, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -152,11 +170,18 @@ _dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_clear(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); - return NULL; + goto exit; } - return _dbm_dbm_clear_impl((dbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _dbm_dbm_clear_impl((dbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(dbmopen__doc__, @@ -221,4 +246,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=3b456118f231b160 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=279511ea7cda38dd input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_gdbmmodule.c.h b/Modules/clinic/_gdbmmodule.c.h index 00950f18e53541..6fd6aa3da50335 100644 --- a/Modules/clinic/_gdbmmodule.c.h +++ b/Modules/clinic/_gdbmmodule.c.h @@ -5,6 +5,7 @@ preserve #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) # include "pycore_runtime.h" // _Py_SINGLETON() #endif +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(_gdbm_gdbm_get__doc__, @@ -70,7 +71,9 @@ _gdbm_gdbm_setdefault(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = _gdbm_gdbm_setdefault_impl((gdbmobject *)self, key, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -91,7 +94,13 @@ _gdbm_gdbm_close_impl(gdbmobject *self); static PyObject * _gdbm_gdbm_close(PyObject *self, PyObject *Py_UNUSED(ignored)) { - return _gdbm_gdbm_close_impl((gdbmobject *)self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_close_impl((gdbmobject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(_gdbm_gdbm_keys__doc__, @@ -109,11 +118,18 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_keys(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); - return NULL; + goto exit; } - return _gdbm_gdbm_keys_impl((gdbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_keys_impl((gdbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_gdbm_gdbm_firstkey__doc__, @@ -135,11 +151,18 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_firstkey(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "firstkey() takes no arguments"); - return NULL; + goto exit; } - return _gdbm_gdbm_firstkey_impl((gdbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_firstkey_impl((gdbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_gdbm_gdbm_nextkey__doc__, @@ -187,7 +210,9 @@ _gdbm_gdbm_nextkey(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ &key, &key_length)) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = _gdbm_gdbm_nextkey_impl((gdbmobject *)self, cls, key, key_length); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -214,11 +239,18 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_reorganize(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "reorganize() takes no arguments"); - return NULL; + goto exit; } - return _gdbm_gdbm_reorganize_impl((gdbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_reorganize_impl((gdbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_gdbm_gdbm_sync__doc__, @@ -239,11 +271,18 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_sync(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "sync() takes no arguments"); - return NULL; + goto exit; } - return _gdbm_gdbm_sync_impl((gdbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_sync_impl((gdbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_gdbm_gdbm_clear__doc__, @@ -261,11 +300,18 @@ _gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_clear(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); - return NULL; + goto exit; } - return _gdbm_gdbm_clear_impl((gdbmobject *)self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _gdbm_gdbm_clear_impl((gdbmobject *)self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(dbmopen__doc__, @@ -343,4 +389,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=d974cb39e4ee5d67 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8bca34ce9d4493dd input=a9049054013a1b77]*/ From 07748700577a15944b4addb33665efb241c02daa Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Mon, 21 Apr 2025 11:55:44 +1200 Subject: [PATCH 2/2] Switch to calling a *_lock_held variant while holding the critical section instead of using gotos everywhere. --- Modules/_dbmmodule.c | 126 +++++++++++++++++++++++------------------- Modules/_gdbmmodule.c | 114 ++++++++++++++++++++++---------------- 2 files changed, 135 insertions(+), 105 deletions(-) diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c index b293b9ee0b9f6a..0cd0f043de453d 100644 --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -68,8 +68,8 @@ typedef struct { #include "clinic/_dbmmodule.c.h" -/* NOTE: Must be used within a critical section! */ #define check_dbmobject_open(v, err) \ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((v)) \ if ((v)->di_dbm == NULL) { \ PyErr_SetString(err, "DBM object has already been closed"); \ return NULL; \ @@ -117,17 +117,14 @@ dbm_dealloc(PyObject *self) } static Py_ssize_t -dbm_length(PyObject *self) +dbm_length_lock_held(PyObject *self) { - Py_ssize_t result = -1; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); - - Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - goto done; + return -1; } if ( dp->di_size < 0 ) { datum key; @@ -139,95 +136,103 @@ dbm_length(PyObject *self) size++; dp->di_size = size; } - result = dp->di_size; -done:; + return dp->di_size; +} + +static Py_ssize_t +dbm_length(PyObject *self) +{ + Py_ssize_t result; + Py_BEGIN_CRITICAL_SECTION(self); + result = dbm_length_lock_held(self); Py_END_CRITICAL_SECTION(); return result; } static int -dbm_bool(PyObject *self) +dbm_bool_lock_held(PyObject *self) { - int result; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); - Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - result = -1; - goto done; + return -1; } if (dp->di_size > 0) { /* Known non-zero size. */ - result = 1; - goto done; + return 1; } if (dp->di_size == 0) { /* Known zero size. */ - result = 0; - goto done; + return 0; } /* Unknown size. Ensure DBM object has an entry. */ datum key = dbm_firstkey(dp->di_dbm); if (key.dptr == NULL) { /* Empty. Cache this fact. */ - result = dp->di_size = 0; - goto done; + dp->di_size = 0; + return 0; } - /* Non-empty. Don't cache the length since we don't know. */ - result = 1; -done:; + return 1; +} + +static int +dbm_bool(PyObject *self) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(self); + result = dbm_bool_lock_held(self); Py_END_CRITICAL_SECTION(); return result; } static PyObject * -dbm_subscript(PyObject *self, PyObject *key) +dbm_subscript_lock_held(PyObject *self, PyObject *key) { datum drec, krec; Py_ssize_t tmp_size; - PyObject *result = NULL; dbmobject *dp = dbmobject_CAST(self); _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); if (!PyArg_Parse(key, "s#", &krec.dptr, &tmp_size)) { return NULL; } - krec.dsize = tmp_size; - Py_BEGIN_CRITICAL_SECTION(self); - /* Can't use the macro here as it returns. */ - if (dp->di_dbm == NULL) { - PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - goto done; - } + krec.dsize = tmp_size; + check_dbmobject_open(dp, state->dbm_error); drec = dbm_fetch(dp->di_dbm, krec); if ( drec.dptr == 0 ) { PyErr_SetObject(PyExc_KeyError, key); - goto done; + return NULL; } if ( dbm_error(dp->di_dbm) ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, ""); - goto done; + return NULL; } - result = PyBytes_FromStringAndSize(drec.dptr, drec.dsize); -done:; + return PyBytes_FromStringAndSize(drec.dptr, drec.dsize); +} + +static PyObject * +dbm_subscript(PyObject *self, PyObject *key) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(self); + result = dbm_subscript_lock_held(self, key); Py_END_CRITICAL_SECTION(); return result; } static int -dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) +dbm_ass_sub_lock_held(PyObject *self, PyObject *v, PyObject *w) { datum krec, drec; Py_ssize_t tmp_size; - int result = -1; dbmobject *dp = dbmobject_CAST(self); if ( !PyArg_Parse(v, "s#", &krec.dptr, &tmp_size) ) { @@ -238,13 +243,10 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); krec.dsize = tmp_size; - - Py_BEGIN_CRITICAL_SECTION(self); if (dp->di_dbm == NULL) { - PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - goto done; + PyErr_SetString(state->dbm_error, "DBM object has already been closed"); + return -1; } - dp->di_size = -1; if (w == NULL) { if ( dbm_delete(dp->di_dbm, krec) < 0 ) { @@ -257,30 +259,36 @@ dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) else { PyErr_SetString(state->dbm_error, "cannot delete item from database"); } - goto done; + return -1; } } else { if ( !PyArg_Parse(w, "s#", &drec.dptr, &tmp_size) ) { PyErr_SetString(PyExc_TypeError, "dbm mappings have bytes or string elements only"); - goto done; + return -1; } drec.dsize = tmp_size; if ( dbm_store(dp->di_dbm, krec, drec, DBM_REPLACE) < 0 ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, "cannot add item to database"); - goto done; + return -1; } } if ( dbm_error(dp->di_dbm) ) { dbm_clearerr(dp->di_dbm); PyErr_SetString(state->dbm_error, ""); - goto done; + return -1; } + return 0; +} - result = 0; -done:; +static int +dbm_ass_sub(PyObject *self, PyObject *v, PyObject *w) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(self); + result = dbm_ass_sub_lock_held(self, v, w); Py_END_CRITICAL_SECTION(); return result; } @@ -345,41 +353,45 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls) } static int -dbm_contains(PyObject *self, PyObject *arg) +dbm_contains_lock_held(PyObject *self, PyObject *arg) { dbmobject *dp = dbmobject_CAST(self); datum key, val; Py_ssize_t size; - int result = -1; _dbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); assert(state != NULL); - - Py_BEGIN_CRITICAL_SECTION(self); if ((dp)->di_dbm == NULL) { PyErr_SetString(state->dbm_error, "DBM object has already been closed"); - goto done; + return -1; } if (PyUnicode_Check(arg)) { key.dptr = (char *)PyUnicode_AsUTF8AndSize(arg, &size); key.dsize = size; if (key.dptr == NULL) - goto done; + return -1; } else if (!PyBytes_Check(arg)) { PyErr_Format(PyExc_TypeError, "dbm key must be bytes or string, not %.100s", Py_TYPE(arg)->tp_name); - goto done; + return -1; } else { key.dptr = PyBytes_AS_STRING(arg); key.dsize = PyBytes_GET_SIZE(arg); } val = dbm_fetch(dp->di_dbm, key); - result = val.dptr != NULL; -done:; + return val.dptr != NULL; +} + +static int +dbm_contains(PyObject *self, PyObject *arg) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(self); + result = dbm_contains_lock_held(self, arg); Py_END_CRITICAL_SECTION(); return result; } diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index 7f9afabf652b88..9c402e20e513b9 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -80,8 +80,8 @@ typedef struct { #include "clinic/_gdbmmodule.c.h" -/* NOTE: Must be used within a critical section! */ #define check_gdbmobject_open(v, err) \ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED((v)) \ if ((v)->di_dbm == NULL) { \ PyErr_SetString(err, "GDBM object has already been closed"); \ return NULL; \ @@ -143,12 +143,10 @@ gdbm_dealloc(PyObject *op) } static Py_ssize_t -gdbm_length(PyObject *op) +gdbm_length_lock_held(PyObject *op) { - Py_ssize_t result = -1; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); - Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); return -1; @@ -164,11 +162,11 @@ gdbm_length(PyObject *op) else { set_gdbm_error(state, "gdbm_count() error"); } - goto done; + return -1; } if (count > PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, "count exceeds PY_SSIZE_T_MAX"); - goto done; + return -1; } dp->di_size = count; #else @@ -188,48 +186,55 @@ gdbm_length(PyObject *op) dp->di_size = size; #endif } - result = dp->di_size; -done:; + return dp->di_size; +} + +static Py_ssize_t +gdbm_length(PyObject *op) +{ + Py_ssize_t result; + Py_BEGIN_CRITICAL_SECTION(op); + result = gdbm_length_lock_held(op); Py_END_CRITICAL_SECTION(); return result; } static int -gdbm_bool(PyObject *op) +gdbm_bool_lock_held(PyObject *op) { - int result; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); - - Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - result = -1; - goto done; + return -1; } if (dp->di_size > 0) { /* Known non-zero size. */ - result = 1; - goto done; + return 1; } if (dp->di_size == 0) { /* Known zero size. */ - result = 0; - goto done; + return 0; } /* Unknown size. Ensure DBM object has an entry. */ datum key = gdbm_firstkey(dp->di_dbm); if (key.dptr == NULL) { /* Empty. Cache this fact. */ - result = dp->di_size = 0; - goto done; + dp->di_size = 0; + return 0; } /* Non-empty. Don't cache the length since we don't know. */ free(key.dptr); - result = 1; + return 1; +} -done:; +static int +gdbm_bool(PyObject *op) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(op); + result = gdbm_bool_lock_held(op); Py_END_CRITICAL_SECTION(); return result; } @@ -256,9 +261,9 @@ parse_datum(PyObject *o, datum *d, const char *failmsg) } static PyObject * -gdbm_subscript(PyObject *op, PyObject *key) +gdbm_subscript_lock_held(PyObject *op, PyObject *key) { - PyObject *v = NULL; + PyObject *v; datum drec, krec; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); @@ -266,25 +271,31 @@ gdbm_subscript(PyObject *op, PyObject *key) if (!parse_datum(key, &krec, NULL)) { return NULL; } - - Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - goto done; + return NULL; } drec = gdbm_fetch(dp->di_dbm, krec); if (drec.dptr == 0) { PyErr_SetObject(PyExc_KeyError, key); - goto done; + return NULL; } v = PyBytes_FromStringAndSize(drec.dptr, drec.dsize); free(drec.dptr); -done:; - Py_END_CRITICAL_SECTION(); return v; } +static PyObject * +gdbm_subscript(PyObject *op, PyObject *key) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(op); + result = gdbm_subscript_lock_held(op, key); + Py_END_CRITICAL_SECTION(); + return result; +} + /*[clinic input] _gdbm.gdbm.get @@ -310,23 +321,20 @@ _gdbm_gdbm_get_impl(gdbmobject *self, PyObject *key, PyObject *default_value) } static int -gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) +gdbm_ass_sub_lock_held(PyObject *op, PyObject *v, PyObject *w) { datum krec, drec; const char *failmsg = "gdbm mappings have bytes or string indices only"; - int result = -1; gdbmobject *dp = _gdbmobject_CAST(op); _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); if (!parse_datum(v, &krec, failmsg)) { return -1; } - - Py_BEGIN_CRITICAL_SECTION(op); if (dp->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - goto done; + return -1; } dp->di_size = -1; if (w == NULL) { @@ -337,12 +345,12 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) else { set_gdbm_error(state, "gdbm_delete() error"); } - goto done; + return -1; } } else { if (!parse_datum(w, &drec, failmsg)) { - goto done; + return -1; } errno = 0; if (gdbm_store(dp->di_dbm, krec, drec, GDBM_REPLACE) < 0) { @@ -352,12 +360,18 @@ gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) else { set_gdbm_error(state, "gdbm_store() error"); } - goto done; + return -1; } } - result = 0; + return 0; +} -done:; +static int +gdbm_ass_sub(PyObject *op, PyObject *v, PyObject *w) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(op); + result = gdbm_ass_sub_lock_held(op, v, w); Py_END_CRITICAL_SECTION(); return result; } @@ -462,39 +476,43 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls) } static int -gdbm_contains(PyObject *self, PyObject *arg) +gdbm_contains_lock_held(PyObject *self, PyObject *arg) { gdbmobject *dp = (gdbmobject *)self; datum key; Py_ssize_t size; - int result = -1; _gdbm_state *state = PyType_GetModuleState(Py_TYPE(dp)); - Py_BEGIN_CRITICAL_SECTION(self); if ((dp)->di_dbm == NULL) { PyErr_SetString(state->gdbm_error, "GDBM object has already been closed"); - goto done; + return -1; } if (PyUnicode_Check(arg)) { key.dptr = (char *)PyUnicode_AsUTF8AndSize(arg, &size); key.dsize = size; if (key.dptr == NULL) - goto done; + return -1; } else if (!PyBytes_Check(arg)) { PyErr_Format(PyExc_TypeError, "gdbm key must be bytes or string, not %.100s", Py_TYPE(arg)->tp_name); - goto done; + return -1; } else { key.dptr = PyBytes_AS_STRING(arg); key.dsize = PyBytes_GET_SIZE(arg); } - result = gdbm_exists(dp->di_dbm, key); + return gdbm_exists(dp->di_dbm, key); +} -done:; +static int +gdbm_contains(PyObject *self, PyObject *arg) +{ + int result; + Py_BEGIN_CRITICAL_SECTION(self); + result = gdbm_contains_lock_held(self, arg); Py_END_CRITICAL_SECTION(); return result; }