From a0491c893956668ec09308e6334f7817b18efbe9 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 29 Sep 2026 22:03:16 +0200 Subject: [PATCH 1/4] gh-158439: Add _PyUnicodeWriter.recheck_maxchar (#158442) If PyUnicodeWriter_WriteUTF8() or PyUnicodeWriter_Format() fails, PyUnicodeWriter_Finish() now checks if the string uses the most efficient storage. Previously, PyUnicodeWriter_Finish() could return a string with an inefficient storage. * Add _PyUnicodeWriter.recheck_maxchar. PyUnicodeWriter_WriteUTF8() and PyUnicodeWriter_Format() now set recheck_maxchar to 1 on error. * Move error handling from caller to _PyUnicode_DecodeUTF8Writer(). * PyUnicodeWriter_DecodeUTF8Stateful(): rename length parameter to size and rename string parameter to str. --- Doc/c-api/unicode.rst | 2 +- Include/cpython/unicodeobject.h | 7 +- Lib/test/test_capi/test_unicode.py | 38 ++++++++-- ...-09-29-19-01-56.gh-issue-158439.ybKo_6.rst | 4 + Objects/unicode_writer.c | 76 +++++++++++-------- Objects/unicodeobject.c | 47 +++++++++--- 6 files changed, 121 insertions(+), 53 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-29-19-01-56.gh-issue-158439.ybKo_6.rst diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 3b635fa7fa3744..5f918b447950d9 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -1945,7 +1945,7 @@ object. On success, return ``0``. On error, set an exception, leave the writer unchanged, and return ``-1``. -.. c:function:: int PyUnicodeWriter_DecodeUTF8Stateful(PyUnicodeWriter *writer, const char *string, Py_ssize_t length, const char *errors, Py_ssize_t *consumed) +.. c:function:: int PyUnicodeWriter_DecodeUTF8Stateful(PyUnicodeWriter *writer, const char *str, Py_ssize_t size, const char *errors, Py_ssize_t *consumed) Decode the string *str* from UTF-8 with *errors* error handler and write the output into *writer*. diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index ea91f4158eb392..3da18a6ad35db3 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -516,8 +516,8 @@ PyAPI_FUNC(int) PyUnicodeWriter_Format( ...); PyAPI_FUNC(int) PyUnicodeWriter_DecodeUTF8Stateful( PyUnicodeWriter *writer, - const char *string, /* UTF-8 encoded string */ - Py_ssize_t length, /* size of string */ + const char *str, /* UTF-8 encoded string */ + Py_ssize_t size, /* size of string */ const char *errors, /* error handling */ Py_ssize_t *consumed); /* bytes consumed */ @@ -538,6 +538,9 @@ typedef struct { /* minimum character (default: 127, ASCII) */ Py_UCS4 min_char; + // If non-zero, _PyUnicodeWriter_Finish() needs to check maxchar. + int recheck_maxchar; + /* If non-zero, overallocate the buffer (default: 0). */ unsigned char overallocate; diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index 032b910a280083..fc2bec2124a39b 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -1976,7 +1976,11 @@ def test_invalid_utf8(self): writer.write_utf8(b"invalid=\xFF", -1) def test_recover_utf8_error(self): - # test recovering from PyUnicodeWriter_WriteUTF8() error + # Recover from PyUnicodeWriter_WriteUTF8() errors. A temporary write + # changes the buffer kind to UCS-2 before raising UnicodeDecodeError. + # Then, PyUnicodeWriter_Finish() has to change the buffer kind back to + # ASCII. + writer = self.create_writer(0) writer.write_utf8(b"value=", -1) @@ -1986,12 +1990,13 @@ def test_recover_utf8_error(self): with self.assertRaises(UnicodeDecodeError): s = "truncated\u20AC".encode() writer.write_utf8(s, len(s) - 1) + with self.assertRaises(UnicodeDecodeError): + # Change buffer kind to UCS-2 then raise UnicodeDecodeError + s = "\u20AC\u20AC".encode() + writer.write_utf8(s, len(s) - 1) - # retry write with a valid string writer.write_utf8(b"valid", -1) - - self.assertEqual(writer.finish(), - "value=valid") + self.assertEqual(writer.finish(), "value=valid") def test_decode_utf8(self): # test PyUnicodeWriter_DecodeUTF8Stateful() @@ -2299,6 +2304,29 @@ def test_recover_error(self): self.assertEqual(writer.finish(), 'Hello World.') + def test_recheck_maxchar(self): + # PyUnicodeWriter_Format() changes buffer kind to UCS-2 before raising + # an exception. Then, PyUnicodeWriter_Finish() has to change the buffer + # kind back to ASCII. + from ctypes import py_object + + class StrError: + def __str__(self): + raise RuntimeError("bug") + + writer = self.create_writer(0) + # Allocate ASCII buffer + writer.write_str('ascii') + + obj = StrError() + ucs2_utf8 = '\u20ac'.encode() + with self.assertRaises(RuntimeError): + # Change buffer kind to UCS-2, but then raise RuntimeError + self.writer_format(writer, b"%s%S", ucs2_utf8, py_object(obj)) + + writer.write_str('.') + self.assertEqual(writer.finish(), 'ascii.') + def test_readonly_optim(self): # Read-only optimization: if the first and only write is a Python str # object and no buffer was allocated yet, return the object unchanged diff --git a/Misc/NEWS.d/next/C_API/2026-09-29-19-01-56.gh-issue-158439.ybKo_6.rst b/Misc/NEWS.d/next/C_API/2026-09-29-19-01-56.gh-issue-158439.ybKo_6.rst new file mode 100644 index 00000000000000..59739c7a5e1609 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-09-29-19-01-56.gh-issue-158439.ybKo_6.rst @@ -0,0 +1,4 @@ +If :c:func:`PyUnicodeWriter_WriteUTF8` or :c:func:`PyUnicodeWriter_Format` +fails, :c:func:`PyUnicodeWriter_Finish` now checks if the string uses the most +efficient storage. Previously, :c:func:`PyUnicodeWriter_Finish` could return a +string with an inefficient storage. Patch by Victor Stinner. diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c index c1a2af4d9ac1fe..26deffa6baac63 100644 --- a/Objects/unicode_writer.c +++ b/Objects/unicode_writer.c @@ -160,7 +160,8 @@ PyUnicodeWriter_Create(Py_ssize_t length) } -void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +void +PyUnicodeWriter_Discard(PyUnicodeWriter *writer) { if (writer == NULL) { return; @@ -511,8 +512,7 @@ PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, assert(writer != NULL); _Py_AssertHoldsTstate(); - _PyUnicodeWriter *priv_writer = (_PyUnicodeWriter*)writer; - return _PyUnicodeWriter_WriteASCIIString(priv_writer, str, size); + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, str, size); } @@ -525,40 +525,25 @@ PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, size = strlen(str); } - _PyUnicodeWriter *_writer = (_PyUnicodeWriter*)writer; - Py_ssize_t old_pos = _writer->pos; - int res = _PyUnicode_DecodeUTF8Writer(_writer, str, size, - _Py_ERROR_STRICT, NULL, NULL); - if (res < 0) { - _writer->pos = old_pos; - } - return res; + return _PyUnicode_DecodeUTF8Writer((_PyUnicodeWriter*)writer, str, size, + _Py_ERROR_STRICT, NULL, NULL); } int PyUnicodeWriter_DecodeUTF8Stateful(PyUnicodeWriter *writer, - const char *string, - Py_ssize_t length, + const char *str, + Py_ssize_t size, const char *errors, Py_ssize_t *consumed) { - if (length < 0) { - length = strlen(string); - } - - _PyUnicodeWriter *_writer = (_PyUnicodeWriter*)writer; - Py_ssize_t old_pos = _writer->pos; - int res = _PyUnicode_DecodeUTF8Writer(_writer, string, length, - _Py_ERROR_UNKNOWN, errors, - consumed); - if (res < 0) { - _writer->pos = old_pos; - if (consumed) { - *consumed = 0; - } + if (size < 0) { + size = strlen(str); } - return res; + + return _PyUnicode_DecodeUTF8Writer((_PyUnicodeWriter*)writer, str, size, + _Py_ERROR_UNKNOWN, errors, + consumed); } @@ -600,30 +585,55 @@ _PyUnicodeWriter_Finish(_PyUnicodeWriter *writer) Py_ssize_t final_size = writer->pos; if (final_size == 0) { + // Get the empty string singleton PyObject *empty = _PyUnicode_GetEmpty(); Py_XDECREF(str); // writer->buffer can be NULL if the position is 0 return empty; } - Py_ssize_t length = PyUnicode_GET_LENGTH(str); + if (writer->readonly) { + assert(final_size == PyUnicode_GET_LENGTH(str)); + goto done; + } + assert(final_size <= PyUnicode_GET_LENGTH(str)); + if (final_size == 1 && PyUnicode_KIND(str) == PyUnicode_1BYTE_KIND) { - assert(length >= 1); + // Get the single character singleton + assert(PyUnicode_GET_LENGTH(str) >= 1); const Py_UCS1 *data = PyUnicode_1BYTE_DATA(str); Py_UCS1 ch = data[0]; - PyObject *latin1_char = _Py_LATIN1_CHR(ch); Py_DECREF(str); - return latin1_char; + str = _Py_LATIN1_CHR(ch); + goto done; + } + + if (writer->recheck_maxchar) { + Py_UCS4 maxchar = _PyUnicode_FindMaxChar(str, 0, final_size); + if (maxchar != writer->maxchar) { + // Adjust the string kind + PyObject *str2 = PyUnicode_New(final_size, maxchar); + if (str2 == NULL) { + Py_DECREF(str); + return NULL; + } + _PyUnicode_FastCopyCharacters(str2, 0, str, 0, final_size); + Py_SETREF(str, str2); + goto done; + } } - if (!writer->readonly && length != final_size) { + if (PyUnicode_GET_LENGTH(str) != final_size) { + // Truncate the string PyObject *str2 = _PyUnicode_ResizeCompact(str, final_size); if (str2 == NULL) { Py_DECREF(str); return NULL; } str = str2; + goto done; } +done: assert(_PyUnicode_CheckConsistency(str, 1)); return str; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 8446fdbfcb64a9..893621f041c9ad 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -3166,16 +3166,21 @@ PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) } int -_PyUnicodeWriter_FormatV(PyUnicodeWriter *writer, const char *format, +_PyUnicodeWriter_FormatV(PyUnicodeWriter *pub_writer, const char *format, va_list vargs) { - _PyUnicodeWriter *_writer = (_PyUnicodeWriter*)writer; - Py_ssize_t old_pos = _writer->pos; + _PyUnicodeWriter *writer = (_PyUnicodeWriter*)pub_writer; + Py_ssize_t old_pos = writer->pos; + Py_UCS4 old_maxchar = writer->maxchar; - int res = unicode_from_format(_writer, format, vargs); + int res = unicode_from_format(writer, format, vargs); if (res < 0) { - _writer->pos = old_pos; + writer->pos = old_pos; + if (writer->maxchar > old_maxchar) { + // _PyUnicodeWriter_Finish() will check maxchar + writer->recheck_maxchar = 1; + } } return res; } @@ -5364,7 +5369,7 @@ unicode_decode_utf8(const char *s, Py_ssize_t size, } -// Used by PyUnicodeWriter_WriteUTF8() implementation +// Used by PyUnicodeWriter_WriteUTF8() and PyUnicodeWriter_DecodeUTF8Stateful() int _PyUnicode_DecodeUTF8Writer(_PyUnicodeWriter *writer, const char *s, Py_ssize_t size, @@ -5378,18 +5383,20 @@ _PyUnicode_DecodeUTF8Writer(_PyUnicodeWriter *writer, return 0; } + Py_ssize_t old_pos = writer->pos; + Py_UCS4 old_maxchar = writer->maxchar; + // fast path: try ASCII string. if (_PyUnicodeWriter_Prepare(writer, size, 127) < 0) { - return -1; + goto error; } assert(_PyUnicodeWriter_CanWrite(writer)); const char *starts = s; const char *end = s + size; - Py_ssize_t decoded = 0; - Py_UCS1 *dest = (Py_UCS1*)writer->data + writer->pos * writer->kind; if (writer->kind == PyUnicode_1BYTE_KIND) { - decoded = ascii_decode(s, end, dest); + Py_UCS1 *dest = (Py_UCS1*)writer->data + writer->pos * writer->kind; + Py_ssize_t decoded = ascii_decode(s, end, dest); writer->pos += decoded; if (decoded == size) { @@ -5401,8 +5408,24 @@ _PyUnicode_DecodeUTF8Writer(_PyUnicodeWriter *writer, s += decoded; } - return unicode_decode_utf8_impl(writer, starts, s, end, - error_handler, errors, consumed); + int res = unicode_decode_utf8_impl(writer, starts, s, end, + error_handler, errors, consumed); + if (res < 0) { + goto error; + } + return 0; + +error: + // Restore the writer to its previous state + writer->pos = old_pos; + if (writer->maxchar > old_maxchar) { + // _PyUnicodeWriter_Finish() will check maxchar + writer->recheck_maxchar = 1; + } + if (consumed) { + *consumed = 0; + } + return -1; } From 39e5ed7fea8364dc30efd3fa5d68644606b0eac8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 29 Sep 2026 22:15:41 +0200 Subject: [PATCH 2/4] gh-157710: Avoid copy for large int in PyUnicodeWriter_WriteStr() (#158448) Enable the PyUnicodeWriter read-only optimization for large integer (more than 1000 digits) in PyUnicodeWriter_WriteStr() and PyUnicodeWriter_WriteRepr(). --- Lib/test/test_capi/test_unicode.py | 29 +++++++++++++++++++++++++++++ Objects/longobject.c | 4 ---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index fc2bec2124a39b..36ecca48230ddd 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -2267,6 +2267,35 @@ def __repr__(self): self.assertEqual(writer.get_buffer(), expected) self.assertIs(writer.finish(), unique_string) + def test_readonly_optim_large_int(self): + # Read-only optimization in _PyLong_FormatWriter() for large integer: + # use _pylong.int_to_decimal_string() result as a read-only string. + # See pylong_int_to_decimal_string(). + + self.addCleanup(sys.set_int_max_str_digits, + sys.get_int_max_str_digits()) + sys.set_int_max_str_digits(0) + + # _PyLong_FormatWriter() calls _pylong.int_to_decimal_string() for + # integer with Py_SIZE() > 1000. + large_int = 1 << (sys.int_info.bits_per_digit * 1020) + large_int_str = str(large_int) + expected = (len(large_int_str), 127, True) + + for size in (0, 123): + with self.subTest(size=size): + # Test PyUnicodeWriter_WriteStr() + writer = self.create_writer(size) + writer.write_str(large_int) + self.assertEqual(writer.get_buffer(), expected) + self.assertEqual(writer.finish(), large_int_str) + + # Test PyUnicodeWriter_WriteRepr() + writer = self.create_writer(size) + writer.write_repr(large_int) + self.assertEqual(writer.get_buffer(), expected) + self.assertEqual(writer.finish(), large_int_str) + # Test PyUnicodeWriter_Format() @unittest.skipIf(ctypes is None, 'need ctypes') diff --git a/Objects/longobject.c b/Objects/longobject.c index 1dac70820d142b..b9f00ca6fb471b 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -2045,10 +2045,6 @@ pylong_int_to_decimal_string(PyObject *aa, goto error; } if (writer) { - Py_ssize_t size = PyUnicode_GET_LENGTH(s); - if (_PyUnicodeWriter_Prepare(writer, size, '9') == -1) { - goto error; - } if (_PyUnicodeWriter_WriteStr(writer, s) < 0) { goto error; } From 596d92387ab0930f88cfa9da595135abeb26595e Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Tue, 29 Sep 2026 16:50:14 -0500 Subject: [PATCH 3/4] gh-158010: Update Android and iOS build scripts to use OpenSSL 3.5.9 (GH-158417) --- .../next/Build/2026-08-26-16-10-15.gh-issue-156369.55jGEj.rst | 1 - .../next/Build/2026-09-29-09-40-25.gh-issue-158010.H4Top_.rst | 1 + .../Security/2026-06-09-10-23-57.gh-issue-151159.91GpWQ.rst | 1 - Platforms/Android/__main__.py | 2 +- Platforms/Apple/__main__.py | 2 +- 5 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 Misc/NEWS.d/next/Build/2026-08-26-16-10-15.gh-issue-156369.55jGEj.rst create mode 100644 Misc/NEWS.d/next/Build/2026-09-29-09-40-25.gh-issue-158010.H4Top_.rst delete mode 100644 Misc/NEWS.d/next/Security/2026-06-09-10-23-57.gh-issue-151159.91GpWQ.rst diff --git a/Misc/NEWS.d/next/Build/2026-08-26-16-10-15.gh-issue-156369.55jGEj.rst b/Misc/NEWS.d/next/Build/2026-08-26-16-10-15.gh-issue-156369.55jGEj.rst deleted file mode 100644 index 22c0bb4514f98d..00000000000000 --- a/Misc/NEWS.d/next/Build/2026-08-26-16-10-15.gh-issue-156369.55jGEj.rst +++ /dev/null @@ -1 +0,0 @@ -Update Android and iOS build scripts to use OpenSSL 3.5.8. diff --git a/Misc/NEWS.d/next/Build/2026-09-29-09-40-25.gh-issue-158010.H4Top_.rst b/Misc/NEWS.d/next/Build/2026-09-29-09-40-25.gh-issue-158010.H4Top_.rst new file mode 100644 index 00000000000000..76e81855af90b0 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-09-29-09-40-25.gh-issue-158010.H4Top_.rst @@ -0,0 +1 @@ +Update Android and iOS build scripts to use OpenSSL 3.5.9. diff --git a/Misc/NEWS.d/next/Security/2026-06-09-10-23-57.gh-issue-151159.91GpWQ.rst b/Misc/NEWS.d/next/Security/2026-06-09-10-23-57.gh-issue-151159.91GpWQ.rst deleted file mode 100644 index 735164c1a65ec3..00000000000000 --- a/Misc/NEWS.d/next/Security/2026-06-09-10-23-57.gh-issue-151159.91GpWQ.rst +++ /dev/null @@ -1 +0,0 @@ -Update Android and iOS installers to use OpenSSL 3.5.7. diff --git a/Platforms/Android/__main__.py b/Platforms/Android/__main__.py index e153bc86811be6..27de5d01c1f19b 100755 --- a/Platforms/Android/__main__.py +++ b/Platforms/Android/__main__.py @@ -224,7 +224,7 @@ def unpack_deps(host, prefix_dir, cache_dir): for name_ver in [ "bzip2-1.0.8-3", "libffi-3.4.4-3", - "openssl-3.5.8-0", + "openssl-3.5.9-0", "sqlite-3.53.4-0", "xz-5.4.6-1", "zstd-1.5.7-2" diff --git a/Platforms/Apple/__main__.py b/Platforms/Apple/__main__.py index 8be0d8719f5b1b..4d29c240baac5a 100644 --- a/Platforms/Apple/__main__.py +++ b/Platforms/Apple/__main__.py @@ -328,7 +328,7 @@ def unpack_deps( for name_ver in [ "BZip2-1.0.8-2", "libFFI-3.4.7-2", - "OpenSSL-3.5.8-1", + "OpenSSL-3.5.9-1", "XZ-5.6.4-2", "mpdecimal-4.0.0-2", "zstd-1.5.7-1", From 540274c649e29a88f56cf55d5634a41f68ead430 Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Tue, 29 Sep 2026 18:19:28 -0500 Subject: [PATCH 4/4] gh-158010: Update macOS installer to use OpenSSL 3.5.9 (#158415) --- Mac/BuildScript/build-installer.py | 6 +++--- .../Security/2026-06-09-23-38-08.gh-issue-151159.ds-9f8.rst | 1 - .../macOS/2026-08-27-03-19-21.gh-issue-156369.FKlSGY.rst | 1 - .../macOS/2026-09-29-09-29-17.gh-issue-158010.2zJnoj.rst | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 Misc/NEWS.d/next/Security/2026-06-09-23-38-08.gh-issue-151159.ds-9f8.rst delete mode 100644 Misc/NEWS.d/next/macOS/2026-08-27-03-19-21.gh-issue-156369.FKlSGY.rst create mode 100644 Misc/NEWS.d/next/macOS/2026-09-29-09-29-17.gh-issue-158010.2zJnoj.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index f22256c1f506cb..f54a95b22e118f 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -246,9 +246,9 @@ def library_recipes(): result.extend([ dict( - name="OpenSSL 3.5.8", - url="https://github.com/openssl/openssl/releases/download/openssl-3.5.8/openssl-3.5.8.tar.gz", - checksum="a8f84a39918ec6415ce765d9b429d313ba97b8143169c172e734b9514464f5b2", + name="OpenSSL 3.5.9", + url="https://github.com/openssl/openssl/releases/download/openssl-3.5.9/openssl-3.5.9.tar.gz", + checksum="603f5602e2eef00d77fbd429d34dcd5822bb301757a1bc9cdb24c670f1eb859a", buildrecipe=build_universal_openssl, configure=None, install=None, diff --git a/Misc/NEWS.d/next/Security/2026-06-09-23-38-08.gh-issue-151159.ds-9f8.rst b/Misc/NEWS.d/next/Security/2026-06-09-23-38-08.gh-issue-151159.ds-9f8.rst deleted file mode 100644 index d9251a93b40b2c..00000000000000 --- a/Misc/NEWS.d/next/Security/2026-06-09-23-38-08.gh-issue-151159.ds-9f8.rst +++ /dev/null @@ -1 +0,0 @@ -Update macOS installer to use OpenSSL 3.5.7. diff --git a/Misc/NEWS.d/next/macOS/2026-08-27-03-19-21.gh-issue-156369.FKlSGY.rst b/Misc/NEWS.d/next/macOS/2026-08-27-03-19-21.gh-issue-156369.FKlSGY.rst deleted file mode 100644 index e953a92f5a106d..00000000000000 --- a/Misc/NEWS.d/next/macOS/2026-08-27-03-19-21.gh-issue-156369.FKlSGY.rst +++ /dev/null @@ -1 +0,0 @@ -Update macOS installer to use OpenSSL 3.5.8. diff --git a/Misc/NEWS.d/next/macOS/2026-09-29-09-29-17.gh-issue-158010.2zJnoj.rst b/Misc/NEWS.d/next/macOS/2026-09-29-09-29-17.gh-issue-158010.2zJnoj.rst new file mode 100644 index 00000000000000..cf9c417eec7fe6 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2026-09-29-09-29-17.gh-issue-158010.2zJnoj.rst @@ -0,0 +1 @@ +Update macOS installer to use OpenSSL 3.5.9.