From 00307b0e69341ed3325012e6a83a810c397f3649 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 30 Sep 2026 02:47:46 +0200 Subject: [PATCH 1/6] gh-158451: Add _PyUnicodeWriter_SetBuffer() helper function (#158452) Replace _PyUnicodeWriter_Update() with _PyUnicodeWriter_SetBuffer(). Add _PyUnicodeWriter_SetReadOnly() helper function. Add _PyUnicodeWriter_FinishWithSize() for PyUnicode_DecodeUTF7Stateful(). --- Include/internal/pycore_unicodeobject.h | 53 ++++++++++------ Objects/unicode_writer.c | 81 +++++++++++++++---------- Objects/unicodeobject.c | 13 ++-- 3 files changed, 90 insertions(+), 57 deletions(-) diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index c091aa94371a75..1ee4fc4c4517ff 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -130,22 +130,39 @@ _PyUnicodeWriter_CanWrite(_PyUnicodeWriter *writer) #endif static inline void -_PyUnicodeWriter_Update(_PyUnicodeWriter *writer) +_PyUnicodeWriter_SetBuffer(_PyUnicodeWriter *writer, PyObject *buffer) { - PyObject *buffer = writer->buffer; - writer->maxchar = PyUnicode_MAX_CHAR_VALUE(buffer); + assert(writer->pos <= PyUnicode_GET_LENGTH(buffer)); + + // Py_DECREF() the previous buffer (if any) + Py_XSETREF(writer->buffer, buffer); writer->data = PyUnicode_DATA(buffer); writer->kind = PyUnicode_KIND(buffer); + writer->maxchar = PyUnicode_MAX_CHAR_VALUE(buffer); + writer->size = PyUnicode_GET_LENGTH(buffer); + writer->readonly = 0; +} - if (!writer->readonly) { - writer->size = PyUnicode_GET_LENGTH(buffer); - } - else { - /* Copy-on-write mode: set buffer size to 0 so - * _PyUnicodeWriter_Prepare() will copy (and enlarge) the buffer on - * next write. */ - writer->size = 0; - } +static inline void +_PyUnicodeWriter_SetReadOnly(_PyUnicodeWriter *writer, PyObject *obj, + Py_ssize_t length) +{ + assert(writer->buffer == NULL); + assert(writer->pos == 0); + // Micro-optimization: pass length as a parameter, as it's usually known + // by the caller + assert(length == PyUnicode_GET_LENGTH(obj)); + + writer->buffer = obj; + writer->data = NULL; + /* Set kind and size to 0 to make sure that the next + * _PyUnicodeWriter_Prepare() call allocates a new buffer and copies + * characters. */ + writer->kind = 0; + writer->maxchar = PyUnicode_MAX_CHAR_VALUE(obj); + writer->size = 0; + writer->pos = length; + writer->readonly = 1; } static inline int @@ -156,11 +173,9 @@ _PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch) // If the first write is a Latin1 character, use the singleton // as a read-only object PyObject *obj = _Py_LATIN1_CHR(ch); - writer->readonly = 1; - writer->buffer = obj; // Py_NewRef() is not need on immortal object - _PyUnicodeWriter_Update(writer); - assert(writer->pos == 0); - writer->pos = 1; + // Py_NewRef() is not need on immortal object + _PyUnicodeWriter_SetReadOnly(writer, obj, 1); + // The next write will create a new buffer and copy the string return 0; } @@ -176,6 +191,10 @@ _PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch) return 0; } +extern PyObject* _PyUnicodeWriter_FinishWithSize( + _PyUnicodeWriter *writer, + Py_ssize_t size); + /* --- Unicode API -------------------------------------------------------- */ // Export for '_json' shared extension diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index 26deffa6baac63..8637be921e2454 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -178,8 +178,7 @@ _PyUnicodeWriter_InitWithBuffer(_PyUnicodeWriter *writer, PyObject *buffer) assert(PyUnstable_Object_IsUniquelyReferenced(buffer)); memset(writer, 0, sizeof(*writer)); - writer->buffer = buffer; - _PyUnicodeWriter_Update(writer); + _PyUnicodeWriter_SetBuffer(writer, buffer); writer->min_length = writer->size; assert(_PyUnicodeWriter_CanWrite(writer)); } @@ -204,16 +203,19 @@ _PyUnicodeWriter_PrepareInternal(_PyUnicodeWriter *writer, maxchar = Py_MAX(maxchar, writer->min_char); - PyObject *newbuffer; + PyObject *new_buffer; if (writer->buffer == NULL) { assert(!writer->readonly); + // Do not overallocate at the first allocation, but use min_length - if (alloc < writer->min_length) + if (alloc < writer->min_length) { alloc = writer->min_length; + } - writer->buffer = PyUnicode_New(alloc, maxchar); - if (writer->buffer == NULL) + new_buffer = PyUnicode_New(alloc, maxchar); + if (new_buffer == NULL) { return -1; + } } else if (alloc > writer->size) { // Do not overallocate at the first allocation, but use min_length @@ -223,38 +225,42 @@ _PyUnicodeWriter_PrepareInternal(_PyUnicodeWriter *writer, /* overallocate to limit the number of realloc() */ alloc += alloc / OVERALLOCATE_FACTOR; } - if (alloc < writer->min_length) + if (alloc < writer->min_length) { alloc = writer->min_length; + } if (maxchar > writer->maxchar || writer->readonly) { /* resize + widen */ maxchar = Py_MAX(maxchar, writer->maxchar); - newbuffer = PyUnicode_New(alloc, maxchar); - if (newbuffer == NULL) + new_buffer = PyUnicode_New(alloc, maxchar); + if (new_buffer == NULL) { return -1; - _PyUnicode_FastCopyCharacters(newbuffer, 0, + } + _PyUnicode_FastCopyCharacters(new_buffer, 0, writer->buffer, 0, writer->pos); - writer->readonly = 0; - Py_DECREF(writer->buffer); - writer->buffer = newbuffer; } else { - newbuffer = _PyUnicode_ResizeCompact(writer->buffer, alloc); - if (newbuffer == NULL) + new_buffer = _PyUnicode_ResizeCompact(writer->buffer, alloc); + if (new_buffer == NULL) { return -1; - writer->buffer = newbuffer; + } + // Do not DECREF the old buffer + writer->buffer = NULL; } } - else if (maxchar > writer->maxchar) { + else { + assert(maxchar > writer->maxchar); assert(!writer->readonly); - newbuffer = PyUnicode_New(writer->size, maxchar); - if (newbuffer == NULL) + + new_buffer = PyUnicode_New(writer->size, maxchar); + if (new_buffer == NULL) { return -1; - _PyUnicode_FastCopyCharacters(newbuffer, 0, + } + _PyUnicode_FastCopyCharacters(new_buffer, 0, writer->buffer, 0, writer->pos); - Py_SETREF(writer->buffer, newbuffer); } - _PyUnicodeWriter_Update(writer); + + _PyUnicodeWriter_SetBuffer(writer, new_buffer); return 0; #undef OVERALLOCATE_FACTOR @@ -316,11 +322,7 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str) if (maxchar > writer->maxchar || len > writer->size - writer->pos) { if (writer->buffer == NULL && PyUnicode_CheckExact(str)) { assert(_PyUnicode_CheckConsistency(str, 1)); - writer->readonly = 1; - writer->buffer = Py_NewRef(str); - _PyUnicodeWriter_Update(writer); - writer->pos += len; - // The next write will create a new buffer and copy the string + _PyUnicodeWriter_SetReadOnly(writer, Py_NewRef(str), len); return 0; } if (_PyUnicodeWriter_PrepareInternal(writer, len, maxchar) == -1) @@ -457,10 +459,7 @@ _PyUnicodeWriter_WriteASCIIString(_PyUnicodeWriter *writer, if (str == NULL) return -1; - writer->readonly = 1; - writer->buffer = str; - _PyUnicodeWriter_Update(writer); - writer->pos += len; + _PyUnicodeWriter_SetReadOnly(writer, str, len); return 0; } @@ -639,6 +638,26 @@ _PyUnicodeWriter_Finish(_PyUnicodeWriter *writer) } +PyObject * +_PyUnicodeWriter_FinishWithSize(_PyUnicodeWriter *writer, Py_ssize_t size) +{ + assert(0 <= size); + if (writer->buffer != NULL) { + assert(size <= writer->pos); + assert(size <= PyUnicode_GET_LENGTH(writer->buffer)); + if (size < writer->pos) { + // Truncate the string: we may need to adjust the string kind + writer->recheck_maxchar = 1; + } + } + else { + assert(size == 0); + } + writer->pos = size; + return _PyUnicodeWriter_Finish(writer); +} + + PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) { diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 893621f041c9ad..43b720e7cde218 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -4761,15 +4761,10 @@ PyUnicode_DecodeUTF7Stateful(const char *s, if (consumed) { if (inShift) { *consumed = startinpos; - if (writer.pos != shiftOutStart && writer.maxchar > 127) { - PyObject *result = PyUnicode_FromKindAndData( - writer.kind, writer.data, shiftOutStart); - Py_XDECREF(errorHandler); - Py_XDECREF(exc); - _PyUnicodeWriter_Dealloc(&writer); - return result; - } - writer.pos = shiftOutStart; /* back off output */ + + Py_XDECREF(errorHandler); + Py_XDECREF(exc); + return _PyUnicodeWriter_FinishWithSize(&writer, shiftOutStart); } else { *consumed = s-starts; From 26d3f5dd6a9c6699f430138db0e92d224f958747 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 30 Sep 2026 03:53:38 +0200 Subject: [PATCH 2/6] gh-158451: Add _PyUnicodeWriter_WriteLatin1String() test (#158464) Fix _PyUnicodeWriter_WriteLatin1String() when the writer buffer kind is UCS-2 or UCS-4. --- Lib/test/test_capi/test_unicode.py | 18 ++++ ...-09-30-03-19-34.gh-issue-158451.pUb2BJ.rst | 2 + Modules/_testcapi/unicode.c | 27 ++++++ Objects/unicode_writer.c | 91 +++++++------------ 4 files changed, 81 insertions(+), 57 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-30-03-19-34.gh-issue-158451.pUb2BJ.rst diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index 36ecca48230ddd..c511ffc0fda13c 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -1970,6 +1970,24 @@ def test_ascii(self): writer.write_ascii(b"Python! ", 6) self.assertEqual(writer.finish(), "Hello Python") + def test_write_latin1(self): + # Test _PyUnicodeWriter_WriteLatin1String() + writer = self.create_writer(0) + # Start with ASCII buffer + writer.write_latin1(b"abc IGNORED", 3) + writer.write_latin1(b"IGNORED", 0) + # Change buffer kind to UCS-1 + writer.write_latin1(b"\xe9", 1) + # Change buffer kind to UCS-2 + writer.write_str('[\u20ac]') + writer.write_latin1(b"def\xa0", 4) + # Change buffer kind to UCS-4 + writer.write_str('[\U0010ffff]') + writer.write_latin1(b"ghi\xff.", 5) + writer.write_latin1(b"IGNORED", 0) + self.assertEqual(writer.finish(), + "abc\xe9[\u20ac]def\xa0[\U0010ffff]ghi\xff.") + def test_invalid_utf8(self): writer = self.create_writer(0) with self.assertRaises(UnicodeDecodeError): diff --git a/Misc/NEWS.d/next/C_API/2026-09-30-03-19-34.gh-issue-158451.pUb2BJ.rst b/Misc/NEWS.d/next/C_API/2026-09-30-03-19-34.gh-issue-158451.pUb2BJ.rst new file mode 100644 index 00000000000000..a16dd024287076 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-30-03-19-34.gh-issue-158451.pUb2BJ.rst @@ -0,0 +1,2 @@ +Fix :c:func:`!_PyUnicodeWriter_WriteLatin1String` when the writer buffer +kind is UCS-2 or UCS-4. Patch by Victor Stinner. diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c index c62813f4f3768d..ba9b205e07ef8b 100644 --- a/Modules/_testcapi/unicode.c +++ b/Modules/_testcapi/unicode.c @@ -685,6 +685,32 @@ writer_write_substring(PyObject *self_raw, PyObject *args) } +static PyObject* +writer_write_latin1(PyObject *self_raw, PyObject *args) +{ + WriterObject *self = (WriterObject *)self_raw; + if (writer_check(self) < 0) { + return NULL; + } + + const char *str; + Py_ssize_t bsize, size; + if (!PyArg_ParseTuple(args, "z#n", &str, &bsize, &size)) { + return NULL; + } + + _PyUnicodeWriter *writer = (_PyUnicodeWriter*)self->writer; +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + if (_PyUnicodeWriter_WriteLatin1String(writer, str, size) < 0) { + return NULL; + } +_Py_COMP_DIAG_POP + + Py_RETURN_NONE; +} + + static PyObject* writer_decodeutf8stateful(PyObject *self_raw, PyObject *args) { @@ -778,6 +804,7 @@ static PyMethodDef writer_methods[] = { {"write_str", _PyCFunction_CAST(writer_write_str), METH_O}, {"write_repr", _PyCFunction_CAST(writer_write_repr), METH_O}, {"write_substring", _PyCFunction_CAST(writer_write_substring), METH_VARARGS}, + {"write_latin1", _PyCFunction_CAST(writer_write_latin1), METH_VARARGS}, {"decodeutf8stateful", _PyCFunction_CAST(writer_decodeutf8stateful), METH_VARARGS}, {"get_pointer", _PyCFunction_CAST(writer_get_pointer), METH_VARARGS}, {"get_buffer", _PyCFunction_CAST(writer_get_buffer), METH_VARARGS}, diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index 8637be921e2454..0949e45d51cbad 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -62,59 +62,6 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "stringlib/undef.h" -/* Copy an ASCII or latin1 char* string into a Python Unicode string. - - WARNING: The function doesn't copy the terminating null character and - doesn't check the maximum character (may write a latin1 character in an - ASCII string). */ -static void -unicode_write_cstr(PyObject *unicode, Py_ssize_t index, - const char *str, Py_ssize_t len) -{ - int kind = PyUnicode_KIND(unicode); - const void *data = PyUnicode_DATA(unicode); - const char *end = str + len; - - assert(index + len <= PyUnicode_GET_LENGTH(unicode)); - switch (kind) { - case PyUnicode_1BYTE_KIND: { -#ifdef Py_DEBUG - if (PyUnicode_IS_ASCII(unicode)) { - Py_UCS4 maxchar = ucs1lib_find_max_char( - (const Py_UCS1*)str, - (const Py_UCS1*)str + len); - assert(maxchar < 128); - } -#endif - memcpy((char *) data + index, str, len); - break; - } - case PyUnicode_2BYTE_KIND: { - Py_UCS2 *start = (Py_UCS2 *)data + index; - Py_UCS2 *ucs2 = start; - - for (; str < end; ++ucs2, ++str) - *ucs2 = (Py_UCS2)*str; - - assert((ucs2 - start) <= PyUnicode_GET_LENGTH(unicode)); - break; - } - case PyUnicode_4BYTE_KIND: { - Py_UCS4 *start = (Py_UCS4 *)data + index; - Py_UCS4 *ucs4 = start; - - for (; str < end; ++ucs4, ++str) - *ucs4 = (Py_UCS4)*str; - - assert((ucs4 - start) <= PyUnicode_GET_LENGTH(unicode)); - break; - } - default: - Py_UNREACHABLE(); - } -} - - void _PyUnicodeWriter_Init(_PyUnicodeWriter *writer) { @@ -550,13 +497,43 @@ int _PyUnicodeWriter_WriteLatin1String(_PyUnicodeWriter *writer, const char *str, Py_ssize_t len) { - Py_UCS4 maxchar; + if (len == 0) { + return 0; + } - maxchar = ucs1lib_find_max_char((const Py_UCS1*)str, (const Py_UCS1*)str + len); - if (_PyUnicodeWriter_Prepare(writer, len, maxchar) == -1) + const Py_UCS1 *ucs1 = (const Py_UCS1 *)str; + Py_UCS4 maxchar = ucs1lib_find_max_char(ucs1, ucs1 + len); + if (_PyUnicodeWriter_Prepare(writer, len, maxchar) < 0) { return -1; + } assert(_PyUnicodeWriter_CanWrite(writer)); - unicode_write_cstr(writer->buffer, writer->pos, str, len); + + Py_ssize_t index = writer->pos; + switch (writer->kind) { + case PyUnicode_1BYTE_KIND: { + memcpy((Py_UCS1 *)writer->data + index, ucs1, len); + break; + } + case PyUnicode_2BYTE_KIND: { + Py_UCS2 *ucs2 = (Py_UCS2 *)writer->data + index; + const Py_UCS1 *end = ucs1 + len; + for (; ucs1 < end; ++ucs2, ++ucs1) { + *ucs2 = (Py_UCS2)*ucs1; + } + break; + } + case PyUnicode_4BYTE_KIND: { + Py_UCS4 *ucs4 = (Py_UCS4 *)writer->data + index; + const Py_UCS1 *end = ucs1 + len; + for (; ucs1 < end; ++ucs4, ++ucs1) { + *ucs4 = (Py_UCS4)*ucs1; + } + break; + } + default: + Py_UNREACHABLE(); + } + writer->pos += len; return 0; } From c3cb46870bdb18d104557d8b69d451a692c64c8a Mon Sep 17 00:00:00 2001 From: Himesh Rupchandani Date: Wed, 30 Sep 2026 07:54:24 +0530 Subject: [PATCH 3/6] gh-158364: Don't report other interpreters' threads in `sys._current_frames` (GH-158369) --- Lib/test/test_sys.py | 65 ++++++++++++ ...-09-28-18-58-27.gh-issue-158364.jBqmTs.rst | 3 + Python/pystate.c | 98 +++++++++---------- 3 files changed, 114 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index da1bd381dd182e..1ccdcf5a187bbb 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -563,6 +563,38 @@ def g456(): leave_g.set() t.join() + @support.cpython_only + @requires_subinterpreters + @threading_helper.requires_working_threading() + def test_current_frames_other_interpreters(self): + # gh-158364: sys._current_frames() would access frames of another + # interpreter and crash + import threading + + entered = threading.Event() + left = threading.Event() + + def park(): + entered.set() + left.wait() + + t = threading.Thread(target=park) + with threading_helper.start_threads([t], unlock=left.set): + entered.wait() + interp = interpreters.create() + try: + interp.exec(f"""if True: + import sys + import threading + + frames = sys._current_frames() + assert threading.get_ident() in frames, frames + assert frames[threading.get_ident()].f_globals is globals() + assert {t.ident} not in frames, frames + """) + finally: + interp.close() + @threading_helper.reap_threads @threading_helper.requires_working_threading() def test_current_exceptions(self): @@ -629,6 +661,39 @@ def g456(): leave_g.set() t.join() + @support.cpython_only + @requires_subinterpreters + @threading_helper.requires_working_threading() + def test_current_exceptions_other_interpreters(self): + # gh-158364: sys._current_exceptions() would hand out exceptions of + # another interpreter and crash + import threading + + entered = threading.Event() + left = threading.Event() + + def hold(): + # The thread has to be handling an exception, otherwise + # sys._current_exceptions() has nothing to report for it. + try: + raise ValueError + except ValueError: + entered.set() + left.wait() + + t = threading.Thread(target=hold) + with threading_helper.start_threads([t], unlock=left.set): + entered.wait() + interp = interpreters.create() + try: + interp.exec(f"""if True: + import sys + + assert {t.ident} not in sys._current_exceptions() + """) + finally: + interp.close() + def test_attributes(self): self.assertIsInstance(sys.api_version, int) self.assertIsInstance(sys.argv, list) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst new file mode 100644 index 00000000000000..14ab4ef12becae --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst @@ -0,0 +1,3 @@ +Fix crash when :func:`sys._current_frames` or +:func:`sys._current_exceptions` is called while another interpreter is +running. diff --git a/Python/pystate.c b/Python/pystate.c index 737bf0f216bd34..76ad958478a3ca 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2801,36 +2801,33 @@ _PyThread_CurrentFrames(void) return NULL; } - /* for i in all interpreters: - * for t in all of i's thread states: - * if t's frame isn't NULL, map t's id to its frame + /* for t in all of the current interpreter's thread states: + * if t's frame isn't NULL, map t's id to its frame * Because these lists can mutate even when the GIL is held, we * need to grab head_mutex for the duration. */ - _PyEval_StopTheWorldAll(runtime); + PyInterpreterState *interp = tstate->interp; + _PyEval_StopTheWorld(interp); HEAD_LOCK(runtime); - PyInterpreterState *i; - for (i = runtime->interpreters.head; i != NULL; i = i->next) { - _Py_FOR_EACH_TSTATE_UNLOCKED(i, t) { - _PyInterpreterFrame *frame = t->current_frame; - frame = _PyFrame_GetFirstComplete(frame); - if (frame == NULL) { - continue; - } - PyObject *id = PyLong_FromUnsignedLong(t->thread_id); - if (id == NULL) { - goto fail; - } - PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame); - if (frameobj == NULL) { - Py_DECREF(id); - goto fail; - } - int stat = PyDict_SetItem(result, id, frameobj); + _Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) { + _PyInterpreterFrame *frame = t->current_frame; + frame = _PyFrame_GetFirstComplete(frame); + if (frame == NULL) { + continue; + } + PyObject *id = PyLong_FromUnsignedLong(t->thread_id); + if (id == NULL) { + goto fail; + } + PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame); + if (frameobj == NULL) { Py_DECREF(id); - if (stat < 0) { - goto fail; - } + goto fail; + } + int stat = PyDict_SetItem(result, id, frameobj); + Py_DECREF(id); + if (stat < 0) { + goto fail; } } goto done; @@ -2840,7 +2837,7 @@ _PyThread_CurrentFrames(void) done: HEAD_UNLOCK(runtime); - _PyEval_StartTheWorldAll(runtime); + _PyEval_StartTheWorld(interp); return result; } @@ -2866,35 +2863,32 @@ _PyThread_CurrentExceptions(void) return NULL; } - /* for i in all interpreters: - * for t in all of i's thread states: - * if t's frame isn't NULL, map t's id to its frame + /* for t in all of the current interpreter's thread states: + * if t's frame isn't NULL, map t's id to its exception * Because these lists can mutate even when the GIL is held, we * need to grab head_mutex for the duration. */ - _PyEval_StopTheWorldAll(runtime); + PyInterpreterState *interp = tstate->interp; + _PyEval_StopTheWorld(interp); HEAD_LOCK(runtime); - PyInterpreterState *i; - for (i = runtime->interpreters.head; i != NULL; i = i->next) { - _Py_FOR_EACH_TSTATE_UNLOCKED(i, t) { - _PyErr_StackItem *err_info = _PyErr_GetTopmostException(t); - if (err_info == NULL) { - continue; - } - PyObject *id = PyLong_FromUnsignedLong(t->thread_id); - if (id == NULL) { - goto fail; - } - PyObject *exc = err_info->exc_value; - assert(exc == NULL || - exc == Py_None || - PyExceptionInstance_Check(exc)); - - int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc); - Py_DECREF(id); - if (stat < 0) { - goto fail; - } + _Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) { + _PyErr_StackItem *err_info = _PyErr_GetTopmostException(t); + if (err_info == NULL) { + continue; + } + PyObject *id = PyLong_FromUnsignedLong(t->thread_id); + if (id == NULL) { + goto fail; + } + PyObject *exc = err_info->exc_value; + assert(exc == NULL || + exc == Py_None || + PyExceptionInstance_Check(exc)); + + int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc); + Py_DECREF(id); + if (stat < 0) { + goto fail; } } goto done; @@ -2904,7 +2898,7 @@ _PyThread_CurrentExceptions(void) done: HEAD_UNLOCK(runtime); - _PyEval_StartTheWorldAll(runtime); + _PyEval_StartTheWorld(interp); return result; } From 82a89b38aa48688c593f7888964b153015d38462 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 30 Sep 2026 05:30:22 +0200 Subject: [PATCH 4/6] gh-158451: Reuse PyUnicodeWriter_WriteStr() in PyUnicode_Format() (#158467) Remove special case for int in PyUnicode_Format(). Instead, reuse PyUnicodeWriter_WriteStr() and PyUnicodeWriter_WriteRepr() in PyUnicode_Format(). Add PyUnicode_Format() tests on integer formats. --- Lib/test/test_format.py | 15 +++++++++++++++ Objects/unicode_format.c | 38 ++++++++++++++++++++++---------------- Objects/unicode_writer.c | 18 ++++++++++-------- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 5d322cb444cfb6..a556faae252fa5 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -240,6 +240,8 @@ def test_common_format(self): testcommon("%d", 42, "42") testcommon("%d", -42, "-42") testcommon("%d", 42.0, "42") + testcommon("%#d", 42, "42") + testcommon("%#d", -42, "-42") testcommon("%#x", 1, "0x1") testcommon("%#X", 1, "0X1") testcommon("%#o", 1, "0o1") @@ -250,8 +252,12 @@ def test_common_format(self): testcommon("%#X", 0, "0X0") testcommon("%x", 0x42, "42") testcommon("%x", -0x42, "-42") + testcommon("%#x", 0x42, "0x42") + testcommon("%#x", -0x42, "-0x42") testcommon("%o", 0o42, "42") testcommon("%o", -0o42, "-42") + testcommon("%#o", 0o42, "0o42") + testcommon("%#o", -0o42, "-0o42") # alternate float formatting testcommon('%g', 1.1, '1.1') testcommon('%#g', 1.1, '1.10000') @@ -344,12 +350,21 @@ def test_common_format(self): "format argument 1: %g requires a real number, not str") def test_str_format(self): + testformat("%s", "abc", "abc") testformat("%r", "\u0378", "'\\u0378'") # non printable testformat("%a", "\u0378", "'\\u0378'") # non printable testformat("%r", "\u0374", "'\u0374'") # printable testformat("%a", "\u0374", "'\\u0374'") # printable testformat('%(x)r', {'x': 1}, '1') + # Some small ints + for fmt in ('s', 'r', 'a'): + with self.subTest(fmt=fmt): + testformat("%" + fmt, 42, "42") + testformat("%#" + fmt, 42, "42") + testformat("%" + fmt, -42, "-42") + testformat("%#" + fmt, -42, "-42") + # Test exception for unknown format characters, etc. if verbose: print('Testing exceptions') diff --git a/Objects/unicode_format.c b/Objects/unicode_format.c index 1d6f3f7d9a6f6a..caadf18f6a5d30 100644 --- a/Objects/unicode_format.c +++ b/Objects/unicode_format.c @@ -571,6 +571,7 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, arg->ch = FORMAT_READ(ctx); ctx->fmtpos++; } + assert(arg->width >= 0); } else if (arg->ch >= '0' && arg->ch <= '9') { arg->width = arg->ch - '0'; @@ -590,6 +591,7 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, } arg->width = arg->width*10 + (arg->ch - '0'); } + assert(arg->width >= 0); } /* Parse precision. Example: "%.3f" => prec=3 */ @@ -645,6 +647,7 @@ unicode_format_arg_parse(struct unicode_formatter_t *ctx, arg->prec = arg->prec*10 + (arg->ch - '0'); } } + assert(arg->prec >= 0); } /* Ignore "h", "l" and "L" format prefix (ex: "%hi" or "%ls") */ @@ -701,24 +704,27 @@ unicode_format_arg_format(struct unicode_formatter_t *ctx, case 's': case 'r': case 'a': - if (PyLong_CheckExact(v) && arg->width == -1 && arg->prec == -1) { - /* Fast path */ - if (_PyLong_FormatWriter(writer, v, 10, arg->flags & F_ALT) == -1) - return -1; - return 1; + if (arg->width < 0 && arg->prec < 0) { + if (arg->ch == 's') { + if (PyUnicodeWriter_WriteStr((PyUnicodeWriter*)writer, v) < 0) { + return -1; + } + return 1; + } + else if (arg->ch == 'r') { + if (PyUnicodeWriter_WriteRepr((PyUnicodeWriter*)writer, v) < 0) { + return -1; + } + return 1; + } } - if (PyUnicode_CheckExact(v) && arg->ch == 's') { - *p_str = Py_NewRef(v); - } - else { - if (arg->ch == 's') - *p_str = PyObject_Str(v); - else if (arg->ch == 'r') - *p_str = PyObject_Repr(v); - else - *p_str = PyObject_ASCII(v); - } + if (arg->ch == 's') + *p_str = PyObject_Str(v); + else if (arg->ch == 'r') + *p_str = PyObject_Repr(v); + else + *p_str = PyObject_ASCII(v); break; case 'i': diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index 0949e45d51cbad..be3cfb2538b780 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -285,15 +285,16 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str) int -PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) +PyUnicodeWriter_WriteStr(PyUnicodeWriter *pub_writer, PyObject *obj) { + _PyUnicodeWriter *writer = (_PyUnicodeWriter*)pub_writer; PyTypeObject *type = Py_TYPE(obj); if (type == &PyUnicode_Type) { - return _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, obj); + return _PyUnicodeWriter_WriteStr(writer, obj); } if (type == &PyLong_Type) { - return _PyLong_FormatWriter((_PyUnicodeWriter*)writer, obj, 10, 0); + return _PyLong_FormatWriter(writer, obj, 10, 0); } PyObject *str = PyObject_Str(obj); @@ -301,21 +302,22 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) return -1; } - int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + int res = _PyUnicodeWriter_WriteStr(writer, str); Py_DECREF(str); return res; } int -PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) +PyUnicodeWriter_WriteRepr(PyUnicodeWriter *pub_writer, PyObject *obj) { + _PyUnicodeWriter *writer = (_PyUnicodeWriter*)pub_writer; if (obj == NULL) { - return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, "", 6); + return _PyUnicodeWriter_WriteASCIIString(writer, "", 6); } if (Py_TYPE(obj) == &PyLong_Type) { - return _PyLong_FormatWriter((_PyUnicodeWriter*)writer, obj, 10, 0); + return _PyLong_FormatWriter(writer, obj, 10, 0); } PyObject *repr = PyObject_Repr(obj); @@ -323,7 +325,7 @@ PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) return -1; } - int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, repr); + int res = _PyUnicodeWriter_WriteStr(writer, repr); Py_DECREF(repr); return res; } From 70e6f3cfe1da77859f1d497f6354b8a87048ed65 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 30 Sep 2026 05:31:36 +0200 Subject: [PATCH 5/6] gh-158451: Convert _PyUnicodeWriter_Prepare() to static inline function (#158466) Add assertion to _PyUnicodeWriter_Prepare() to check that length is not negative. Add some assertions to check that _PyUnicodeWriter_Prepare() is called with len >= 1. --- Include/cpython/unicodeobject.h | 36 +++++++++++++++---------- Include/internal/pycore_unicodeobject.h | 6 ++--- Lib/test/test_capi/test_unicode.py | 8 ++++++ Objects/longobject.c | 7 ++--- Objects/unicode_formatter.c | 3 +++ Objects/unicode_writer.c | 12 +++++---- Objects/unicodeobject.c | 6 ++--- 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 3da18a6ad35db3..54defdb2061aa9 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -557,25 +557,33 @@ typedef struct { _Py_DEPRECATED_EXTERNALLY(3.14) PyAPI_FUNC(void) _PyUnicodeWriter_Init( _PyUnicodeWriter *writer); -/* Prepare the buffer to write 'length' characters - with the specified maximum character. - - Return 0 on success, raise an exception and return -1 on error. */ -#define _PyUnicodeWriter_Prepare(WRITER, LENGTH, MAXCHAR) \ - (((MAXCHAR) <= (WRITER)->maxchar \ - && (LENGTH) <= (WRITER)->size - (WRITER)->pos) \ - ? 0 \ - : (((LENGTH) == 0) \ - ? 0 \ - : _PyUnicodeWriter_PrepareInternal((WRITER), (LENGTH), (MAXCHAR)))) - -/* Don't call this function directly, use the _PyUnicodeWriter_Prepare() macro - instead. */ +// Don't call this function directly, use _PyUnicodeWriter_Prepare() instead. _Py_DEPRECATED_EXTERNALLY(3.14) PyAPI_FUNC(int) _PyUnicodeWriter_PrepareInternal( _PyUnicodeWriter *writer, Py_ssize_t length, Py_UCS4 maxchar); +// Prepare the buffer to write 'length' characters +// with the specified maximum character. +// +// Return 0 on success. Set an exception and return -1 on error. +_Py_DEPRECATED_EXTERNALLY(3.14) static inline int +_PyUnicodeWriter_Prepare(_PyUnicodeWriter *writer, + Py_ssize_t length, Py_UCS4 maxchar) +{ + assert(0 <= length); + if (maxchar <= writer->maxchar && length <= (writer->size - writer->pos)) { + return 0; + } + if (length == 0) { + return 0; + } +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + return _PyUnicodeWriter_PrepareInternal(writer, length, maxchar); +_Py_COMP_DIAG_POP +} + /* Prepare the buffer to have at least the kind KIND. For example, kind=PyUnicode_2BYTE_KIND ensures that the writer will support characters in range U+000-U+FFFF. diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index 1ee4fc4c4517ff..3b930e857e3704 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -168,15 +168,13 @@ _PyUnicodeWriter_SetReadOnly(_PyUnicodeWriter *writer, PyObject *obj, static inline int _PyUnicodeWriter_WriteCharInline(_PyUnicodeWriter *writer, Py_UCS4 ch) { - if (ch > writer->maxchar || 1 > writer->size - writer->pos) { + if (ch > writer->maxchar || 1 > (writer->size - writer->pos)) { if (writer->buffer == NULL && ch <= 255) { // If the first write is a Latin1 character, use the singleton // as a read-only object PyObject *obj = _Py_LATIN1_CHR(ch); - // Py_NewRef() is not need on immortal object + // Py_NewRef() is not needed on immortal object _PyUnicodeWriter_SetReadOnly(writer, obj, 1); - - // The next write will create a new buffer and copy the string return 0; } diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index c511ffc0fda13c..6965b9dc11b57d 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -461,6 +461,14 @@ def check_format(expected, format, *args): check_format('%abc', b'%%%s', b'abc') + # test "%s" with empty string + check_format('x=', + b'x=%s', b'') + check_format('x=', + b'x=%0s', b'') + check_format('x=', + b'x=%.3s', b'') + # truncated string check_format('abc', b'%.3s', b'abcdef') diff --git a/Objects/longobject.c b/Objects/longobject.c index b9f00ca6fb471b..9577c8ef4d6ed0 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -2212,7 +2212,7 @@ long_to_decimal_string_internal(PyObject *aa, } } if (writer) { - if (_PyUnicodeWriter_Prepare(writer, strlen, '9') == -1) { + if (_PyUnicodeWriter_Prepare(writer, strlen, 127) == -1) { Py_DECREF(scratch); return -1; } @@ -2227,7 +2227,7 @@ long_to_decimal_string_internal(PyObject *aa, } } else { - str = PyUnicode_New(strlen, '9'); + str = PyUnicode_New(strlen, 127); if (str == NULL) { Py_DECREF(scratch); return -1; @@ -2385,9 +2385,10 @@ long_format_binary(PyObject *aa, int base, int alternate, /* 2 characters for prefix */ sz += 2; } + assert(sz >= 1); if (writer) { - if (_PyUnicodeWriter_Prepare(writer, sz, 'x') == -1) { + if (_PyUnicodeWriter_Prepare(writer, sz, 127) == -1) { return -1; } assert(_PyUnicodeWriter_CanWrite(writer)); diff --git a/Objects/unicode_formatter.c b/Objects/unicode_formatter.c index 2b7681f7ff3ce9..8cbc774584fa68 100644 --- a/Objects/unicode_formatter.c +++ b/Objects/unicode_formatter.c @@ -1349,6 +1349,7 @@ format_long_internal(PyObject *value, const InternalFormatSpec *format, if (n_total == -1) { goto done; } + assert(n_total >= 1); /* Allocate the memory. */ if (_PyUnicodeWriter_Prepare(writer, n_total, maxchar) == -1) @@ -1503,6 +1504,7 @@ format_float_internal(PyObject *value, if (n_total == -1) { goto done; } + assert(n_total >= 1); /* Allocate the memory. */ if (_PyUnicodeWriter_Prepare(writer, n_total, maxchar) == -1) @@ -1714,6 +1716,7 @@ format_complex_internal(PyObject *value, /* Add 1 for the 'j', and optionally 2 for parens. */ calc_padding(n_re_total + n_im_total + 1 + add_parens * 2, format->width, format->align, &lpad, &rpad, &total); + assert(total >= 1); if (lpad || rpad) maxchar = Py_MAX(maxchar, format->fill_char); diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index be3cfb2538b780..6d921ed24de298 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -138,9 +138,10 @@ _PyUnicodeWriter_PrepareInternal(_PyUnicodeWriter *writer, assert(length >= 0); assert(maxchar <= _Py_MAX_UNICODE); - /* ensure that the _PyUnicodeWriter_Prepare macro was used */ - assert((maxchar > writer->maxchar && length >= 0) - || length > 0); + // Check that _PyUnicodeWriter_Prepare() or _PyUnicodeWriter_PrepareKind() + // was used + assert(maxchar > writer->maxchar + || (length > (writer->size - writer->pos) && length >= 1)); if (length > PY_SSIZE_T_MAX - writer->pos) { PyErr_NoMemory(); @@ -336,11 +337,12 @@ _PyUnicodeWriter_WriteSubstring(_PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) { assert(0 <= start); - assert(end <= PyUnicode_GET_LENGTH(str)); assert(start <= end); + assert(end <= PyUnicode_GET_LENGTH(str)); - if (start == 0 && end == PyUnicode_GET_LENGTH(str)) + if (start == 0 && end == PyUnicode_GET_LENGTH(str)) { return _PyUnicodeWriter_WriteStr(writer, str); + } Py_ssize_t len = end - start; if (len == 0) { diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 43b720e7cde218..68ce13afd9bf37 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -2545,9 +2545,9 @@ unicode_fromformat_write_str(_PyUnicodeWriter *writer, PyObject *str, Py_UCS4 maxchar; length = PyUnicode_GET_LENGTH(str); - if ((precision == -1 || precision >= length) - && width <= length) + if ((precision == -1 || precision >= length) && width <= length) { return _PyUnicodeWriter_WriteStr(writer, str); + } if (precision != -1) length = Py_MIN(precision, length); @@ -2837,7 +2837,7 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer, #undef SPRINT #undef DO_SPRINTS - assert(len >= 0); + assert(len >= 1); int sign = (buffer[0] == '-'); len -= sign; From b7b4f3ecf2fce4451ee1a8e8c1b6e92dea60ce3d Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Tue, 29 Sep 2026 22:22:37 -0700 Subject: [PATCH 6/6] gh-158446: Reject float format precision near INT_MAX (GH-158474) Formatting a float or complex with a precision within about 1000 of INT_MAX could crash or produce incorrect output. PyOS_double_to_string() now raises ValueError("precision too big") for such precisions, as the format string parsers already do for precisions above INT_MAX. The limit applies regardless of presentation type or value, so a few calls that previously succeeded (inf, nan, or 'g' with such a precision) now raise as well. --- Lib/test/test_format.py | 22 ++++++++++++++++++ ...-09-29-16-32-46.gh-issue-158446.dToaPr.rst | 5 ++++ Python/dtoa.c | 16 ++++++++++++- Python/pystrtod.c | 23 +++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index a556faae252fa5..a69a69537d568d 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -654,6 +654,28 @@ def test_precision_c_limits(self): with self.assertRaises(ValueError) as cm: format(c, ".%sf" % (INT_MAX + 1)) + @support.cpython_only + def test_precision_near_int_max(self): + # gh-158446: Precisions just below INT_MAX are rejected before any + # output buffer size is computed from them. + _testcapi = import_module("_testcapi") + INT_MAX = _testcapi.INT_MAX + + f = 1e300 + c = complex(f) + for prec in (INT_MAX, INT_MAX - 1023): + for code in "feg": + spec = ".%d%s" % (prec, code) + with self.subTest(spec=spec): + with self.assertRaises(ValueError): + format(f, spec) + with self.assertRaises(ValueError): + format(c, spec) + with self.assertRaises(ValueError): + ("%" + spec) % f + with self.assertRaises(ValueError): + ("%" + spec).encode() % f + def test_g_format_has_no_trailing_zeros(self): # regression test for bugs.python.org/issue40780 self.assertEqual("%.3g" % 1505.0, "1.5e+03") diff --git a/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst b/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst new file mode 100644 index 00000000000000..f17a3f68b93e85 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst @@ -0,0 +1,5 @@ +Fix a crash or incorrect output that could occur when formatting a +:class:`float` or :class:`complex` with a precision close to the platform's +``INT_MAX``. :c:func:`PyOS_double_to_string` now raises :exc:`ValueError` for +any precision of that magnitude, regardless of presentation type or value, as +the format string parsers already did for precisions above ``INT_MAX``. diff --git a/Python/dtoa.c b/Python/dtoa.c index 89fadd33391cb4..f412278765c8e3 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -67,6 +67,10 @@ * 8. A corner case where _Py_dg_dtoa didn't strip trailing zeros has been * fixed. (bugs.python.org/issue40780) * + * 9. _Py_dg_dtoa clamps ndigits in modes 3 and 5 so that its buffer size + * arithmetic cannot exceed the int range, and rv_alloc's size doubling + * uses size_t. (gh-158446) + * ***************************************************************/ /* Please send bug reports for the original dtoa.c code to David M. Gay (dmg @@ -2108,7 +2112,8 @@ _Py_dg_strtod(const char *s00, char **se) static char * rv_alloc(int i) { - int j, k, *r; + int k, *r; + size_t j; /* size_t so that j <<= 1 cannot overflow for i near INT_MAX */ j = sizeof(ULong); for(k = 0; @@ -2372,6 +2377,15 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, leftright = 0; _Py_FALLTHROUGH; case 5: + /* -330 < k < 330 for any finite nonzero double. Clamp ndigits so + that ndigits + k + 1 stays within int range; no double has + anywhere near this many decimal digits so the digits returned + are unaffected (*decpt saturates in the no_digits case). Same + bound as DOUBLE_TO_STRING_PRECISION_MAX in pystrtod.c. */ + if (ndigits > INT_MAX - 1024) + ndigits = INT_MAX - 1024; + else if (ndigits < -(INT_MAX - 1024)) + ndigits = -(INT_MAX - 1024); i = ndigits + k + 1; ilim = i; ilim1 = i - 1; diff --git a/Python/pystrtod.c b/Python/pystrtod.c index e8aca939d1fb98..7753d1732cf78c 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -401,6 +401,15 @@ _Py_string_to_number_with_underscores( return NULL; } +/* Largest precision magnitude accepted by PyOS_double_to_string(). The + output buffer sizes computed below and within _Py_dg_dtoa() use int and + Py_ssize_t arithmetic on roughly precision + (digits before the point, at + most DBL_MAX_10_EXP + 1 == 309) + a few bytes of sign, point and exponent. + Staying this far inside the int range keeps all of those sums in range. + (Only C callers can pass a negative precision.) _Py_dg_dtoa() applies + the same bound to its ndigits argument. */ +#define DOUBLE_TO_STRING_PRECISION_MAX (INT_MAX - 1024) + #if _PY_SHORT_FLOAT_REPR == 0 /* Given a string that may have a decimal point in the current @@ -766,6 +775,13 @@ char * PyOS_double_to_string(double val, int t, exp; int upper = 0; + if (precision > DOUBLE_TO_STRING_PRECISION_MAX + || precision < -DOUBLE_TO_STRING_PRECISION_MAX) + { + PyErr_SetString(PyExc_ValueError, "precision too big"); + return NULL; + } + /* Validate format_code, and map upper and lower case */ switch (format_code) { case 'e': /* exponent */ @@ -1227,6 +1243,13 @@ char * PyOS_double_to_string(double val, const char * const *float_strings = lc_float_strings; int mode; + if (precision > DOUBLE_TO_STRING_PRECISION_MAX + || precision < -DOUBLE_TO_STRING_PRECISION_MAX) + { + PyErr_SetString(PyExc_ValueError, "precision too big"); + return NULL; + } + /* Validate format_code, and map upper and lower case. Compute the mode and make any adjustments as needed. */ switch (format_code) {