summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-12-01 14:50:16 (GMT)
committerGitHub <noreply@github.com>2023-12-01 14:50:16 (GMT)
commit5f6ac2d88a49b8a7c764691365cd41ee6226a8d0 (patch)
tree317433a73a42b5b11d85ff220d8238f76171d0f5
parentc2982380f827e53057068eccf9f1a16b5a653728 (diff)
downloadcpython-5f6ac2d88a49b8a7c764691365cd41ee6226a8d0.zip
cpython-5f6ac2d88a49b8a7c764691365cd41ee6226a8d0.tar.gz
cpython-5f6ac2d88a49b8a7c764691365cd41ee6226a8d0.tar.bz2
gh-110481: Fix Py_SET_REFCNT() integer overflow (#112174)
If Py_NOGIL is defined and Py_SET_REFCNT() is called with a reference count larger than UINT32_MAX, make the object immortal. Set _Py_IMMORTAL_REFCNT constant type to Py_ssize_t to fix the following compiler warning: Include/internal/pycore_global_objects_fini_generated.h:14:24: warning: comparison of integers of different signs: 'Py_ssize_t' (aka 'long') and 'unsigned int' [-Wsign-compare] if (Py_REFCNT(obj) < _Py_IMMORTAL_REFCNT) { ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~
-rw-r--r--Doc/c-api/refcounting.rst3
-rw-r--r--Doc/using/configure.rst2
-rw-r--r--Include/object.h31
3 files changed, 26 insertions, 10 deletions
diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst
index 68119a2..75e1d46 100644
--- a/Doc/c-api/refcounting.rst
+++ b/Doc/c-api/refcounting.rst
@@ -34,6 +34,9 @@ of Python objects.
Set the object *o* reference counter to *refcnt*.
+ On :ref:`Python build with Free Threading <free-threading-build>`, if
+ *refcnt* is larger than ``UINT32_MAX``, the object is made :term:`immortal`.
+
This function has no effect on :term:`immortal` objects.
.. versionadded:: 3.9
diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index b51546e..56d2d6d 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -287,6 +287,8 @@ General Options
.. versionadded:: 3.11
+.. _free-threading-build:
+
.. option:: --disable-gil
Enables **experimental** support for running Python without the
diff --git a/Include/object.h b/Include/object.h
index 6b70a49..86fcba2 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -88,7 +88,7 @@ having all the lower 32 bits set, which will avoid the reference count to go
beyond the refcount limit. Immortality checks for reference count decreases will
be done by checking the bit sign flag in the lower 32 bits.
*/
-#define _Py_IMMORTAL_REFCNT UINT_MAX
+#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX)
#else
/*
@@ -103,7 +103,7 @@ immortality, but the execution would still be correct.
Reference count increases and decreases will first go through an immortality
check by comparing the reference count field to the immortality reference count.
*/
-#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2)
+#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX >> 2)
#endif
// Py_GIL_DISABLED builds indicate immortal objects using `ob_ref_local`, which is
@@ -317,11 +317,11 @@ static inline Py_ssize_t Py_SIZE(PyObject *ob) {
static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
{
#if defined(Py_GIL_DISABLED)
- return op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL;
+ return (op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL);
#elif SIZEOF_VOID_P > 4
- return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0;
+ return (_Py_CAST(PY_INT32_T, op->ob_refcnt) < 0);
#else
- return op->ob_refcnt == _Py_IMMORTAL_REFCNT;
+ return (op->ob_refcnt == _Py_IMMORTAL_REFCNT);
#endif
}
#define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op))
@@ -350,15 +350,23 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
if (_Py_IsImmortal(ob)) {
return;
}
+
#ifndef Py_GIL_DISABLED
ob->ob_refcnt = refcnt;
#else
if (_Py_IsOwnedByCurrentThread(ob)) {
- // Set local refcount to desired refcount and shared refcount to zero,
- // but preserve the shared refcount flags.
- assert(refcnt < UINT32_MAX);
- ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt);
- ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK;
+ if ((size_t)refcnt > (size_t)UINT32_MAX) {
+ // On overflow, make the object immortal
+ op->ob_tid = _Py_UNOWNED_TID;
+ op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
+ op->ob_ref_shared = 0;
+ }
+ else {
+ // Set local refcount to desired refcount and shared refcount
+ // to zero, but preserve the shared refcount flags.
+ ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt);
+ ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK;
+ }
}
else {
// Set local refcount to zero and shared refcount to desired refcount.
@@ -750,6 +758,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
uint32_t new_local = local + 1;
if (new_local == 0) {
+ // local is equal to _Py_IMMORTAL_REFCNT: do nothing
return;
}
if (_Py_IsOwnedByCurrentThread(op)) {
@@ -763,6 +772,8 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
PY_UINT32_T new_refcnt = cur_refcnt + 1;
if (new_refcnt == 0) {
+ // cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal,
+ // do nothing
return;
}
op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;