Skip to content

Commit 6d9d6aa

Browse files
committed
gh-157710: Add _PyUnicodeWriter_CanWrite() function (#157712)
Make sure that a writer can be modified before writing into it. * Document that PyUnicodeWriter is not thread safe. * Add singletons tests to test_capi.test_unicode. * Check PyUnicode_CheckExact() earlier in _PyUnicode_IsModifiable(). (cherry picked from commit 8542958)
1 parent 2c23505 commit 6d9d6aa

6 files changed

Lines changed: 52 additions & 11 deletions

File tree

‎Doc/c-api/unicode.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,9 @@ object.
18261826
The instance must be destroyed by :c:func:`PyUnicodeWriter_Finish` on
18271827
success, or :c:func:`PyUnicodeWriter_Discard` on error.
18281828
1829+
The API is **not thread safe**. To share a writer with multiple threads, a
1830+
critical section or a lock is needed.
1831+
18291832
.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length)
18301833
18311834
Create a Unicode writer instance.

‎Include/internal/pycore_unicodeobject.h‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ extern "C" {
1616
#define _Py_MAX_UNICODE 0x10ffff
1717

1818

19-
extern int _PyUnicode_IsModifiable(PyObject *unicode);
19+
// Export for '_multibytecodec' shared extension. _PyUnicodeWriter_CanWrite()
20+
// calls this function when assertions are enabled.
21+
PyAPI_FUNC(int) _PyUnicode_IsModifiable(PyObject *unicode);
2022
extern void _PyUnicodeWriter_InitWithBuffer(
2123
_PyUnicodeWriter *writer,
2224
PyObject *buffer);
@@ -104,12 +106,31 @@ _PyUnicode_EnsureUnicode(PyObject *obj)
104106
return 0;
105107
}
106108

