From 32b6cd056e3f6e95d6895dc79d56c4bca2cda137 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 29 Apr 2025 19:37:09 +0000 Subject: [PATCH 01/11] gh-133164: Add `PyUnstable_Object_IsUniqueTemporary` C API After gh-130704, the interpreter replaces some uses of `LOAD_FAST` with `LOAD_FAST_BORROW` which avoid incref/decrefs by "borrowing" references on the interpreter stack when the bytecode compiler can determine that its safe. This change broke some checks in C API extensions that relied on `Py_REFCNT()` of `1` to determine if it's safe to modify an object in-place. Objects may have a reference count of one, but still be referenced further up the interpreter stack due to borrowing of references. This provides a replacement function for those checks. `PyUnstable_Object_IsUniqueTemporary` is more conservative: it checks that the object has a reference count of one and that it exists as a unique strong reference in the interpreter's stack of temporary variables in the top most frame. See also: * https://github.com/numpy/numpy/issues/28681 --- Doc/c-api/object.rst | 24 +++++++++++++++ Include/cpython/object.h | 5 ++++ Lib/test/test_capi/test_object.py | 12 ++++++++ ...-04-29-19-39-16.gh-issue-133164.W-XTU7.rst | 5 ++++ Modules/_testcapi/object.c | 8 +++++ Objects/object.c | 30 +++++++++++++++++++ 6 files changed, 84 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index bef3a79ccd0e21..276b1a3221ffe5 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -613,6 +613,30 @@ Object Protocol .. versionadded:: 3.14 +.. c:function:: int PyUnstable_Object_IsUniqueTemporary(PyObject *obj) + + Check if *obj* is a unique temporary object on the top most frame of the + interpreter stack. Returns ``1`` if *obj* is a unique temporary object, + and ``0`` otherwise. This check is conservative, and may return ``0`` + in some cases even if *obj* is a unique temporary object. + + If an object is a unique temporary, it is guaranteed that the reference + count is ``1`` and it may be safe to modify the object in-place becuase + it is not visible to any other code. + + In the example below, ``my_func`` is called with a unique temporary object + as its argument:: + + my_func([1, 2, 3]) + + In the example below, ``my_func`` is **not** called with a unique temporary + object as its argument:: + + my_list = [1, 2, 3] + my_func(my_list) + + .. versionadded:: 3.14 + .. c:function:: int PyUnstable_IsImmortal(PyObject *obj) This function returns non-zero if *obj* is :term:`immortal`, and zero diff --git a/Include/cpython/object.h b/Include/cpython/object.h index e2300aee7a207a..b612cdc6c15f4d 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -543,6 +543,11 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); */ PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); +/* Determine if the object exists as a unique temporary variable on the + * top most frame of the interpreter. + */ +PyAPI_FUNC(int) PyUnstable_Object_IsUniqueTemporary(PyObject *); + /* Check whether the object is immortal. This cannot fail. */ PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *); diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 3e8fd91b9a67a0..e1945e3c636ad2 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -1,4 +1,5 @@ import enum +import sys import textwrap import unittest from test import support @@ -223,5 +224,16 @@ def __del__(self): obj = MyObj() _testinternalcapi.incref_decref_delayed(obj) + def test_is_unique_temporary(self): + self.assertEqual(1, _testcapi.pyobject_is_unique_temporary(object())) + obj = object() + self.assertEqual(0, _testcapi.pyobject_is_unique_temporary(obj)) + + def func(x): + self.assertEqual(sys.getrefcount(x), 1) + self.assertFalse(_testcapi.pyobject_is_unique_temporary(x)) + + func(object()) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst new file mode 100644 index 00000000000000..4e902807670ad8 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst @@ -0,0 +1,5 @@ +Added the :c:func:`PyUnstable_Object_IsUniqueTemporary` function for +determining if an object exists as a unqiue temporary variable on the +interpreter's stack. This is a replacement for some cases where checking +that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's +safe to modify a Python object in-place with no visible side effects. diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 2d538627d213fd..68d275501288e1 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -131,6 +131,13 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) return PyLong_FromLong(result); } +static PyObject * +pyobject_is_unique_temporary(PyObject *self, PyObject *obj) +{ + int result = PyUnstable_Object_IsUniqueTemporary(obj); + return PyLong_FromLong(result); +} + static int MyObject_dealloc_called = 0; static void @@ -478,6 +485,7 @@ static PyMethodDef test_methods[] = { {"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS}, {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O}, {"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O}, + {"pyobject_is_unique_temporary", pyobject_is_unique_temporary, METH_O}, {"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS}, {"test_xincref_doesnt_leak",test_xincref_doesnt_leak, METH_NOARGS}, {"test_incref_doesnt_leak", test_incref_doesnt_leak, METH_NOARGS}, diff --git a/Objects/object.c b/Objects/object.c index 99bb1d9c0bfad5..79135b8ff9baa3 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -15,6 +15,7 @@ #include "pycore_hamt.h" // _PyHamtItems_Type #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_instruction_sequence.h" // _PyInstructionSequence_Type +#include "pycore_interpframe.h" // _PyFrame_Stackbase() #include "pycore_list.h" // _PyList_DebugMallocStats() #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_memoryobject.h" // _PyManagedBuffer_Type @@ -2616,6 +2617,35 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) #endif } +int +PyUnstable_Object_IsUniqueTemporary(PyObject *op) +{ + if (!_PyObject_IsUniquelyReferenced(op)) { + return 0; + } + + _PyInterpreterFrame *frame = _PyEval_GetFrame(); + if (frame == NULL) { + return 0; + } + + _PyStackRef *base = _PyFrame_Stackbase(frame); + _PyStackRef *stackpointer = frame->stackpointer; + int found = 0; + while (stackpointer > base) { + stackpointer--; + if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) { + if (!PyStackRef_IsHeapSafe(*stackpointer)) { + return 0; + } + found++; + } + } + + // Check that we found exactly one reference to `op` + return found == 1; +} + int PyUnstable_TryIncRef(PyObject *op) { From 479f185fc1af57a90e0c29693f48f47a4eca1715 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 29 Apr 2025 19:46:48 +0000 Subject: [PATCH 02/11] Make the asserts use consistent APIs --- Lib/test/test_capi/test_object.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index e1945e3c636ad2..8a2ed798a1eb65 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -225,9 +225,9 @@ def __del__(self): _testinternalcapi.incref_decref_delayed(obj) def test_is_unique_temporary(self): - self.assertEqual(1, _testcapi.pyobject_is_unique_temporary(object())) + self.assertTrue(_testcapi.pyobject_is_unique_temporary(object())) obj = object() - self.assertEqual(0, _testcapi.pyobject_is_unique_temporary(obj)) + self.assertFalse(_testcapi.pyobject_is_unique_temporary(obj)) def func(x): self.assertEqual(sys.getrefcount(x), 1) From 4a53a98dabc53258684cc714c3269df9fe5b6c91 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 29 Apr 2025 20:26:51 +0000 Subject: [PATCH 03/11] Minor edits --- Doc/c-api/object.rst | 5 +++-- .../C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 276b1a3221ffe5..d49b0eec52f0c8 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -617,8 +617,9 @@ Object Protocol Check if *obj* is a unique temporary object on the top most frame of the interpreter stack. Returns ``1`` if *obj* is a unique temporary object, - and ``0`` otherwise. This check is conservative, and may return ``0`` - in some cases even if *obj* is a unique temporary object. + and ``0`` otherwise. This function cannot fail, but the check is + conservative, and may return ``0`` in some cases even if *obj* is a unique + temporary object. If an object is a unique temporary, it is guaranteed that the reference count is ``1`` and it may be safe to modify the object in-place becuase diff --git a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst index 4e902807670ad8..0ef1700f498aa1 100644 --- a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst +++ b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst @@ -1,5 +1,5 @@ -Added the :c:func:`PyUnstable_Object_IsUniqueTemporary` function for -determining if an object exists as a unqiue temporary variable on the -interpreter's stack. This is a replacement for some cases where checking +Add :c:func:`PyUnstable_Object_IsUniqueTemporary` function for +determining if an object exists as a unique temporary variable on the +interpreter's stack. This is a replacement for some cases where checking that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's safe to modify a Python object in-place with no visible side effects. From fdc438afbfaf8c81bbaad2e4dfd5c95ef13887f9 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 29 Apr 2025 21:09:24 +0000 Subject: [PATCH 04/11] Add what's new --- Doc/whatsnew/3.14.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 185a4670d8517d..ff34eea7e05bed 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -88,6 +88,10 @@ If you encounter :exc:`NameError`\s or pickling errors coming out of :mod:`multiprocessing` or :mod:`concurrent.futures`, see the :ref:`forkserver restrictions `. +The interpreter avoids some reference count modifications internally when +it's safe to do so. This can lead to different values returned from +:func:`sys.getrefcount` and :c:func:`Py_REFCNT` compared to previous versions +of Python. See :ref:`below ` for details. New features ============ @@ -2135,6 +2139,11 @@ New features take a C integer and produce a Python :class:`bool` object. (Contributed by Pablo Galindo in :issue:`45325`.) +* Add :c:func:`PyUnstable_Object_IsUniqueTemporary` to determine if an object + is a unique temporary object on the interpreter's operand stack. This can + be used in some cases as a replacement for checking if :c:func:`Py_REFCNT` + is ``1`` for Python objects passed as arguments to C API functions. + Limited C API changes --------------------- @@ -2169,6 +2178,17 @@ Porting to Python 3.14 a :exc:`UnicodeError` object. (Contributed by Bénédikt Tran in :gh:`127691`.) +.. _whatsnew314-refcount: + +* The interpreter internally avoids some reference count modifications when + loading objects onto the operands stack by :term:`borrowing ` + references when possible. This can lead to smaller reference count values + compared to previous Python versions. C API extensions that checked + :c:func:`Py_REFCNT` of ``1`` to determine if an function argument is not + referenced by any other code should isntead use + :c:func:`PyUnstable_Object_IsUniqueTemporary` as a safer replacement. + + * Private functions promoted to public C APIs: * ``_PyBytes_Join()``: :c:func:`PyBytes_Join`. From b6508df13de21f2e2287fcbcef401509c5b86a19 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 29 Apr 2025 20:04:32 -0400 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Pieter Eendebak Co-authored-by: T. Wouters Co-authored-by: mpage --- Doc/c-api/object.rst | 6 +++--- Doc/whatsnew/3.14.rst | 2 +- Include/cpython/object.h | 2 +- Lib/test/test_capi/test_object.py | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index d49b0eec52f0c8..9f8a6bfd65d274 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -615,14 +615,14 @@ Object Protocol .. c:function:: int PyUnstable_Object_IsUniqueTemporary(PyObject *obj) - Check if *obj* is a unique temporary object on the top most frame of the + Check if *obj* is a unique temporary object on the topmost frame of the interpreter stack. Returns ``1`` if *obj* is a unique temporary object, and ``0`` otherwise. This function cannot fail, but the check is conservative, and may return ``0`` in some cases even if *obj* is a unique temporary object. If an object is a unique temporary, it is guaranteed that the reference - count is ``1`` and it may be safe to modify the object in-place becuase + count is ``1`` and it may be safe to modify the object in-place because it is not visible to any other code. In the example below, ``my_func`` is called with a unique temporary object @@ -631,7 +631,7 @@ Object Protocol my_func([1, 2, 3]) In the example below, ``my_func`` is **not** called with a unique temporary - object as its argument:: + object as its argument, even if its refcount is ``1``:: my_list = [1, 2, 3] my_func(my_list) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ff34eea7e05bed..a159c204861e3e 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2185,7 +2185,7 @@ Porting to Python 3.14 references when possible. This can lead to smaller reference count values compared to previous Python versions. C API extensions that checked :c:func:`Py_REFCNT` of ``1`` to determine if an function argument is not - referenced by any other code should isntead use + referenced by any other code should instead use :c:func:`PyUnstable_Object_IsUniqueTemporary` as a safer replacement. diff --git a/Include/cpython/object.h b/Include/cpython/object.h index b612cdc6c15f4d..70bb6e4dcda833 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -544,7 +544,7 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); /* Determine if the object exists as a unique temporary variable on the - * top most frame of the interpreter. + * topmost frame of the interpreter. */ PyAPI_FUNC(int) PyUnstable_Object_IsUniqueTemporary(PyObject *); diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 8a2ed798a1eb65..54a01ac7c4a7ae 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -230,6 +230,7 @@ def test_is_unique_temporary(self): self.assertFalse(_testcapi.pyobject_is_unique_temporary(obj)) def func(x): + # This relies on the LOAD_FAST_BORROW optimization (gh-130704) self.assertEqual(sys.getrefcount(x), 1) self.assertFalse(_testcapi.pyobject_is_unique_temporary(x)) From 07ba5400595e2c5b1830cdb0913f546e3993704f Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 30 Apr 2025 00:13:31 +0000 Subject: [PATCH 06/11] Edit docs --- Doc/c-api/object.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 9f8a6bfd65d274..e8cb9a16c11ead 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -621,9 +621,14 @@ Object Protocol conservative, and may return ``0`` in some cases even if *obj* is a unique temporary object. - If an object is a unique temporary, it is guaranteed that the reference - count is ``1`` and it may be safe to modify the object in-place because - it is not visible to any other code. + If an object is a unique temporary, it is guaranteed that the current code + has the only reference to the object. For arguments to C functions, this + should be used instead of checking if the reference count is ``1``. Starting + with Python 3.14, the interpreter internally avoids some reference count + modifications when loading objects onto the operands stack by + :term:`borrowing ` references when possible, which means + that a reference count of ``1`` by itself does not guarantee that a function + argument uniquely referenced. In the example below, ``my_func`` is called with a unique temporary object as its argument:: From 99e32651aa6572b6c3188b9bd9f6964799d4a11f Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 30 Apr 2025 10:17:25 -0400 Subject: [PATCH 07/11] Apply suggestions from code review Co-authored-by: Mark Shannon --- Doc/c-api/object.rst | 4 ++-- Objects/object.c | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index e8cb9a16c11ead..1455d732584740 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -615,8 +615,8 @@ Object Protocol .. c:function:: int PyUnstable_Object_IsUniqueTemporary(PyObject *obj) - Check if *obj* is a unique temporary object on the topmost frame of the - interpreter stack. Returns ``1`` if *obj* is a unique temporary object, + Check if *obj* is a unique temporary object + Returns ``1`` if *obj* is known to be a unique temporary object, and ``0`` otherwise. This function cannot fail, but the check is conservative, and may return ``0`` in some cases even if *obj* is a unique temporary object. diff --git a/Objects/object.c b/Objects/object.c index 79135b8ff9baa3..71a07a15541b48 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2631,19 +2631,18 @@ PyUnstable_Object_IsUniqueTemporary(PyObject *op) _PyStackRef *base = _PyFrame_Stackbase(frame); _PyStackRef *stackpointer = frame->stackpointer; - int found = 0; while (stackpointer > base) { stackpointer--; if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) { if (!PyStackRef_IsHeapSafe(*stackpointer)) { return 0; } - found++; + return 1; } } // Check that we found exactly one reference to `op` - return found == 1; + return 0; } int From 0e0e67473388747b8718ac73b5d15320dd617fdb Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 30 Apr 2025 14:20:36 +0000 Subject: [PATCH 08/11] Rename function and simplify implementation --- Doc/c-api/object.rst | 2 +- Doc/whatsnew/3.14.rst | 4 ++-- Include/cpython/object.h | 2 +- .../C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst | 2 +- Modules/_testcapi/object.c | 2 +- Objects/object.c | 9 ++------- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 1455d732584740..3d90233035868d 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -613,7 +613,7 @@ Object Protocol .. versionadded:: 3.14 -.. c:function:: int PyUnstable_Object_IsUniqueTemporary(PyObject *obj) +.. c:function:: int PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *obj) Check if *obj* is a unique temporary object Returns ``1`` if *obj* is known to be a unique temporary object, diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index a159c204861e3e..e4ca9a14344ff2 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -2139,7 +2139,7 @@ New features take a C integer and produce a Python :class:`bool` object. (Contributed by Pablo Galindo in :issue:`45325`.) -* Add :c:func:`PyUnstable_Object_IsUniqueTemporary` to determine if an object +* Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` to determine if an object is a unique temporary object on the interpreter's operand stack. This can be used in some cases as a replacement for checking if :c:func:`Py_REFCNT` is ``1`` for Python objects passed as arguments to C API functions. @@ -2186,7 +2186,7 @@ Porting to Python 3.14 compared to previous Python versions. C API extensions that checked :c:func:`Py_REFCNT` of ``1`` to determine if an function argument is not referenced by any other code should instead use - :c:func:`PyUnstable_Object_IsUniqueTemporary` as a safer replacement. + :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` as a safer replacement. * Private functions promoted to public C APIs: diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 70bb6e4dcda833..2c4ca64f586e98 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -546,7 +546,7 @@ PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); /* Determine if the object exists as a unique temporary variable on the * topmost frame of the interpreter. */ -PyAPI_FUNC(int) PyUnstable_Object_IsUniqueTemporary(PyObject *); +PyAPI_FUNC(int) PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *); /* Check whether the object is immortal. This cannot fail. */ PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *); diff --git a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst index 0ef1700f498aa1..dec7c76dd95173 100644 --- a/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst +++ b/Misc/NEWS.d/next/C_API/2025-04-29-19-39-16.gh-issue-133164.W-XTU7.rst @@ -1,4 +1,4 @@ -Add :c:func:`PyUnstable_Object_IsUniqueTemporary` function for +Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` function for determining if an object exists as a unique temporary variable on the interpreter's stack. This is a replacement for some cases where checking that :c:func:`Py_REFCNT` is one is no longer sufficient to determine if it's diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 68d275501288e1..5c67adfee29dc1 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -134,7 +134,7 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) static PyObject * pyobject_is_unique_temporary(PyObject *self, PyObject *obj) { - int result = PyUnstable_Object_IsUniqueTemporary(obj); + int result = PyUnstable_Object_IsUniqueReferencedTemporary(obj); return PyLong_FromLong(result); } diff --git a/Objects/object.c b/Objects/object.c index 71a07a15541b48..4b8ef18d7f9776 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2618,7 +2618,7 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) } int -PyUnstable_Object_IsUniqueTemporary(PyObject *op) +PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *op) { if (!_PyObject_IsUniquelyReferenced(op)) { return 0; @@ -2634,14 +2634,9 @@ PyUnstable_Object_IsUniqueTemporary(PyObject *op) while (stackpointer > base) { stackpointer--; if (op == PyStackRef_AsPyObjectBorrow(*stackpointer)) { - if (!PyStackRef_IsHeapSafe(*stackpointer)) { - return 0; - } - return 1; + return PyStackRef_IsHeapSafe(*stackpointer); } } - - // Check that we found exactly one reference to `op` return 0; } From bba589faf2e750be817b57cbd6da7ae4646bf8d6 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 1 May 2025 10:48:33 -0400 Subject: [PATCH 09/11] Update Doc/c-api/object.rst Co-authored-by: Victor Stinner --- Doc/c-api/object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 3d90233035868d..c6b07d3af3d719 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -615,7 +615,7 @@ Object Protocol .. c:function:: int PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *obj) - Check if *obj* is a unique temporary object + Check if *obj* is a unique temporary object. Returns ``1`` if *obj* is known to be a unique temporary object, and ``0`` otherwise. This function cannot fail, but the check is conservative, and may return ``0`` in some cases even if *obj* is a unique From f23de3351d4cb9b44978b578fb3735b2c58f3096 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 1 May 2025 15:59:43 +0000 Subject: [PATCH 10/11] Add reference to Py_REFCNT --- Doc/c-api/object.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index c6b07d3af3d719..efad4d215b1986 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -641,6 +641,8 @@ Object Protocol my_list = [1, 2, 3] my_func(my_list) + See also the function :c:func:`Py_REFCNT`. + .. versionadded:: 3.14 .. c:function:: int PyUnstable_IsImmortal(PyObject *obj) From 6efe2fb68af81a9231ce3fd565a3d3328ceff504 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 1 May 2025 17:46:15 +0000 Subject: [PATCH 11/11] Add link to PyUnstable_Object_IsUniqueReferencedTemporary in Py_REFCNT --- Doc/c-api/refcounting.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst index d75dad737bc992..83febcf70a5548 100644 --- a/Doc/c-api/refcounting.rst +++ b/Doc/c-api/refcounting.rst @@ -23,6 +23,8 @@ of Python objects. Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count. + See also the function :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary()`. + .. versionchanged:: 3.10 :c:func:`Py_REFCNT()` is changed to the inline static function.