From 3da4e0910c23309121e083718a1513847bc36d7a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:46:24 +0300 Subject: [PATCH 001/129] Post 3.14.4 --- Include/patchlevel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/patchlevel.h b/Include/patchlevel.h index ba0a2745774daf6..a9f72493bdea71f 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -24,7 +24,7 @@ #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "3.14.4" +#define PY_VERSION "3.14.4+" /*--end constants--*/ From 5df7652859c8a303a02101e0496ae56e839b3269 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:37:46 +0200 Subject: [PATCH 002/129] [3.14] gh-146458: Fix REPL height and width tracking on resize (GH-146459) (#148232) gh-146458: Fix REPL height and width tracking on resize (GH-146459) (cherry picked from commit 0b20bff386141ee0e8c62da8366f674bad17e048) Co-authored-by: Gabriel Volles Marinho <147559808+GabrielvMarinho@users.noreply.github.com> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Victor Stinner --- Lib/_pyrepl/reader.py | 1 + Lib/_pyrepl/unix_console.py | 1 - Lib/test/test_pyrepl/support.py | 2 ++ Lib/test/test_pyrepl/test_reader.py | 1 + Lib/test/test_pyrepl/test_unix_console.py | 6 ++---- Lib/test/test_pyrepl/test_windows_console.py | 14 ++++---------- .../2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst | 1 + 7 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 9ab92f64d1ef63d..f0116e742d2398e 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -644,6 +644,7 @@ def update_screen(self) -> None: def refresh(self) -> None: """Recalculate and refresh the screen.""" + self.console.height, self.console.width = self.console.getheightwidth() # this call sets up self.cxy, so call it first. self.screen = self.calc_screen() self.console.refresh(self.screen, self.cxy) diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index 937b5df6ff7d4c3..639d16db3f88d41 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -776,7 +776,6 @@ def __move_tall(self, x, y): self.__write_code(self._cup, y - self.__offset, x) def __sigwinch(self, signum, frame): - self.height, self.width = self.getheightwidth() self.event_queue.insert(Event("resize", None)) def __hide_cursor(self): diff --git a/Lib/test/test_pyrepl/support.py b/Lib/test/test_pyrepl/support.py index 4f7f9d77933336f..307bf4505550d61 100644 --- a/Lib/test/test_pyrepl/support.py +++ b/Lib/test/test_pyrepl/support.py @@ -88,6 +88,8 @@ def prepare_console(events: Iterable[Event], **kwargs) -> MagicMock | Console: console.get_event.side_effect = events console.height = 100 console.width = 80 + console.getheightwidth = MagicMock(side_effect=lambda: (console.height, console.width)) + for key, val in kwargs.items(): setattr(console, key, val) return console diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index b1b6ae16a1e592c..fbf557115f8a254 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -228,6 +228,7 @@ def _prepare_console(events): console.get_event.side_effect = events console.height = 100 console.width = 80 + console.getheightwidth = MagicMock(side_effect=lambda: (console.height, console.width)) console.input_hook = input_hook return console diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index a1ee6d4878fe93b..8198d489188f1e1 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -250,8 +250,7 @@ def test_resize_bigger_on_multiline_function(self, _os_write): events = itertools.chain(code_to_events(code)) reader, console = handle_events_short_unix_console(events) - console.height = 2 - console.getheightwidth = MagicMock(lambda _: (2, 80)) + console.getheightwidth = MagicMock(side_effect=lambda: (2, 80)) def same_reader(_): return reader @@ -286,8 +285,7 @@ def test_resize_smaller_on_multiline_function(self, _os_write): events = itertools.chain(code_to_events(code)) reader, console = handle_events_unix_console_height_3(events) - console.height = 1 - console.getheightwidth = MagicMock(lambda _: (1, 80)) + console.getheightwidth = MagicMock(side_effect=lambda: (1, 80)) def same_reader(_): return reader diff --git a/Lib/test/test_pyrepl/test_windows_console.py b/Lib/test/test_pyrepl/test_windows_console.py index 3587b834f3cd077..518ddee11253970 100644 --- a/Lib/test/test_pyrepl/test_windows_console.py +++ b/Lib/test/test_pyrepl/test_windows_console.py @@ -115,9 +115,7 @@ def test_resize_wider(self): events = code_to_events(code) reader, console = self.handle_events_narrow(events) - console.height = 20 - console.width = 80 - console.getheightwidth = MagicMock(lambda _: (20, 80)) + console.getheightwidth = MagicMock(side_effect=lambda: (20, 80)) def same_reader(_): return reader @@ -143,9 +141,7 @@ def test_resize_narrower(self): events = code_to_events(code) reader, console = self.handle_events(events) - console.height = 20 - console.width = 4 - console.getheightwidth = MagicMock(lambda _: (20, 4)) + console.getheightwidth = MagicMock(side_effect=lambda: (20, 4)) def same_reader(_): return reader @@ -278,8 +274,7 @@ def test_resize_bigger_on_multiline_function(self): events = itertools.chain(code_to_events(code)) reader, console = self.handle_events_short(events) - console.height = 2 - console.getheightwidth = MagicMock(lambda _: (2, 80)) + console.getheightwidth = MagicMock(side_effect=lambda: (2, 80)) def same_reader(_): return reader @@ -316,8 +311,7 @@ def test_resize_smaller_on_multiline_function(self): events = itertools.chain(code_to_events(code)) reader, console = self.handle_events_height_3(events) - console.height = 1 - console.getheightwidth = MagicMock(lambda _: (1, 80)) + console.getheightwidth = MagicMock(side_effect=lambda: (1, 80)) def same_reader(_): return reader diff --git a/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst b/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst new file mode 100644 index 000000000000000..178c04c657ecbbd --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2026-03-27-22-06-10.gh-issue-146458.fYj0UQ.rst @@ -0,0 +1 @@ +Fix incorrect REPL height and width tracking on console window resize on Windows. From a84e2dba24286d9cf13c29807d6994a13dc102a1 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:55:20 +0200 Subject: [PATCH 003/129] [3.14] Minor edit: Four space indent in example (gh-148264) (gh-148265) --- Doc/library/itertools.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 53f4b31e17d52bb..1be8406bbdf0c14 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -853,7 +853,7 @@ and :term:`generators ` which incur interpreter overhead. def running_mean(iterable): "Yield the average of all values seen so far." - # running_mean([8.5, 9.5, 7.5, 6.5]) -> 8.5 9.0 8.5 8.0 + # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0 return map(truediv, accumulate(iterable), count(1)) def repeatfunc(function, times=None, *args): @@ -935,10 +935,10 @@ and :term:`generators ` which incur interpreter overhead. yield element def unique(iterable, key=None, reverse=False): - "Yield unique elements in sorted order. Supports unhashable inputs." - # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4] - sequenced = sorted(iterable, key=key, reverse=reverse) - return unique_justseen(sequenced, key=key) + "Yield unique elements in sorted order. Supports unhashable inputs." + # unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4] + sequenced = sorted(iterable, key=key, reverse=reverse) + return unique_justseen(sequenced, key=key) def sliding_window(iterable, n): "Collect data into overlapping fixed-length chunks or blocks." From f46a17b19ff535591b75f5f3df5b48f7b4f939b4 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 00:14:15 +0200 Subject: [PATCH 004/129] [3.14] gh-70039: smtplib: store the server name in ._host in .connect() (GH-115259) (#148273) Original patch by gigaplastik, extended with a few more tests. Addresses gh-70039 and bpo-25852: failure of starttls if connect is called explicitly. (cherry picked from commit 442f83a5ea1b4d334befd231a79c40d6ff41a0bd) Co-authored-by: nmartensen --- Lib/smtplib.py | 2 +- Lib/test/test_smtpnet.py | 53 +++++++++++++++++++ ...4-02-10-21-25-22.gh-issue-70039.6wvcAP.rst | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 72093f7f8b0f2dd..4cfc2338d99c67e 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -251,7 +251,6 @@ def __init__(self, host='', port=0, local_hostname=None, will be used. """ - self._host = host self.timeout = timeout self.esmtp_features = {} self.command_encoding = 'ascii' @@ -342,6 +341,7 @@ def connect(self, host='localhost', port=0, source_address=None): port = int(port) except ValueError: raise OSError("nonnumeric port") + self._host = host if not port: port = self.default_port sys.audit("smtplib.connect", self, host, port) diff --git a/Lib/test/test_smtpnet.py b/Lib/test/test_smtpnet.py index d765746987bc4b0..861e7e6a725c407 100644 --- a/Lib/test/test_smtpnet.py +++ b/Lib/test/test_smtpnet.py @@ -45,6 +45,59 @@ def test_connect_starttls(self): server.ehlo() server.quit() + def test_connect_host_port_starttls(self): + support.get_attribute(smtplib, 'SMTP_SSL') + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket_helper.transient_internet(self.testServer): + server = smtplib.SMTP(f'{self.testServer}:{self.remotePort}') + try: + server.starttls(context=context) + except smtplib.SMTPException as e: + if e.args[0] == 'STARTTLS extension not supported by server.': + unittest.skip(e.args[0]) + else: + raise + server.ehlo() + server.quit() + + def test_explicit_connect_starttls(self): + support.get_attribute(smtplib, 'SMTP_SSL') + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket_helper.transient_internet(self.testServer): + server = smtplib.SMTP() + server.connect(self.testServer, self.remotePort) + try: + server.starttls(context=context) + except smtplib.SMTPException as e: + if e.args[0] == 'STARTTLS extension not supported by server.': + unittest.skip(e.args[0]) + else: + raise + server.ehlo() + server.quit() + + def test_explicit_connect_host_port_starttls(self): + support.get_attribute(smtplib, 'SMTP_SSL') + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + with socket_helper.transient_internet(self.testServer): + server = smtplib.SMTP() + server.connect(f'{self.testServer}:{self.remotePort}') + try: + server.starttls(context=context) + except smtplib.SMTPException as e: + if e.args[0] == 'STARTTLS extension not supported by server.': + unittest.skip(e.args[0]) + else: + raise + server.ehlo() + server.quit() + class SmtpSSLTest(unittest.TestCase): testServer = SMTP_TEST_SERVER diff --git a/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst b/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst new file mode 100644 index 000000000000000..8bb2cd188e89fa4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-10-21-25-22.gh-issue-70039.6wvcAP.rst @@ -0,0 +1 @@ +Fixed bug where :meth:`smtplib.SMTP.starttls` could fail if :meth:`smtplib.SMTP.connect` is called explicitly rather than implicitly. From d31a16e66224738d5bd0398385aa8b8a8cfe666a Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:00:23 +0200 Subject: [PATCH 005/129] [3.14] gh-146646: Document that glob functions suppress OSError (GH-147996) (#148288) gh-146646: Document that glob functions suppress OSError (GH-147996) (cherry picked from commit 8000a9de3c0b22f8202898a424c1008e13bd16ce) Co-authored-by: WYSIATI --- Doc/library/glob.rst | 10 ++++++++++ Doc/library/pathlib.rst | 10 ++++++++++ .../2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst diff --git a/Doc/library/glob.rst b/Doc/library/glob.rst index 52c449281533372..b24e4da7bc8c079 100644 --- a/Doc/library/glob.rst +++ b/Doc/library/glob.rst @@ -83,6 +83,11 @@ The :mod:`!glob` module defines the following functions: This function may return duplicate path names if *pathname* contains multiple "``**``" patterns and *recursive* is true. + .. note:: + Any :exc:`OSError` exceptions raised from scanning the filesystem are + suppressed. This includes :exc:`PermissionError` when accessing + directories without read permission. + .. versionchanged:: 3.5 Support for recursive globs using "``**``". @@ -106,6 +111,11 @@ The :mod:`!glob` module defines the following functions: This function may return duplicate path names if *pathname* contains multiple "``**``" patterns and *recursive* is true. + .. note:: + Any :exc:`OSError` exceptions raised from scanning the filesystem are + suppressed. This includes :exc:`PermissionError` when accessing + directories without read permission. + .. versionchanged:: 3.5 Support for recursive globs using "``**``". diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 9484103e5cece42..792cd255753c5bf 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1365,6 +1365,11 @@ Reading directories ``False``, this method follows symlinks except when expanding "``**``" wildcards. Set *recurse_symlinks* to ``True`` to always follow symlinks. + .. note:: + Any :exc:`OSError` exceptions raised from scanning the filesystem are + suppressed. This includes :exc:`PermissionError` when accessing + directories without read permission. + .. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob .. versionchanged:: 3.12 @@ -1391,6 +1396,11 @@ Reading directories The paths are returned in no particular order. If you need a specific order, sort the results. + .. note:: + Any :exc:`OSError` exceptions raised from scanning the filesystem are + suppressed. This includes :exc:`PermissionError` when accessing + directories without read permission. + .. seealso:: :ref:`pathlib-pattern-language` and :meth:`Path.glob` documentation. diff --git a/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst b/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst new file mode 100644 index 000000000000000..4e89270442a33b1 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-04-02-07-20-00.gh-issue-146646.GlobDoc1.rst @@ -0,0 +1,3 @@ +Document that :func:`glob.glob`, :func:`glob.iglob`, +:meth:`pathlib.Path.glob`, and :meth:`pathlib.Path.rglob` silently suppress +:exc:`OSError` exceptions raised from scanning the filesystem. From 88fc1e60038378fddb4db67a7dc7427fe9982a29 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:59:06 +0200 Subject: [PATCH 006/129] [3.14] gh-148250: Mention str subclasses in PyUnicodeWriter_WriteStr() doc (GH-148251) (#148293) gh-148250: Mention str subclasses in PyUnicodeWriter_WriteStr() doc (GH-148251) (cherry picked from commit 8c524503cd728d609d63d9024a9e2c418ba71f40) Co-authored-by: Victor Stinner --- Doc/c-api/unicode.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 53a13e80944c703..ab58a64647ac610 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -1868,6 +1868,10 @@ object. On success, return ``0``. On error, set an exception, leave the writer unchanged, and return ``-1``. + To write a :class:`str` subclass which overrides the :meth:`~object.__str__` + method, :c:func:`PyUnicode_FromObject` can be used to get the original + string. + .. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) Call :c:func:`PyObject_Repr` on *obj* and write the output into *writer*. From 571c337a5dd80e4dabc8fe0e92003d77b583665d Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:39:39 +0200 Subject: [PATCH 007/129] [3.14] gh-106318: Add example for str.swapcase() method (GH-144575) (#148296) Co-authored-by: Adorilson Bezerra --- Doc/library/stdtypes.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index be1ef1a1d1c6caa..37b940a07e6cc0d 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2771,8 +2771,22 @@ expression support in the :mod:`re` module). .. method:: str.swapcase() Return a copy of the string with uppercase characters converted to lowercase and - vice versa. Note that it is not necessarily true that - ``s.swapcase().swapcase() == s``. + vice versa. For example: + + .. doctest:: + + >>> 'Hello World'.swapcase() + 'hELLO wORLD' + + Note that it is not necessarily true that ``s.swapcase().swapcase() == s``. + For example: + + .. doctest:: + + >>> 'straße'.swapcase().swapcase() + 'strasse' + + See also :meth:`str.lower` and :meth:`str.upper`. .. method:: str.title() From bb78ec8fa85a6699db600bbcf30117686b67cea1 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:49:09 +0200 Subject: [PATCH 008/129] [3.14] gh-148274: properly handle result from `PyObject_VisitManagedDict` (GH-148275) (#148295) gh-148274: properly handle result from `PyObject_VisitManagedDict` (GH-148275) (cherry picked from commit ee2775cfae6bce18541e18797b67e09f2d12f72b) Co-authored-by: Max Bachmann --- Modules/_asynciomodule.c | 6 ++---- Modules/_testcapimodule.c | 3 +-- Objects/typevarobject.c | 9 +++------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 50ee180e4952352..75317b55ad6f70a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -955,8 +955,7 @@ FutureObj_traverse(PyObject *op, visitproc visit, void *arg) Py_VISIT(fut->fut_cancel_msg); Py_VISIT(fut->fut_cancelled_exc); Py_VISIT(fut->fut_awaited_by); - PyObject_VisitManagedDict((PyObject *)fut, visit, arg); - return 0; + return PyObject_VisitManagedDict((PyObject *)fut, visit, arg); } /*[clinic input] @@ -2445,8 +2444,7 @@ TaskObj_traverse(PyObject *op, visitproc visit, void *arg) Py_VISIT(fut->fut_cancel_msg); Py_VISIT(fut->fut_cancelled_exc); Py_VISIT(fut->fut_awaited_by); - PyObject_VisitManagedDict((PyObject *)fut, visit, arg); - return 0; + return PyObject_VisitManagedDict((PyObject *)fut, visit, arg); } /*[clinic input] diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 53f630832f8bbc4..567966ca558bd0f 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3203,9 +3203,8 @@ typedef struct { } ManagedDictObject; int ManagedDict_traverse(PyObject *self, visitproc visit, void *arg) { - PyObject_VisitManagedDict(self, visit, arg); Py_VISIT(Py_TYPE(self)); - return 0; + return PyObject_VisitManagedDict(self, visit, arg); } int ManagedDict_clear(PyObject *self) { diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index c1737205c86b396..a52a4cb7d5b4d6f 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -498,8 +498,7 @@ typevar_traverse(PyObject *self, visitproc visit, void *arg) Py_VISIT(tv->evaluate_constraints); Py_VISIT(tv->default_value); Py_VISIT(tv->evaluate_default); - PyObject_VisitManagedDict(self, visit, arg); - return 0; + return PyObject_VisitManagedDict(self, visit, arg); } static int @@ -1193,8 +1192,7 @@ paramspec_traverse(PyObject *self, visitproc visit, void *arg) Py_VISIT(ps->bound); Py_VISIT(ps->default_value); Py_VISIT(ps->evaluate_default); - PyObject_VisitManagedDict(self, visit, arg); - return 0; + return PyObject_VisitManagedDict(self, visit, arg); } static int @@ -1690,8 +1688,7 @@ typevartuple_traverse(PyObject *self, visitproc visit, void *arg) Py_VISIT(tvt->name); Py_VISIT(tvt->default_value); Py_VISIT(tvt->evaluate_default); - PyObject_VisitManagedDict(self, visit, arg); - return 0; + return PyObject_VisitManagedDict(self, visit, arg); } static int From 1a3c03c3c98f91bec719398cdac6dd4847c44344 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:07:55 +0200 Subject: [PATCH 009/129] [3.14] gh-148067: Fix typo in asyncio event loop docs: 'signals' -> 'signal' (GH-148073) (#148246) Co-authored-by: TT <70463940+Herrtian@users.noreply.github.com> --- Doc/library/asyncio-eventloop.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index d1a5b4e7b4638eb..79c9516cda2d60b 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -2055,7 +2055,7 @@ Wait until a file descriptor received some data using the Set signal handlers for SIGINT and SIGTERM ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -(This ``signals`` example only works on Unix.) +(This ``signal`` example only works on Unix.) Register handlers for signals :const:`~signal.SIGINT` and :const:`~signal.SIGTERM` using the :meth:`loop.add_signal_handler` method:: From 1f177749c52bfaa015e962cbf2e0e1c15d0979df Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:17:55 +0300 Subject: [PATCH 010/129] [3.14] gh-148254: Use singular "sec" in timeit verbose output (GH-148290) (#148303) Co-authored-by: gaweng <38250674+gaweng@users.noreply.github.com> --- Lib/test/test_timeit.py | 28 +++++++++---------- Lib/timeit.py | 2 +- ...-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst | 2 ++ 3 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py index 2aeebea9f93d436..2b0745e947cee9d 100644 --- a/Lib/test/test_timeit.py +++ b/Lib/test/test_timeit.py @@ -302,7 +302,7 @@ def test_main_help(self): def test_main_verbose(self): s = self.run_main(switches=['-v']) self.assertEqual(s, dedent("""\ - 1 loop -> 1 secs + 1 loop -> 1 sec raw times: 1 sec, 1 sec, 1 sec, 1 sec, 1 sec @@ -312,19 +312,19 @@ def test_main_verbose(self): def test_main_very_verbose(self): s = self.run_main(seconds_per_increment=0.000_030, switches=['-vv']) self.assertEqual(s, dedent("""\ - 1 loop -> 3e-05 secs - 2 loops -> 6e-05 secs - 5 loops -> 0.00015 secs - 10 loops -> 0.0003 secs - 20 loops -> 0.0006 secs - 50 loops -> 0.0015 secs - 100 loops -> 0.003 secs - 200 loops -> 0.006 secs - 500 loops -> 0.015 secs - 1000 loops -> 0.03 secs - 2000 loops -> 0.06 secs - 5000 loops -> 0.15 secs - 10000 loops -> 0.3 secs + 1 loop -> 3e-05 sec + 2 loops -> 6e-05 sec + 5 loops -> 0.00015 sec + 10 loops -> 0.0003 sec + 20 loops -> 0.0006 sec + 50 loops -> 0.0015 sec + 100 loops -> 0.003 sec + 200 loops -> 0.006 sec + 500 loops -> 0.015 sec + 1000 loops -> 0.03 sec + 2000 loops -> 0.06 sec + 5000 loops -> 0.15 sec + 10000 loops -> 0.3 sec raw times: 300 msec, 300 msec, 300 msec, 300 msec, 300 msec diff --git a/Lib/timeit.py b/Lib/timeit.py index e767f0187826df6..0451f66bdf1ca78 100644 --- a/Lib/timeit.py +++ b/Lib/timeit.py @@ -319,7 +319,7 @@ def main(args=None, *, _wrap_timer=None): callback = None if verbose: def callback(number, time_taken): - msg = "{num} loop{s} -> {secs:.{prec}g} secs" + msg = "{num} loop{s} -> {secs:.{prec}g} sec" plural = (number != 1) print(msg.format(num=number, s='s' if plural else '', secs=time_taken, prec=precision)) diff --git a/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst b/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst new file mode 100644 index 000000000000000..818310c31b9de6f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-09-12-42-42.gh-issue-148254.Xt7vKs.rst @@ -0,0 +1,2 @@ +Use singular "sec" instead of "secs" in :mod:`timeit` verbose output for +consistency with other time units. From b87590fd275b992364b716ea79341fd6069009c5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:21:16 +0200 Subject: [PATCH 011/129] [3.14] gh-148091: clarify asyncio.Future.cancel(msg) behaviour (GH-148248) (#148299) gh-148091: clarify asyncio.Future.cancel(msg) behaviour (GH-148248) (cherry picked from commit 2acb8d9257c4f5049777d9d462092373b0b3feca) Co-authored-by: Manoj K M <136242596+manoj-k-m@users.noreply.github.com> --- Doc/library/asyncio-future.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/asyncio-future.rst b/Doc/library/asyncio-future.rst index 4b69e569523c58b..43977de273e61f6 100644 --- a/Doc/library/asyncio-future.rst +++ b/Doc/library/asyncio-future.rst @@ -196,6 +196,10 @@ Future Object Otherwise, change the Future's state to *cancelled*, schedule the callbacks, and return ``True``. + The optional string argument *msg* is passed as the argument to the + :exc:`CancelledError` exception raised when a cancelled Future + is awaited. + .. versionchanged:: 3.9 Added the *msg* parameter. From e8f3f7668f44ac83aa61f3ba0afe10e150b9fc21 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:49:59 +0200 Subject: [PATCH 012/129] [3.14] gh-145831: email.quoprimime: `decode()` leaves stray `\r` when `eol='\r\n'` (GH-145832) (#148312) decoded[:-1] only strips one character, leaving a stray \r when eol is two characters. Fix: decoded[:-len(eol)]. (cherry picked from commit 1a0edb1fa899c067f19b09598b45cdb6e733c4ee) Co-authored-by: Stefan Zetzsche <120379523+stefanzetzsche@users.noreply.github.com> --- Lib/email/quoprimime.py | 2 +- Lib/test/test_email/test_email.py | 9 +++++++++ .../2026-03-11-15-09-52.gh-issue-145831._sW94w.rst | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst diff --git a/Lib/email/quoprimime.py b/Lib/email/quoprimime.py index 27c7ea55c7871fd..bc53b3768213102 100644 --- a/Lib/email/quoprimime.py +++ b/Lib/email/quoprimime.py @@ -272,7 +272,7 @@ def decode(encoded, eol=NL): decoded += eol # Special case if original string did not end with eol if encoded[-1] not in '\r\n' and decoded.endswith(eol): - decoded = decoded[:-1] + decoded = decoded[:-len(eol)] return decoded diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 0f138182fda2bab..3ba3e86ab65bd73 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -4827,6 +4827,15 @@ def test_decode_soft_line_break(self): def test_decode_false_quoting(self): self._test_decode('A=1,B=A ==> A+B==2', 'A=1,B=A ==> A+B==2') + def test_decode_crlf_eol_no_trailing_newline(self): + self._test_decode('abc', 'abc', eol='\r\n') + + def test_decode_crlf_eol_multiline_no_trailing_newline(self): + self._test_decode('a\r\nb', 'a\r\nb', eol='\r\n') + + def test_decode_crlf_eol_with_trailing_newline(self): + self._test_decode('abc\r\n', 'abc\r\n', eol='\r\n') + def _test_encode(self, body, expected_encoded_body, maxlinelen=None, eol=None): kwargs = {} if maxlinelen is None: diff --git a/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst b/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst new file mode 100644 index 000000000000000..454b62bc0db95fb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-11-15-09-52.gh-issue-145831._sW94w.rst @@ -0,0 +1,2 @@ +Fix :func:`!email.quoprimime.decode` leaving a stray ``\r`` when +``eol='\r\n'`` by stripping the full *eol* string instead of one character. From 429c1d3c19a0d88fbe86e29b60227c77ef766915 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:36:12 +0300 Subject: [PATCH 013/129] [3.14] Fix mixed line endings with pre-commit (GH-148336) (#148338) Co-authored-by: Zachary Ware --- .pre-commit-config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d09596671ad19e..eaa3986bf6a0905 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,9 @@ repos: exclude: Lib/test/tokenizedata/coding20731.py - id: end-of-file-fixer files: '^\.github/CODEOWNERS$' + - id: mixed-line-ending + args: [--fix=auto] + exclude: '^Lib/test/.*data/' - id: trailing-whitespace types_or: [c, inc, python, rst, yaml] - id: trailing-whitespace From 288cbacfb91377eb591f44c9ae16e7cbf092abd8 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:32:26 +0200 Subject: [PATCH 014/129] [3.14] gh-148284: Block inlining of gigantic functions in ceval.c for clang 22 (GH-148334) (GH-148349) gh-148284: Block inlining of gigantic functions in ceval.c for clang 22 (GH-148334) (cherry picked from commit e007631e9949ab806742eb61076112e9e2e3e22e) Co-authored-by: Ken Jin Co-authored-by: Victor Stinner --- Doc/using/configure.rst | 6 +++ Makefile.pre.in | 5 ++ ...-04-10-14-20-54.gh-issue-148284.HKs-S_.rst | 1 + configure | 47 +++++++++++++++++++ configure.ac | 28 +++++++++++ 5 files changed, 87 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 157432acbc9d212..2c0d81d48007013 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -1506,6 +1506,12 @@ Compiler flags .. versionadded:: 3.7 +.. envvar:: CFLAGS_CEVAL + + Flags used to compile ``Python/ceval.c``. + + .. versionadded:: 3.14.5 + .. envvar:: CCSHARED Compiler flags used to build a shared library. diff --git a/Makefile.pre.in b/Makefile.pre.in index 80a1b590c2f9b8c..797a6d6cceece00 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -126,6 +126,8 @@ PY_CORE_CFLAGS= $(PY_STDMODULE_CFLAGS) -DPy_BUILD_CORE PY_CORE_LDFLAGS=$(PY_LDFLAGS) $(PY_LDFLAGS_NODIST) # Strict or non-strict aliasing flags used to compile dtoa.c, see above CFLAGS_ALIASING=@CFLAGS_ALIASING@ +# Compilation flags only for ceval.c. +CFLAGS_CEVAL=@CFLAGS_CEVAL@ # Machine-dependent subdirectories @@ -3142,6 +3144,9 @@ regen-jit: Python/dtoa.o: Python/dtoa.c $(CC) -c $(PY_CORE_CFLAGS) $(CFLAGS_ALIASING) -o $@ $< +Python/ceval.o: Python/ceval.c + $(CC) -c $(PY_CORE_CFLAGS) $(CFLAGS_CEVAL) -o $@ $< + # Run reindent on the library .PHONY: reindent reindent: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst new file mode 100644 index 000000000000000..a74f6c1a61affd8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-10-14-20-54.gh-issue-148284.HKs-S_.rst @@ -0,0 +1 @@ +Fix high stack consumption in Python's interpreter loop on Clang 22 by setting function limits for inlining when building with computed gotos. diff --git a/configure b/configure index 8cfdda5a29b00da..7621eeabeb85db9 100755 --- a/configure +++ b/configure @@ -826,6 +826,7 @@ OPENSSL_LDFLAGS OPENSSL_LIBS OPENSSL_INCLUDES ENSUREPIP +CFLAGS_CEVAL SRCDIRS THREADHEADERS PANEL_LIBS @@ -30049,6 +30050,52 @@ printf "%s\n" "#define HAVE_GLIBC_MEMMOVE_BUG 1" >>confdefs.h fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we need to manually block large inlining in ceval.c" >&5 +printf %s "checking if we need to manually block large inlining in ceval.c... " >&6; } +if test "$cross_compiling" = yes +then : + block_huge_inlining_in_ceval=undefined +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int main(void) { +// See gh-148284: Clang 22 seems to have interactions with inlining +// and the stackref buffer which cause 40 kB of stack usage on x86-64 +// in buggy versions of _PyEval_EvalFrameDefault() in computed goto +// interpreter. The normal usage seen is normally 1-2 kB. +#if defined(__clang__) && (__clang_major__ == 22) + return 1; +#else + return 0; +#endif +} + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + block_huge_inlining_in_ceval=no +else case e in #( + e) block_huge_inlining_in_ceval=yes ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $block_huge_inlining_in_ceval" >&5 +printf "%s\n" "$block_huge_inlining_in_ceval" >&6; } + +if test "$block_huge_inlining_in_ceval" = yes && test "$ac_cv_computed_gotos" = yes; then + # gh-148284: Suppress inlining of functions whose stack size exceeds + # 512 bytes. This number should be tuned to follow the C stack + # consumption in _PyEval_EvalFrameDefault() on computed goto + # interpreter. + CFLAGS_CEVAL="$CFLAGS_CEVAL -finline-max-stacksize=512" +fi + + if test "$ac_cv_gcc_asm_for_x87" = yes; then # Some versions of gcc miscompile inline asm: # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46491 diff --git a/configure.ac b/configure.ac index 1acb91fd27b9d2b..21df2245402eea1 100644 --- a/configure.ac +++ b/configure.ac @@ -7244,6 +7244,34 @@ if test "$have_glibc_memmove_bug" = yes; then for memmove and bcopy.]) fi +AC_MSG_CHECKING([if we need to manually block large inlining in ceval.c]) +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +int main(void) { +// See gh-148284: Clang 22 seems to have interactions with inlining +// and the stackref buffer which cause 40 kB of stack usage on x86-64 +// in buggy versions of _PyEval_EvalFrameDefault() in computed goto +// interpreter. The normal usage seen is normally 1-2 kB. +#if defined(__clang__) && (__clang_major__ == 22) + return 1; +#else + return 0; +#endif +} +]])], +[block_huge_inlining_in_ceval=no], +[block_huge_inlining_in_ceval=yes], +[block_huge_inlining_in_ceval=undefined]) +AC_MSG_RESULT([$block_huge_inlining_in_ceval]) + +if test "$block_huge_inlining_in_ceval" = yes && test "$ac_cv_computed_gotos" = yes; then + # gh-148284: Suppress inlining of functions whose stack size exceeds + # 512 bytes. This number should be tuned to follow the C stack + # consumption in _PyEval_EvalFrameDefault() on computed goto + # interpreter. + CFLAGS_CEVAL="$CFLAGS_CEVAL -finline-max-stacksize=512" +fi +AC_SUBST([CFLAGS_CEVAL]) + if test "$ac_cv_gcc_asm_for_x87" = yes; then # Some versions of gcc miscompile inline asm: # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46491 From f36da66c716e892aacdf9453325ff05b89fe7e70 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 10 Apr 2026 23:59:38 +0530 Subject: [PATCH 015/129] =?UTF-8?q?[3.14]=20gh-148037:=20remove=20critical?= =?UTF-8?q?=20section=20from=20`PyCode=5FAddr2Line`=20(GH=E2=80=A6=20(#148?= =?UTF-8?q?353)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [3.14] gh-148037: remove critical section from `PyCode_Addr2Line` (GH-148103) (cherry picked from commit d3b7b93cbbbf53061a95eb60cc116c9fec31c5b4) --- Include/internal/pycore_instruments.h | 5 +-- ...-04-09-14-18-33.gh-issue-148037.aP3CSX.rst | 1 + Objects/codeobject.c | 24 ++++------- Python/instrumentation.c | 41 +++++++++---------- Python/legacy_tracing.c | 4 +- 5 files changed, 33 insertions(+), 42 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst diff --git a/Include/internal/pycore_instruments.h b/Include/internal/pycore_instruments.h index 7658adca719e86d..98684ff1024f6e4 100644 --- a/Include/internal/pycore_instruments.h +++ b/Include/internal/pycore_instruments.h @@ -62,9 +62,6 @@ extern void _Py_call_instrumentation_exc2(PyThreadState *tstate, int event, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, PyObject *arg0, PyObject *arg1); -extern int -_Py_Instrumentation_GetLine(PyCodeObject *code, int index); - extern PyObject _PyInstrumentation_MISSING; extern PyObject _PyInstrumentation_DISABLE; @@ -120,6 +117,8 @@ typedef struct _PyCoMonitoringData { uint8_t *per_instruction_tools; } _PyCoMonitoringData; +extern int +_Py_Instrumentation_GetLine(PyCodeObject *code, _PyCoLineInstrumentationData *line_data, int index); #ifdef __cplusplus } diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst new file mode 100644 index 000000000000000..b0cef5951298172 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-09-14-18-33.gh-issue-148037.aP3CSX.rst @@ -0,0 +1 @@ +Remove critical section from :c:func:`!PyCode_Addr2Line` in free-threading. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index f0b2e5f36c60608..acaa9f5501af12f 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1013,14 +1013,18 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno) * source location tracking (co_lines/co_positions) ******************/ -static int -_PyCode_Addr2Line(PyCodeObject *co, int addrq) +int +PyCode_Addr2Line(PyCodeObject *co, int addrq) { if (addrq < 0) { return co->co_firstlineno; } - if (co->_co_monitoring && co->_co_monitoring->lines) { - return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT)); + _PyCoMonitoringData *data = _Py_atomic_load_ptr_acquire(&co->_co_monitoring); + if (data) { + _PyCoLineInstrumentationData *lines = _Py_atomic_load_ptr_acquire(&data->lines); + if (lines) { + return _Py_Instrumentation_GetLine(co, lines, addrq/sizeof(_Py_CODEUNIT)); + } } assert(addrq >= 0 && addrq < _PyCode_NBYTES(co)); PyCodeAddressRange bounds; @@ -1035,7 +1039,7 @@ _PyCode_SafeAddr2Line(PyCodeObject *co, int addrq) return co->co_firstlineno; } if (co->_co_monitoring && co->_co_monitoring->lines) { - return _Py_Instrumentation_GetLine(co, addrq/sizeof(_Py_CODEUNIT)); + return _Py_Instrumentation_GetLine(co, co->_co_monitoring->lines, addrq/sizeof(_Py_CODEUNIT)); } if (!(addrq >= 0 && addrq < _PyCode_NBYTES(co))) { return -1; @@ -1045,16 +1049,6 @@ _PyCode_SafeAddr2Line(PyCodeObject *co, int addrq) return _PyCode_CheckLineNumber(addrq, &bounds); } -int -PyCode_Addr2Line(PyCodeObject *co, int addrq) -{ - int lineno; - Py_BEGIN_CRITICAL_SECTION(co); - lineno = _PyCode_Addr2Line(co, addrq); - Py_END_CRITICAL_SECTION(); - return lineno; -} - void _PyLineTable_InitAddressRange(const char *linetable, Py_ssize_t length, int firstlineno, PyCodeAddressRange *range) { diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 32d6a2041828928..d06cda7c589c74b 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -1285,13 +1285,10 @@ _Py_call_instrumentation_exc2( } int -_Py_Instrumentation_GetLine(PyCodeObject *code, int index) +_Py_Instrumentation_GetLine(PyCodeObject *code, _PyCoLineInstrumentationData *line_data, int index) { - _PyCoMonitoringData *monitoring = code->_co_monitoring; - assert(monitoring != NULL); - assert(monitoring->lines != NULL); + assert(line_data != NULL); assert(index < Py_SIZE(code)); - _PyCoLineInstrumentationData *line_data = monitoring->lines; int line_delta = get_line_delta(line_data, index); int line = compute_line(code, line_delta); return line; @@ -1309,11 +1306,11 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame, _PyCoMonitoringData *monitoring = code->_co_monitoring; _PyCoLineInstrumentationData *line_data = monitoring->lines; PyInterpreterState *interp = tstate->interp; - int line = _Py_Instrumentation_GetLine(code, i); + int line = _Py_Instrumentation_GetLine(code, line_data, i); assert(line >= 0); assert(prev != NULL); int prev_index = (int)(prev - bytecode); - int prev_line = _Py_Instrumentation_GetLine(code, prev_index); + int prev_line = _Py_Instrumentation_GetLine(code, line_data, prev_index); if (prev_line == line) { int prev_opcode = bytecode[prev_index].op.code; /* RESUME and INSTRUMENTED_RESUME are needed for the operation of @@ -1510,11 +1507,9 @@ initialize_tools(PyCodeObject *code) } static void -initialize_lines(PyCodeObject *code, int bytes_per_entry) +initialize_lines(_PyCoLineInstrumentationData *line_data, PyCodeObject *code, int bytes_per_entry) { ASSERT_WORLD_STOPPED_OR_LOCKED(code); - _PyCoLineInstrumentationData *line_data = code->_co_monitoring->lines; - assert(line_data != NULL); line_data->bytes_per_entry = bytes_per_entry; int code_len = (int)Py_SIZE(code); @@ -1655,18 +1650,19 @@ allocate_instrumentation_data(PyCodeObject *code) ASSERT_WORLD_STOPPED_OR_LOCKED(code); if (code->_co_monitoring == NULL) { - code->_co_monitoring = PyMem_Malloc(sizeof(_PyCoMonitoringData)); - if (code->_co_monitoring == NULL) { + _PyCoMonitoringData *monitoring = PyMem_Malloc(sizeof(_PyCoMonitoringData)); + if (monitoring == NULL) { PyErr_NoMemory(); return -1; } - code->_co_monitoring->local_monitors = (_Py_LocalMonitors){ 0 }; - code->_co_monitoring->active_monitors = (_Py_LocalMonitors){ 0 }; - code->_co_monitoring->tools = NULL; - code->_co_monitoring->lines = NULL; - code->_co_monitoring->line_tools = NULL; - code->_co_monitoring->per_instruction_opcodes = NULL; - code->_co_monitoring->per_instruction_tools = NULL; + monitoring->local_monitors = (_Py_LocalMonitors){ 0 }; + monitoring->active_monitors = (_Py_LocalMonitors){ 0 }; + monitoring->tools = NULL; + monitoring->lines = NULL; + monitoring->line_tools = NULL; + monitoring->per_instruction_opcodes = NULL; + monitoring->per_instruction_tools = NULL; + _Py_atomic_store_ptr_release(&code->_co_monitoring, monitoring); } return 0; } @@ -1731,12 +1727,13 @@ update_instrumentation_data(PyCodeObject *code, PyInterpreterState *interp) else { bytes_per_entry = 5; } - code->_co_monitoring->lines = PyMem_Malloc(1 + code_len * bytes_per_entry); - if (code->_co_monitoring->lines == NULL) { + _PyCoLineInstrumentationData *lines = PyMem_Malloc(1 + code_len * bytes_per_entry); + if (lines == NULL) { PyErr_NoMemory(); return -1; } - initialize_lines(code, bytes_per_entry); + initialize_lines(lines, code, bytes_per_entry); + _Py_atomic_store_ptr_release(&code->_co_monitoring->lines, lines); } if (multitools && code->_co_monitoring->line_tools == NULL) { code->_co_monitoring->line_tools = PyMem_Malloc(code_len); diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c index 9c72fab298201ef..2e29e1aa208ac0e 100644 --- a/Python/legacy_tracing.c +++ b/Python/legacy_tracing.c @@ -398,8 +398,8 @@ sys_trace_jump_func( assert(PyCode_Check(code)); /* We can call _Py_Instrumentation_GetLine because we always set * line events for tracing */ - int to_line = _Py_Instrumentation_GetLine(code, to); - int from_line = _Py_Instrumentation_GetLine(code, from); + int to_line = _Py_Instrumentation_GetLine(code, code->_co_monitoring->lines, to); + int from_line = _Py_Instrumentation_GetLine(code, code->_co_monitoring->lines, from); if (to_line != from_line) { /* Will be handled by target INSTRUMENTED_LINE */ return &_PyInstrumentation_DISABLE; From 620fb74384a1ddda9156c07d7e5e22453db48fda Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 11 Apr 2026 17:30:24 +0200 Subject: [PATCH 016/129] [3.14] gh-148320: document that `import sys.monitoring` raises `ModuleNotFoundError` (GH-148365) (#148385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh-148320: document that `import sys.monitoring` raises `ModuleNotFoundError` (GH-148365) (cherry picked from commit d7c9f1877cbd733d6c0fda395a9fcf78171f51b0) Co-authored-by: Jonathan Dung Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/sys.monitoring.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst index 4a460479e4afd74..16e6b1d6dc786b0 100644 --- a/Doc/library/sys.monitoring.rst +++ b/Doc/library/sys.monitoring.rst @@ -11,9 +11,9 @@ .. note:: :mod:`!sys.monitoring` is a namespace within the :mod:`sys` module, - not an independent module, so there is no need to - ``import sys.monitoring``, simply ``import sys`` and then use - ``sys.monitoring``. + not an independent module, and ``import sys.monitoring`` would fail + with a :exc:`ModuleNotFoundError`. Instead, simply ``import sys`` + and then use ``sys.monitoring``. This namespace provides access to the functions and constants necessary to From 6112e2dd444883ecfbc5e91a7b03ae07607a935a Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sat, 11 Apr 2026 21:52:38 +0530 Subject: [PATCH 017/129] [3.14] gh-142518: add thread safety docs for dict and set APIs (#148392) Co-authored-by: Lysandros Nikolaou Co-authored-by: Victor Stinner --- Doc/c-api/dict.rst | 94 +++++++++++++++++++++++++++++++++++++++ Doc/c-api/set.rst | 31 +++++++++++++ Doc/data/threadsafety.dat | 88 +++++++++++++++++++++++++++++++++++- 3 files changed, 211 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index 9c4428ced41b5a2..1629e544d55fc27 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -65,6 +65,11 @@ Dictionary Objects *key*, return ``1``, otherwise return ``0``. On error, return ``-1``. This is equivalent to the Python expression ``key in p``. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. c:function:: int PyDict_ContainsString(PyObject *p, const char *key) @@ -87,6 +92,11 @@ Dictionary Objects ``0`` on success or ``-1`` on failure. This function *does not* steal a reference to *val*. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. c:function:: int PyDict_SetItemString(PyObject *p, const char *key, PyObject *val) @@ -102,6 +112,11 @@ Dictionary Objects If *key* is not in the dictionary, :exc:`KeyError` is raised. Return ``0`` on success or ``-1`` on failure. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. c:function:: int PyDict_DelItemString(PyObject *p, const char *key) @@ -120,6 +135,11 @@ Dictionary Objects * If the key is missing, set *\*result* to ``NULL`` and return ``0``. * On error, raise an exception and return ``-1``. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. versionadded:: 3.13 See also the :c:func:`PyObject_GetItem` function. @@ -137,6 +157,13 @@ Dictionary Objects :meth:`~object.__eq__` methods are silently ignored. Prefer the :c:func:`PyDict_GetItemWithError` function instead. + .. note:: + + In the :term:`free-threaded build`, the returned + :term:`borrowed reference` may become invalid if another thread modifies + the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which + returns a :term:`strong reference`. + .. versionchanged:: 3.10 Calling this API without an :term:`attached thread state` had been allowed for historical reason. It is no longer allowed. @@ -149,6 +176,13 @@ Dictionary Objects occurred. Return ``NULL`` **without** an exception set if the key wasn't present. + .. note:: + + In the :term:`free-threaded build`, the returned + :term:`borrowed reference` may become invalid if another thread modifies + the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which + returns a :term:`strong reference`. + .. c:function:: PyObject* PyDict_GetItemString(PyObject *p, const char *key) @@ -164,6 +198,13 @@ Dictionary Objects Prefer using the :c:func:`PyDict_GetItemWithError` function with your own :c:func:`PyUnicode_FromString` *key* instead. + .. note:: + + In the :term:`free-threaded build`, the returned + :term:`borrowed reference` may become invalid if another thread modifies + the dictionary concurrently. Prefer :c:func:`PyDict_GetItemStringRef`, + which returns a :term:`strong reference`. + .. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result) @@ -184,6 +225,14 @@ Dictionary Objects .. versionadded:: 3.4 + .. note:: + + In the :term:`free-threaded build`, the returned + :term:`borrowed reference` may become invalid if another thread modifies + the dictionary concurrently. Prefer :c:func:`PyDict_SetDefaultRef`, + which returns a :term:`strong reference`. + + .. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result) @@ -203,6 +252,11 @@ Dictionary Objects These may refer to the same object: in that case you hold two separate references to it. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. versionadded:: 3.13 @@ -220,6 +274,11 @@ Dictionary Objects Similar to :meth:`dict.pop`, but without the default value and not raising :exc:`KeyError` if the key is missing. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. versionadded:: 3.13 @@ -336,6 +395,13 @@ Dictionary Objects only be added if there is not a matching key in *a*. Return ``0`` on success or ``-1`` if an exception was raised. + .. note:: + + In the :term:`free-threaded build`, when *b* is a + :class:`dict` (with the standard iterator), both *a* and *b* are locked + for the duration of the operation. When *b* is a non-dict mapping, only + *a* is locked; *b* may be concurrently modified by another thread. + .. c:function:: int PyDict_Update(PyObject *a, PyObject *b) @@ -345,6 +411,13 @@ Dictionary Objects argument has no "keys" attribute. Return ``0`` on success or ``-1`` if an exception was raised. + .. note:: + + In the :term:`free-threaded build`, when *b* is a + :class:`dict` (with the standard iterator), both *a* and *b* are locked + for the duration of the operation. When *b* is a non-dict mapping, only + *a* is locked; *b* may be concurrently modified by another thread. + .. c:function:: int PyDict_MergeFromSeq2(PyObject *a, PyObject *seq2, int override) @@ -360,6 +433,13 @@ Dictionary Objects if override or key not in a: a[key] = value + .. note:: + + In the :term:`free-threaded ` build, only *a* is locked. + The iteration over *seq2* is not synchronized; *seq2* may be concurrently + modified by another thread. + + .. c:function:: int PyDict_AddWatcher(PyDict_WatchCallback callback) Register *callback* as a dictionary watcher. Return a non-negative integer @@ -367,6 +447,13 @@ Dictionary Objects of error (e.g. no more watcher IDs available), return ``-1`` and set an exception. + .. note:: + + This function is not internally synchronized. In the + :term:`free-threaded ` build, callers should ensure no + concurrent calls to :c:func:`PyDict_AddWatcher` or + :c:func:`PyDict_ClearWatcher` are in progress. + .. versionadded:: 3.12 .. c:function:: int PyDict_ClearWatcher(int watcher_id) @@ -375,6 +462,13 @@ Dictionary Objects :c:func:`PyDict_AddWatcher`. Return ``0`` on success, ``-1`` on error (e.g. if the given *watcher_id* was never registered.) + .. note:: + + This function is not internally synchronized. In the + :term:`free-threaded ` build, callers should ensure no + concurrent calls to :c:func:`PyDict_AddWatcher` or + :c:func:`PyDict_ClearWatcher` are in progress. + .. versionadded:: 3.12 .. c:function:: int PyDict_Watch(int watcher_id, PyObject *dict) diff --git a/Doc/c-api/set.rst b/Doc/c-api/set.rst index b74859dd669c54a..c81087697b498db 100644 --- a/Doc/c-api/set.rst +++ b/Doc/c-api/set.rst @@ -92,6 +92,11 @@ the constructor functions work with any iterable Python object. actually iterable. The constructor is also useful for copying a set (``c=set(s)``). + .. note:: + + The operation is atomic on :term:`free threading ` + when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`. + .. c:function:: PyObject* PyFrozenSet_New(PyObject *iterable) @@ -100,6 +105,11 @@ the constructor functions work with any iterable Python object. set on success or ``NULL`` on failure. Raise :exc:`TypeError` if *iterable* is not actually iterable. + .. note:: + + The operation is atomic on :term:`free threading ` + when *iterable* is a :class:`set`, :class:`frozenset` or :class:`dict`. + The following functions and macros are available for instances of :class:`set` or :class:`frozenset` or instances of their subtypes. @@ -127,6 +137,10 @@ or :class:`frozenset` or instances of their subtypes. the *key* is unhashable. Raise :exc:`SystemError` if *anyset* is not a :class:`set`, :class:`frozenset`, or an instance of a subtype. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. .. c:function:: int PySet_Add(PyObject *set, PyObject *key) @@ -138,6 +152,12 @@ or :class:`frozenset` or instances of their subtypes. :exc:`SystemError` if *set* is not an instance of :class:`set` or its subtype. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + + The following functions are available for instances of :class:`set` or its subtypes but not for instances of :class:`frozenset` or its subtypes. @@ -152,6 +172,11 @@ subtypes but not for instances of :class:`frozenset` or its subtypes. temporary frozensets. Raise :exc:`SystemError` if *set* is not an instance of :class:`set` or its subtype. + .. note:: + + The operation is atomic on :term:`free threading ` + when *key* is :class:`str`, :class:`int`, :class:`float`, :class:`bool` or :class:`bytes`. + .. c:function:: PyObject* PySet_Pop(PyObject *set) @@ -167,6 +192,12 @@ subtypes but not for instances of :class:`frozenset` or its subtypes. success. Return ``-1`` and raise :exc:`SystemError` if *set* is not an instance of :class:`set` or its subtype. + .. note:: + + In the :term:`free-threaded build`, the set is emptied before its entries + are cleared, so other threads will observe an empty set rather than + intermediate states. + Deprecated API ^^^^^^^^^^^^^^ diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat index 82edd1167ef1282..7f9110620db4f37 100644 --- a/Doc/data/threadsafety.dat +++ b/Doc/data/threadsafety.dat @@ -14,10 +14,71 @@ # The function name must match the C domain identifier used in the documentation. # Synchronization primitives (Doc/c-api/synchronization.rst) -PyMutex_Lock:shared: -PyMutex_Unlock:shared: +PyMutex_Lock:atomic: +PyMutex_Unlock:atomic: PyMutex_IsLocked:atomic: + +# Dictionary objects (Doc/c-api/dict.rst) + +# Type checks - read ob_type pointer, always safe +PyDict_Check:atomic: +PyDict_CheckExact:atomic: + +# Creation - pure allocation, no shared state +PyDict_New:atomic: + +# Lock-free lookups - use _Py_dict_lookup_threadsafe(), no locking. +# Atomic with simple types. +PyDict_Contains:shared: +PyDict_ContainsString:atomic: +PyDict_GetItemRef:shared: +PyDict_GetItemStringRef:atomic: +PyDict_Size:atomic: +PyDict_GET_SIZE:atomic: + +# Borrowed-reference lookups - lock-free dict access but returned +# borrowed reference is unsafe in free-threaded builds without +# external synchronization +PyDict_GetItem:compatible: +PyDict_GetItemWithError:compatible: +PyDict_GetItemString:compatible: +PyDict_SetDefault:compatible: + +# Iteration - no locking; returns borrowed refs +PyDict_Next:compatible: + +# Single-item mutations - protected by per-object critical section +PyDict_SetItem:shared: +PyDict_SetItemString:atomic: +PyDict_DelItem:shared: +PyDict_DelItemString:atomic: +PyDict_SetDefaultRef:shared: +PyDict_Pop:shared: +PyDict_PopString:atomic: + +# Bulk reads - hold per-object lock for duration +PyDict_Clear:atomic: +PyDict_Copy:atomic: +PyDict_Keys:atomic: +PyDict_Values:atomic: +PyDict_Items:atomic: + +# Merge/update - lock target dict; also lock source when it is a dict +PyDict_Update:shared: +PyDict_Merge:shared: +PyDict_MergeFromSeq2:shared: + +# Watcher registration - no synchronization on interpreter state +PyDict_AddWatcher:compatible: +PyDict_ClearWatcher:compatible: + +# Per-dict watcher tags - non-atomic RMW on _ma_watcher_tag; +# safe on distinct dicts only +PyDict_Watch:distinct: +PyDict_Unwatch:distinct: + + # List objects (Doc/c-api/list.rst) # Type checks - read ob_type pointer, always safe @@ -125,6 +186,29 @@ PyByteArray_GET_SIZE:atomic: PyByteArray_AsString:compatible: PyByteArray_AS_STRING:compatible: +# Creation - may iterate the iterable argument, calling arbitrary code. +# Atomic for sets, frozensets, dicts, and frozendicts. +PySet_New:shared: +PyFrozenSet_New:shared: + +# Size - uses atomic load on free-threaded builds +PySet_Size:atomic: +PySet_GET_SIZE:atomic: + +# Contains - lock-free, atomic with simple types +PySet_Contains:shared: + +# Mutations - hold per-object lock for duration +# atomic with simple types +PySet_Add:shared: +PySet_Discard:shared: + +# Pop - hold per-object lock for duration +PySet_Pop:atomic: + +# Clear - empties the set before clearing +PySet_Clear:atomic: + # Capsule objects (Doc/c-api/capsule.rst) # Type check - read ob_type pointer, always safe From d6be9fb07731efaaf70027b38cfc752caa0b91d2 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:54:29 +0200 Subject: [PATCH 018/129] [3.14] gh-146264: Use static HACL deps for static module builds (GH-146265) (#148403) gh-146264: Use static HACL deps for static module builds (GH-146265) (cherry picked from commit f445d2e8666c5585d613add075cabe2abc7f972b) Co-authored-by: Ihar Hrachyshka --- .../next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst | 3 +++ configure | 2 +- configure.ac | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst diff --git a/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst b/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst new file mode 100644 index 000000000000000..1fdafe560432a66 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-03-21-18-51-31.gh-issue-146264.Q9Ej4m.rst @@ -0,0 +1,3 @@ +Fix static module builds on non-WASI targets by linking HACL dependencies as +static libraries when ``MODULE_BUILDTYPE=static``, preventing duplicate +``_Py_LibHacl_*`` symbol errors at link time. diff --git a/configure b/configure index 7621eeabeb85db9..1cc1d2a2a54dd48 100755 --- a/configure +++ b/configure @@ -32758,7 +32758,7 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for HACL* library linking type" >&5 printf %s "checking for HACL* library linking type... " >&6; } -if test "$ac_sys_system" = "WASI"; then +if test "$ac_sys_system" = "WASI" || test "$MODULE_BUILDTYPE" = "static"; then LIBHACL_LDEPS_LIBTYPE=STATIC { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: static" >&5 printf "%s\n" "static" >&6; } diff --git a/configure.ac b/configure.ac index 21df2245402eea1..21cef7a98181b66 100644 --- a/configure.ac +++ b/configure.ac @@ -8089,7 +8089,7 @@ AC_SUBST([LIBHACL_BLAKE2_SIMD256_OBJS]) # HACL*-based cryptographic primitives AC_MSG_CHECKING([for HACL* library linking type]) -if test "$ac_sys_system" = "WASI"; then +if test "$ac_sys_system" = "WASI" || test "$MODULE_BUILDTYPE" = "static"; then LIBHACL_LDEPS_LIBTYPE=STATIC AC_MSG_RESULT([static]) else From 4f8a77bf3fa8d0c10ddd50a8dda4ad89e8d2434e Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:46:26 +0200 Subject: [PATCH 019/129] [3.14] gh-145105: Fix crash in csv.reader with re-entrant iterator (GH-145106) (#148404) gh-145105: Fix crash in csv.reader with re-entrant iterator (GH-145106) When a custom iterator calls next() on the same csv.reader from within __next__, the inner iteration sets self->fields to NULL. The outer iteration then crashes in parse_save_field() by passing NULL to PyList_Append. Add a guard after PyIter_Next() to detect that fields was set to NULL by a re-entrant call, and raise csv.Error instead of crashing. (cherry picked from commit 20994b1809f9c162e4cae01a5af08bd492ede9f9) Co-authored-by: Ramin Farajpour Cami --- Lib/test/test_csv.py | 27 +++++++++++++++++++ ...0.gh-issue-145105.csv-reader-reentrant.rst | 2 ++ Modules/_csv.c | 6 +++++ 3 files changed, 35 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 98ee0c3cdd7a06f..a0a1e91777d8934 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -553,6 +553,33 @@ def test_roundtrip_escaped_unquoted_newlines(self): self.assertEqual(row, rows[i]) + def test_reader_reentrant_iterator(self): + # gh-145105: re-entering the reader from the iterator must not crash. + class ReentrantIter: + def __init__(self): + self.reader = None + self.n = 0 + def __iter__(self): + return self + def __next__(self): + self.n += 1 + if self.n == 1: + try: + next(self.reader) + except StopIteration: + pass + return "a,b" + if self.n == 2: + return "x" + raise StopIteration + + it = ReentrantIter() + reader = csv.reader(it) + it.reader = reader + with self.assertRaises(csv.Error): + next(reader) + + class TestDialectRegistry(unittest.TestCase): def test_registry_badargs(self): self.assertRaises(TypeError, csv.list_dialects, None) diff --git a/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst b/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst new file mode 100644 index 000000000000000..bc61cc43a5aa339 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-22-00-00-00.gh-issue-145105.csv-reader-reentrant.rst @@ -0,0 +1,2 @@ +Fix crash in :mod:`csv` reader when iterating with a re-entrant iterator +that calls :func:`next` on the same reader from within ``__next__``. diff --git a/Modules/_csv.c b/Modules/_csv.c index b994a42775178da..fd681f81a3b1972 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -965,6 +965,12 @@ Reader_iternext_lock_held(PyObject *op) Py_DECREF(lineobj); return NULL; } + if (self->fields == NULL) { + PyErr_SetString(module_state->error_obj, + "iterator has already advanced the reader"); + Py_DECREF(lineobj); + return NULL; + } ++self->line_num; kind = PyUnicode_KIND(lineobj); data = PyUnicode_DATA(lineobj); From af2f5189a12888afa4e93243653c331e9fffddb5 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sun, 12 Apr 2026 02:05:10 +0200 Subject: [PATCH 020/129] [3.14] gh-146333: Fix quadratic regex backtracking in configparser option parsing (GH-146399) (#148287) gh-146333: Fix quadratic regex backtracking in configparser option parsing (GH-146399) Use negative lookahead in option regex to prevent backtracking, and to avoid changing logic outside the regexes (since people could use the regex directly). (cherry picked from commit 7e0a0be4097f9d29d66fe23f5af86f18a34ed7dd) Co-authored-by: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com> --- Lib/configparser.py | 8 ++++++-- Lib/test/test_configparser.py | 20 +++++++++++++++++++ ...3-25-00-51-03.gh-issue-146333.LqdL__bn.rst | 3 +++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-03-25-00-51-03.gh-issue-146333.LqdL__bn.rst diff --git a/Lib/configparser.py b/Lib/configparser.py index d435a5c2fe0da24..e76647d339e9138 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -613,7 +613,9 @@ class RawConfigParser(MutableMapping): \] # ] """ _OPT_TMPL = r""" - (?P