109+
#ifndef NDEBUG
110+
static inline int
111+
_PyUnicodeWriter_CanWrite(_PyUnicodeWriter *writer)
112+
{
113+
// Code adapted from _PyUnicode_IsModifiable()
114+
assert(!writer->readonly);
115+
PyObject *buffer = writer->buffer;
116+
assert(buffer != NULL);
117+
// Do not use _PyObject_IsUniquelyReferenced(): the caller can have its own
118+
// lock to prevent a writer being used by two theads at the same time.
119+
assert(Py_REFCNT(buffer) == 1);
120+
assert(PyUnstable_Unicode_GET_CACHED_HASH(buffer) == -1);
121+
assert(!PyUnicode_CHECK_INTERNED(buffer));
122+
assert(!_Py_IsImmortal(buffer));
123+
return 1;
124+
}
125+
#endif
126+
107127
static inline int
108128
_PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch)
109129
{
110130
assert(ch <= _Py_MAX_UNICODE);
111131
if (_PyUnicodeWriter_Prepare(writer, 1, ch) < 0)
112132
return -1;
133+
assert(_PyUnicodeWriter_CanWrite(writer));
113134
PyUnicode_WRITE(writer->kind, writer->data, writer->pos, ch);
114135
writer->pos++;
115136
return 0;

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,10 +2101,8 @@ def test_substring_empty(self):
21012101
self.assertEqual(writer.finish(), '')
21022102

21032103
def test_singletons(self):
2104-
for size in (0, 123):
2105-
with self.subTest(size=size):
2106-
writer = self.create_writer(size)
2107-
self.assertIs(writer.finish(), '')
2104+
writer = self.create_writer(5)
2105+
self.assertIs(writer.finish(), '')
21082106

21092107
for ch in range(256):
21102108
with self.subTest(ch=ch):

‎Objects/longobject.c‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,7 @@ long_to_decimal_string_internal(PyObject *aa,
22202220
Py_DECREF(scratch);
22212221
return -1;
22222222
}
2223+
assert(_PyUnicodeWriter_CanWrite(writer));
22232224
}
22242225
else if (bytes_writer) {
22252226
*bytes_str = PyBytesWriter_GrowAndUpdatePointer(bytes_writer, strlen,
@@ -2390,8 +2391,10 @@ long_format_binary(PyObject *aa, int base, int alternate,
23902391
}
23912392

23922393
if (writer) {
2393-
if (_PyUnicodeWriter_Prepare(writer, sz, 'x') == -1)
2394+
if (_PyUnicodeWriter_Prepare(writer, sz, 'x') == -1) {
23942395
return -1;
2396+
}
2397+
assert(_PyUnicodeWriter_CanWrite(writer));
23952398
}
23962399
else if (bytes_writer) {
23972400
*bytes_str = PyBytesWriter_GrowAndUpdatePointer(bytes_writer, sz,

‎Objects/unicode_writer.c‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str)
350350
if (_PyUnicodeWriter_PrepareInternal(writer, len, maxchar) == -1)
351351
return -1;
352352
}
353+
354+
assert(_PyUnicodeWriter_CanWrite(writer));
353355
_PyUnicode_FastCopyCharacters(writer->buffer, writer->pos,
354356
str, 0, len);
355357
writer->pos += len;
@@ -428,6 +430,7 @@ _PyUnicodeWriter_WriteSubstring(_PyUnicodeWriter *writer, PyObject *str,
428430
if (_PyUnicodeWriter_Prepare(writer, len, maxchar) < 0) {
429431
return -1;
430432
}
433+
assert(_PyUnicodeWriter_CanWrite(writer));
431434

432435
_PyUnicode_FastCopyCharacters(writer->buffer, writer->pos,
433436
str, start, len);
@@ -485,8 +488,10 @@ _PyUnicodeWriter_WriteASCIIString(_PyUnicodeWriter *writer,
485488
return 0;
486489
}
487490

488-
if (_PyUnicodeWriter_Prepare(writer, len, 127) == -1)
491+
if (_PyUnicodeWriter_Prepare(writer, len, 127) == -1) {
489492
return -1;
493+
}
494+
assert(_PyUnicodeWriter_CanWrite(writer));
490495

491496
switch (writer->kind)
492497
{
@@ -591,6 +596,7 @@ _PyUnicodeWriter_WriteLatin1String(_PyUnicodeWriter *writer,
591596
maxchar = ucs1lib_find_max_char((const Py_UCS1*)str, (const Py_UCS1*)str + len);
592597
if (_PyUnicodeWriter_Prepare(writer, len, maxchar) == -1)
593598
return -1;
599+
assert(_PyUnicodeWriter_CanWrite(writer));
594600
unicode_write_cstr(writer->buffer, writer->pos, str, len);
595601
writer->pos += len;
596602
return 0;

‎Objects/unicodeobject.c‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,18 +1743,21 @@ unicode_is_singleton(PyObject *unicode)
17431743
}
17441744
#endif
17451745

1746+
// If this function is updated, update also _PyUnicodeWriter_CanWrite().
17461747
int
17471748
_PyUnicode_IsModifiable(PyObject *unicode)
17481749
{
17491750
assert(_PyUnicode_CHECK(unicode));
1751+
if (!PyUnicode_CheckExact(unicode))
1752+
return 0;
1753+
// On Free Threading, this test fails if called from a thread other
1754+
// than the one which created the str object.
17501755
if (!_PyObject_IsUniquelyReferenced(unicode))
17511756
return 0;
17521757
if (PyUnicode_HASH(unicode) != -1)
17531758
return 0;
17541759
if (PyUnicode_CHECK_INTERNED(unicode))
17551760
return 0;
1756-
if (!PyUnicode_CheckExact(unicode))
1757-
return 0;
17581761
#ifdef Py_DEBUG
17591762
/* singleton refcount is greater than 1 */
17601763
assert(!unicode_is_singleton(unicode));
@@ -2006,6 +2009,7 @@ PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *pub_writer,
20062009
if (_PyUnicodeWriter_Prepare(writer, size - num_surrogates, maxchar) < 0) {
20072010
return -1;
20082011
}
2012+
assert(_PyUnicodeWriter_CanWrite(writer));
20092013

20102014
int kind = writer->kind;
20112015
void *data = (Py_UCS1*)writer->data + writer->pos * kind;
@@ -2303,6 +2307,7 @@ PyUnicodeWriter_WriteUCS4(PyUnicodeWriter *pub_writer,
23032307
if (_PyUnicodeWriter_Prepare(writer, size, maxchar) < 0) {
23042308
return -1;
23052309
}
2310+
assert(_PyUnicodeWriter_CanWrite(writer));
23062311

23072312
int kind = writer->kind;
23082313
void *data = (Py_UCS1*)writer->data + writer->pos * kind;
@@ -2589,8 +2594,10 @@ unicode_fromformat_write_str(_PyUnicodeWriter *writer, PyObject *str,
25892594
else
25902595
maxchar = writer->maxchar;
25912596

2592-
if (_PyUnicodeWriter_Prepare(writer, arglen, maxchar) == -1)
2597+
if (_PyUnicodeWriter_Prepare(writer, arglen, maxchar) == -1) {
25932598
return -1;
2599+
}
2600+
assert(_PyUnicodeWriter_CanWrite(writer));
25942601

25952602
fill = Py_MAX(width - length, 0);
25962603
if (fill && !(flags & F_LJUST)) {
@@ -2880,8 +2887,10 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
28802887
Py_ssize_t spacepad = Py_MAX(width - precision - sign, 0);
28812888
Py_ssize_t zeropad = Py_MAX(precision - len, 0);
28822889

2883-
if (_PyUnicodeWriter_Prepare(writer, width, 127) == -1)
2890+
if (_PyUnicodeWriter_Prepare(writer, width, 127) == -1) {
28842891
return NULL;
2892+
}
2893+
assert(_PyUnicodeWriter_CanWrite(writer));
28852894

28862895
if (spacepad && !(flags & F_LJUST)) {
28872896
if (PyUnicode_Fill(writer->buffer, writer->pos, spacepad, ' ') == -1)
@@ -5408,6 +5417,7 @@ _PyUnicode_DecodeUTF8Writer(_PyUnicodeWriter *writer,
54085417
if (_PyUnicodeWriter_Prepare(writer, size, 127) < 0) {
54095418
return -1;
54105419
}
5420+
assert(_PyUnicodeWriter_CanWrite(writer));
54115421

54125422
const char *starts = s;
54135423
const char *end = s + size;

0 commit comments

Comments
 (0)