From 4f40bf2781f1b0051c9ef1eab99dcfbca90c31cf Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 29 Apr 2025 09:48:53 +0900 Subject: [PATCH 1/2] gh-132070: Use _PyObject_IsUniquelyReferenced in unicodeobject (gh-133039) --------- (cherry picked from commit 75cbb8d89e7e92ccaba5c615c72459f241dca8b1) Co-authored-by: Donghee Na Co-authored-by: Kumar Aditya Co-authored-by: Serhiy Storchaka --- Objects/unicodeobject.c | 43 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index a00125345b2dd5..27e66b2a7ca18c 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1121,6 +1121,21 @@ unicode_fill_invalid(PyObject *unicode, Py_ssize_t old_length) } #endif +static PyObject* +resize_copy(PyObject *unicode, Py_ssize_t length) +{ + Py_ssize_t copy_length; + PyObject *copy; + + copy = PyUnicode_New(length, PyUnicode_MAX_CHAR_VALUE(unicode)); + if (copy == NULL) + return NULL; + + copy_length = Py_MIN(length, PyUnicode_GET_LENGTH(unicode)); + _PyUnicode_FastCopyCharacters(copy, 0, unicode, 0, copy_length); + return copy; +} + static PyObject* resize_compact(PyObject *unicode, Py_ssize_t length) { @@ -1132,7 +1147,14 @@ resize_compact(PyObject *unicode, Py_ssize_t length) Py_ssize_t old_length = _PyUnicode_LENGTH(unicode); #endif - assert(unicode_modifiable(unicode)); + if (!unicode_modifiable(unicode)) { + PyObject *copy = resize_copy(unicode, length); + if (copy == NULL) { + return NULL; + } + Py_DECREF(unicode); + return copy; + } assert(PyUnicode_IS_COMPACT(unicode)); char_size = PyUnicode_KIND(unicode); @@ -1232,21 +1254,6 @@ resize_inplace(PyObject *unicode, Py_ssize_t length) return 0; } -static PyObject* -resize_copy(PyObject *unicode, Py_ssize_t length) -{ - Py_ssize_t copy_length; - PyObject *copy; - - copy = PyUnicode_New(length, PyUnicode_MAX_CHAR_VALUE(unicode)); - if (copy == NULL) - return NULL; - - copy_length = Py_MIN(length, PyUnicode_GET_LENGTH(unicode)); - _PyUnicode_FastCopyCharacters(copy, 0, unicode, 0, copy_length); - return copy; -} - static const char* unicode_kind_name(PyObject *unicode) { @@ -1836,7 +1843,7 @@ static int unicode_modifiable(PyObject *unicode) { assert(_PyUnicode_CHECK(unicode)); - if (Py_REFCNT(unicode) != 1) + if (!_PyObject_IsUniquelyReferenced(unicode)) return 0; if (PyUnicode_HASH(unicode) != -1) return 0; @@ -14025,7 +14032,7 @@ _PyUnicode_FormatLong(PyObject *val, int alt, int prec, int type) assert(PyUnicode_IS_ASCII(result)); /* To modify the string in-place, there can only be one reference. */ - if (Py_REFCNT(result) != 1) { + if (!_PyObject_IsUniquelyReferenced(result)) { Py_DECREF(result); PyErr_BadInternalCall(); return NULL; From 143bba0ddf3c66a65f0721cd214b426ffcc0c6d3 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 29 Apr 2025 11:15:42 +0900 Subject: [PATCH 2/2] Add _PyObject_IsUniquelyReferenced --- Include/internal/pycore_object.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 07b5a95ae1a037..bed8b9b171ba02 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -165,6 +165,23 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) extern void _Py_SetImmortal(PyObject *op); extern void _Py_SetImmortalUntracked(PyObject *op); +// Checks if an object has a single, unique reference. If the caller holds a +// unique reference, it may be able to safely modify the object in-place. +static inline int +_PyObject_IsUniquelyReferenced(PyObject *ob) +{ +#if !defined(Py_GIL_DISABLED) + return Py_REFCNT(ob) == 1; +#else + // NOTE: the entire ob_ref_shared field must be zero, including flags, to + // ensure that other threads cannot concurrently create new references to + // this object. + return (_Py_IsOwnedByCurrentThread(ob) && + _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local) == 1 && + _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared) == 0); +#endif +} + // Makes an immortal object mortal again with the specified refcnt. Should only // be used during runtime finalization. static inline void _Py_SetMortal(PyObject *op, Py_ssize_t refcnt)