diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 71927006d1cd48..102eb05cdbfe90 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -87,7 +87,8 @@ extern PyObject *_PyDict_FromKeys(PyObject *, PyObject *, PyObject *); /* Gets a version number unique to the current state of the keys of dict, if possible. * Returns the version number, or zero if it was not possible to get a version number. */ -extern uint32_t _PyDictKeys_GetVersionForCurrentState( +// Export for '_testinternalcapi' extension + PyAPI_FUNC(uint32_t) _PyDictKeys_GetVersionForCurrentState( PyInterpreterState *interp, PyDictKeysObject *dictkeys); /* Gets a version number unique to the current state of the keys of dict, if possible. diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 79f452f8068c7f..6afd0d2d8080b3 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -583,6 +583,9 @@ def assert_races_do_not_crash( # Reset: if check_items: for item in items: + # Checking for overflow + if _testinternalcapi.is_dict_version_overflowed(item.__globals__): + return unittest.skip("Version number is overflowed; no rerun is possible") item.__code__ = item.__code__.replace() else: read.__code__ = read.__code__.replace() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 150d34d168f5e4..0e5220606e9e94 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2030,6 +2030,22 @@ gh_119213_getargs_impl(PyObject *module, PyObject *spam) return Py_NewRef(spam); } +/*[clinic input] +is_dict_version_overflowed + dict: object(type="PyDictObject *", subclass_of="&PyDict_Type") +[clinic start generated code]*/ + +static PyObject * +is_dict_version_overflowed_impl(PyObject *module, PyDictObject *dict) +/*[clinic end generated code: output=85daaa03aef739fd input=78fbf25d89b33b9a]*/ +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(interp, dict->ma_keys); + if (keys_version != (uint16_t)keys_version){ + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} static PyObject * get_static_builtin_types(PyObject *self, PyObject *Py_UNUSED(ignored)) @@ -2165,6 +2181,7 @@ static PyMethodDef module_functions[] = { {"has_deferred_refcount", has_deferred_refcount, METH_O}, {"get_tracked_heap_size", get_tracked_heap_size, METH_NOARGS}, {"is_static_immortal", is_static_immortal, METH_O}, + IS_DICT_VERSION_OVERFLOWED_METHODDEF {NULL, NULL} /* sentinel */ }; diff --git a/Modules/clinic/_testinternalcapi.c.h b/Modules/clinic/_testinternalcapi.c.h index d98d69df22f982..8cc2278faca256 100644 --- a/Modules/clinic/_testinternalcapi.c.h +++ b/Modules/clinic/_testinternalcapi.c.h @@ -365,4 +365,63 @@ gh_119213_getargs(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=ec77971c6c2663da input=a9049054013a1b77]*/ + +PyDoc_STRVAR(is_dict_version_overflowed__doc__, +"is_dict_version_overflowed($module, /, dict)\n" +"--\n" +"\n"); + +#define IS_DICT_VERSION_OVERFLOWED_METHODDEF \ + {"is_dict_version_overflowed", _PyCFunction_CAST(is_dict_version_overflowed), METH_FASTCALL|METH_KEYWORDS, is_dict_version_overflowed__doc__}, + +static PyObject * +is_dict_version_overflowed_impl(PyObject *module, PyDictObject *dict); + +static PyObject * +is_dict_version_overflowed(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(dict), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"dict", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "is_dict_version_overflowed", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyDictObject *dict; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!PyDict_Check(args[0])) { + _PyArg_BadArgument("is_dict_version_overflowed", "argument 'dict'", "dict", args[0]); + goto exit; + } + dict = (PyDictObject *)args[0]; + return_value = is_dict_version_overflowed_impl(module, dict); + +exit: + return return_value; +} +/*[clinic end generated code: output=1812ca5f99fec2a7 input=a9049054013a1b77]*/