https://github.com/python/cpython/commit/32c264982ec67460642b907dabc3304019318291
commit: 32c264982ec67460642b907dabc3304019318291
branch: main
author: Sergey Miryanov <[email protected]>
committer: colesbury <[email protected]>
date: 2025-10-15T09:48:21-04:00
summary:
gh-140061: Use `_PyObject_IsUniquelyReferenced()` to check if objects are
uniquely referenced (gh-140062)
The previous `Py_REFCNT(x) == 1` checks can have data races in the free
threaded build. `_PyObject_IsUniquelyReferenced(x)` is a more conservative
check that is safe in the free threaded build and is identical to
`Py_REFCNT(x) == 1` in the default GIL-enabled build.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst
M Modules/_elementtree.c
M Modules/_functoolsmodule.c
M Modules/itertoolsmodule.c
M Objects/bytesobject.c
M Objects/dictobject.c
M Objects/longobject.c
M Objects/setobject.c
M Objects/tupleobject.c
M Python/marshal.c
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst
new file mode 100644
index 00000000000000..7c3924195eb85f
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-15-00-21-40.gh-issue-140061.J0XeDV.rst
@@ -0,0 +1,2 @@
+Fixing the checking of whether an object is uniquely referenced to ensure
+free-threaded compatibility. Patch by Sergey Miryanov.
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 9263f14b57f972..3173b52afb31b6 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -912,7 +912,7 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject
*memo)
return Py_NewRef(object);
}
- if (Py_REFCNT(object) == 1) {
+ if (_PyObject_IsUniquelyReferenced(object)) {
if (PyDict_CheckExact(object)) {
PyObject *key, *value;
Py_ssize_t pos = 0;
@@ -2794,8 +2794,9 @@ treebuilder_handle_data(TreeBuilderObject* self,
PyObject* data)
self->data = Py_NewRef(data);
} else {
/* more than one item; use a list to collect items */
- if (PyBytes_CheckExact(self->data) && Py_REFCNT(self->data) == 1 &&
- PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) {
+ if (PyBytes_CheckExact(self->data)
+ && _PyObject_IsUniquelyReferenced(self->data)
+ && PyBytes_CheckExact(data) && PyBytes_GET_SIZE(data) == 1) {
/* XXX this code path unused in Python 3? */
/* expat often generates single character data sections; handle
the most common case by resizing the existing string... */
diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index 257d5c6d53611c..44b1a302d49ed2 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -291,7 +291,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject
*kw)
if (kw == NULL) {
pto->kw = PyDict_New();
}
- else if (Py_REFCNT(kw) == 1) {
+ else if (_PyObject_IsUniquelyReferenced(kw)) {
pto->kw = Py_NewRef(kw);
}
else {
@@ -1076,7 +1076,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func,
PyObject *seq,
for (;;) {
PyObject *op2;
- if (Py_REFCNT(args) > 1) {
+ if (!_PyObject_IsUniquelyReferenced(args)) {
Py_DECREF(args);
if ((args = PyTuple_New(2)) == NULL)
goto Fail;
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index 60ef6f9ff4cd98..8685eff8be65c3 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -376,7 +376,7 @@ pairwise_next(PyObject *op)
}
result = po->result;
- if (Py_REFCNT(result) == 1) {
+ if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
PyObject *last_old = PyTuple_GET_ITEM(result, 0);
PyObject *last_new = PyTuple_GET_ITEM(result, 1);
@@ -802,7 +802,7 @@ teedataobject_traverse(PyObject *op, visitproc visit, void
* arg)
static void
teedataobject_safe_decref(PyObject *obj)
{
- while (obj && Py_REFCNT(obj) == 1) {
+ while (obj && _PyObject_IsUniquelyReferenced(obj)) {
teedataobject *tmp = teedataobject_CAST(obj);
PyObject *nextlink = tmp->nextlink;
tmp->nextlink = NULL;
@@ -2129,7 +2129,7 @@ product_next_lock_held(PyObject *op)
Py_ssize_t *indices = lz->indices;
/* Copy the previous result tuple or re-use it if available */
- if (Py_REFCNT(result) > 1) {
+ if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), npools);
if (result == NULL)
@@ -2364,7 +2364,7 @@ combinations_next_lock_held(PyObject *op)
}
} else {
/* Copy the previous result tuple or re-use it if available */
- if (Py_REFCNT(result) > 1) {
+ if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@@ -2618,7 +2618,7 @@ cwr_next(PyObject *op)
}
} else {
/* Copy the previous result tuple or re-use it if available */
- if (Py_REFCNT(result) > 1) {
+ if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@@ -2879,7 +2879,7 @@ permutations_next(PyObject *op)
goto empty;
/* Copy the previous result tuple or re-use it if available */
- if (Py_REFCNT(result) > 1) {
+ if (!_PyObject_IsUniquelyReferenced(result)) {
PyObject *old_result = result;
result = PyTuple_FromArray(_PyTuple_ITEMS(old_result), r);
if (result == NULL)
@@ -3847,7 +3847,7 @@ zip_longest_next(PyObject *op)
return NULL;
if (lz->numactive == 0)
return NULL;
- if (Py_REFCNT(result) == 1) {
+ if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
for (i=0 ; i < tuplesize ; i++) {
it = PyTuple_GET_ITEM(lz->ittuple, i);
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index de8ab26db1e966..9c807b3dd166ee 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3171,7 +3171,7 @@ PyBytes_Concat(PyObject **pv, PyObject *w)
return;
}
- if (Py_REFCNT(*pv) == 1 && PyBytes_CheckExact(*pv)) {
+ if (_PyObject_IsUniquelyReferenced(*pv) && PyBytes_CheckExact(*pv)) {
/* Only one reference, so we can resize in place */
Py_ssize_t oldsize;
Py_buffer wb;
@@ -3256,7 +3256,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
Py_DECREF(v);
return 0;
}
- if (Py_REFCNT(v) != 1) {
+ if (!_PyObject_IsUniquelyReferenced(v)) {
if (oldsize < newsize) {
*pv = _PyBytes_FromSize(newsize, 0);
if (*pv) {
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index b95a4a8dc5efec..73d4db4cac7963 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -5667,22 +5667,10 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject
*self,
#endif
-static bool
-has_unique_reference(PyObject *op)
-{
-#ifdef Py_GIL_DISABLED
- return (_Py_IsOwnedByCurrentThread(op) &&
- op->ob_ref_local == 1 &&
- _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0);
-#else
- return Py_REFCNT(op) == 1;
-#endif
-}
-
static bool
acquire_iter_result(PyObject *result)
{
- if (has_unique_reference(result)) {
+ if (_PyObject_IsUniquelyReferenced(result)) {
Py_INCREF(result);
return true;
}
@@ -5831,7 +5819,7 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject
*self)
}
else if (Py_IS_TYPE(di, &PyDictRevIterItem_Type)) {
result = di->di_result;
- if (Py_REFCNT(result) == 1) {
+ if (_PyObject_IsUniquelyReferenced(result)) {
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
PyTuple_SET_ITEM(result, 0, Py_NewRef(key));
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 5eb4063f861f7a..298399210afd58 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -352,7 +352,7 @@ _PyLong_Negate(PyLongObject **x_p)
PyLongObject *x;
x = (PyLongObject *)*x_p;
- if (Py_REFCNT(x) == 1) {
+ if (_PyObject_IsUniquelyReferenced((PyObject *)x)) {
_PyLong_FlipSign(x);
return;
}
@@ -5849,7 +5849,7 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg)
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(c, 1, size_a);
}
- else if (Py_REFCNT(a) == 1) {
+ else if (_PyObject_IsUniquelyReferenced((PyObject *)a)) {
c = (PyLongObject*)Py_NewRef(a);
}
else {
@@ -5863,7 +5863,8 @@ _PyLong_GCD(PyObject *aarg, PyObject *barg)
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(d, 1, size_a);
}
- else if (Py_REFCNT(b) == 1 && size_a <= alloc_b) {
+ else if (_PyObject_IsUniquelyReferenced((PyObject *)b)
+ && size_a <= alloc_b) {
d = (PyLongObject*)Py_NewRef(b);
assert(size_a >= 0);
_PyLong_SetSignAndDigitCount(d, 1, size_a);
diff --git a/Objects/setobject.c b/Objects/setobject.c
index d8340499be5aae..213bd821d8a1b9 100644
--- a/Objects/setobject.c
+++ b/Objects/setobject.c
@@ -2444,7 +2444,7 @@ set_init(PyObject *so, PyObject *args, PyObject *kwds)
if (!PyArg_UnpackTuple(args, Py_TYPE(self)->tp_name, 0, 1, &iterable))
return -1;
- if (Py_REFCNT(self) == 1 && self->fill == 0) {
+ if (_PyObject_IsUniquelyReferenced((PyObject *)self) && self->fill == 0) {
self->hash = -1;
if (iterable == NULL) {
return 0;
@@ -2774,7 +2774,7 @@ int
PySet_Add(PyObject *anyset, PyObject *key)
{
if (!PySet_Check(anyset) &&
- (!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) {
+ (!PyFrozenSet_Check(anyset) ||
!_PyObject_IsUniquelyReferenced(anyset))) {
PyErr_BadInternalCall();
return -1;
}
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 94b7ae7e642283..cd90b06d499faf 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -118,7 +118,7 @@ int
PyTuple_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem)
{
PyObject **p;
- if (!PyTuple_Check(op) || Py_REFCNT(op) != 1) {
+ if (!PyTuple_Check(op) || !_PyObject_IsUniquelyReferenced(op)) {
Py_XDECREF(newitem);
PyErr_BadInternalCall();
return -1;
@@ -923,7 +923,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
v = (PyTupleObject *) *pv;
if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) ||
- (Py_SIZE(v) != 0 && Py_REFCNT(v) != 1)) {
+ (Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(*pv))) {
*pv = 0;
Py_XDECREF(v);
PyErr_BadInternalCall();
diff --git a/Python/marshal.c b/Python/marshal.c
index 4f444d4671cbff..8b56de6575559c 100644
--- a/Python/marshal.c
+++ b/Python/marshal.c
@@ -11,6 +11,7 @@
#include "pycore_code.h" // _PyCode_New()
#include "pycore_hashtable.h" // _Py_hashtable_t
#include "pycore_long.h" // _PyLong_IsZero()
+#include "pycore_object.h" // _PyObject_IsUniquelyReferenced
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_setobject.h" // _PySet_NextEntryRef()
#include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal()
@@ -388,7 +389,7 @@ w_ref(PyObject *v, char *flag, WFILE *p)
* But we use TYPE_REF always for interned string, to PYC file stable
* as possible.
*/
- if (Py_REFCNT(v) == 1 &&
+ if (_PyObject_IsUniquelyReferenced(v) &&
!(PyUnicode_CheckExact(v) && PyUnicode_CHECK_INTERNED(v))) {
return 0;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]