From 3fb5f6eb9b2b966142b576610210d2b491c1eebb Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 27 Jan 2025 08:36:33 -0500 Subject: [PATCH] gh-128509: Add `PyUnstable_IsImmortal` for finding immortal objects (GH-129182) Co-authored-by: Victor Stinner Co-authored-by: Serhiy Storchaka Co-authored-by: Petr Viktorin --- Doc/c-api/object.rst | 11 ++++++++++ Doc/whatsnew/3.14.rst | 3 +++ Include/cpython/object.h | 3 +++ Lib/test/test_capi/test_immortal.py | 20 ++++++++++++++----- ...-01-22-09-28-04.gh-issue-128509.gqQ36L.rst | 2 ++ Modules/_testcapi/immortal.c | 7 +++++++ Modules/_testcapi/object.c | 1 + Objects/object.c | 9 +++++++++ 8 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-01-22-09-28-04.gh-issue-128509.gqQ36L.rst diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 3a434a4173eafa..934b2ef06d3108 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -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 diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 7b9e5aca782d06..8d209f597eaf4d 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 ---------------------- diff --git a/Include/cpython/object.h b/Include/cpython/object.h index ba31e2464abf84..4c9e4f6c6e0434 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -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 *); diff --git a/Lib/test/test_capi/test_immortal.py b/Lib/test/test_capi/test_immortal.py index 3e36913ac301c3..660e8a0e789366 100644 --- a/Lib/test/test_capi/test_immortal.py +++ b/Lib/test/test_capi/test_immortal.py @@ -5,12 +5,22 @@ _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): diff --git a/Misc/NEWS.d/next/C_API/2025-01-22-09-28-04.gh-issue-128509.gqQ36L.rst b/Misc/NEWS.d/next/C_API/2025-01-22-09-28-04.gh-issue-128509.gqQ36L.rst new file mode 100644 index 00000000000000..c4a048fe3195d1 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-01-22-09-28-04.gh-issue-128509.gqQ36L.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyUnstable_IsImmortal` for determining whether an object is +:term:`immortal`. diff --git a/Modules/_testcapi/immortal.c b/Modules/_testcapi/immortal.c index 9f81389811c645..5bdae2e99d5375 100644 --- a/Modules/_testcapi/immortal.c +++ b/Modules/_testcapi/immortal.c @@ -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}, }; diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 841410c52b3ce2..1d0169b2af9469 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -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}, diff --git a/Objects/object.c b/Objects/object.c index 51b6016b9c191c..eb1a7825c45450 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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); +}