gh-128509: Add PyUnstable_IsImmortal for finding immortal objects (GH-129182)

Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Peter Bierma 2025-01-27 08:36:33 -05:00 committed by GitHub
parent 7ec17429d4
commit 3fb5f6eb9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 51 additions and 5 deletions

View File

@ -613,3 +613,14 @@ Object Protocol
.. versionadded:: 3.14
.. c:function:: int PyUnstable_IsImmortal(PyObject *obj)
This function returns non-zero if *obj* is :term:`immortal`, and zero
otherwise. This function cannot fail.
.. note::
Objects that are immortal in one CPython version are not guaranteed to
be immortal in another.
.. versionadded:: next

View File

@ -1330,6 +1330,9 @@ New features
bit-packing Python version numbers.
(Contributed by Petr Viktorin in :gh:`128629`.)
* Add :c:func:`PyUnstable_IsImmortal` for determining whether an object is :term:`immortal`,
for debugging purposes.
Porting to Python 3.14
----------------------

View File

@ -541,3 +541,6 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**);
* 0 if the runtime ignored it. This function cannot fail.
*/
PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *);
/* Check whether the object is immortal. This cannot fail. */
PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);

View File

@ -5,12 +5,22 @@ _testcapi = import_helper.import_module('_testcapi')
_testinternalcapi = import_helper.import_module('_testinternalcapi')
class TestCAPI(unittest.TestCase):
def test_immortal_builtins(self):
_testcapi.test_immortal_builtins()
class TestUnstableCAPI(unittest.TestCase):
def test_immortal(self):
# Not extensive
known_immortals = (True, False, None, 0, ())
for immortal in known_immortals:
with self.subTest(immortal=immortal):
self.assertTrue(_testcapi.is_immortal(immortal))
# Some arbitrary mutable objects
non_immortals = (object(), self, [object()])
for non_immortal in non_immortals:
with self.subTest(non_immortal=non_immortal):
self.assertFalse(_testcapi.is_immortal(non_immortal))
# CRASHES _testcapi.is_immortal(NULL)
def test_immortal_small_ints(self):
_testcapi.test_immortal_small_ints()
class TestInternalCAPI(unittest.TestCase):

View File

@ -0,0 +1,2 @@
Add :c:func:`PyUnstable_IsImmortal` for determining whether an object is
:term:`immortal`.

View File

@ -31,9 +31,16 @@ test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
Py_RETURN_NONE;
}
static PyObject *
is_immortal(PyObject *self, PyObject *op)
{
return PyBool_FromLong(PyUnstable_IsImmortal(op));
}
static PyMethodDef test_methods[] = {
{"test_immortal_builtins", test_immortal_builtins, METH_NOARGS},
{"test_immortal_small_ints", test_immortal_small_ints, METH_NOARGS},
{"is_immortal", is_immortal, METH_O},
{NULL},
};

View File

@ -131,6 +131,7 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj)
return PyLong_FromLong(result);
}
static PyMethodDef test_methods[] = {
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},

View File

@ -3155,3 +3155,12 @@ Py_REFCNT(PyObject *ob)
{
return _Py_REFCNT(ob);
}
int
PyUnstable_IsImmortal(PyObject *op)
{
/* Checking a reference count requires a thread state */
_Py_AssertHoldsTstate();
assert(op != NULL);
return _Py_IsImmortal(op);
}