From 5c3dd1c9db6a503aaceede5308291b4ef17cd80b Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 5 Mar 2025 23:10:12 +0000 Subject: [PATCH 1/2] gh-130851: Only intern constants of types generated by compiler The free threading build interns and immortalizes most constants generated by the bytecode compiler. However, users can construct their own code objects with arbitrary constants. Don't intern or immortalize these objects if they are not a type that we know how to handle. This fixes a refleak failure in the recently added `test_code.test_unusual_constants` test. It also fixes a potential crash that could happen when we try to destory a immortalized object on interpreter shutdown. --- Objects/codeobject.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 5d23a7a1e808c4..7d917aaef1478c 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -135,6 +135,43 @@ should_intern_string(PyObject *o) #ifdef Py_GIL_DISABLED static PyObject *intern_one_constant(PyObject *op); + +// gh-130851: In the free threading build, we intern and immortalize most +// constants, except code objects. However, users can generate code objects +// with arbitrary co_consts. We don't want to immortalize or intern unexpected +// constants or tuples/sets containing unexpected constants. +static int +should_immortalize_constant(PyObject *v) +{ + if (PyTuple_CheckExact(v)) { + for (Py_ssize_t i = PyTuple_GET_SIZE(v); --i >= 0; ) { + if (!should_immortalize_constant(PyTuple_GET_ITEM(v, i))) { + return 0; + } + } + return 1; + } + else if (PyFrozenSet_CheckExact(v)) { + PyObject *item; + Py_hash_t hash; + Py_ssize_t pos = 0; + while (_PySet_NextEntry(v, &pos, &item, &hash)) { + if (!should_immortalize_constant(item)) { + return 0; + } + } + return 1; + } + else if (PySlice_Check(v)) { + PySliceObject *slice = (PySliceObject *)v; + return (should_immortalize_constant(slice->start) && + should_immortalize_constant(slice->stop) && + should_immortalize_constant(slice->step)); + } + return (PyUnicode_CheckExact(v) || PyLong_CheckExact(v) || + PyFloat_CheckExact(v) || PyComplex_Check(v) || + PyBytes_CheckExact(v) || _Py_IsImmortal(v)); +} #endif static int @@ -241,8 +278,9 @@ intern_constants(PyObject *tuple, int *modified) // Intern non-string constants in the free-threaded build _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); - if (!_Py_IsImmortal(v) && !PyCode_Check(v) && - !PyUnicode_CheckExact(v) && !tstate->suppress_co_const_immortalization) + if (!_Py_IsImmortal(v) && !PyUnicode_CheckExact(v) && + should_immortalize_constant(v) && + !tstate->suppress_co_const_immortalization) { PyObject *interned = intern_one_constant(v); if (interned == NULL) { From f6670ce7228f330d648ff64f62963c520ca0de6e Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 6 Mar 2025 19:09:50 -0500 Subject: [PATCH 2/2] Use _Py_IsImmortal instead of recursive call in should_immortalize_constant --- Objects/codeobject.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 7d917aaef1478c..60563ea7575604 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -143,9 +143,11 @@ static PyObject *intern_one_constant(PyObject *op); static int should_immortalize_constant(PyObject *v) { + // Only immortalize containers if we've already immortalized all their + // elements. if (PyTuple_CheckExact(v)) { for (Py_ssize_t i = PyTuple_GET_SIZE(v); --i >= 0; ) { - if (!should_immortalize_constant(PyTuple_GET_ITEM(v, i))) { + if (!_Py_IsImmortal(PyTuple_GET_ITEM(v, i))) { return 0; } } @@ -156,7 +158,7 @@ should_immortalize_constant(PyObject *v) Py_hash_t hash; Py_ssize_t pos = 0; while (_PySet_NextEntry(v, &pos, &item, &hash)) { - if (!should_immortalize_constant(item)) { + if (!_Py_IsImmortal(item)) { return 0; } } @@ -164,13 +166,12 @@ should_immortalize_constant(PyObject *v) } else if (PySlice_Check(v)) { PySliceObject *slice = (PySliceObject *)v; - return (should_immortalize_constant(slice->start) && - should_immortalize_constant(slice->stop) && - should_immortalize_constant(slice->step)); + return (_Py_IsImmortal(slice->start) && + _Py_IsImmortal(slice->stop) && + _Py_IsImmortal(slice->step)); } - return (PyUnicode_CheckExact(v) || PyLong_CheckExact(v) || - PyFloat_CheckExact(v) || PyComplex_Check(v) || - PyBytes_CheckExact(v) || _Py_IsImmortal(v)); + return (PyLong_CheckExact(v) || PyFloat_CheckExact(v) || + PyComplex_Check(v) || PyBytes_CheckExact(v)); } #endif