From 925907ea362c4c014086be48625ac7dd67645cfc Mon Sep 17 00:00:00 2001 From: mpage Date: Tue, 23 Jan 2024 11:25:41 -0800 Subject: [PATCH 001/263] gh-113884: Make queue.SimpleQueue thread-safe when the GIL is disabled (#114161) * use the ParkingLot API to manage waiting threads * use Argument Clinic's critical section directive to protect queue methods * remove unnecessary overflow check Co-authored-by: Erlend E. Aasland --- ...-01-17-00-52-57.gh-issue-113884.CvEjUE.rst | 1 + Modules/_queuemodule.c | 202 ++++++++++-------- Modules/clinic/_queuemodule.c.h | 24 ++- 3 files changed, 137 insertions(+), 90 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst new file mode 100644 index 000000000000000..6a39fd2f60ab813 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-00-52-57.gh-issue-113884.CvEjUE.rst @@ -0,0 +1 @@ +Make :class:`queue.SimpleQueue` thread safe when the GIL is disabled. diff --git a/Modules/_queuemodule.c b/Modules/_queuemodule.c index 8fca3cdd0deb188..18b24855c52ad61 100644 --- a/Modules/_queuemodule.c +++ b/Modules/_queuemodule.c @@ -3,8 +3,9 @@ #endif #include "Python.h" -#include "pycore_ceval.h" // _PyEval_MakePendingCalls() +#include "pycore_ceval.h" // Py_MakePendingCalls() #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_parking_lot.h" #include "pycore_time.h" // _PyTime_t #include @@ -151,7 +152,9 @@ RingBuf_Get(RingBuf *buf) return item; } -// Returns 0 on success or -1 if the buffer failed to grow +// Returns 0 on success or -1 if the buffer failed to grow. +// +// Steals a reference to item. static int RingBuf_Put(RingBuf *buf, PyObject *item) { @@ -164,7 +167,7 @@ RingBuf_Put(RingBuf *buf, PyObject *item) return -1; } } - buf->items[buf->put_idx] = Py_NewRef(item); + buf->items[buf->put_idx] = item; buf->put_idx = (buf->put_idx + 1) % buf->items_cap; buf->num_items++; return 0; @@ -184,9 +187,13 @@ RingBuf_IsEmpty(RingBuf *buf) typedef struct { PyObject_HEAD - PyThread_type_lock lock; - int locked; + + // Are there threads waiting for items + bool has_threads_waiting; + + // Items in the queue RingBuf buf; + PyObject *weakreflist; } simplequeueobject; @@ -209,12 +216,6 @@ simplequeue_dealloc(simplequeueobject *self) PyTypeObject *tp = Py_TYPE(self); PyObject_GC_UnTrack(self); - if (self->lock != NULL) { - /* Unlock the lock so it's safe to free it */ - if (self->locked > 0) - PyThread_release_lock(self->lock); - PyThread_free_lock(self->lock); - } (void)simplequeue_clear(self); if (self->weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *) self); @@ -249,12 +250,6 @@ simplequeue_new_impl(PyTypeObject *type) self = (simplequeueobject *) type->tp_alloc(type, 0); if (self != NULL) { self->weakreflist = NULL; - self->lock = PyThread_allocate_lock(); - if (self->lock == NULL) { - Py_DECREF(self); - PyErr_SetString(PyExc_MemoryError, "can't allocate lock"); - return NULL; - } if (RingBuf_Init(&self->buf) < 0) { Py_DECREF(self); return NULL; @@ -264,7 +259,29 @@ simplequeue_new_impl(PyTypeObject *type) return (PyObject *) self; } +typedef struct { + bool handed_off; + simplequeueobject *queue; + PyObject *item; +} HandoffData; + +static void +maybe_handoff_item(HandoffData *data, PyObject **item, int has_more_waiters) +{ + if (item == NULL) { + // No threads were waiting + data->handed_off = false; + } + else { + // There was at least one waiting thread, hand off the item + *item = data->item; + data->handed_off = true; + } + data->queue->has_threads_waiting = has_more_waiters; +} + /*[clinic input] +@critical_section _queue.SimpleQueue.put item: object block: bool = True @@ -280,21 +297,28 @@ never blocks. They are provided for compatibility with the Queue class. static PyObject * _queue_SimpleQueue_put_impl(simplequeueobject *self, PyObject *item, int block, PyObject *timeout) -/*[clinic end generated code: output=4333136e88f90d8b input=6e601fa707a782d5]*/ +/*[clinic end generated code: output=4333136e88f90d8b input=a16dbb33363c0fa8]*/ { - /* BEGIN GIL-protected critical section */ - if (RingBuf_Put(&self->buf, item) < 0) - return NULL; - if (self->locked) { - /* A get() may be waiting, wake it up */ - self->locked = 0; - PyThread_release_lock(self->lock); + HandoffData data = { + .handed_off = 0, + .item = Py_NewRef(item), + .queue = self, + }; + if (self->has_threads_waiting) { + // Try to hand the item off directly if there are threads waiting + _PyParkingLot_Unpark(&self->has_threads_waiting, + (_Py_unpark_fn_t *)maybe_handoff_item, &data); + } + if (!data.handed_off) { + if (RingBuf_Put(&self->buf, item) < 0) { + return NULL; + } } - /* END GIL-protected critical section */ Py_RETURN_NONE; } /*[clinic input] +@critical_section _queue.SimpleQueue.put_nowait item: object @@ -307,12 +331,23 @@ for compatibility with the Queue class. static PyObject * _queue_SimpleQueue_put_nowait_impl(simplequeueobject *self, PyObject *item) -/*[clinic end generated code: output=0990536715efb1f1 input=36b1ea96756b2ece]*/ +/*[clinic end generated code: output=0990536715efb1f1 input=ce949cc2cd8a4119]*/ { return _queue_SimpleQueue_put_impl(self, item, 0, Py_None); } +static PyObject * +empty_error(PyTypeObject *cls) +{ + PyObject *module = PyType_GetModule(cls); + assert(module != NULL); + simplequeue_state *state = simplequeue_get_state(module); + PyErr_SetNone(state->EmptyError); + return NULL; +} + /*[clinic input] +@critical_section _queue.SimpleQueue.get cls: defining_class @@ -335,23 +370,15 @@ in that case). static PyObject * _queue_SimpleQueue_get_impl(simplequeueobject *self, PyTypeObject *cls, int block, PyObject *timeout_obj) -/*[clinic end generated code: output=5c2cca914cd1e55b input=5b4047bfbc645ec1]*/ +/*[clinic end generated code: output=5c2cca914cd1e55b input=f7836c65e5839c51]*/ { _PyTime_t endtime = 0; - _PyTime_t timeout; - PyObject *item; - PyLockStatus r; - PY_TIMEOUT_T microseconds; - PyThreadState *tstate = PyThreadState_Get(); // XXX Use PyThread_ParseTimeoutArg(). - if (block == 0) { - /* Non-blocking */ - microseconds = 0; - } - else if (timeout_obj != Py_None) { + if (block != 0 && !Py_IsNone(timeout_obj)) { /* With timeout */ + _PyTime_t timeout; if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_CEILING) < 0) { return NULL; @@ -361,65 +388,64 @@ _queue_SimpleQueue_get_impl(simplequeueobject *self, PyTypeObject *cls, "'timeout' must be a non-negative number"); return NULL; } - microseconds = _PyTime_AsMicroseconds(timeout, - _PyTime_ROUND_CEILING); - if (microseconds > PY_TIMEOUT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "timeout value is too large"); - return NULL; - } endtime = _PyDeadline_Init(timeout); } - else { - /* Infinitely blocking */ - microseconds = -1; - } - /* put() signals the queue to be non-empty by releasing the lock. - * So we simply try to acquire the lock in a loop, until the condition - * (queue non-empty) becomes true. - */ - while (RingBuf_IsEmpty(&self->buf)) { - /* First a simple non-blocking try without releasing the GIL */ - r = PyThread_acquire_lock_timed(self->lock, 0, 0); - if (r == PY_LOCK_FAILURE && microseconds != 0) { - Py_BEGIN_ALLOW_THREADS - r = PyThread_acquire_lock_timed(self->lock, microseconds, 1); - Py_END_ALLOW_THREADS + for (;;) { + if (!RingBuf_IsEmpty(&self->buf)) { + return RingBuf_Get(&self->buf); } - if (r == PY_LOCK_INTR && _PyEval_MakePendingCalls(tstate) < 0) { - return NULL; - } - if (r == PY_LOCK_FAILURE) { - PyObject *module = PyType_GetModule(cls); - simplequeue_state *state = simplequeue_get_state(module); - /* Timed out */ - PyErr_SetNone(state->EmptyError); - return NULL; + if (!block) { + return empty_error(cls); } - self->locked = 1; - /* Adjust timeout for next iteration (if any) */ - if (microseconds > 0) { - timeout = _PyDeadline_Get(endtime); - microseconds = _PyTime_AsMicroseconds(timeout, - _PyTime_ROUND_CEILING); + int64_t timeout_ns = -1; + if (endtime != 0) { + timeout_ns = _PyDeadline_Get(endtime); + if (timeout_ns < 0) { + return empty_error(cls); + } } - } - /* BEGIN GIL-protected critical section */ - item = RingBuf_Get(&self->buf); - if (self->locked) { - PyThread_release_lock(self->lock); - self->locked = 0; + bool waiting = 1; + self->has_threads_waiting = waiting; + + PyObject *item = NULL; + int st = _PyParkingLot_Park(&self->has_threads_waiting, &waiting, + sizeof(bool), timeout_ns, &item, + /* detach */ 1); + switch (st) { + case Py_PARK_OK: { + assert(item != NULL); + return item; + } + case Py_PARK_TIMEOUT: { + return empty_error(cls); + } + case Py_PARK_INTR: { + // Interrupted + if (Py_MakePendingCalls() < 0) { + return NULL; + } + break; + } + case Py_PARK_AGAIN: { + // This should be impossible with the current implementation of + // PyParkingLot, but would be possible if critical sections / + // the GIL were released before the thread was added to the + // internal thread queue in the parking lot. + break; + } + default: { + Py_UNREACHABLE(); + } + } } - /* END GIL-protected critical section */ - - return item; } /*[clinic input] +@critical_section _queue.SimpleQueue.get_nowait cls: defining_class @@ -434,12 +460,13 @@ raise the Empty exception. static PyObject * _queue_SimpleQueue_get_nowait_impl(simplequeueobject *self, PyTypeObject *cls) -/*[clinic end generated code: output=620c58e2750f8b8a input=842f732bf04216d3]*/ +/*[clinic end generated code: output=620c58e2750f8b8a input=d48be63633fefae9]*/ { return _queue_SimpleQueue_get_impl(self, cls, 0, Py_None); } /*[clinic input] +@critical_section _queue.SimpleQueue.empty -> bool Return True if the queue is empty, False otherwise (not reliable!). @@ -447,12 +474,13 @@ Return True if the queue is empty, False otherwise (not reliable!). static int _queue_SimpleQueue_empty_impl(simplequeueobject *self) -/*[clinic end generated code: output=1a02a1b87c0ef838 input=1a98431c45fd66f9]*/ +/*[clinic end generated code: output=1a02a1b87c0ef838 input=96cb22df5a67d831]*/ { return RingBuf_IsEmpty(&self->buf); } /*[clinic input] +@critical_section _queue.SimpleQueue.qsize -> Py_ssize_t Return the approximate size of the queue (not reliable!). @@ -460,7 +488,7 @@ Return the approximate size of the queue (not reliable!). static Py_ssize_t _queue_SimpleQueue_qsize_impl(simplequeueobject *self) -/*[clinic end generated code: output=f9dcd9d0a90e121e input=7a74852b407868a1]*/ +/*[clinic end generated code: output=f9dcd9d0a90e121e input=e218623cb8c16a79]*/ { return RingBuf_Len(&self->buf); } diff --git a/Modules/clinic/_queuemodule.c.h b/Modules/clinic/_queuemodule.c.h index 8e2a430835e35f7..b3b6b8e96c135e4 100644 --- a/Modules/clinic/_queuemodule.c.h +++ b/Modules/clinic/_queuemodule.c.h @@ -6,6 +6,7 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_NoKeywords() PyDoc_STRVAR(simplequeue_new__doc__, @@ -107,7 +108,9 @@ _queue_SimpleQueue_put(simplequeueobject *self, PyObject *const *args, Py_ssize_ } timeout = args[2]; skip_optional_pos: + Py_BEGIN_CRITICAL_SECTION(self); return_value = _queue_SimpleQueue_put_impl(self, item, block, timeout); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -165,7 +168,9 @@ _queue_SimpleQueue_put_nowait(simplequeueobject *self, PyObject *const *args, Py goto exit; } item = args[0]; + Py_BEGIN_CRITICAL_SECTION(self); return_value = _queue_SimpleQueue_put_nowait_impl(self, item); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -244,7 +249,9 @@ _queue_SimpleQueue_get(simplequeueobject *self, PyTypeObject *cls, PyObject *con } timeout_obj = args[1]; skip_optional_pos: + Py_BEGIN_CRITICAL_SECTION(self); return_value = _queue_SimpleQueue_get_impl(self, cls, block, timeout_obj); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -269,11 +276,18 @@ _queue_SimpleQueue_get_nowait_impl(simplequeueobject *self, static PyObject * _queue_SimpleQueue_get_nowait(simplequeueobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { + PyObject *return_value = NULL; + if (nargs) { PyErr_SetString(PyExc_TypeError, "get_nowait() takes no arguments"); - return NULL; + goto exit; } - return _queue_SimpleQueue_get_nowait_impl(self, cls); + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _queue_SimpleQueue_get_nowait_impl(self, cls); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; } PyDoc_STRVAR(_queue_SimpleQueue_empty__doc__, @@ -294,7 +308,9 @@ _queue_SimpleQueue_empty(simplequeueobject *self, PyObject *Py_UNUSED(ignored)) PyObject *return_value = NULL; int _return_value; + Py_BEGIN_CRITICAL_SECTION(self); _return_value = _queue_SimpleQueue_empty_impl(self); + Py_END_CRITICAL_SECTION(); if ((_return_value == -1) && PyErr_Occurred()) { goto exit; } @@ -322,7 +338,9 @@ _queue_SimpleQueue_qsize(simplequeueobject *self, PyObject *Py_UNUSED(ignored)) PyObject *return_value = NULL; Py_ssize_t _return_value; + Py_BEGIN_CRITICAL_SECTION(self); _return_value = _queue_SimpleQueue_qsize_impl(self); + Py_END_CRITICAL_SECTION(); if ((_return_value == -1) && PyErr_Occurred()) { goto exit; } @@ -331,4 +349,4 @@ _queue_SimpleQueue_qsize(simplequeueobject *self, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=457310b20cb61cf8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=242950edc8f7dfd7 input=a9049054013a1b77]*/ From 8c265408c51609c6b4a6788cac9cc5fea7a14888 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 23 Jan 2024 20:54:44 +0100 Subject: [PATCH 002/263] Docs: use placeholders in dbm flag param docs (#114482) Also correct the default flag param for dbm.dumb.open(); it's 'c', not 'r'. --- Doc/library/dbm.rst | 103 ++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index cb95c61322582f6..eca1c25602a018e 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -36,6 +36,21 @@ the Oracle Berkeley DB. .. versionchanged:: 3.11 Accepts :term:`path-like object` for filename. +.. Substitutions for the open() flag param docs; + all submodules use the same text. + +.. |flag_r| replace:: + Open existing database for reading only. + +.. |flag_w| replace:: + Open existing database for reading and writing. + +.. |flag_c| replace:: + Open database for reading and writing, creating it if it doesn't exist. + +.. |flag_n| replace:: + Always create a new, empty database, open for reading and writing. + .. function:: open(file, flag='r', mode=0o666) Open the database file *file* and return a corresponding object. @@ -46,21 +61,13 @@ the Oracle Berkeley DB. The optional *flag* argument can be: - +---------+-------------------------------------------+ - | Value | Meaning | - +=========+===========================================+ - | ``'r'`` | Open existing database for reading only | - | | (default) | - +---------+-------------------------------------------+ - | ``'w'`` | Open existing database for reading and | - | | writing | - +---------+-------------------------------------------+ - | ``'c'`` | Open database for reading and writing, | - | | creating it if it doesn't exist | - +---------+-------------------------------------------+ - | ``'n'`` | Always create a new, empty database, open | - | | for reading and writing | - +---------+-------------------------------------------+ + .. csv-table:: + :header: "Value", "Meaning" + + ``'r'`` (default), |flag_r| + ``'w'``, |flag_w| + ``'c'``, |flag_c| + ``'n'``, |flag_n| The optional *mode* argument is the Unix mode of the file, used only when the database has to be created. It defaults to octal ``0o666`` (and will be @@ -165,21 +172,13 @@ supported. The optional *flag* argument can be: - +---------+-------------------------------------------+ - | Value | Meaning | - +=========+===========================================+ - | ``'r'`` | Open existing database for reading only | - | | (default) | - +---------+-------------------------------------------+ - | ``'w'`` | Open existing database for reading and | - | | writing | - +---------+-------------------------------------------+ - | ``'c'`` | Open database for reading and writing, | - | | creating it if it doesn't exist | - +---------+-------------------------------------------+ - | ``'n'`` | Always create a new, empty database, open | - | | for reading and writing | - +---------+-------------------------------------------+ + .. csv-table:: + :header: "Value", "Meaning" + + ``'r'`` (default), |flag_r| + ``'w'``, |flag_w| + ``'c'``, |flag_c| + ``'n'``, |flag_n| The following additional characters may be appended to the flag to control how the database is opened: @@ -297,21 +296,13 @@ to locate the appropriate header file to simplify building this module. The optional *flag* argument must be one of these values: - +---------+-------------------------------------------+ - | Value | Meaning | - +=========+===========================================+ - | ``'r'`` | Open existing database for reading only | - | | (default) | - +---------+-------------------------------------------+ - | ``'w'`` | Open existing database for reading and | - | | writing | - +---------+-------------------------------------------+ - | ``'c'`` | Open database for reading and writing, | - | | creating it if it doesn't exist | - +---------+-------------------------------------------+ - | ``'n'`` | Always create a new, empty database, open | - | | for reading and writing | - +---------+-------------------------------------------+ + .. csv-table:: + :header: "Value", "Meaning" + + ``'r'`` (default), |flag_r| + ``'w'``, |flag_w| + ``'c'``, |flag_c| + ``'n'``, |flag_n| The optional *mode* argument is the Unix mode of the file, used only when the database has to be created. It defaults to octal ``0o666`` (and will be @@ -376,21 +367,13 @@ The module defines the following: The optional *flag* argument can be: - +---------+-------------------------------------------+ - | Value | Meaning | - +=========+===========================================+ - | ``'r'`` | Open existing database for reading only | - | | (default) | - +---------+-------------------------------------------+ - | ``'w'`` | Open existing database for reading and | - | | writing | - +---------+-------------------------------------------+ - | ``'c'`` | Open database for reading and writing, | - | | creating it if it doesn't exist | - +---------+-------------------------------------------+ - | ``'n'`` | Always create a new, empty database, open | - | | for reading and writing | - +---------+-------------------------------------------+ + .. csv-table:: + :header: "Value", "Meaning" + + ``'r'``, |flag_r| + ``'w'``, |flag_w| + ``'c'`` (default), |flag_c| + ``'n'``, |flag_n| The optional *mode* argument is the Unix mode of the file, used only when the database has to be created. It defaults to octal ``0o666`` (and will be modified From ce01ab536f22a3cf095d621f3b3579c1e3567859 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 23 Jan 2024 15:14:46 -0500 Subject: [PATCH 003/263] gh-101438: Avoid reference cycle in ElementTree.iterparse. (GH-114269) The iterator returned by ElementTree.iterparse() may hold on to a file descriptor. The reference cycle prevented prompt clean-up of the file descriptor if the returned iterator was not exhausted. --- Lib/xml/etree/ElementTree.py | 27 ++++++++++++------- ...-01-18-22-29-28.gh-issue-101438.1-uUi_.rst | 4 +++ 2 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 42574eefd81bebe..ae6575028be11cb 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -99,6 +99,7 @@ import collections import collections.abc import contextlib +import weakref from . import ElementPath @@ -1223,13 +1224,14 @@ def iterparse(source, events=None, parser=None): # parser argument of iterparse is removed, this can be killed. pullparser = XMLPullParser(events=events, _parser=parser) - def iterator(source): + if not hasattr(source, "read"): + source = open(source, "rb") + close_source = True + else: close_source = False + + def iterator(source): try: - if not hasattr(source, "read"): - source = open(source, "rb") - close_source = True - yield None while True: yield from pullparser.read_events() # load event buffer @@ -1239,18 +1241,23 @@ def iterator(source): pullparser.feed(data) root = pullparser._close_and_return_root() yield from pullparser.read_events() - it.root = root + it = wr() + if it is not None: + it.root = root finally: if close_source: source.close() class IterParseIterator(collections.abc.Iterator): __next__ = iterator(source).__next__ - it = IterParseIterator() - it.root = None - del iterator, IterParseIterator - next(it) + def __del__(self): + if close_source: + source.close() + + it = IterParseIterator() + wr = weakref.ref(it) + del IterParseIterator return it diff --git a/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst b/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst new file mode 100644 index 000000000000000..9b69b5deb1b5a06 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-18-22-29-28.gh-issue-101438.1-uUi_.rst @@ -0,0 +1,4 @@ +Avoid reference cycle in ElementTree.iterparse. The iterator returned by +``ElementTree.iterparse`` may hold on to a file descriptor. The reference +cycle prevented prompt clean-up of the file descriptor if the returned +iterator was not exhausted. From d22c066b802592932f9eb18434782299e80ca42e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 23 Jan 2024 23:27:04 +0200 Subject: [PATCH 004/263] gh-114492: Initialize struct termios before calling tcgetattr() (GH-114495) On Alpine Linux it could leave some field non-initialized. --- .../next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst | 2 ++ Modules/termios.c | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst diff --git a/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst b/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst new file mode 100644 index 000000000000000..8df8299d0dffcd6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-21-20-40.gh-issue-114492.vKxl5o.rst @@ -0,0 +1,2 @@ +Make the result of :func:`termios.tcgetattr` reproducible on Alpine Linux. +Previously it could leave a random garbage in some fields. diff --git a/Modules/termios.c b/Modules/termios.c index c4f0fd9d50044aa..69dbd88be5fcc20 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -98,6 +98,8 @@ termios_tcgetattr_impl(PyObject *module, int fd) struct termios mode; int r; + /* Alpine Linux can leave some fields uninitialized. */ + memset(&mode, 0, sizeof(mode)); Py_BEGIN_ALLOW_THREADS r = tcgetattr(fd, &mode); Py_END_ALLOW_THREADS From afe8f376c096d5d6e8b12fbc691ca9b35381470b Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Tue, 23 Jan 2024 14:10:04 -0800 Subject: [PATCH 005/263] gh-112075: Adapt more dict methods to Argument Clinic (#114256) * Move more dict objects to argument clinic * Improve doc strings * More doc string improvements * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland * Update Objects/dictobject.c Co-authored-by: Erlend E. Aasland --------- Co-authored-by: Erlend E. Aasland --- Objects/clinic/dictobject.c.h | 110 ++++++++++++++++++++++- Objects/dictobject.c | 162 +++++++++++++++++----------------- 2 files changed, 191 insertions(+), 81 deletions(-) diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index 641514235c23414..8f532f454156dec 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -38,6 +38,24 @@ dict_fromkeys(PyTypeObject *type, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(dict_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of the dict."); + +#define DICT_COPY_METHODDEF \ + {"copy", (PyCFunction)dict_copy, METH_NOARGS, dict_copy__doc__}, + +static PyObject * +dict_copy_impl(PyDictObject *self); + +static PyObject * +dict_copy(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict_copy_impl(self); +} + PyDoc_STRVAR(dict___contains____doc__, "__contains__($self, key, /)\n" "--\n" @@ -118,6 +136,24 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(dict_clear__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all items from the dict."); + +#define DICT_CLEAR_METHODDEF \ + {"clear", (PyCFunction)dict_clear, METH_NOARGS, dict_clear__doc__}, + +static PyObject * +dict_clear_impl(PyDictObject *self); + +static PyObject * +dict_clear(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict_clear_impl(self); +} + PyDoc_STRVAR(dict_pop__doc__, "pop($self, key, default=, /)\n" "--\n" @@ -176,6 +212,24 @@ dict_popitem(PyDictObject *self, PyObject *Py_UNUSED(ignored)) return dict_popitem_impl(self); } +PyDoc_STRVAR(dict___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"Return the size of the dict in memory, in bytes."); + +#define DICT___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)dict___sizeof__, METH_NOARGS, dict___sizeof____doc__}, + +static PyObject * +dict___sizeof___impl(PyDictObject *self); + +static PyObject * +dict___sizeof__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict___sizeof___impl(self); +} + PyDoc_STRVAR(dict___reversed____doc__, "__reversed__($self, /)\n" "--\n" @@ -193,4 +247,58 @@ dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { return dict___reversed___impl(self); } -/*[clinic end generated code: output=17c3c4cf9a9b95a7 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(dict_keys__doc__, +"keys($self, /)\n" +"--\n" +"\n" +"Return a set-like object providing a view on the dict\'s keys."); + +#define DICT_KEYS_METHODDEF \ + {"keys", (PyCFunction)dict_keys, METH_NOARGS, dict_keys__doc__}, + +static PyObject * +dict_keys_impl(PyDictObject *self); + +static PyObject * +dict_keys(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict_keys_impl(self); +} + +PyDoc_STRVAR(dict_items__doc__, +"items($self, /)\n" +"--\n" +"\n" +"Return a set-like object providing a view on the dict\'s items."); + +#define DICT_ITEMS_METHODDEF \ + {"items", (PyCFunction)dict_items, METH_NOARGS, dict_items__doc__}, + +static PyObject * +dict_items_impl(PyDictObject *self); + +static PyObject * +dict_items(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict_items_impl(self); +} + +PyDoc_STRVAR(dict_values__doc__, +"values($self, /)\n" +"--\n" +"\n" +"Return an object providing a view on the dict\'s values."); + +#define DICT_VALUES_METHODDEF \ + {"values", (PyCFunction)dict_values, METH_NOARGS, dict_values__doc__}, + +static PyObject * +dict_values_impl(PyDictObject *self); + +static PyObject * +dict_values(PyDictObject *self, PyObject *Py_UNUSED(ignored)) +{ + return dict_values_impl(self); +} +/*[clinic end generated code: output=f3ac47dfbf341b23 input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2482a918ba983bd..e608b91679b5688 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2641,9 +2641,14 @@ static PyMappingMethods dict_as_mapping = { dict_ass_sub, /*mp_ass_subscript*/ }; -static PyObject * -dict_keys(PyDictObject *mp) +PyObject * +PyDict_Keys(PyObject *dict) { + if (dict == NULL || !PyDict_Check(dict)) { + PyErr_BadInternalCall(); + return NULL; + } + PyDictObject *mp = (PyDictObject *)dict; PyObject *v; Py_ssize_t n; @@ -2672,9 +2677,14 @@ dict_keys(PyDictObject *mp) return v; } -static PyObject * -dict_values(PyDictObject *mp) +PyObject * +PyDict_Values(PyObject *dict) { + if (dict == NULL || !PyDict_Check(dict)) { + PyErr_BadInternalCall(); + return NULL; + } + PyDictObject *mp = (PyDictObject *)dict; PyObject *v; Py_ssize_t n; @@ -2703,9 +2713,14 @@ dict_values(PyDictObject *mp) return v; } -static PyObject * -dict_items(PyDictObject *mp) +PyObject * +PyDict_Items(PyObject *dict) { + if (dict == NULL || !PyDict_Check(dict)) { + PyErr_BadInternalCall(); + return NULL; + } + PyDictObject *mp = (PyDictObject *)dict; PyObject *v; Py_ssize_t i, n; PyObject *item; @@ -3108,10 +3123,17 @@ _PyDict_MergeEx(PyObject *a, PyObject *b, int override) return dict_merge(interp, a, b, override); } +/*[clinic input] +dict.copy + +Return a shallow copy of the dict. +[clinic start generated code]*/ + static PyObject * -dict_copy(PyObject *mp, PyObject *Py_UNUSED(ignored)) +dict_copy_impl(PyDictObject *self) +/*[clinic end generated code: output=ffb782cf970a5c39 input=73935f042b639de4]*/ { - return PyDict_Copy(mp); + return PyDict_Copy((PyObject *)self); } PyObject * @@ -3217,36 +3239,6 @@ PyDict_Size(PyObject *mp) return ((PyDictObject *)mp)->ma_used; } -PyObject * -PyDict_Keys(PyObject *mp) -{ - if (mp == NULL || !PyDict_Check(mp)) { - PyErr_BadInternalCall(); - return NULL; - } - return dict_keys((PyDictObject *)mp); -} - -PyObject * -PyDict_Values(PyObject *mp) -{ - if (mp == NULL || !PyDict_Check(mp)) { - PyErr_BadInternalCall(); - return NULL; - } - return dict_values((PyDictObject *)mp); -} - -PyObject * -PyDict_Items(PyObject *mp) -{ - if (mp == NULL || !PyDict_Check(mp)) { - PyErr_BadInternalCall(); - return NULL; - } - return dict_items((PyDictObject *)mp); -} - /* Return 1 if dicts equal, 0 if not, -1 if error. * Gets out as soon as any difference is detected. * Uses only Py_EQ comparison. @@ -3512,10 +3504,18 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key, return Py_XNewRef(val); } + +/*[clinic input] +dict.clear + +Remove all items from the dict. +[clinic start generated code]*/ + static PyObject * -dict_clear(PyObject *mp, PyObject *Py_UNUSED(ignored)) +dict_clear_impl(PyDictObject *self) +/*[clinic end generated code: output=5139a830df00830a input=0bf729baba97a4c2]*/ { - PyDict_Clear(mp); + PyDict_Clear((PyObject *)self); Py_RETURN_NONE; } @@ -3703,11 +3703,17 @@ _PyDict_KeysSize(PyDictKeysObject *keys) return size; } +/*[clinic input] +dict.__sizeof__ + +Return the size of the dict in memory, in bytes. +[clinic start generated code]*/ + static PyObject * -dict_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) +dict___sizeof___impl(PyDictObject *self) +/*[clinic end generated code: output=44279379b3824bda input=4fec4ddfc44a4d1a]*/ { - PyDictObject *mp = (PyDictObject *)self; - return PyLong_FromSsize_t(_PyDict_SizeOf(mp)); + return PyLong_FromSsize_t(_PyDict_SizeOf(self)); } static PyObject * @@ -3739,56 +3745,31 @@ dict_ior(PyObject *self, PyObject *other) PyDoc_STRVAR(getitem__doc__, "__getitem__($self, key, /)\n--\n\nReturn self[key]."); -PyDoc_STRVAR(sizeof__doc__, -"D.__sizeof__() -> size of D in memory, in bytes"); - PyDoc_STRVAR(update__doc__, "D.update([E, ]**F) -> None. Update D from dict/iterable E and F.\n\ If E is present and has a .keys() method, then does: for k in E: D[k] = E[k]\n\ If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v\n\ In either case, this is followed by: for k in F: D[k] = F[k]"); -PyDoc_STRVAR(clear__doc__, -"D.clear() -> None. Remove all items from D."); - -PyDoc_STRVAR(copy__doc__, -"D.copy() -> a shallow copy of D"); - /* Forward */ -static PyObject *dictkeys_new(PyObject *, PyObject *); -static PyObject *dictitems_new(PyObject *, PyObject *); -static PyObject *dictvalues_new(PyObject *, PyObject *); - -PyDoc_STRVAR(keys__doc__, - "D.keys() -> a set-like object providing a view on D's keys"); -PyDoc_STRVAR(items__doc__, - "D.items() -> a set-like object providing a view on D's items"); -PyDoc_STRVAR(values__doc__, - "D.values() -> an object providing a view on D's values"); static PyMethodDef mapp_methods[] = { DICT___CONTAINS___METHODDEF {"__getitem__", dict_subscript, METH_O | METH_COEXIST, getitem__doc__}, - {"__sizeof__", dict_sizeof, METH_NOARGS, - sizeof__doc__}, + DICT___SIZEOF___METHODDEF DICT_GET_METHODDEF DICT_SETDEFAULT_METHODDEF DICT_POP_METHODDEF DICT_POPITEM_METHODDEF - {"keys", dictkeys_new, METH_NOARGS, - keys__doc__}, - {"items", dictitems_new, METH_NOARGS, - items__doc__}, - {"values", dictvalues_new, METH_NOARGS, - values__doc__}, + DICT_KEYS_METHODDEF + DICT_ITEMS_METHODDEF + DICT_VALUES_METHODDEF {"update", _PyCFunction_CAST(dict_update), METH_VARARGS | METH_KEYWORDS, update__doc__}, DICT_FROMKEYS_METHODDEF - {"clear", dict_clear, METH_NOARGS, - clear__doc__}, - {"copy", dict_copy, METH_NOARGS, - copy__doc__}, + DICT_CLEAR_METHODDEF + DICT_COPY_METHODDEF DICT___REVERSED___METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ @@ -5263,10 +5244,17 @@ PyTypeObject PyDictKeys_Type = { .tp_getset = dictview_getset, }; +/*[clinic input] +dict.keys + +Return a set-like object providing a view on the dict's keys. +[clinic start generated code]*/ + static PyObject * -dictkeys_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) +dict_keys_impl(PyDictObject *self) +/*[clinic end generated code: output=aac2830c62990358 input=42f48a7a771212a7]*/ { - return _PyDictView_New(dict, &PyDictKeys_Type); + return _PyDictView_New((PyObject *)self, &PyDictKeys_Type); } static PyObject * @@ -5368,10 +5356,17 @@ PyTypeObject PyDictItems_Type = { .tp_getset = dictview_getset, }; +/*[clinic input] +dict.items + +Return a set-like object providing a view on the dict's items. +[clinic start generated code]*/ + static PyObject * -dictitems_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) +dict_items_impl(PyDictObject *self) +/*[clinic end generated code: output=88c7db7150c7909a input=87c822872eb71f5a]*/ { - return _PyDictView_New(dict, &PyDictItems_Type); + return _PyDictView_New((PyObject *)self, &PyDictItems_Type); } static PyObject * @@ -5451,10 +5446,17 @@ PyTypeObject PyDictValues_Type = { .tp_getset = dictview_getset, }; +/*[clinic input] +dict.values + +Return an object providing a view on the dict's values. +[clinic start generated code]*/ + static PyObject * -dictvalues_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) +dict_values_impl(PyDictObject *self) +/*[clinic end generated code: output=ce9f2e9e8a959dd4 input=b46944f85493b230]*/ { - return _PyDictView_New(dict, &PyDictValues_Type); + return _PyDictView_New((PyObject *)self, &PyDictValues_Type); } static PyObject * From f59f90b5bccb9e7ac522bc779ab1f6bf11bb4aa3 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 23 Jan 2024 15:48:14 -0800 Subject: [PATCH 006/263] GH-114456: lower the recursion limit under WASI for debug builds (GH-114457) Testing under wasmtime 16.0.0 w/ code from https://github.com/python/cpython/issues/114413 is how the value was found. --- Include/cpython/pystate.h | 11 +++++++---- Lib/test/test_dynamic.py | 3 ++- Lib/test/test_pickle.py | 4 +++- .../2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 60b056bdcc2f1f2..1dbf97660f382fd 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -217,11 +217,14 @@ struct _ts { #ifdef Py_DEBUG // A debug build is likely built with low optimization level which implies // higher stack memory usage than a release build: use a lower limit. -# define Py_C_RECURSION_LIMIT 500 +# if defined(__wasi__) + // Based on wasmtime 16. +# define Py_C_RECURSION_LIMIT 150 +# else +# define Py_C_RECURSION_LIMIT 500 +# endif #elif defined(__wasi__) - // WASI has limited call stack. Python's recursion limit depends on code - // layout, optimization, and WASI runtime. Wasmtime can handle about 700 - // recursions, sometimes less. 500 is a more conservative limit. + // Based on wasmtime 16. # define Py_C_RECURSION_LIMIT 500 #elif defined(__s390x__) # define Py_C_RECURSION_LIMIT 800 diff --git a/Lib/test/test_dynamic.py b/Lib/test/test_dynamic.py index 0aa3be6a1bde6ae..3928bbab4423c22 100644 --- a/Lib/test/test_dynamic.py +++ b/Lib/test/test_dynamic.py @@ -4,7 +4,7 @@ import sys import unittest -from test.support import swap_item, swap_attr +from test.support import is_wasi, Py_DEBUG, swap_item, swap_attr class RebindBuiltinsTests(unittest.TestCase): @@ -134,6 +134,7 @@ def test_eval_gives_lambda_custom_globals(self): self.assertEqual(foo(), 7) + @unittest.skipIf(is_wasi and Py_DEBUG, "stack depth too shallow in pydebug WASI") def test_load_global_specialization_failure_keeps_oparg(self): # https://github.com/python/cpython/issues/91625 class MyGlobals(dict): diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index f6405d6dd44ef6f..b2245ddf72f7085 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -402,7 +402,9 @@ def recurse(deep): check_unpickler(recurse(1), 32, 20) check_unpickler(recurse(20), 32, 20) check_unpickler(recurse(50), 64, 60) - check_unpickler(recurse(100), 128, 140) + if not (support.is_wasi and support.Py_DEBUG): + # stack depth too shallow in pydebug WASI. + check_unpickler(recurse(100), 128, 140) u = unpickler(io.BytesIO(pickle.dumps('a', 0)), encoding='ASCII', errors='strict') diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst new file mode 100644 index 000000000000000..2b30ad98fb5c79b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-22-15-10-01.gh-issue-114456.fBFEJF.rst @@ -0,0 +1 @@ +Lower the recursion limit under a debug build of WASI. From 82cd8fee31823b560e664f81b430a9186d6019dd Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Wed, 24 Jan 2024 04:16:31 +0000 Subject: [PATCH 007/263] Fix a typo in the contextlib documentation (#114507) --- Doc/library/contextlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index b73373bc2363fb0..73e53aec9cbf1c1 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -185,7 +185,7 @@ Functions and classes provided: .. note:: Most types managing resources support the :term:`context manager` protocol, - which closes *thing* on leaving the :keyword:`with` statment. + which closes *thing* on leaving the :keyword:`with` statement. As such, :func:`!closing` is most useful for third party types that don't support context managers. This example is purely for illustration purposes, From ce75b4c26d18dcd840fd2e7ee362a84209648d06 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 24 Jan 2024 09:13:09 +0200 Subject: [PATCH 008/263] gh-113205: test_multiprocessing.test_terminate: Give tasks a chance to start (GH-114249) --- Lib/test/_test_multiprocessing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 6a050fa541db1ed..c0d3ca50f17d69d 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2705,6 +2705,7 @@ def test_terminate(self): p = self.Pool(3) args = [sleep_time for i in range(10_000)] result = p.map_async(time.sleep, args, chunksize=1) + time.sleep(0.2) # give some tasks a chance to start p.terminate() p.join() From 1e4f00ebd8db44a031b61eed0803b4c3d731aed7 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 24 Jan 2024 10:23:34 +0300 Subject: [PATCH 009/263] gh-101100: Fix sphinx warnings in `asyncio-task.rst` (#114469) Co-authored-by: Serhiy Storchaka --- Doc/library/asyncio-task.rst | 33 ++++++++++++++++----------------- Doc/tools/.nitignore | 1 - 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 797065c8ccf8941..24bd36e6431b4f4 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -828,23 +828,22 @@ Waiting Primitives *return_when* indicates when this function should return. It must be one of the following constants: - .. tabularcolumns:: |l|L| - - +-----------------------------+----------------------------------------+ - | Constant | Description | - +=============================+========================================+ - | :const:`FIRST_COMPLETED` | The function will return when any | - | | future finishes or is cancelled. | - +-----------------------------+----------------------------------------+ - | :const:`FIRST_EXCEPTION` | The function will return when any | - | | future finishes by raising an | - | | exception. If no future raises an | - | | exception then it is equivalent to | - | | :const:`ALL_COMPLETED`. | - +-----------------------------+----------------------------------------+ - | :const:`ALL_COMPLETED` | The function will return when all | - | | futures finish or are cancelled. | - +-----------------------------+----------------------------------------+ + .. list-table:: + :header-rows: 1 + + * - Constant + - Description + + * - .. data:: FIRST_COMPLETED + - The function will return when any future finishes or is cancelled. + + * - .. data:: FIRST_EXCEPTION + - The function will return when any future finishes by raising an + exception. If no future raises an exception + then it is equivalent to :const:`ALL_COMPLETED`. + + * - .. data:: ALL_COMPLETED + - The function will return when all futures finish or are cancelled. Unlike :func:`~asyncio.wait_for`, ``wait()`` does not cancel the futures when a timeout occurs. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 221a1f05c11e49d..2114ec6dfacd7d9 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -27,7 +27,6 @@ Doc/library/ast.rst Doc/library/asyncio-extending.rst Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst -Doc/library/asyncio-task.rst Doc/library/bdb.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst From 384429d1c0cf011dcf88d4043e0328de8b063c24 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 24 Jan 2024 12:08:31 +0000 Subject: [PATCH 010/263] GH-113710: Add a tier 2 peephole optimization pass. (GH-114487) * Convert _LOAD_CONST to inline versions * Remove PEP 523 checks --- Include/internal/pycore_uop_ids.h | 7 +++--- Include/internal/pycore_uop_metadata.h | 2 ++ Python/bytecodes.c | 4 +++ Python/executor_cases.c.h | 9 +++++++ Python/optimizer.c | 6 +++++ Python/optimizer_analysis.c | 34 ++++++++++++++++++++++++++ Python/pystate.c | 10 +++++--- 7 files changed, 66 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 8ee90d79a13c2f8..a7056586ff04c01 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -230,9 +230,10 @@ extern "C" { #define _JUMP_TO_TOP 377 #define _SAVE_RETURN_OFFSET 378 #define _CHECK_VALIDITY 379 -#define _LOAD_CONST_INLINE_BORROW 380 -#define _INTERNAL_INCREMENT_OPT_COUNTER 381 -#define MAX_UOP_ID 381 +#define _LOAD_CONST_INLINE 380 +#define _LOAD_CONST_INLINE_BORROW 381 +#define _INTERNAL_INCREMENT_OPT_COUNTER 382 +#define MAX_UOP_ID 382 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 9bfb4f4f3a4dea3..14d3382e895cdf4 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -202,6 +202,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, [_EXIT_TRACE] = HAS_DEOPT_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, + [_LOAD_CONST_INLINE] = 0, [_LOAD_CONST_INLINE_BORROW] = 0, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, }; @@ -329,6 +330,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", [_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS", [_LOAD_CONST] = "_LOAD_CONST", + [_LOAD_CONST_INLINE] = "_LOAD_CONST_INLINE", [_LOAD_CONST_INLINE_BORROW] = "_LOAD_CONST_INLINE_BORROW", [_LOAD_DEREF] = "_LOAD_DEREF", [_LOAD_FAST] = "_LOAD_FAST", diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 7674ff81f64cec0..18749ce60ecd45b 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4070,6 +4070,10 @@ dummy_func( DEOPT_IF(!current_executor->vm_data.valid); } + op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + value = Py_NewRef(ptr); + } + op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { value = ptr; } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2b4399b25bae2bc..241b9056207715d 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3390,6 +3390,15 @@ break; } + case _LOAD_CONST_INLINE: { + PyObject *value; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + value = Py_NewRef(ptr); + stack_pointer[0] = value; + stack_pointer += 1; + break; + } + case _LOAD_CONST_INLINE_BORROW: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); diff --git a/Python/optimizer.c b/Python/optimizer.c index 1551a5ef61f892f..4b6ed1781b5b782 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -588,6 +588,9 @@ translate_bytecode_to_trace( ADD_TO_TRACE(uop, oparg, operand, target); if (uop == _POP_FRAME) { TRACE_STACK_POP(); + /* Set the operand to the code object returned to, + * to assist optimization passes */ + trace[trace_length-1].operand = (uintptr_t)code; DPRINTF(2, "Returning to %s (%s:%d) at byte offset %d\n", PyUnicode_AsUTF8(code->co_qualname), @@ -629,6 +632,9 @@ translate_bytecode_to_trace( instr += _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + 1; TRACE_STACK_PUSH(); _Py_BloomFilter_Add(dependencies, new_code); + /* Set the operand to the callee's code object, + * to assist optimization passes */ + trace[trace_length-1].operand = (uintptr_t)new_code; code = new_code; instr = _PyCode_CODE(code); DPRINTF(2, diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 7db51f0d90a4530..d1225997e10be2b 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -12,6 +12,39 @@ #include #include "pycore_optimizer.h" +static void +peephole_opt(PyCodeObject *co, _PyUOpInstruction *buffer, int buffer_size) +{ + for (int pc = 0; pc < buffer_size; pc++) { + int opcode = buffer[pc].opcode; + switch(opcode) { + case _LOAD_CONST: { + assert(co != NULL); + PyObject *val = PyTuple_GET_ITEM(co->co_consts, buffer[pc].oparg); + buffer[pc].opcode = _Py_IsImmortal(val) ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE; + buffer[pc].operand = (uintptr_t)val; + break; + } + case _CHECK_PEP_523: + { + /* Setting the eval frame function invalidates + * all executors, so no need to check dynamically */ + if (_PyInterpreterState_GET()->eval_frame == NULL) { + buffer[pc].opcode = _NOP; + } + break; + } + case _PUSH_FRAME: + case _POP_FRAME: + co = (PyCodeObject *)buffer[pc].operand; + break; + case _JUMP_TO_TOP: + case _EXIT_TRACE: + return; + } + } +} + static void remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) { @@ -59,6 +92,7 @@ _Py_uop_analyze_and_optimize( int curr_stacklen ) { + peephole_opt(co, buffer, buffer_size); remove_unneeded_uops(buffer, buffer_size); return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 23ddc781434ac88..548c77b7dc7ebb8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2608,11 +2608,15 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame) { if (eval_frame == _PyEval_EvalFrameDefault) { - interp->eval_frame = NULL; + eval_frame = NULL; } - else { - interp->eval_frame = eval_frame; + if (eval_frame == interp->eval_frame) { + return; + } + if (eval_frame != NULL) { + _Py_Executors_InvalidateAll(interp); } + interp->eval_frame = eval_frame; } From 8744ecf5896ccf57875574a9aed46369b8d48dc1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Wed, 24 Jan 2024 16:30:50 +0300 Subject: [PATCH 011/263] gh-101100: Fix sphinx warnings in `concurrent.futures.rst` (#114521) --- Doc/library/concurrent.futures.rst | 42 ++++++++++++++++-------------- Doc/tools/.nitignore | 1 - 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 800c7f6739d8a36..d3c7a40aa9d390d 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -275,7 +275,8 @@ to a :class:`ProcessPoolExecutor` will result in deadlock. .. versionchanged:: 3.3 When one of the worker processes terminates abruptly, a - :exc:`BrokenProcessPool` error is now raised. Previously, behaviour + :exc:`~concurrent.futures.process.BrokenProcessPool` error is now raised. + Previously, behaviour was undefined but operations on the executor or its futures would often freeze or deadlock. @@ -493,23 +494,22 @@ Module Functions *return_when* indicates when this function should return. It must be one of the following constants: - .. tabularcolumns:: |l|L| - - +-----------------------------+----------------------------------------+ - | Constant | Description | - +=============================+========================================+ - | :const:`FIRST_COMPLETED` | The function will return when any | - | | future finishes or is cancelled. | - +-----------------------------+----------------------------------------+ - | :const:`FIRST_EXCEPTION` | The function will return when any | - | | future finishes by raising an | - | | exception. If no future raises an | - | | exception then it is equivalent to | - | | :const:`ALL_COMPLETED`. | - +-----------------------------+----------------------------------------+ - | :const:`ALL_COMPLETED` | The function will return when all | - | | futures finish or are cancelled. | - +-----------------------------+----------------------------------------+ + .. list-table:: + :header-rows: 1 + + * - Constant + - Description + + * - .. data:: FIRST_COMPLETED + - The function will return when any future finishes or is cancelled. + + * - .. data:: FIRST_EXCEPTION + - The function will return when any future finishes by raising an + exception. If no future raises an exception + then it is equivalent to :const:`ALL_COMPLETED`. + + * - .. data:: ALL_COMPLETED + - The function will return when all futures finish or are cancelled. .. function:: as_completed(fs, timeout=None) @@ -570,7 +570,8 @@ Exception classes .. exception:: BrokenThreadPool Derived from :exc:`~concurrent.futures.BrokenExecutor`, this exception - class is raised when one of the workers of a :class:`ThreadPoolExecutor` + class is raised when one of the workers + of a :class:`~concurrent.futures.ThreadPoolExecutor` has failed initializing. .. versionadded:: 3.7 @@ -581,7 +582,8 @@ Exception classes Derived from :exc:`~concurrent.futures.BrokenExecutor` (formerly :exc:`RuntimeError`), this exception class is raised when one of the - workers of a :class:`ProcessPoolExecutor` has terminated in a non-clean + workers of a :class:`~concurrent.futures.ProcessPoolExecutor` + has terminated in a non-clean fashion (for example, if it was killed from the outside). .. versionadded:: 3.3 diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 2114ec6dfacd7d9..6778e57c272ffb4 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -29,7 +29,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/bdb.rst Doc/library/collections.rst -Doc/library/concurrent.futures.rst Doc/library/csv.rst Doc/library/datetime.rst Doc/library/dbm.rst From 51d9068ede41d49e86c9637960f212e2a0f07f4c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:40:09 +0200 Subject: [PATCH 012/263] gh-101100: Fix Sphinx warnings in `c-api/structures.rst` (#113564) Co-authored-by: Hugo van Kemenade Co-authored-by: Petr Viktorin --- Doc/c-api/structures.rst | 19 ++++++++++--------- Doc/tools/.nitignore | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 86c779472fd244e..0032da9659636c5 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -551,11 +551,11 @@ The following flags can be used with :c:member:`PyMemberDef.flags`: from ``PyObject``. Can only be used as part of :c:member:`Py_tp_members ` - :c:type:`slot ` when creating a class using negative + :c:type:`slot ` when creating a class using negative :c:member:`~PyType_Spec.basicsize`. It is mandatory in that case. - This flag is only used in :c:type:`PyTypeSlot`. + This flag is only used in :c:type:`PyType_Slot`. When setting :c:member:`~PyTypeObject.tp_members` during class creation, Python clears it and sets :c:member:`PyMemberDef.offset` to the offset from the ``PyObject`` struct. @@ -693,7 +693,8 @@ Defining Getters and Setters .. c:member:: setter set - Optional C function to set or delete the attribute, if omitted the attribute is readonly. + Optional C function to set or delete the attribute. + If ``NULL``, the attribute is read-only. .. c:member:: const char* doc @@ -703,18 +704,18 @@ Defining Getters and Setters Optional function pointer, providing additional data for getter and setter. - The ``get`` function takes one :c:expr:`PyObject*` parameter (the - instance) and a function pointer (the associated ``closure``):: +.. c:type:: PyObject *(*getter)(PyObject *, void *) - typedef PyObject *(*getter)(PyObject *, void *); + The ``get`` function takes one :c:expr:`PyObject*` parameter (the + instance) and a function pointer (the associated ``closure``): It should return a new reference on success or ``NULL`` with a set exception on failure. - ``set`` functions take two :c:expr:`PyObject*` parameters (the instance and - the value to be set) and a function pointer (the associated ``closure``):: +.. c:type:: int (*setter)(PyObject *, PyObject *, void *) - typedef int (*setter)(PyObject *, PyObject *, void *); + ``set`` functions take two :c:expr:`PyObject*` parameters (the instance and + the value to be set) and a function pointer (the associated ``closure``): In case the attribute should be deleted the second parameter is ``NULL``. Should return ``0`` on success or ``-1`` with a set exception on failure. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 6778e57c272ffb4..00b4b6919ff14a1 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -14,7 +14,6 @@ Doc/c-api/memoryview.rst Doc/c-api/module.rst Doc/c-api/object.rst Doc/c-api/stable.rst -Doc/c-api/structures.rst Doc/c-api/sys.rst Doc/c-api/type.rst Doc/c-api/typeobj.rst From 127a49785247ac8af158b18e38b722e520054d71 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 24 Jan 2024 14:24:00 +0000 Subject: [PATCH 013/263] gh-104360: remove reference to removed module-level wrap_socket (GH-104361) * remove reference to removed module-level wrap_socket * drive by typo fix --- Doc/library/ssl.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index e8709b516ae07a2..f9648fa6744bdc0 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2574,12 +2574,8 @@ provided. :exc:`SSLWantReadError` if it needs more data than the incoming BIO has available. - - There is no module-level ``wrap_bio()`` call like there is for - :meth:`~SSLContext.wrap_socket`. An :class:`SSLObject` is always created - via an :class:`SSLContext`. - .. versionchanged:: 3.7 - :class:`SSLObject` instances must to created with + :class:`SSLObject` instances must be created with :meth:`~SSLContext.wrap_bio`. In earlier versions, it was possible to create instances directly. This was never documented or officially supported. From 6fadd68da5dd928847264b17f62a5b8b369c1c1e Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 24 Jan 2024 16:06:14 +0100 Subject: [PATCH 014/263] Docs: mark up the FTP_TLS() docs with param list (#114510) Also turn sentence about prot_p() into a note. --- Doc/library/ftplib.rst | 56 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/Doc/library/ftplib.rst b/Doc/library/ftplib.rst index e93a1e85598e3a7..2f98a272c297ae3 100644 --- a/Doc/library/ftplib.rst +++ b/Doc/library/ftplib.rst @@ -78,6 +78,9 @@ FTP objects A 2-tuple ``(host, port)`` for the socket to bind to as its source address before connecting. +.. |param_doc_encoding| replace:: + The encoding for directories and filenames (default: ``'utf-8'``). + .. class:: FTP(host='', user='', passwd='', acct='', timeout=None, \ source_address=None, *, encoding='utf-8') @@ -108,8 +111,7 @@ FTP objects :type source_address: tuple | None :param str encoding: - The *encoding* parameter specifies the encoding - for directories and filenames. + |param_doc_encoding| The :class:`FTP` class supports the :keyword:`with` statement, e.g.: @@ -447,19 +449,53 @@ FTP_TLS objects .. class:: FTP_TLS(host='', user='', passwd='', acct='', *, context=None, \ timeout=None, source_address=None, encoding='utf-8') - A :class:`FTP` subclass which adds TLS support to FTP as described in + An :class:`FTP` subclass which adds TLS support to FTP as described in :rfc:`4217`. - Connect as usual to port 21 implicitly securing the FTP control connection - before authenticating. Securing the data connection requires the user to - explicitly ask for it by calling the :meth:`prot_p` method. *context* - is a :class:`ssl.SSLContext` object which allows bundling SSL configuration - options, certificates and private keys into a single (potentially - long-lived) structure. Please read :ref:`ssl-security` for best practices. + Connect to port 21 implicitly securing the FTP control connection + before authenticating. + + .. note:: + The user must explicitly secure the data connection + by calling the :meth:`prot_p` method. + + :param str host: + The hostname to connect to. + If given, :code:`connect(host)` is implicitly called by the constructor. + + :param str user: + |param_doc_user| + If given, :code:`login(host, passwd, acct)` is implicitly called + by the constructor. + + :param str passwd: + |param_doc_passwd| + + :param str acct: + |param_doc_acct| + + :param context: + An SSL context object which allows bundling SSL configuration options, + certificates and private keys into a single, potentially long-lived, + structure. + Please read :ref:`ssl-security` for best practices. + :type context: :class:`ssl.SSLContext` + + :param timeout: + A timeout in seconds for blocking operations like :meth:`~FTP.connect` + (default: the global default timeout setting). + :type timeout: int | None + + :param source_address: + |param_doc_source_address| + :type source_address: tuple | None + + :param str encoding: + |param_doc_encoding| .. versionadded:: 3.2 .. versionchanged:: 3.3 - *source_address* parameter was added. + Added the *source_address* parameter. .. versionchanged:: 3.4 The class now supports hostname check with From 981d172f7f0613d30bef4a8934b361db7fcf0672 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 24 Jan 2024 15:10:17 +0000 Subject: [PATCH 015/263] GH-112354: `END_FOR` instruction to only pop one value. (GH-114247) * Compiler emits END_FOR; POP_TOP instead of END_FOR. To support tier 2 side exits in loops. --- Doc/library/dis.rst | 4 +- .../pycore_global_objects_fini_generated.h | 6 + Include/internal/pycore_global_strings.h | 6 + Include/internal/pycore_opcode_metadata.h | 6 +- .../internal/pycore_runtime_init_generated.h | 6 + .../internal/pycore_unicodeobject_generated.h | 18 ++ Lib/importlib/_bootstrap_external.py | 3 +- Lib/test/test_compiler_codegen.py | 1 + Lib/test/test_dis.py | 236 +++++++++--------- ...-01-17-05-09-32.gh-issue-112354.Run9ko.rst | 2 + Programs/test_frozenmain.h | 50 ++-- Python/bytecodes.c | 24 +- Python/compile.c | 10 + Python/generated_cases.c.h | 35 +-- Python/optimizer.c | 7 +- Tools/build/generate_global_objects.py | 8 + 16 files changed, 238 insertions(+), 184 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index b97d48fafab3b6c..e654760fb91c650 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -546,8 +546,8 @@ operations on it as if it was a Python list. The top of the stack corresponds to .. opcode:: END_FOR - Removes the top two values from the stack. - Equivalent to ``POP_TOP``; ``POP_TOP``. + Removes the top-of-stack item. + Equivalent to ``POP_TOP``. Used to clean up at the end of loops, hence the name. .. versionadded:: 3.12 diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 0a24b127192c9b8..e92707051c12b7f 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -787,9 +787,11 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_child)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_parent)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aggregate_class)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(append)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(argdefs)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(args)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(arguments)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(argv)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(as_integer_ratio)); @@ -913,6 +915,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(errors)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(event)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(eventmask)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_type)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exc_value)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(excepthook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(exception)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(existing_file_name)); @@ -1166,6 +1170,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seek)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seekable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(selectors)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(self)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(send)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sep)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sequence)); @@ -1228,6 +1233,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(timetuple)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(top)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(trace_callback)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(traceback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(trailers)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(translate)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(true)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index efb659c5806e6e7..eb60b80c964d423 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -276,9 +276,11 @@ struct _Py_global_strings { STRUCT_FOR_ID(after_in_child) STRUCT_FOR_ID(after_in_parent) STRUCT_FOR_ID(aggregate_class) + STRUCT_FOR_ID(alias) STRUCT_FOR_ID(allow_code) STRUCT_FOR_ID(append) STRUCT_FOR_ID(argdefs) + STRUCT_FOR_ID(args) STRUCT_FOR_ID(arguments) STRUCT_FOR_ID(argv) STRUCT_FOR_ID(as_integer_ratio) @@ -402,6 +404,8 @@ struct _Py_global_strings { STRUCT_FOR_ID(errors) STRUCT_FOR_ID(event) STRUCT_FOR_ID(eventmask) + STRUCT_FOR_ID(exc_type) + STRUCT_FOR_ID(exc_value) STRUCT_FOR_ID(excepthook) STRUCT_FOR_ID(exception) STRUCT_FOR_ID(existing_file_name) @@ -655,6 +659,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(seek) STRUCT_FOR_ID(seekable) STRUCT_FOR_ID(selectors) + STRUCT_FOR_ID(self) STRUCT_FOR_ID(send) STRUCT_FOR_ID(sep) STRUCT_FOR_ID(sequence) @@ -717,6 +722,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(timetuple) STRUCT_FOR_ID(top) STRUCT_FOR_ID(trace_callback) + STRUCT_FOR_ID(traceback) STRUCT_FOR_ID(trailers) STRUCT_FOR_ID(translate) STRUCT_FOR_ID(true) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index fbb448f663369a4..75d7f44025328ee 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -176,7 +176,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) { case END_ASYNC_FOR: return 2; case END_FOR: - return 2; + return 1; case END_SEND: return 2; case ENTER_EXECUTOR: @@ -647,7 +647,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case INSTRUMENTED_CALL_KW: return 0; case INSTRUMENTED_END_FOR: - return 0; + return 1; case INSTRUMENTED_END_SEND: return 1; case INSTRUMENTED_FOR_ITER: @@ -1232,7 +1232,7 @@ _PyOpcode_macro_expansion[256] = { [DELETE_SUBSCR] = { .nuops = 1, .uops = { { _DELETE_SUBSCR, 0, 0 } } }, [DICT_MERGE] = { .nuops = 1, .uops = { { _DICT_MERGE, 0, 0 } } }, [DICT_UPDATE] = { .nuops = 1, .uops = { { _DICT_UPDATE, 0, 0 } } }, - [END_FOR] = { .nuops = 2, .uops = { { _POP_TOP, 0, 0 }, { _POP_TOP, 0, 0 } } }, + [END_FOR] = { .nuops = 1, .uops = { { _POP_TOP, 0, 0 } } }, [END_SEND] = { .nuops = 1, .uops = { { _END_SEND, 0, 0 } } }, [EXIT_INIT_CHECK] = { .nuops = 1, .uops = { { _EXIT_INIT_CHECK, 0, 0 } } }, [FORMAT_SIMPLE] = { .nuops = 1, .uops = { { _FORMAT_SIMPLE, 0, 0 } } }, diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index e3ebd80745e6103..9b39de1d69c6c7f 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -785,9 +785,11 @@ extern "C" { INIT_ID(after_in_child), \ INIT_ID(after_in_parent), \ INIT_ID(aggregate_class), \ + INIT_ID(alias), \ INIT_ID(allow_code), \ INIT_ID(append), \ INIT_ID(argdefs), \ + INIT_ID(args), \ INIT_ID(arguments), \ INIT_ID(argv), \ INIT_ID(as_integer_ratio), \ @@ -911,6 +913,8 @@ extern "C" { INIT_ID(errors), \ INIT_ID(event), \ INIT_ID(eventmask), \ + INIT_ID(exc_type), \ + INIT_ID(exc_value), \ INIT_ID(excepthook), \ INIT_ID(exception), \ INIT_ID(existing_file_name), \ @@ -1164,6 +1168,7 @@ extern "C" { INIT_ID(seek), \ INIT_ID(seekable), \ INIT_ID(selectors), \ + INIT_ID(self), \ INIT_ID(send), \ INIT_ID(sep), \ INIT_ID(sequence), \ @@ -1226,6 +1231,7 @@ extern "C" { INIT_ID(timetuple), \ INIT_ID(top), \ INIT_ID(trace_callback), \ + INIT_ID(traceback), \ INIT_ID(trailers), \ INIT_ID(translate), \ INIT_ID(true), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 9fa6c896c1a3285..898d386f4cfd05d 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -669,6 +669,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(aggregate_class); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(alias); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(allow_code); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -678,6 +681,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(argdefs); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(args); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(arguments); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1047,6 +1053,12 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(eventmask); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(exc_type); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(exc_value); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(excepthook); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1806,6 +1818,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(selectors); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(self); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(send); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1992,6 +2007,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(trace_callback); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(traceback); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(trailers); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index a4d2b7e01844092..2a9aef03179f6fb 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -464,6 +464,7 @@ def _write_atomic(path, data, mode=0o666): # Python 3.13a1 3565 (Oparg of YIELD_VALUE indicates whether it is in a yield-from) # Python 3.13a1 3566 (Emit JUMP_NO_INTERRUPT instead of JUMP for non-loop no-lineno cases) # Python 3.13a1 3567 (Reimplement line number propagation by the compiler) +# Python 3.13a1 3568 (Change semantics of END_FOR) # Python 3.14 will start with 3600 @@ -480,7 +481,7 @@ def _write_atomic(path, data, mode=0o666): # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3567).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3568).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index b5d1e2f9e4752cd..dbeadd9ca47c63d 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -49,6 +49,7 @@ def test_for_loop(self): ('JUMP', loop_lbl), exit_lbl, ('END_FOR', None), + ('POP_TOP', None), ('LOAD_CONST', 0), ('RETURN_VALUE', None), ] diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 3ae81b2f5d62b06..a5917da346dded5 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -158,6 +158,7 @@ def bug708901(): %3d JUMP_BACKWARD 5 (to L1) %3d L2: END_FOR + POP_TOP RETURN_CONST 0 (None) """ % (bug708901.__code__.co_firstlineno, bug708901.__code__.co_firstlineno + 1, @@ -791,6 +792,7 @@ def foo(x): POP_TOP JUMP_BACKWARD 12 (to L2) L3: END_FOR + POP_TOP RETURN_CONST 0 (None) -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) @@ -843,6 +845,7 @@ def loop_test(): JUMP_BACKWARD 16 (to L1) %3d L2: END_FOR + POP_TOP RETURN_CONST 0 (None) """ % (loop_test.__code__.co_firstlineno, loop_test.__code__.co_firstlineno + 1, @@ -1648,122 +1651,123 @@ def _prepare_test_cases(): ] expected_opinfo_jumpy = [ - Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=10, argrepr='10', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='GET_ITER', opcode=19, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='FOR_ITER', opcode=72, arg=30, argval=88, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, positions=None), - Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=54, start_offset=54, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=68, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=22, argval=24, argrepr='to L1', offset=64, start_offset=64, starts_line=True, line_number=6, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=68, start_offset=68, starts_line=True, line_number=7, label=2, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=70, start_offset=70, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=72, start_offset=72, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=2, argval=84, argrepr='to L3', offset=76, start_offset=76, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=30, argval=24, argrepr='to L1', offset=80, start_offset=80, starts_line=False, line_number=7, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=84, start_offset=84, starts_line=True, line_number=8, label=3, positions=None), - Instruction(opname='JUMP_FORWARD', opcode=79, arg=12, argval=112, argrepr='to L5', offset=86, start_offset=86, starts_line=False, line_number=8, label=None, positions=None), - Instruction(opname='END_FOR', opcode=11, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=90, start_offset=90, starts_line=True, line_number=10, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=100, start_offset=100, starts_line=False, line_number=10, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=110, start_offset=110, starts_line=False, line_number=10, label=None, positions=None), - Instruction(opname='LOAD_FAST_CHECK', opcode=87, arg=0, argval='i', argrepr='i', offset=112, start_offset=112, starts_line=True, line_number=11, label=5, positions=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=114, start_offset=114, starts_line=False, line_number=11, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=40, argval=206, argrepr='to L9', offset=122, start_offset=122, starts_line=False, line_number=11, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=126, start_offset=126, starts_line=True, line_number=12, label=6, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=136, start_offset=136, starts_line=False, line_number=12, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=138, start_offset=138, starts_line=False, line_number=12, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=146, start_offset=146, starts_line=False, line_number=12, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=148, start_offset=148, starts_line=True, line_number=13, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=150, start_offset=150, starts_line=False, line_number=13, label=None, positions=None), - Instruction(opname='BINARY_OP', opcode=45, arg=23, argval=23, argrepr='-=', offset=152, start_offset=152, starts_line=False, line_number=13, label=None, positions=None), - Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=156, start_offset=156, starts_line=False, line_number=13, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=True, line_number=14, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=160, start_offset=160, starts_line=False, line_number=14, label=None, positions=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=162, start_offset=162, starts_line=False, line_number=14, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=174, argrepr='to L7', offset=166, start_offset=166, starts_line=False, line_number=14, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=31, argval=112, argrepr='to L5', offset=170, start_offset=170, starts_line=True, line_number=15, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=174, start_offset=174, starts_line=True, line_number=16, label=7, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=176, start_offset=176, starts_line=False, line_number=16, label=None, positions=None), - Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=178, start_offset=178, starts_line=False, line_number=16, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=1, argval=188, argrepr='to L8', offset=182, start_offset=182, starts_line=False, line_number=16, label=None, positions=None), - Instruction(opname='JUMP_FORWARD', opcode=79, arg=20, argval=228, argrepr='to L10', offset=186, start_offset=186, starts_line=True, line_number=17, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=188, start_offset=188, starts_line=True, line_number=11, label=8, positions=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=190, start_offset=190, starts_line=False, line_number=11, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=206, argrepr='to L9', offset=198, start_offset=198, starts_line=False, line_number=11, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD', opcode=77, arg=40, argval=126, argrepr='to L6', offset=202, start_offset=202, starts_line=False, line_number=11, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=206, start_offset=206, starts_line=True, line_number=19, label=9, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=216, start_offset=216, starts_line=False, line_number=19, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=218, start_offset=218, starts_line=False, line_number=19, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=226, start_offset=226, starts_line=False, line_number=19, label=None, positions=None), - Instruction(opname='NOP', opcode=30, arg=None, argval=None, argrepr='', offset=228, start_offset=228, starts_line=True, line_number=20, label=10, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=230, start_offset=230, starts_line=True, line_number=21, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=7, argval=0, argrepr='0', offset=232, start_offset=232, starts_line=False, line_number=21, label=None, positions=None), - Instruction(opname='BINARY_OP', opcode=45, arg=11, argval=11, argrepr='/', offset=234, start_offset=234, starts_line=False, line_number=21, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=238, start_offset=238, starts_line=False, line_number=21, label=None, positions=None), - Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=240, start_offset=240, starts_line=True, line_number=25, label=None, positions=None), - Instruction(opname='BEFORE_WITH', opcode=2, arg=None, argval=None, argrepr='', offset=242, start_offset=242, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='STORE_FAST', opcode=110, arg=1, argval='dodgy', argrepr='dodgy', offset=244, start_offset=244, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=246, start_offset=246, starts_line=True, line_number=26, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=256, start_offset=256, starts_line=False, line_number=26, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=258, start_offset=258, starts_line=False, line_number=26, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=266, start_offset=266, starts_line=False, line_number=26, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=268, start_offset=268, starts_line=True, line_number=25, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=270, start_offset=270, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=2, argval=2, argrepr='', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=282, start_offset=282, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=284, start_offset=284, starts_line=True, line_number=28, label=11, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=294, start_offset=294, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=296, start_offset=296, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=304, start_offset=304, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=308, start_offset=308, starts_line=True, line_number=25, label=None, positions=None), - Instruction(opname='WITH_EXCEPT_START', opcode=44, arg=None, argval=None, argrepr='', offset=310, start_offset=310, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=1, argval=326, argrepr='to L12', offset=320, start_offset=320, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=2, argval=2, argrepr='', offset=324, start_offset=324, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=25, label=12, positions=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=328, start_offset=328, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=330, start_offset=330, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=26, argval=284, argrepr='to L11', offset=334, start_offset=334, starts_line=False, line_number=25, label=None, positions=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=336, start_offset=336, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=338, start_offset=338, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=344, start_offset=344, starts_line=True, line_number=22, label=None, positions=None), - Instruction(opname='CHECK_EXC_MATCH', opcode=7, arg=None, argval=None, argrepr='', offset=354, start_offset=354, starts_line=False, line_number=22, label=None, positions=None), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=14, argval=388, argrepr='to L13', offset=356, start_offset=356, starts_line=False, line_number=22, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=360, start_offset=360, starts_line=False, line_number=22, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=362, start_offset=362, starts_line=True, line_number=23, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=372, start_offset=372, starts_line=False, line_number=23, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=374, start_offset=374, starts_line=False, line_number=23, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=382, start_offset=382, starts_line=False, line_number=23, label=None, positions=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=23, label=None, positions=None), - Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=52, argval=284, argrepr='to L11', offset=386, start_offset=386, starts_line=False, line_number=23, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=388, start_offset=388, starts_line=True, line_number=22, label=13, positions=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=390, start_offset=390, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=392, start_offset=392, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=394, start_offset=394, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=398, start_offset=398, starts_line=True, line_number=28, label=None, positions=None), - Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=408, start_offset=408, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=410, start_offset=410, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=418, start_offset=418, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=28, label=None, positions=None), - Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=422, start_offset=422, starts_line=True, line_number=None, label=None, positions=None), - Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=424, start_offset=424, starts_line=False, line_number=None, label=None, positions=None), - Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=None, label=None, positions=None), + Instruction(opname='RESUME', opcode=149, arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=1, argval=10, argrepr='10', offset=12, start_offset=12, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='GET_ITER', opcode=19, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='FOR_ITER', opcode=72, arg=30, argval=88, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=54, start_offset=54, starts_line=False, line_number=5, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=68, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=22, argval=24, argrepr='to L1', offset=64, start_offset=64, starts_line=True, line_number=6, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=68, start_offset=68, starts_line=True, line_number=7, label=2, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=70, start_offset=70, starts_line=False, line_number=7, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=72, start_offset=72, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=2, argval=84, argrepr='to L3', offset=76, start_offset=76, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=30, argval=24, argrepr='to L1', offset=80, start_offset=80, starts_line=False, line_number=7, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=84, start_offset=84, starts_line=True, line_number=8, label=3, positions=None, cache_info=None), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=13, argval=114, argrepr='to L5', offset=86, start_offset=86, starts_line=False, line_number=8, label=None, positions=None, cache_info=None), + Instruction(opname='END_FOR', opcode=11, arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=3, label=4, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=90, start_offset=90, starts_line=False, line_number=3, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=92, start_offset=92, starts_line=True, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=102, start_offset=102, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=104, start_offset=104, starts_line=False, line_number=10, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=112, start_offset=112, starts_line=False, line_number=10, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST_CHECK', opcode=87, arg=0, argval='i', argrepr='i', offset=114, start_offset=114, starts_line=True, line_number=11, label=5, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=116, start_offset=116, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=40, argval=208, argrepr='to L9', offset=124, start_offset=124, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=128, start_offset=128, starts_line=True, line_number=12, label=6, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=138, start_offset=138, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=140, start_offset=140, starts_line=False, line_number=12, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=148, start_offset=148, starts_line=False, line_number=12, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=150, start_offset=150, starts_line=True, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=152, start_offset=152, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=23, argval=23, argrepr='-=', offset=154, start_offset=154, starts_line=False, line_number=13, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='STORE_FAST', opcode=110, arg=0, argval='i', argrepr='i', offset=158, start_offset=158, starts_line=False, line_number=13, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=160, start_offset=160, starts_line=True, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=3, argval=6, argrepr='6', offset=162, start_offset=162, starts_line=False, line_number=14, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=148, argval='>', argrepr='bool(>)', offset=164, start_offset=164, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=176, argrepr='to L7', offset=168, start_offset=168, starts_line=False, line_number=14, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=31, argval=114, argrepr='to L5', offset=172, start_offset=172, starts_line=True, line_number=15, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=176, start_offset=176, starts_line=True, line_number=16, label=7, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=2, argval=4, argrepr='4', offset=178, start_offset=178, starts_line=False, line_number=16, label=None, positions=None, cache_info=None), + Instruction(opname='COMPARE_OP', opcode=58, arg=18, argval='<', argrepr='bool(<)', offset=180, start_offset=180, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=1, argval=190, argrepr='to L8', offset=184, start_offset=184, starts_line=False, line_number=16, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_FORWARD', opcode=79, arg=20, argval=230, argrepr='to L10', offset=188, start_offset=188, starts_line=True, line_number=17, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=190, start_offset=190, starts_line=True, line_number=11, label=8, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=192, start_offset=192, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=2, argval=208, argrepr='to L9', offset=200, start_offset=200, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='JUMP_BACKWARD', opcode=77, arg=40, argval=128, argrepr='to L6', offset=204, start_offset=204, starts_line=False, line_number=11, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=208, start_offset=208, starts_line=True, line_number=19, label=9, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=218, start_offset=218, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=220, start_offset=220, starts_line=False, line_number=19, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=228, start_offset=228, starts_line=False, line_number=19, label=None, positions=None, cache_info=None), + Instruction(opname='NOP', opcode=30, arg=None, argval=None, argrepr='', offset=230, start_offset=230, starts_line=True, line_number=20, label=10, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=5, argval=1, argrepr='1', offset=232, start_offset=232, starts_line=True, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=7, argval=0, argrepr='0', offset=234, start_offset=234, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='BINARY_OP', opcode=45, arg=11, argval=11, argrepr='/', offset=236, start_offset=236, starts_line=False, line_number=21, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=240, start_offset=240, starts_line=False, line_number=21, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_FAST', opcode=85, arg=0, argval='i', argrepr='i', offset=242, start_offset=242, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='BEFORE_WITH', opcode=2, arg=None, argval=None, argrepr='', offset=244, start_offset=244, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='STORE_FAST', opcode=110, arg=1, argval='dodgy', argrepr='dodgy', offset=246, start_offset=246, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=248, start_offset=248, starts_line=True, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=8, argval='Never reach this', argrepr="'Never reach this'", offset=258, start_offset=258, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=26, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=26, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=270, start_offset=270, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=272, start_offset=272, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_CONST', opcode=83, arg=0, argval=None, argrepr='None', offset=274, start_offset=274, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=2, argval=2, argrepr='', offset=276, start_offset=276, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=284, start_offset=284, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=286, start_offset=286, starts_line=True, line_number=28, label=11, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=296, start_offset=296, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=298, start_offset=298, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=306, start_offset=306, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RETURN_CONST', opcode=103, arg=0, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=310, start_offset=310, starts_line=True, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='WITH_EXCEPT_START', opcode=44, arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='TO_BOOL', opcode=40, arg=None, argval=None, argrepr='', offset=314, start_offset=314, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=100, arg=1, argval=328, argrepr='to L12', offset=322, start_offset=322, starts_line=False, line_number=25, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='RERAISE', opcode=102, arg=2, argval=2, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=328, start_offset=328, starts_line=False, line_number=25, label=12, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=330, start_offset=330, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=334, start_offset=334, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=26, argval=286, argrepr='to L11', offset=336, start_offset=336, starts_line=False, line_number=25, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=338, start_offset=338, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=346, start_offset=346, starts_line=True, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='CHECK_EXC_MATCH', opcode=7, arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=97, arg=14, argval=390, argrepr='to L13', offset=358, start_offset=358, starts_line=False, line_number=22, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=22, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=364, start_offset=364, starts_line=True, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=9, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=374, start_offset=374, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=23, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=386, start_offset=386, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='JUMP_BACKWARD_NO_INTERRUPT', opcode=78, arg=52, argval=286, argrepr='to L11', offset=388, start_offset=388, starts_line=False, line_number=23, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=390, start_offset=390, starts_line=True, line_number=22, label=13, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=392, start_offset=392, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=394, start_offset=394, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='PUSH_EXC_INFO', opcode=33, arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='LOAD_GLOBAL', opcode=91, arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, starts_line=True, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + Instruction(opname='LOAD_CONST', opcode=83, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=410, start_offset=410, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='CALL', opcode=53, arg=1, argval=1, argrepr='', offset=412, start_offset=412, starts_line=False, line_number=28, label=None, positions=None, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + Instruction(opname='POP_TOP', opcode=32, arg=None, argval=None, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=0, argval=0, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=28, label=None, positions=None, cache_info=None), + Instruction(opname='COPY', opcode=61, arg=3, argval=3, argrepr='', offset=424, start_offset=424, starts_line=True, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='POP_EXCEPT', opcode=31, arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), + Instruction(opname='RERAISE', opcode=102, arg=1, argval=1, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=None, label=None, positions=None, cache_info=None), ] # One last piece of inspect fodder to check the default line number handling diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst new file mode 100644 index 000000000000000..ed45ba49c3ad421 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-17-05-09-32.gh-issue-112354.Run9ko.rst @@ -0,0 +1,2 @@ +The ``END_FOR`` instruction now pops only one value. This is to better +support side exits in loops. diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index 4fb78cf632d70ef..657e9345cf5ab7d 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -1,7 +1,7 @@ // Auto-generated by Programs/freeze_test_frozenmain.py unsigned char M_test_frozenmain[] = { 227,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0, - 0,0,0,0,0,243,164,0,0,0,149,0,83,0,83,1, + 0,0,0,0,0,243,166,0,0,0,149,0,83,0,83,1, 75,0,114,0,83,0,83,1,75,1,114,1,92,2,34,0, 83,2,53,1,0,0,0,0,0,0,32,0,92,2,34,0, 83,3,92,0,82,6,0,0,0,0,0,0,0,0,0,0, @@ -11,28 +11,28 @@ unsigned char M_test_frozenmain[] = { 0,0,83,4,5,0,0,0,114,5,83,5,19,0,72,20, 0,0,114,6,92,2,34,0,83,6,92,6,14,0,83,7, 92,5,92,6,5,0,0,0,14,0,51,4,53,1,0,0, - 0,0,0,0,32,0,77,22,0,0,11,0,103,1,41,8, - 233,0,0,0,0,78,122,18,70,114,111,122,101,110,32,72, - 101,108,108,111,32,87,111,114,108,100,122,8,115,121,115,46, - 97,114,103,118,218,6,99,111,110,102,105,103,41,5,218,12, - 112,114,111,103,114,97,109,95,110,97,109,101,218,10,101,120, - 101,99,117,116,97,98,108,101,218,15,117,115,101,95,101,110, - 118,105,114,111,110,109,101,110,116,218,17,99,111,110,102,105, - 103,117,114,101,95,99,95,115,116,100,105,111,218,14,98,117, - 102,102,101,114,101,100,95,115,116,100,105,111,122,7,99,111, - 110,102,105,103,32,122,2,58,32,41,7,218,3,115,121,115, - 218,17,95,116,101,115,116,105,110,116,101,114,110,97,108,99, - 97,112,105,218,5,112,114,105,110,116,218,4,97,114,103,118, - 218,11,103,101,116,95,99,111,110,102,105,103,115,114,3,0, - 0,0,218,3,107,101,121,169,0,243,0,0,0,0,250,18, - 116,101,115,116,95,102,114,111,122,101,110,109,97,105,110,46, - 112,121,250,8,60,109,111,100,117,108,101,62,114,18,0,0, - 0,1,0,0,0,115,99,0,0,0,240,3,1,1,1,243, - 8,0,1,11,219,0,24,225,0,5,208,6,26,212,0,27, - 217,0,5,128,106,144,35,151,40,145,40,212,0,27,216,9, - 26,215,9,38,210,9,38,211,9,40,168,24,209,9,50,128, - 6,240,2,6,12,2,242,0,7,1,42,128,67,241,14,0, - 5,10,136,71,144,67,144,53,152,2,152,54,160,35,153,59, - 152,45,208,10,40,214,4,41,241,15,7,1,42,114,16,0, - 0,0, + 0,0,0,0,32,0,77,22,0,0,11,0,32,0,103,1, + 41,8,233,0,0,0,0,78,122,18,70,114,111,122,101,110, + 32,72,101,108,108,111,32,87,111,114,108,100,122,8,115,121, + 115,46,97,114,103,118,218,6,99,111,110,102,105,103,41,5, + 218,12,112,114,111,103,114,97,109,95,110,97,109,101,218,10, + 101,120,101,99,117,116,97,98,108,101,218,15,117,115,101,95, + 101,110,118,105,114,111,110,109,101,110,116,218,17,99,111,110, + 102,105,103,117,114,101,95,99,95,115,116,100,105,111,218,14, + 98,117,102,102,101,114,101,100,95,115,116,100,105,111,122,7, + 99,111,110,102,105,103,32,122,2,58,32,41,7,218,3,115, + 121,115,218,17,95,116,101,115,116,105,110,116,101,114,110,97, + 108,99,97,112,105,218,5,112,114,105,110,116,218,4,97,114, + 103,118,218,11,103,101,116,95,99,111,110,102,105,103,115,114, + 3,0,0,0,218,3,107,101,121,169,0,243,0,0,0,0, + 250,18,116,101,115,116,95,102,114,111,122,101,110,109,97,105, + 110,46,112,121,250,8,60,109,111,100,117,108,101,62,114,18, + 0,0,0,1,0,0,0,115,99,0,0,0,240,3,1,1, + 1,243,8,0,1,11,219,0,24,225,0,5,208,6,26,212, + 0,27,217,0,5,128,106,144,35,151,40,145,40,212,0,27, + 216,9,26,215,9,38,210,9,38,211,9,40,168,24,209,9, + 50,128,6,240,2,6,12,2,242,0,7,1,42,128,67,241, + 14,0,5,10,136,71,144,67,144,53,152,2,152,54,160,35, + 153,59,152,45,208,10,40,214,4,41,242,15,7,1,42,114, + 16,0,0,0, }; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 18749ce60ecd45b..fef3cd4ff7d7810 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -265,9 +265,9 @@ dummy_func( res = NULL; } - macro(END_FOR) = POP_TOP + POP_TOP; + macro(END_FOR) = POP_TOP; - inst(INSTRUMENTED_END_FOR, (receiver, value --)) { + inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) { TIER_ONE_ONLY /* Need to create a fake StopIteration error here, * to conform to PEP 380 */ @@ -2550,8 +2550,8 @@ dummy_func( next_instr[oparg].op.code == INSTRUMENTED_END_FOR); Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instruction */ + JUMPBY(oparg + 2); DISPATCH(); } // Common case: no jump, leave it to the code generator @@ -2599,8 +2599,8 @@ dummy_func( next_instr[oparg].op.code == INSTRUMENTED_END_FOR); STACK_SHRINK(1); Py_DECREF(iter); - /* Skip END_FOR */ - target = next_instr + oparg + 1; + /* Skip END_FOR and POP_TOP */ + target = next_instr + oparg + 2; } INSTRUMENTED_JUMP(this_instr, target, PY_MONITORING_EVENT_BRANCH); } @@ -2621,8 +2621,8 @@ dummy_func( } Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ + JUMPBY(oparg + 2); DISPATCH(); } } @@ -2667,8 +2667,8 @@ dummy_func( } Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ + JUMPBY(oparg + 2); DISPATCH(); } } @@ -2709,8 +2709,8 @@ dummy_func( if (r->len <= 0) { STACK_SHRINK(1); Py_DECREF(r); - // Jump over END_FOR instruction. - JUMPBY(oparg + 1); + // Jump over END_FOR and POP_TOP instructions. + JUMPBY(oparg + 2); DISPATCH(); } } diff --git a/Python/compile.c b/Python/compile.c index 2a6291ccb51b0c9..7cf05dd06831195 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -3075,7 +3075,12 @@ compiler_for(struct compiler *c, stmt_ty s) ADDOP_JUMP(c, NO_LOCATION, JUMP, start); USE_LABEL(c, cleanup); + /* It is important for instrumentation that the `END_FOR` comes first. + * Iteration over a generator will jump to the first of these instructions, + * but a non-generator will jump to a later instruction. + */ ADDOP(c, NO_LOCATION, END_FOR); + ADDOP(c, NO_LOCATION, POP_TOP); compiler_pop_fblock(c, FOR_LOOP, start); @@ -5390,7 +5395,12 @@ compiler_sync_comprehension_generator(struct compiler *c, location loc, ADDOP_JUMP(c, elt_loc, JUMP, start); USE_LABEL(c, anchor); + /* It is important for instrumentation that the `END_FOR` comes first. + * Iteration over a generator will jump to the first of these instructions, + * but a non-generator will jump to a later instruction. + */ ADDOP(c, NO_LOCATION, END_FOR); + ADDOP(c, NO_LOCATION, POP_TOP); } return SUCCESS; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index c4bb3aeec5e2246..16f1db30620d722 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2342,17 +2342,9 @@ next_instr += 1; INSTRUCTION_STATS(END_FOR); PyObject *value; - // _POP_TOP value = stack_pointer[-1]; - { - Py_DECREF(value); - } - // _POP_TOP - value = stack_pointer[-2]; - { - Py_DECREF(value); - } - stack_pointer += -2; + Py_DECREF(value); + stack_pointer += -1; DISPATCH(); } @@ -2505,8 +2497,8 @@ next_instr[oparg].op.code == INSTRUMENTED_END_FOR); Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instruction */ + JUMPBY(oparg + 2); DISPATCH(); } // Common case: no jump, leave it to the code generator @@ -2567,8 +2559,8 @@ } Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ + JUMPBY(oparg + 2); DISPATCH(); } } @@ -2608,8 +2600,8 @@ if (r->len <= 0) { STACK_SHRINK(1); Py_DECREF(r); - // Jump over END_FOR instruction. - JUMPBY(oparg + 1); + // Jump over END_FOR and POP_TOP instructions. + JUMPBY(oparg + 2); DISPATCH(); } } @@ -2655,8 +2647,8 @@ } Py_DECREF(iter); STACK_SHRINK(1); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); + /* Jump forward oparg, then skip following END_FOR and POP_TOP instructions */ + JUMPBY(oparg + 2); DISPATCH(); } } @@ -2952,9 +2944,8 @@ } PyErr_SetRaisedException(NULL); } - Py_DECREF(receiver); Py_DECREF(value); - stack_pointer += -2; + stack_pointer += -1; DISPATCH(); } @@ -3005,8 +2996,8 @@ next_instr[oparg].op.code == INSTRUMENTED_END_FOR); STACK_SHRINK(1); Py_DECREF(iter); - /* Skip END_FOR */ - target = next_instr + oparg + 1; + /* Skip END_FOR and POP_TOP */ + target = next_instr + oparg + 2; } INSTRUMENTED_JUMP(this_instr, target, PY_MONITORING_EVENT_BRANCH); DISPATCH(); diff --git a/Python/optimizer.c b/Python/optimizer.c index 4b6ed1781b5b782..db615068ff517f6 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -572,9 +572,10 @@ translate_bytecode_to_trace( uop = _PyUOp_Replacements[uop]; assert(uop != 0); if (uop == _FOR_ITER_TIER_TWO) { - target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1 + extended; - assert(_PyCode_CODE(code)[target-1].op.code == END_FOR || - _PyCode_CODE(code)[target-1].op.code == INSTRUMENTED_END_FOR); + target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 2 + extended; + assert(_PyCode_CODE(code)[target-2].op.code == END_FOR || + _PyCode_CODE(code)[target-2].op.code == INSTRUMENTED_END_FOR); + assert(_PyCode_CODE(code)[target-1].op.code == POP_TOP); } break; default: diff --git a/Tools/build/generate_global_objects.py b/Tools/build/generate_global_objects.py index ded19ee489e79b6..33d1b323fc17534 100644 --- a/Tools/build/generate_global_objects.py +++ b/Tools/build/generate_global_objects.py @@ -123,6 +123,14 @@ '__rdivmod__', '__buffer__', '__release_buffer__', + + #Workarounds for GH-108918 + 'alias', + 'args', + 'exc_type', + 'exc_value', + 'self', + 'traceback', ] NON_GENERATED_IMMORTAL_OBJECTS = [ From 191531f352ce387a2d3a61544fb6feefab754d4a Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Wed, 24 Jan 2024 20:14:15 +0300 Subject: [PATCH 016/263] Update outdated comment in ``Python/bytecodes.c`` (#114522) --- Python/bytecodes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fef3cd4ff7d7810..ebd5b06abb2d4e4 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1,6 +1,6 @@ // This file contains instruction definitions. -// It is read by Tools/cases_generator/generate_cases.py -// to generate Python/generated_cases.c.h. +// It is read by generators stored in Tools/cases_generator/ +// to generate Python/generated_cases.c.h and others. // Note that there is some dummy C code at the top and bottom of the file // to fool text editors like VS Code into believing this is valid C code. // The actual instruction definitions start at // BEGIN BYTECODES //. From 6888cccac0776d965cc38a7240e1bdbacb952b91 Mon Sep 17 00:00:00 2001 From: plokmijnuhby <39633434+plokmijnuhby@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:58:34 +0000 Subject: [PATCH 017/263] gh-108731: Add description of __slots__ to MemberDescriptorType docs (GH-108745) --- Doc/library/types.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 8ce67cf77253c30..c8c981024c1aeb1 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -398,6 +398,10 @@ Standard names are defined for the following types: data members which use standard conversion functions; it has the same purpose as the :class:`property` type, but for classes defined in extension modules. + In addition, when a class is defined with a :attr:`~object.__slots__` attribute, then for + each slot, an instance of :class:`!MemberDescriptorType` will be added as an attribute + on the class. This allows the slot to appear in the class's :attr:`~object.__dict__`. + .. impl-detail:: In other implementations of Python, this type may be identical to From d5c21c12c17b6e4db2378755af8e3699516da187 Mon Sep 17 00:00:00 2001 From: Vincent Cunningham Date: Wed, 24 Jan 2024 16:23:28 -0800 Subject: [PATCH 018/263] gh-100107: Make py.exe launcher ignore app aliases that launch Microsoft Store (GH-114358) --- ...-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst | 1 + PC/launcher2.c | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst diff --git a/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst b/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst new file mode 100644 index 000000000000000..388d61a2b3bd6db --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-01-23-00-05-05.gh-issue-100107.lkbP_Q.rst @@ -0,0 +1 @@ +The ``py.exe`` launcher will no longer attempt to run the Microsoft Store redirector when launching a script containing a ``/usr/bin/env`` shebang diff --git a/PC/launcher2.c b/PC/launcher2.c index 2a8f8a101fc8a61..e426eccd7000447 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -572,6 +572,21 @@ findArgv0End(const wchar_t *buffer, int bufferLength) *** COMMAND-LINE PARSING *** \******************************************************************************/ +// Adapted from https://stackoverflow.com/a/65583702 +typedef struct AppExecLinkFile { // For tag IO_REPARSE_TAG_APPEXECLINK + DWORD reparseTag; + WORD reparseDataLength; + WORD reserved; + ULONG version; + wchar_t stringList[MAX_PATH * 4]; // Multistring (Consecutive UTF-16 strings each ending with a NUL) + /* There are normally 4 strings here. Ex: + Package ID: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" + Entry Point: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector" + Executable: L"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.106910_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe" + Applic. Type: L"0" // Integer as ASCII. "0" = Desktop bridge application; Else sandboxed UWP application + */ +} AppExecLinkFile; + int parseCommandLine(SearchInfo *search) @@ -763,6 +778,55 @@ _shebangStartsWith(const wchar_t *buffer, int bufferLength, const wchar_t *prefi } +int +ensure_no_redirector_stub(wchar_t* filename, wchar_t* buffer) +{ + // Make sure we didn't find a reparse point that will open the Microsoft Store + // If we did, pretend there was no shebang and let normal handling take over + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(buffer, &findData); + if (!hFind) { + // Let normal handling take over + debug(L"# Did not find %s on PATH\n", filename); + return RC_NO_SHEBANG; + } + + FindClose(hFind); + + if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + findData.dwReserved0 & IO_REPARSE_TAG_APPEXECLINK)) { + return 0; + } + + HANDLE hReparsePoint = CreateFileW(buffer, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (!hReparsePoint) { + // Let normal handling take over + debug(L"# Did not find %s on PATH\n", filename); + return RC_NO_SHEBANG; + } + + AppExecLinkFile appExecLink; + + if (!DeviceIoControl(hReparsePoint, FSCTL_GET_REPARSE_POINT, NULL, 0, &appExecLink, sizeof(appExecLink), NULL, NULL)) { + // Let normal handling take over + debug(L"# Did not find %s on PATH\n", filename); + CloseHandle(hReparsePoint); + return RC_NO_SHEBANG; + } + + CloseHandle(hReparsePoint); + + const wchar_t* redirectorPackageId = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; + + if (0 == wcscmp(appExecLink.stringList, redirectorPackageId)) { + debug(L"# ignoring redirector that would launch store\n"); + return RC_NO_SHEBANG; + } + + return 0; +} + + int searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) { @@ -826,6 +890,11 @@ searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) return RC_BAD_VIRTUAL_PATH; } + int result = ensure_no_redirector_stub(filename, buffer); + if (result) { + return result; + } + // Check that we aren't going to call ourselves again // If we are, pretend there was no shebang and let normal handling take over if (GetModuleFileNameW(NULL, filename, MAXLEN) && From c63c6142f9146e1e977f4c824c56e8979e6aca87 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 25 Jan 2024 00:38:34 +0000 Subject: [PATCH 019/263] gh-114272: Fix or skip tests that fail due to spaces in paths (GH-114451) --- Lib/test/test_asyncio/test_subprocess.py | 7 ++- Lib/test/test_launcher.py | 55 +++++++++++++++--------- Lib/test/test_os.py | 7 ++- Lib/test/test_webbrowser.py | 1 + 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 859d2932c33fede..808b21c66175511 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -207,7 +207,7 @@ def test_kill(self): def test_kill_issue43884(self): if sys.platform == 'win32': - blocking_shell_command = f'{sys.executable} -c "import time; time.sleep(2)"' + blocking_shell_command = f'"{sys.executable}" -c "import time; time.sleep(2)"' else: blocking_shell_command = 'sleep 1; sleep 1' creationflags = 0 @@ -745,7 +745,10 @@ async def check_stdout_output(self, coro, output): def test_create_subprocess_env_shell(self) -> None: async def main() -> None: - cmd = f'''{sys.executable} -c "import os, sys; sys.stdout.write(os.getenv('FOO'))"''' + executable = sys.executable + if sys.platform == "win32": + executable = f'"{executable}"' + cmd = f'''{executable} -c "import os, sys; sys.stdout.write(os.getenv('FOO'))"''' env = os.environ.copy() env["FOO"] = "bar" proc = await asyncio.create_subprocess_shell( diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py index 3da6173cfd3f134..2528a51240fbf76 100644 --- a/Lib/test/test_launcher.py +++ b/Lib/test/test_launcher.py @@ -90,6 +90,12 @@ "test-command=TEST_EXE.exe", ]) + +def quote(s): + s = str(s) + return f'"{s}"' if " " in s else s + + def create_registry_data(root, data): def _create_registry_data(root, key, value): if isinstance(value, dict): @@ -542,10 +548,10 @@ def test_virtualenv_with_env(self): data1 = self.run_py([], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) data2 = self.run_py(["-V:PythonTestSuite/3"], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) # Compare stdout, because stderr goes via ascii - self.assertEqual(data1["stdout"].strip(), str(venv_exe)) + self.assertEqual(data1["stdout"].strip(), quote(venv_exe)) self.assertEqual(data1["SearchInfo.lowPriorityTag"], "True") # Ensure passing the argument doesn't trigger the same behaviour - self.assertNotEqual(data2["stdout"].strip(), str(venv_exe)) + self.assertNotEqual(data2["stdout"].strip(), quote(venv_exe)) self.assertNotEqual(data2["SearchInfo.lowPriorityTag"], "True") def test_py_shebang(self): @@ -554,7 +560,7 @@ def test_py_shebang(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip()) def test_python_shebang(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -562,7 +568,7 @@ def test_python_shebang(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip()) def test_py2_shebang(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -570,7 +576,8 @@ def test_py2_shebang(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-32.exe -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_py3_shebang(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -578,7 +585,8 @@ def test_py3_shebang(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_py_shebang_nl(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -586,7 +594,8 @@ def test_py_shebang_nl(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_py2_shebang_nl(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -594,7 +603,8 @@ def test_py2_shebang_nl(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100-32", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-32.exe -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_py3_shebang_nl(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -602,7 +612,8 @@ def test_py3_shebang_nl(self): data = self.run_py([script, "-postarg"]) self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) - self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_py_shebang_short_argv0(self): with self.py_ini(TEST_PY_DEFAULTS): @@ -630,7 +641,8 @@ def test_search_path(self): [script, "-postarg"], env={"PATH": f"{exe.parent};{os.getenv('PATH')}"}, ) - self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_search_path_exe(self): # Leave the .exe on the name to ensure we don't add it a second time @@ -643,7 +655,8 @@ def test_search_path_exe(self): [script, "-postarg"], env={"PATH": f"{exe.parent};{os.getenv('PATH')}"}, ) - self.assertEqual(f"{exe} -prearg {script} -postarg", data["stdout"].strip()) + self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg", + data["stdout"].strip()) def test_recursive_search_path(self): stem = self.get_py_exe().stem @@ -654,7 +667,7 @@ def test_recursive_search_path(self): env={"PATH": f"{self.get_py_exe().parent};{os.getenv('PATH')}"}, ) # The recursive search is ignored and we get normal "py" behavior - self.assertEqual(f"X.Y.exe {script}", data["stdout"].strip()) + self.assertEqual(f"X.Y.exe {quote(script)}", data["stdout"].strip()) def test_install(self): data = self.run_py(["-V:3.10"], env={"PYLAUNCHER_ALWAYS_INSTALL": "1"}, expect_returncode=111) @@ -674,7 +687,7 @@ def test_literal_shebang_absolute(self): with self.script("#! C:/some_random_app -witharg") as script: data = self.run_py([script]) self.assertEqual( - f"C:\\some_random_app -witharg {script}", + f"C:\\some_random_app -witharg {quote(script)}", data["stdout"].strip(), ) @@ -682,7 +695,7 @@ def test_literal_shebang_relative(self): with self.script("#! ..\\some_random_app -witharg") as script: data = self.run_py([script]) self.assertEqual( - f"{script.parent.parent}\\some_random_app -witharg {script}", + f"{quote(script.parent.parent / 'some_random_app')} -witharg {quote(script)}", data["stdout"].strip(), ) @@ -690,14 +703,14 @@ def test_literal_shebang_quoted(self): with self.script('#! "some random app" -witharg') as script: data = self.run_py([script]) self.assertEqual( - f'"{script.parent}\\some random app" -witharg {script}', + f"{quote(script.parent / 'some random app')} -witharg {quote(script)}", data["stdout"].strip(), ) with self.script('#! some" random "app -witharg') as script: data = self.run_py([script]) self.assertEqual( - f'"{script.parent}\\some random app" -witharg {script}', + f"{quote(script.parent / 'some random app')} -witharg {quote(script)}", data["stdout"].strip(), ) @@ -705,7 +718,7 @@ def test_literal_shebang_quoted_escape(self): with self.script('#! some\\" random "app -witharg') as script: data = self.run_py([script]) self.assertEqual( - f'"{script.parent}\\some\\ random app" -witharg {script}', + f"{quote(script.parent / 'some/ random app')} -witharg {quote(script)}", data["stdout"].strip(), ) @@ -714,7 +727,7 @@ def test_literal_shebang_command(self): with self.script('#! test-command arg1') as script: data = self.run_py([script]) self.assertEqual( - f"TEST_EXE.exe arg1 {script}", + f"TEST_EXE.exe arg1 {quote(script)}", data["stdout"].strip(), ) @@ -723,7 +736,7 @@ def test_literal_shebang_invalid_template(self): data = self.run_py([script]) expect = script.parent / "/usr/bin/not-python" self.assertEqual( - f"{expect} arg1 {script}", + f"{quote(expect)} arg1 {quote(script)}", data["stdout"].strip(), ) @@ -746,8 +759,8 @@ def test_shebang_command_in_venv(self): with self.script(f'#! /usr/bin/env {stem} arg1') as script: data = self.run_py([script], env=env) - self.assertEqual(data["stdout"].strip(), f"{venv_exe} arg1 {script}") + self.assertEqual(data["stdout"].strip(), f"{quote(venv_exe)} arg1 {quote(script)}") with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script: data = self.run_py([script], env=env) - self.assertEqual(data["stdout"].strip(), f"{exe} arg1 {script}") + self.assertEqual(data["stdout"].strip(), f"{quote(exe)} arg1 {quote(script)}") diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 98b30d2108a1a12..ed1f304c6c8cac7 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -4596,8 +4596,11 @@ def test_pipe_spawnl(self): with open(filename, "w") as fp: print(code, file=fp, end="") - cmd = [sys.executable, filename] - exitcode = os.spawnl(os.P_WAIT, cmd[0], *cmd) + executable = sys.executable + cmd = [executable, filename] + if os.name == "nt" and " " in cmd[0]: + cmd[0] = f'"{cmd[0]}"' + exitcode = os.spawnl(os.P_WAIT, executable, *cmd) self.assertEqual(exitcode, 0) diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index ca481c57c3d9724..8c074cb28a87e38 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -307,6 +307,7 @@ def test_get(self): webbrowser.get('fakebrowser') self.assertIsNotNone(webbrowser._tryorder) + @unittest.skipIf(" " in sys.executable, "test assumes no space in path (GH-114452)") def test_synthesize(self): webbrowser = import_helper.import_fresh_module('webbrowser') name = os.path.basename(sys.executable).lower() From ea3cd0498c443e93be441736c804258e93d21edd Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 25 Jan 2024 06:10:51 -0500 Subject: [PATCH 020/263] gh-114312: Collect stats for unlikely events (GH-114493) --- Include/cpython/pystats.h | 14 ++++++ Include/internal/pycore_code.h | 2 + Include/internal/pycore_interp.h | 29 ++++++++++++ Lib/test/test_optimizer.py | 75 ++++++++++++++++++++++++++++++++ Modules/_testinternalcapi.c | 16 +++++++ Objects/funcobject.c | 9 ++++ Objects/typeobject.c | 3 ++ Python/pylifecycle.c | 18 ++++++++ Python/pystate.c | 1 + Python/specialize.c | 11 +++++ Tools/scripts/summarize_stats.py | 22 +++++++++- 11 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 Lib/test/test_optimizer.py diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index ba67eefef3e37ad..bf0cfe4cb695b45 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -122,11 +122,25 @@ typedef struct _optimization_stats { uint64_t optimized_trace_length_hist[_Py_UOP_HIST_SIZE]; } OptimizationStats; +typedef struct _rare_event_stats { + /* Setting an object's class, obj.__class__ = ... */ + uint64_t set_class; + /* Setting the bases of a class, cls.__bases__ = ... */ + uint64_t set_bases; + /* Setting the PEP 523 frame eval function, _PyInterpreterState_SetFrameEvalFunc() */ + uint64_t set_eval_frame_func; + /* Modifying the builtins, __builtins__.__dict__[var] = ... */ + uint64_t builtin_dict; + /* Modifying a function, e.g. func.__defaults__ = ..., etc. */ + uint64_t func_modification; +} RareEventStats; + typedef struct _stats { OpcodeStats opcode_stats[256]; CallStats call_stats; ObjectStats object_stats; OptimizationStats optimization_stats; + RareEventStats rare_event_stats; GCStats *gc_stats; } PyStats; diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 73df6c3568ffe0c..fdd5918228455d7 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -295,6 +295,7 @@ extern int _PyStaticCode_Init(PyCodeObject *co); _Py_stats->optimization_stats.name[bucket]++; \ } \ } while (0) +#define RARE_EVENT_STAT_INC(name) do { if (_Py_stats) _Py_stats->rare_event_stats.name++; } while (0) // Export for '_opcode' shared extension PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void); @@ -313,6 +314,7 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void); #define UOP_STAT_INC(opname, name) ((void)0) #define OPT_UNSUPPORTED_OPCODE(opname) ((void)0) #define OPT_HIST(length, name) ((void)0) +#define RARE_EVENT_STAT_INC(name) ((void)0) #endif // !Py_STATS // Utility functions for reading/writing 32/64-bit values in the inline caches. diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index f953b8426e180a0..662a18d93f329d2 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -60,6 +60,21 @@ struct _stoptheworld_state { /* cross-interpreter data registry */ +/* Tracks some rare events per-interpreter, used by the optimizer to turn on/off + specific optimizations. */ +typedef struct _rare_events { + /* Setting an object's class, obj.__class__ = ... */ + uint8_t set_class; + /* Setting the bases of a class, cls.__bases__ = ... */ + uint8_t set_bases; + /* Setting the PEP 523 frame eval function, _PyInterpreterState_SetFrameEvalFunc() */ + uint8_t set_eval_frame_func; + /* Modifying the builtins, __builtins__.__dict__[var] = ... */ + uint8_t builtin_dict; + int builtins_dict_watcher_id; + /* Modifying a function, e.g. func.__defaults__ = ..., etc. */ + uint8_t func_modification; +} _rare_events; /* interpreter state */ @@ -217,6 +232,7 @@ struct _is { uint16_t optimizer_resume_threshold; uint16_t optimizer_backedge_threshold; uint32_t next_func_version; + _rare_events rare_events; _Py_GlobalMonitors monitors; bool sys_profile_initialized; @@ -347,6 +363,19 @@ PyAPI_FUNC(PyStatus) _PyInterpreterState_New( PyInterpreterState **pinterp); +#define RARE_EVENT_INTERP_INC(interp, name) \ + do { \ + /* saturating add */ \ + if (interp->rare_events.name < UINT8_MAX) interp->rare_events.name++; \ + RARE_EVENT_STAT_INC(name); \ + } while (0); \ + +#define RARE_EVENT_INC(name) \ + do { \ + PyInterpreterState *interp = PyInterpreterState_Get(); \ + RARE_EVENT_INTERP_INC(interp, name); \ + } while (0); \ + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py new file mode 100644 index 000000000000000..b56bf3cfd9560ea --- /dev/null +++ b/Lib/test/test_optimizer.py @@ -0,0 +1,75 @@ +import _testinternalcapi +import unittest +import types + + +class TestRareEventCounters(unittest.TestCase): + def test_set_class(self): + class A: + pass + class B: + pass + a = A() + + orig_counter = _testinternalcapi.get_rare_event_counters()["set_class"] + a.__class__ = B + self.assertEqual( + orig_counter + 1, + _testinternalcapi.get_rare_event_counters()["set_class"] + ) + + def test_set_bases(self): + class A: + pass + class B: + pass + class C(B): + pass + + orig_counter = _testinternalcapi.get_rare_event_counters()["set_bases"] + C.__bases__ = (A,) + self.assertEqual( + orig_counter + 1, + _testinternalcapi.get_rare_event_counters()["set_bases"] + ) + + def test_set_eval_frame_func(self): + orig_counter = _testinternalcapi.get_rare_event_counters()["set_eval_frame_func"] + _testinternalcapi.set_eval_frame_record([]) + self.assertEqual( + orig_counter + 1, + _testinternalcapi.get_rare_event_counters()["set_eval_frame_func"] + ) + _testinternalcapi.set_eval_frame_default() + + def test_builtin_dict(self): + orig_counter = _testinternalcapi.get_rare_event_counters()["builtin_dict"] + if isinstance(__builtins__, types.ModuleType): + builtins = __builtins__.__dict__ + else: + builtins = __builtins__ + builtins["FOO"] = 42 + self.assertEqual( + orig_counter + 1, + _testinternalcapi.get_rare_event_counters()["builtin_dict"] + ) + del builtins["FOO"] + + def test_func_modification(self): + def func(x=0): + pass + + for attribute in ( + "__code__", + "__defaults__", + "__kwdefaults__" + ): + orig_counter = _testinternalcapi.get_rare_event_counters()["func_modification"] + setattr(func, attribute, getattr(func, attribute)) + self.assertEqual( + orig_counter + 1, + _testinternalcapi.get_rare_event_counters()["func_modification"] + ) + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 7d277df164d3ec6..2c32c691afa5837 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1635,6 +1635,21 @@ get_type_module_name(PyObject *self, PyObject *type) return _PyType_GetModuleName((PyTypeObject *)type); } +static PyObject * +get_rare_event_counters(PyObject *self, PyObject *type) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + + return Py_BuildValue( + "{sksksksksk}", + "set_class", interp->rare_events.set_class, + "set_bases", interp->rare_events.set_bases, + "set_eval_frame_func", interp->rare_events.set_eval_frame_func, + "builtin_dict", interp->rare_events.builtin_dict, + "func_modification", interp->rare_events.func_modification + ); +} + #ifdef Py_GIL_DISABLED static PyObject * @@ -1711,6 +1726,7 @@ static PyMethodDef module_functions[] = { {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS}, _TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF {"get_type_module_name", get_type_module_name, METH_O}, + {"get_rare_event_counters", get_rare_event_counters, METH_NOARGS}, #ifdef Py_GIL_DISABLED {"py_thread_id", get_py_thread_id, METH_NOARGS}, #endif diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 2620dc69bfd79bd..08b2823d8cf024e 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -53,6 +53,15 @@ handle_func_event(PyFunction_WatchEvent event, PyFunctionObject *func, if (interp->active_func_watchers) { notify_func_watchers(interp, event, func, new_value); } + switch (event) { + case PyFunction_EVENT_MODIFY_CODE: + case PyFunction_EVENT_MODIFY_DEFAULTS: + case PyFunction_EVENT_MODIFY_KWDEFAULTS: + RARE_EVENT_INTERP_INC(interp, func_modification); + break; + default: + break; + } } int diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 3a35a5b5975898e..a8c3b8896d36eb4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1371,6 +1371,7 @@ type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) res = 0; } + RARE_EVENT_INC(set_bases); Py_DECREF(old_bases); Py_DECREF(old_base); @@ -5842,6 +5843,8 @@ object_set_class(PyObject *self, PyObject *value, void *closure) Py_SET_TYPE(self, newto); if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) Py_DECREF(oldto); + + RARE_EVENT_INC(set_class); return 0; } else { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0d5eec06e9b458a..261622adc4cc771 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -605,6 +605,12 @@ init_interp_create_gil(PyThreadState *tstate, int gil) _PyEval_InitGIL(tstate, own_gil); } +static int +builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value) +{ + RARE_EVENT_INC(builtin_dict); + return 0; +} static PyStatus pycore_create_interpreter(_PyRuntimeState *runtime, @@ -1266,6 +1272,14 @@ init_interp_main(PyThreadState *tstate) } } + if ((interp->rare_events.builtins_dict_watcher_id = PyDict_AddWatcher(&builtins_dict_watcher)) == -1) { + return _PyStatus_ERR("failed to add builtin dict watcher"); + } + + if (PyDict_Watch(interp->rare_events.builtins_dict_watcher_id, interp->builtins) != 0) { + return _PyStatus_ERR("failed to set builtin dict watcher"); + } + assert(!_PyErr_Occurred(tstate)); return _PyStatus_OK(); @@ -1592,6 +1606,10 @@ static void finalize_modules(PyThreadState *tstate) { PyInterpreterState *interp = tstate->interp; + + // Stop collecting stats on __builtin__ modifications during teardown + PyDict_Unwatch(interp->rare_events.builtins_dict_watcher_id, interp->builtins); + PyObject *modules = _PyImport_GetModules(interp); if (modules == NULL) { // Already done diff --git a/Python/pystate.c b/Python/pystate.c index 548c77b7dc7ebb8..c9b521351444a71 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2616,6 +2616,7 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, if (eval_frame != NULL) { _Py_Executors_InvalidateAll(interp); } + RARE_EVENT_INC(set_eval_frame_func); interp->eval_frame = eval_frame; } diff --git a/Python/specialize.c b/Python/specialize.c index 13e0440dd9dd0d7..a9efbe0453b94e2 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -267,6 +267,16 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) } } +static void +print_rare_event_stats(FILE *out, RareEventStats *stats) +{ + fprintf(out, "Rare event (set_class): %" PRIu64 "\n", stats->set_class); + fprintf(out, "Rare event (set_bases): %" PRIu64 "\n", stats->set_bases); + fprintf(out, "Rare event (set_eval_frame_func): %" PRIu64 "\n", stats->set_eval_frame_func); + fprintf(out, "Rare event (builtin_dict): %" PRIu64 "\n", stats->builtin_dict); + fprintf(out, "Rare event (func_modification): %" PRIu64 "\n", stats->func_modification); +} + static void print_stats(FILE *out, PyStats *stats) { @@ -275,6 +285,7 @@ print_stats(FILE *out, PyStats *stats) print_object_stats(out, &stats->object_stats); print_gc_stats(out, stats->gc_stats); print_optimization_stats(out, &stats->optimization_stats); + print_rare_event_stats(out, &stats->rare_event_stats); } void diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 1e9dc07bae89812..9b7e7b999ea7c7d 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -412,6 +412,14 @@ def get_histogram(self, prefix: str) -> list[tuple[int, int]]: rows.sort() return rows + def get_rare_events(self) -> list[tuple[str, int]]: + prefix = "Rare event " + return [ + (key[len(prefix) + 1:-1], val) + for key, val in self._data.items() + if key.startswith(prefix) + ] + class Count(int): def markdown(self) -> str: @@ -1064,6 +1072,17 @@ def iter_optimization_tables(base_stats: Stats, head_stats: Stats | None = None) ) +def rare_event_section() -> Section: + def calc_rare_event_table(stats: Stats) -> Table: + return [(x, Count(y)) for x, y in stats.get_rare_events()] + + return Section( + "Rare events", + "Counts of rare/unlikely events", + [Table(("Event", "Count:"), calc_rare_event_table, JoinMode.CHANGE)], + ) + + def meta_stats_section() -> Section: def calc_rows(stats: Stats) -> Rows: return [("Number of data files", Count(stats.get("__nfiles__")))] @@ -1085,6 +1104,7 @@ def calc_rows(stats: Stats) -> Rows: object_stats_section(), gc_stats_section(), optimization_section(), + rare_event_section(), meta_stats_section(), ] @@ -1162,7 +1182,7 @@ def output_stats(inputs: list[Path], json_output=str | None): case 1: data = load_raw_data(Path(inputs[0])) if json_output is not None: - with open(json_output, 'w', encoding='utf-8') as f: + with open(json_output, "w", encoding="utf-8") as f: save_raw_data(data, f) # type: ignore stats = Stats(data) output_markdown(sys.stdout, LAYOUT, stats) From 0315941441f1e5f944a758c67eb1763b00e6e462 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:54:19 +0000 Subject: [PATCH 021/263] gh-114265: remove i_loc_propagated, jump threading does not consider line numbers anymore (#114535) --- Lib/test/test_peepholer.py | 3 +- Python/flowgraph.c | 88 ++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 76a6f25c34bbd35..2ea186c85c8823e 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1150,10 +1150,11 @@ def get_insts(lno1, lno2, op1, op2): lno1, lno2 = (4, 5) with self.subTest(lno = (lno1, lno2), ops = (op1, op2)): insts = get_insts(lno1, lno2, op1, op2) + op = 'JUMP' if 'JUMP' in (op1, op2) else 'JUMP_NO_INTERRUPT' expected_insts = [ ('LOAD_NAME', 0, 10), ('NOP', 0, 4), - (op2, 0, 5), + (op, 0, 5), ] self.cfg_optimization_test(insts, expected_insts, consts=list(range(5))) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 2fc90b8877b475b..de831358eb9ac83 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -29,7 +29,6 @@ typedef struct _PyCfgInstruction { int i_opcode; int i_oparg; _PyCompilerSrcLocation i_loc; - unsigned i_loc_propagated : 1; /* location was set by propagate_line_numbers */ struct _PyCfgBasicblock *i_target; /* target block (if jump instruction) */ struct _PyCfgBasicblock *i_except; /* target block when exception is raised */ } cfg_instr; @@ -146,6 +145,16 @@ basicblock_next_instr(basicblock *b) return b->b_iused++; } +static cfg_instr * +basicblock_last_instr(const basicblock *b) { + assert(b->b_iused >= 0); + if (b->b_iused > 0) { + assert(b->b_instr != NULL); + return &b->b_instr[b->b_iused - 1]; + } + return NULL; +} + /* Allocate a new block and return a pointer to it. Returns NULL on error. */ @@ -186,6 +195,22 @@ basicblock_addop(basicblock *b, int opcode, int oparg, location loc) return SUCCESS; } +static int +basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) +{ + cfg_instr *last = basicblock_last_instr(b); + if (last && is_jump(last)) { + return ERROR; + } + + RETURN_IF_ERROR( + basicblock_addop(b, opcode, target->b_label.id, loc)); + last = basicblock_last_instr(b); + assert(last && last->i_opcode == opcode); + last->i_target = target; + return SUCCESS; +} + static inline int basicblock_append_instructions(basicblock *target, basicblock *source) { @@ -199,16 +224,6 @@ basicblock_append_instructions(basicblock *target, basicblock *source) return SUCCESS; } -static cfg_instr * -basicblock_last_instr(const basicblock *b) { - assert(b->b_iused >= 0); - if (b->b_iused > 0) { - assert(b->b_instr != NULL); - return &b->b_instr[b->b_iused - 1]; - } - return NULL; -} - static inline int basicblock_nofallthrough(const basicblock *b) { cfg_instr *last = basicblock_last_instr(b); @@ -560,8 +575,8 @@ normalize_jumps_in_block(cfg_builder *g, basicblock *b) { if (backwards_jump == NULL) { return ERROR; } - basicblock_addop(backwards_jump, JUMP, target->b_label.id, last->i_loc); - backwards_jump->b_instr[0].i_target = target; + RETURN_IF_ERROR( + basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc)); last->i_opcode = reversed_opcode; last->i_target = b->b_next; @@ -1141,13 +1156,7 @@ remove_redundant_jumps(cfg_builder *g) { basicblock *next = next_nonempty_block(b->b_next); if (jump_target == next) { changes++; - if (last->i_loc_propagated) { - b->b_iused--; - } - else { - assert(last->i_loc.lineno != -1); - INSTR_SET_OP0(last, NOP); - } + INSTR_SET_OP0(last, NOP); } } } @@ -1184,23 +1193,23 @@ inline_small_exit_blocks(basicblock *bb) { // target->i_target using the provided opcode. Return whether or not the // optimization was successful. static bool -jump_thread(cfg_instr *inst, cfg_instr *target, int opcode) +jump_thread(basicblock *bb, cfg_instr *inst, cfg_instr *target, int opcode) { assert(is_jump(inst)); assert(is_jump(target)); + assert(inst == basicblock_last_instr(bb)); // bpo-45773: If inst->i_target == target->i_target, then nothing actually // changes (and we fall into an infinite loop): - if (inst->i_loc.lineno == -1) assert(inst->i_loc_propagated); - if (target->i_loc.lineno == -1) assert(target->i_loc_propagated); - if ((inst->i_loc.lineno == target->i_loc.lineno || - inst->i_loc_propagated || target->i_loc_propagated) && - inst->i_target != target->i_target) - { - inst->i_target = target->i_target; - inst->i_opcode = opcode; - if (inst->i_loc_propagated && !target->i_loc_propagated) { - inst->i_loc = target->i_loc; - } + if (inst->i_target != target->i_target) { + /* Change inst to NOP and append a jump to target->i_target. The + * NOP will be removed later if it's not needed for the lineno. + */ + INSTR_SET_OP0(inst, NOP); + + RETURN_IF_ERROR( + basicblock_add_jump( + bb, opcode, target->i_target, target->i_loc)); + return true; } return false; @@ -1673,29 +1682,29 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) case POP_JUMP_IF_NONE: switch (target->i_opcode) { case JUMP: - i -= jump_thread(inst, target, inst->i_opcode); + i -= jump_thread(bb, inst, target, inst->i_opcode); } break; case POP_JUMP_IF_FALSE: switch (target->i_opcode) { case JUMP: - i -= jump_thread(inst, target, POP_JUMP_IF_FALSE); + i -= jump_thread(bb, inst, target, POP_JUMP_IF_FALSE); } break; case POP_JUMP_IF_TRUE: switch (target->i_opcode) { case JUMP: - i -= jump_thread(inst, target, POP_JUMP_IF_TRUE); + i -= jump_thread(bb, inst, target, POP_JUMP_IF_TRUE); } break; case JUMP: case JUMP_NO_INTERRUPT: switch (target->i_opcode) { case JUMP: - i -= jump_thread(inst, target, JUMP); + i -= jump_thread(bb, inst, target, JUMP); continue; case JUMP_NO_INTERRUPT: - i -= jump_thread(inst, target, opcode); + i -= jump_thread(bb, inst, target, opcode); continue; } break; @@ -1707,7 +1716,7 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) * of FOR_ITER. */ /* - i -= jump_thread(inst, target, FOR_ITER); + i -= jump_thread(bb, inst, target, FOR_ITER); */ } break; @@ -2410,7 +2419,6 @@ propagate_line_numbers(basicblock *entryblock) { for (int i = 0; i < b->b_iused; i++) { if (b->b_instr[i].i_loc.lineno < 0) { b->b_instr[i].i_loc = prev_location; - b->b_instr[i].i_loc_propagated = 1; } else { prev_location = b->b_instr[i].i_loc; @@ -2420,7 +2428,6 @@ propagate_line_numbers(basicblock *entryblock) { if (b->b_next->b_iused > 0) { if (b->b_next->b_instr[0].i_loc.lineno < 0) { b->b_next->b_instr[0].i_loc = prev_location; - b->b_next->b_instr[0].i_loc_propagated = 1; } } } @@ -2429,7 +2436,6 @@ propagate_line_numbers(basicblock *entryblock) { if (target->b_predecessors == 1) { if (target->b_instr[0].i_loc.lineno < 0) { target->b_instr[0].i_loc = prev_location; - target->b_instr[0].i_loc_propagated = 1; } } } From e721adf4bd47b20ba0a93ad6471084de31bf20c7 Mon Sep 17 00:00:00 2001 From: AN Long Date: Thu, 25 Jan 2024 22:35:05 +0800 Subject: [PATCH 022/263] gh-77465: Increase test coverage for the numbers module (GH-111738) Co-authored-by: Serhiy Storchaka --- Lib/test/test_abstract_numbers.py | 158 +++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_abstract_numbers.py b/Lib/test/test_abstract_numbers.py index 2e06f0d16fdd05b..72232b670cdb89e 100644 --- a/Lib/test/test_abstract_numbers.py +++ b/Lib/test/test_abstract_numbers.py @@ -1,14 +1,34 @@ """Unit tests for numbers.py.""" +import abc import math import operator import unittest -from numbers import Complex, Real, Rational, Integral +from numbers import Complex, Real, Rational, Integral, Number + + +def concretize(cls): + def not_implemented(*args, **kwargs): + raise NotImplementedError() + + for name in dir(cls): + try: + value = getattr(cls, name) + if value.__isabstractmethod__: + setattr(cls, name, not_implemented) + except AttributeError: + pass + abc.update_abstractmethods(cls) + return cls + class TestNumbers(unittest.TestCase): def test_int(self): self.assertTrue(issubclass(int, Integral)) + self.assertTrue(issubclass(int, Rational)) + self.assertTrue(issubclass(int, Real)) self.assertTrue(issubclass(int, Complex)) + self.assertTrue(issubclass(int, Number)) self.assertEqual(7, int(7).real) self.assertEqual(0, int(7).imag) @@ -18,8 +38,11 @@ def test_int(self): self.assertEqual(1, int(7).denominator) def test_float(self): + self.assertFalse(issubclass(float, Integral)) self.assertFalse(issubclass(float, Rational)) self.assertTrue(issubclass(float, Real)) + self.assertTrue(issubclass(float, Complex)) + self.assertTrue(issubclass(float, Number)) self.assertEqual(7.3, float(7.3).real) self.assertEqual(0, float(7.3).imag) @@ -27,8 +50,11 @@ def test_float(self): self.assertEqual(-7.3, float(-7.3).conjugate()) def test_complex(self): + self.assertFalse(issubclass(complex, Integral)) + self.assertFalse(issubclass(complex, Rational)) self.assertFalse(issubclass(complex, Real)) self.assertTrue(issubclass(complex, Complex)) + self.assertTrue(issubclass(complex, Number)) c1, c2 = complex(3, 2), complex(4,1) # XXX: This is not ideal, but see the comment in math_trunc(). @@ -40,5 +66,135 @@ def test_complex(self): self.assertRaises(TypeError, int, c1) +class TestNumbersDefaultMethods(unittest.TestCase): + def test_complex(self): + @concretize + class MyComplex(Complex): + def __init__(self, real, imag): + self.r = real + self.i = imag + + @property + def real(self): + return self.r + + @property + def imag(self): + return self.i + + def __add__(self, other): + if isinstance(other, Complex): + return MyComplex(self.imag + other.imag, + self.real + other.real) + raise NotImplementedError + + def __neg__(self): + return MyComplex(-self.real, -self.imag) + + def __eq__(self, other): + if isinstance(other, Complex): + return self.imag == other.imag and self.real == other.real + if isinstance(other, Number): + return self.imag == 0 and self.real == other.real + + # test __bool__ + self.assertTrue(bool(MyComplex(1, 1))) + self.assertTrue(bool(MyComplex(0, 1))) + self.assertTrue(bool(MyComplex(1, 0))) + self.assertFalse(bool(MyComplex(0, 0))) + + # test __sub__ + self.assertEqual(MyComplex(2, 3) - complex(1, 2), MyComplex(1, 1)) + + # test __rsub__ + self.assertEqual(complex(2, 3) - MyComplex(1, 2), MyComplex(1, 1)) + + def test_real(self): + @concretize + class MyReal(Real): + def __init__(self, n): + self.n = n + + def __pos__(self): + return self.n + + def __float__(self): + return float(self.n) + + def __floordiv__(self, other): + return self.n // other + + def __rfloordiv__(self, other): + return other // self.n + + def __mod__(self, other): + return self.n % other + + def __rmod__(self, other): + return other % self.n + + # test __divmod__ + self.assertEqual(divmod(MyReal(3), 2), (1, 1)) + + # test __rdivmod__ + self.assertEqual(divmod(3, MyReal(2)), (1, 1)) + + # test __complex__ + self.assertEqual(complex(MyReal(1)), 1+0j) + + # test real + self.assertEqual(MyReal(3).real, 3) + + # test imag + self.assertEqual(MyReal(3).imag, 0) + + # test conjugate + self.assertEqual(MyReal(123).conjugate(), 123) + + + def test_rational(self): + @concretize + class MyRational(Rational): + def __init__(self, numerator, denominator): + self.n = numerator + self.d = denominator + + @property + def numerator(self): + return self.n + + @property + def denominator(self): + return self.d + + # test__float__ + self.assertEqual(float(MyRational(5, 2)), 2.5) + + + def test_integral(self): + @concretize + class MyIntegral(Integral): + def __init__(self, n): + self.n = n + + def __pos__(self): + return self.n + + def __int__(self): + return self.n + + # test __index__ + self.assertEqual(operator.index(MyIntegral(123)), 123) + + # test __float__ + self.assertEqual(float(MyIntegral(123)), 123.0) + + # test numerator + self.assertEqual(MyIntegral(123).numerator, 123) + + # test denominator + self.assertEqual(MyIntegral(123).denominator, 1) + + if __name__ == "__main__": unittest.main() From 07ef63fb6a0fb996d5f56c79f4ccd7a1887a6b2b Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 25 Jan 2024 09:38:43 -0500 Subject: [PATCH 023/263] Doc/library/sys.monitoring.rst: remove contradictory paragraph. (GH-113619) --- Doc/library/sys.monitoring.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst index 762581b7eda7f1d..4980227c60b21e3 100644 --- a/Doc/library/sys.monitoring.rst +++ b/Doc/library/sys.monitoring.rst @@ -75,9 +75,6 @@ following IDs are pre-defined to make co-operation of tools easier:: sys.monitoring.PROFILER_ID = 2 sys.monitoring.OPTIMIZER_ID = 5 -There is no obligation to set an ID, nor is there anything preventing a tool -from using an ID even it is already in use. -However, tools are encouraged to use a unique ID and respect other tools. Events ------ From 8278fa2f5625b41be91191d18ee8eeab904a54ff Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Thu, 25 Jan 2024 08:48:50 -0800 Subject: [PATCH 024/263] gh-111051: Check if file is modifed during debugging in `pdb` (#111052) --- Lib/pdb.py | 21 +++++ Lib/test/test_pdb.py | 81 +++++++++++++++++++ ...-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst | 1 + 3 files changed, 103 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 68f810620f88263..6f7719eb9ba6c5b 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -233,6 +233,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): # but in case there are recursions, we stop at 999. MAX_CHAINED_EXCEPTION_DEPTH = 999 + _file_mtime_table = {} + def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True): bdb.Bdb.__init__(self, skip=skip) @@ -437,6 +439,20 @@ def _cmdloop(self): except KeyboardInterrupt: self.message('--KeyboardInterrupt--') + def _validate_file_mtime(self): + """Check if the source file of the current frame has been modified since + the last time we saw it. If so, give a warning.""" + try: + filename = self.curframe.f_code.co_filename + mtime = os.path.getmtime(filename) + except Exception: + return + if (filename in self._file_mtime_table and + mtime != self._file_mtime_table[filename]): + self.message(f"*** WARNING: file '{filename}' was edited, " + "running stale code until the program is rerun") + self._file_mtime_table[filename] = mtime + # Called before loop, handles display expressions # Set up convenience variable containers def preloop(self): @@ -681,6 +697,7 @@ def onecmd(self, line): a breakpoint command list definition. """ if not self.commands_defining: + self._validate_file_mtime() return cmd.Cmd.onecmd(self, line) else: return self.handle_command_def(line) @@ -2021,6 +2038,10 @@ def _run(self, target: Union[_ModuleTarget, _ScriptTarget]): __main__.__dict__.clear() __main__.__dict__.update(target.namespace) + # Clear the mtime table for program reruns, assume all the files + # are up to date. + self._file_mtime_table.clear() + self.run(target.code) def _format_exc(self, exc: BaseException): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 03487aa6ffd81f4..c64df62c761471d 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3056,6 +3056,87 @@ def test_blocks_at_first_code_line(self): self.assertTrue(any("__main__.py(4)()" in l for l in stdout.splitlines()), stdout) + def test_file_modified_after_execution(self): + script = """ + print("hello") + """ + + commands = """ + filename = $_frame.f_code.co_filename + f = open(filename, "w") + f.write("print('goodbye')") + f.close() + ll + """ + + stdout, stderr = self.run_pdb_script(script, commands) + self.assertIn("WARNING:", stdout) + self.assertIn("was edited", stdout) + + def test_file_modified_after_execution_with_multiple_instances(self): + script = """ + import pdb; pdb.Pdb().set_trace() + with open(__file__, "w") as f: + f.write("print('goodbye')\\n" * 5) + import pdb; pdb.Pdb().set_trace() + """ + + commands = """ + continue + continue + """ + + filename = 'main.py' + with open(filename, 'w') as f: + f.write(textwrap.dedent(script)) + self.addCleanup(os_helper.unlink, filename) + self.addCleanup(os_helper.rmtree, '__pycache__') + cmd = [sys.executable, filename] + with subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'}, + ) as proc: + stdout, _ = proc.communicate(str.encode(commands)) + stdout = stdout and bytes.decode(stdout) + + self.assertEqual(proc.returncode, 0) + self.assertIn("WARNING:", stdout) + self.assertIn("was edited", stdout) + + def test_file_modified_after_execution_with_restart(self): + script = """ + import random + # Any code with a source to step into so this script is not checked + # for changes when it's being changed + random.randint(1, 4) + print("hello") + """ + + commands = """ + ll + n + s + filename = $_frame.f_back.f_code.co_filename + def change_file(content, filename): + with open(filename, "w") as f: + f.write(f"print({content})") + + change_file('world', filename) + restart + ll + """ + + stdout, stderr = self.run_pdb_script(script, commands) + # Make sure the code is running correctly and the file is edited + self.assertIn("hello", stdout) + self.assertIn("world", stdout) + # The file was edited, but restart should clear the state and consider + # the file as up to date + self.assertNotIn("WARNING:", stdout) + def test_relative_imports(self): self.module_name = 't_main' os_helper.rmtree(self.module_name) diff --git a/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst b/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst new file mode 100644 index 000000000000000..adb3241b89ae3e6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-19-02-08-12.gh-issue-111051.8h1Dpk.rst @@ -0,0 +1 @@ +Added check for file modification during debugging with :mod:`pdb` From 4850410b60183dac021ded219a49be140fe5fefe Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Thu, 25 Jan 2024 09:34:03 -0800 Subject: [PATCH 025/263] gh-112075: Add try-incref functions from nogil branch for use in dict thread safety (#114512) * Bring in a subset of biased reference counting: https://github.com/colesbury/nogil/commit/b6b12a9a94e The NoGIL branch has functions for attempting to do an incref on an object which may or may not be in flight. This just brings those functions over so that they will be usable from in the dict implementation to get items w/o holding a lock. There's a handful of small simple modifications: Adding inline to the force inline functions to avoid a warning, and switching from _Py_ALWAYS_INLINE to Py_ALWAYS_INLINE as that's available Remove _Py_REF_LOCAL_SHIFT as it doesn't exist yet (and is currently 0 in the 3.12 nogil branch anyway) ob_ref_shared is currently Py_ssize_t and not uint32_t, so use that _PY_LIKELY doesn't exist, so drop it _Py_ThreadLocal becomes _Py_IsOwnedByCurrentThread Add '_PyInterpreterState_GET()' to _Py_IncRefTotal calls. Co-Authored-By: Sam Gross --- Include/internal/pycore_object.h | 136 +++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index f413b8451e5ab49..ec14c07a2991ff8 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -376,6 +376,142 @@ static inline void _PyObject_GC_UNTRACK( _PyObject_GC_UNTRACK(__FILE__, __LINE__, _PyObject_CAST(op)) #endif +#ifdef Py_GIL_DISABLED + +/* Tries to increment an object's reference count + * + * This is a specialized version of _Py_TryIncref that only succeeds if the + * object is immortal or local to this thread. It does not handle the case + * where the reference count modification requires an atomic operation. This + * allows call sites to specialize for the immortal/local case. + */ +static inline int +_Py_TryIncrefFast(PyObject *op) { + uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); + local += 1; + if (local == 0) { + // immortal + return 1; + } + if (_Py_IsOwnedByCurrentThread(op)) { + _Py_INCREF_STAT_INC(); + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local); +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif + return 1; + } + return 0; +} + +static inline int +_Py_TryIncRefShared(PyObject *op) +{ + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + for (;;) { + // If the shared refcount is zero and the object is either merged + // or may not have weak references, then we cannot incref it. + if (shared == 0 || shared == _Py_REF_MERGED) { + return 0; + } + + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, + &shared, + shared + (1 << _Py_REF_SHARED_SHIFT))) { +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif + _Py_INCREF_STAT_INC(); + return 1; + } + } +} + +/* Tries to incref the object op and ensures that *src still points to it. */ +static inline int +_Py_TryIncref(PyObject **src, PyObject *op) +{ + if (_Py_TryIncrefFast(op)) { + return 1; + } + if (!_Py_TryIncRefShared(op)) { + return 0; + } + if (op != _Py_atomic_load_ptr(src)) { + Py_DECREF(op); + return 0; + } + return 1; +} + +/* Loads and increfs an object from ptr, which may contain a NULL value. + Safe with concurrent (atomic) updates to ptr. + NOTE: The writer must set maybe-weakref on the stored object! */ +static inline PyObject * +_Py_XGetRef(PyObject **ptr) +{ + for (;;) { + PyObject *value = _Py_atomic_load_ptr(ptr); + if (value == NULL) { + return value; + } + if (_Py_TryIncref(ptr, value)) { + return value; + } + } +} + +/* Attempts to loads and increfs an object from ptr. Returns NULL + on failure, which may be due to a NULL value or a concurrent update. */ +static inline PyObject * +_Py_TryXGetRef(PyObject **ptr) +{ + PyObject *value = _Py_atomic_load_ptr(ptr); + if (value == NULL) { + return value; + } + if (_Py_TryIncref(ptr, value)) { + return value; + } + return NULL; +} + +/* Like Py_NewRef but also optimistically sets _Py_REF_MAYBE_WEAKREF + on objects owned by a different thread. */ +static inline PyObject * +_Py_NewRefWithLock(PyObject *op) +{ + if (_Py_TryIncrefFast(op)) { + return op; + } + _Py_INCREF_STAT_INC(); + for (;;) { + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); + Py_ssize_t new_shared = shared + (1 << _Py_REF_SHARED_SHIFT); + if ((shared & _Py_REF_SHARED_FLAG_MASK) == 0) { + new_shared |= _Py_REF_MAYBE_WEAKREF; + } + if (_Py_atomic_compare_exchange_ssize( + &op->ob_ref_shared, + &shared, + new_shared)) { + return op; + } + } +} + +static inline PyObject * +_Py_XNewRefWithLock(PyObject *obj) +{ + if (obj == NULL) { + return NULL; + } + return _Py_NewRefWithLock(obj); +} + +#endif + #ifdef Py_REF_DEBUG extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *); extern void _Py_FinalizeRefTotal(_PyRuntimeState *); From b52fc70d1ab3be7866ab71065bae61a03a28bfae Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 25 Jan 2024 13:27:36 -0500 Subject: [PATCH 026/263] gh-112529: Implement GC for free-threaded builds (#114262) * gh-112529: Implement GC for free-threaded builds This implements a mark and sweep GC for the free-threaded builds of CPython. The implementation relies on mimalloc to find GC tracked objects (i.e., "containers"). --- Include/internal/pycore_freelist.h | 10 + Include/internal/pycore_gc.h | 35 +- Include/internal/pycore_object.h | 8 + Include/internal/pycore_object_stack.h | 84 + Include/object.h | 4 +- Lib/gzip.py | 2 +- Lib/test/test_gc.py | 13 +- Lib/test/test_io.py | 4 +- Makefile.pre.in | 3 + ...-01-18-20-20-37.gh-issue-112529.oVNvDG.rst | 3 + PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 + Python/gc.c | 8 +- Python/gc_free_threading.c | 1701 ++++++++++++++++- Python/object_stack.c | 87 + Python/pystate.c | 4 +- 18 files changed, 1952 insertions(+), 22 deletions(-) create mode 100644 Include/internal/pycore_object_stack.h create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst create mode 100644 Python/object_stack.c diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index 4ab93ee2bf6c329..dfb12839affedfc 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -20,6 +20,7 @@ extern "C" { # define PyFloat_MAXFREELIST 100 # define PyContext_MAXFREELIST 255 # define _PyAsyncGen_MAXFREELIST 80 +# define _PyObjectStackChunk_MAXFREELIST 4 #else # define PyTuple_NFREELISTS 0 # define PyTuple_MAXFREELIST 0 @@ -27,6 +28,7 @@ extern "C" { # define PyFloat_MAXFREELIST 0 # define PyContext_MAXFREELIST 0 # define _PyAsyncGen_MAXFREELIST 0 +# define _PyObjectStackChunk_MAXFREELIST 0 #endif struct _Py_list_state { @@ -93,6 +95,13 @@ struct _Py_async_gen_state { #endif }; +struct _PyObjectStackChunk; + +struct _Py_object_stack_state { + struct _PyObjectStackChunk *free_list; + Py_ssize_t numfree; +}; + typedef struct _Py_freelist_state { struct _Py_float_state float_state; struct _Py_tuple_state tuple_state; @@ -100,6 +109,7 @@ typedef struct _Py_freelist_state { struct _Py_slice_state slice_state; struct _Py_context_state context_state; struct _Py_async_gen_state async_gen_state; + struct _Py_object_stack_state object_stack_state; } _PyFreeListState; #ifdef __cplusplus diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index d53de97709a782f..b362a294a59042a 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -37,10 +37,22 @@ static inline PyObject* _Py_FROM_GC(PyGC_Head *gc) { } +/* Bit flags for ob_gc_bits (in Py_GIL_DISABLED builds) */ +#ifdef Py_GIL_DISABLED +# define _PyGC_BITS_TRACKED (1) +# define _PyGC_BITS_FINALIZED (2) +# define _PyGC_BITS_UNREACHABLE (4) +# define _PyGC_BITS_FROZEN (8) +#endif + /* True if the object is currently tracked by the GC. */ static inline int _PyObject_GC_IS_TRACKED(PyObject *op) { +#ifdef Py_GIL_DISABLED + return (op->ob_gc_bits & _PyGC_BITS_TRACKED) != 0; +#else PyGC_Head *gc = _Py_AS_GC(op); return (gc->_gc_next != 0); +#endif } #define _PyObject_GC_IS_TRACKED(op) _PyObject_GC_IS_TRACKED(_Py_CAST(PyObject*, op)) @@ -107,24 +119,29 @@ static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) { gc->_gc_prev = ((gc->_gc_prev & ~_PyGC_PREV_MASK) | uprev); } -static inline int _PyGCHead_FINALIZED(PyGC_Head *gc) { - return ((gc->_gc_prev & _PyGC_PREV_MASK_FINALIZED) != 0); -} -static inline void _PyGCHead_SET_FINALIZED(PyGC_Head *gc) { - gc->_gc_prev |= _PyGC_PREV_MASK_FINALIZED; -} - static inline int _PyGC_FINALIZED(PyObject *op) { +#ifdef Py_GIL_DISABLED + return (op->ob_gc_bits & _PyGC_BITS_FINALIZED) != 0; +#else PyGC_Head *gc = _Py_AS_GC(op); - return _PyGCHead_FINALIZED(gc); + return ((gc->_gc_prev & _PyGC_PREV_MASK_FINALIZED) != 0); +#endif } static inline void _PyGC_SET_FINALIZED(PyObject *op) { +#ifdef Py_GIL_DISABLED + op->ob_gc_bits |= _PyGC_BITS_FINALIZED; +#else PyGC_Head *gc = _Py_AS_GC(op); - _PyGCHead_SET_FINALIZED(gc); + gc->_gc_prev |= _PyGC_PREV_MASK_FINALIZED; +#endif } static inline void _PyGC_CLEAR_FINALIZED(PyObject *op) { +#ifdef Py_GIL_DISABLED + op->ob_gc_bits &= ~_PyGC_BITS_FINALIZED; +#else PyGC_Head *gc = _Py_AS_GC(op); gc->_gc_prev &= ~_PyGC_PREV_MASK_FINALIZED; +#endif } diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index ec14c07a2991ff8..4e52ffc77c5956b 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -322,6 +322,9 @@ static inline void _PyObject_GC_TRACK( "object is in generation which is garbage collected", filename, lineno, __func__); +#ifdef Py_GIL_DISABLED + op->ob_gc_bits |= _PyGC_BITS_TRACKED; +#else PyInterpreterState *interp = _PyInterpreterState_GET(); PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); @@ -329,6 +332,7 @@ static inline void _PyObject_GC_TRACK( _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); generation0->_gc_prev = (uintptr_t)gc; +#endif } /* Tell the GC to stop tracking this object. @@ -352,6 +356,9 @@ static inline void _PyObject_GC_UNTRACK( "object not tracked by the garbage collector", filename, lineno, __func__); +#ifdef Py_GIL_DISABLED + op->ob_gc_bits &= ~_PyGC_BITS_TRACKED; +#else PyGC_Head *gc = _Py_AS_GC(op); PyGC_Head *prev = _PyGCHead_PREV(gc); PyGC_Head *next = _PyGCHead_NEXT(gc); @@ -359,6 +366,7 @@ static inline void _PyObject_GC_UNTRACK( _PyGCHead_SET_PREV(next, prev); gc->_gc_next = 0; gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED; +#endif } // Macros to accept any type for the parameter, and to automatically pass diff --git a/Include/internal/pycore_object_stack.h b/Include/internal/pycore_object_stack.h new file mode 100644 index 000000000000000..1dc1c1591525ded --- /dev/null +++ b/Include/internal/pycore_object_stack.h @@ -0,0 +1,84 @@ +#ifndef Py_INTERNAL_OBJECT_STACK_H +#define Py_INTERNAL_OBJECT_STACK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +// _PyObjectStack is a stack of Python objects implemented as a linked list of +// fixed size buffers. + +// Chosen so that _PyObjectStackChunk is a power-of-two size. +#define _Py_OBJECT_STACK_CHUNK_SIZE 254 + +typedef struct _PyObjectStackChunk { + struct _PyObjectStackChunk *prev; + Py_ssize_t n; + PyObject *objs[_Py_OBJECT_STACK_CHUNK_SIZE]; +} _PyObjectStackChunk; + +typedef struct _PyObjectStack { + _PyObjectStackChunk *head; +} _PyObjectStack; + + +extern _PyObjectStackChunk * +_PyObjectStackChunk_New(void); + +extern void +_PyObjectStackChunk_Free(_PyObjectStackChunk *); + +extern void +_PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); + +// Push an item onto the stack. Return -1 on allocation failure, 0 on success. +static inline int +_PyObjectStack_Push(_PyObjectStack *stack, PyObject *obj) +{ + _PyObjectStackChunk *buf = stack->head; + if (buf == NULL || buf->n == _Py_OBJECT_STACK_CHUNK_SIZE) { + buf = _PyObjectStackChunk_New(); + if (buf == NULL) { + return -1; + } + buf->prev = stack->head; + buf->n = 0; + stack->head = buf; + } + + assert(buf->n >= 0 && buf->n < _Py_OBJECT_STACK_CHUNK_SIZE); + buf->objs[buf->n] = obj; + buf->n++; + return 0; +} + +// Pop the top item from the stack. Return NULL if the stack is empty. +static inline PyObject * +_PyObjectStack_Pop(_PyObjectStack *stack) +{ + _PyObjectStackChunk *buf = stack->head; + if (buf == NULL) { + return NULL; + } + assert(buf->n > 0 && buf->n <= _Py_OBJECT_STACK_CHUNK_SIZE); + buf->n--; + PyObject *obj = buf->objs[buf->n]; + if (buf->n == 0) { + stack->head = buf->prev; + _PyObjectStackChunk_Free(buf); + } + return obj; +} + +// Remove all items from the stack +extern void +_PyObjectStack_Clear(_PyObjectStack *stack); + +#ifdef __cplusplus +} +#endif +#endif // !Py_INTERNAL_OBJECT_STACK_H diff --git a/Include/object.h b/Include/object.h index 48f1ddf7510887d..ef3fb721c2b0122 100644 --- a/Include/object.h +++ b/Include/object.h @@ -212,7 +212,9 @@ struct _object { struct _PyMutex { uint8_t v; }; struct _object { - uintptr_t ob_tid; // thread id (or zero) + // ob_tid stores the thread id (or zero). It is also used by the GC to + // store linked lists and the computed "gc_refs" refcount. + uintptr_t ob_tid; uint16_t _padding; struct _PyMutex ob_mutex; // per-object lock uint8_t ob_gc_bits; // gc-related state diff --git a/Lib/gzip.py b/Lib/gzip.py index 177f9080dc5af8b..fda93e0261e0285 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -349,7 +349,7 @@ def closed(self): def close(self): fileobj = self.fileobj - if fileobj is None: + if fileobj is None or self._buffer.closed: return try: if self.mode == WRITE: diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 1d71dd9e262a6ae..b01f344cb14a1a7 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1,7 +1,7 @@ import unittest import unittest.mock from test.support import (verbose, refcount_test, - cpython_only, requires_subprocess) + cpython_only, requires_subprocess, Py_GIL_DISABLED) from test.support.import_helper import import_module from test.support.os_helper import temp_dir, TESTFN, unlink from test.support.script_helper import assert_python_ok, make_script @@ -815,6 +815,15 @@ def test_freeze(self): self.assertEqual(gc.get_freeze_count(), 0) def test_get_objects(self): + gc.collect() + l = [] + l.append(l) + self.assertTrue( + any(l is element for element in gc.get_objects()) + ) + + @unittest.skipIf(Py_GIL_DISABLED, 'need generational GC') + def test_get_objects_generations(self): gc.collect() l = [] l.append(l) @@ -1225,7 +1234,7 @@ def test_refcount_errors(self): p.stderr.close() # Verify that stderr has a useful error message: self.assertRegex(stderr, - br'gc\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.') + br'gc.*\.c:[0-9]+: .*: Assertion "gc_get_refs\(.+\) .*" failed.') self.assertRegex(stderr, br'refcount is too small') # "address : 0x7fb5062efc18" diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 936edea3cad70c6..9e28b936e00bd57 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -3652,10 +3652,8 @@ def _check_create_at_shutdown(self, **kwargs): codecs.lookup('utf-8') class C: - def __init__(self): - self.buf = io.BytesIO() def __del__(self): - io.TextIOWrapper(self.buf, **{kwargs}) + io.TextIOWrapper(io.BytesIO(), **{kwargs}) print("ok") c = C() """.format(iomod=iomod, kwargs=kwargs) diff --git a/Makefile.pre.in b/Makefile.pre.in index 21b122ae0fcd9fd..37a8b06987c7107 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -439,6 +439,7 @@ PYTHON_OBJS= \ Python/modsupport.o \ Python/mysnprintf.o \ Python/mystrtoul.o \ + Python/object_stack.o \ Python/optimizer.o \ Python/optimizer_analysis.o \ Python/parking_lot.o \ @@ -1832,6 +1833,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_frame.h \ $(srcdir)/Include/internal/pycore_freelist.h \ $(srcdir)/Include/internal/pycore_function.h \ + $(srcdir)/Include/internal/pycore_gc.h \ $(srcdir)/Include/internal/pycore_genobject.h \ $(srcdir)/Include/internal/pycore_getopt.h \ $(srcdir)/Include/internal/pycore_gil.h \ @@ -1853,6 +1855,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_namespace.h \ $(srcdir)/Include/internal/pycore_object.h \ $(srcdir)/Include/internal/pycore_object_alloc.h \ + $(srcdir)/Include/internal/pycore_object_stack.h \ $(srcdir)/Include/internal/pycore_object_state.h \ $(srcdir)/Include/internal/pycore_obmalloc.h \ $(srcdir)/Include/internal/pycore_obmalloc_init.h \ diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst new file mode 100644 index 000000000000000..b3aa43801da488a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-18-20-20-37.gh-issue-112529.oVNvDG.rst @@ -0,0 +1,3 @@ +The free-threaded build now has its own thread-safe GC implementation that +uses mimalloc to find GC tracked objects. It is non-generational, unlike the +existing GC implementation. diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 610581bc96cb1a2..dde801fc0fd525e 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -230,6 +230,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 3141913c0438695..90ccb954b424bc1 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -289,6 +289,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 57275fb2039ee0b..e0b9fc137457a08 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -591,6 +591,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 51cbb079b5b550b..fd79436f5add970 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1355,6 +1355,9 @@ Python + + Python + Python diff --git a/Python/gc.c b/Python/gc.c index 14870505ef1308e..466467602915264 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -15,6 +15,8 @@ #include "pycore_weakref.h" // _PyWeakref_ClearRef() #include "pydtrace.h" +#ifndef Py_GIL_DISABLED + typedef struct _gc_runtime_state GCState; #ifdef Py_DEBUG @@ -964,10 +966,10 @@ finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable) PyGC_Head *gc = GC_NEXT(collectable); PyObject *op = FROM_GC(gc); gc_list_move(gc, &seen); - if (!_PyGCHead_FINALIZED(gc) && + if (!_PyGC_FINALIZED(op) && (finalize = Py_TYPE(op)->tp_finalize) != NULL) { - _PyGCHead_SET_FINALIZED(gc); + _PyGC_SET_FINALIZED(op); Py_INCREF(op); finalize(op); assert(!_PyErr_Occurred(tstate)); @@ -1942,3 +1944,5 @@ PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) done: gcstate->enabled = origenstate; } + +#endif // Py_GIL_DISABLED diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 207a43b68d21f58..f2cd84981461a4f 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1,9 +1,1704 @@ +// Cyclic garbage collector implementation for free-threaded build. #include "Python.h" -#include "pycore_pystate.h" // _PyFreeListState_GET() -#include "pycore_tstate.h" // _PyThreadStateImpl +#include "pycore_ceval.h" // _Py_set_eval_breaker_bit() +#include "pycore_context.h" +#include "pycore_dict.h" // _PyDict_MaybeUntrack() +#include "pycore_initconfig.h" +#include "pycore_interp.h" // PyInterpreterState.gc +#include "pycore_object.h" +#include "pycore_object_alloc.h" // _PyObject_MallocWithType() +#include "pycore_object_stack.h" +#include "pycore_pyerrors.h" +#include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_tstate.h" // _PyThreadStateImpl +#include "pycore_weakref.h" // _PyWeakref_ClearRef() +#include "pydtrace.h" #ifdef Py_GIL_DISABLED +typedef struct _gc_runtime_state GCState; + +#ifdef Py_DEBUG +# define GC_DEBUG +#endif + +// Automatically choose the generation that needs collecting. +#define GENERATION_AUTO (-1) + +// A linked-list of objects using the `ob_tid` field as the next pointer. +struct worklist { + uintptr_t head; +}; + +struct worklist_iter { + uintptr_t *ptr; // pointer to current object + uintptr_t *next; // next value of ptr +}; + +struct visitor_args { + size_t offset; // offset of PyObject from start of block +}; + +// Per-collection state +struct collection_state { + struct visitor_args base; + PyInterpreterState *interp; + GCState *gcstate; + Py_ssize_t collected; + Py_ssize_t uncollectable; + struct worklist unreachable; + struct worklist legacy_finalizers; + struct worklist wrcb_to_call; + struct worklist objs_to_decref; +}; + +// iterate over a worklist +#define WORKSTACK_FOR_EACH(stack, op) \ + for ((op) = (PyObject *)(stack)->head; (op) != NULL; (op) = (PyObject *)(op)->ob_tid) + +// iterate over a worklist with support for removing the current object +#define WORKSTACK_FOR_EACH_ITER(stack, iter, op) \ + for (worklist_iter_init((iter), &(stack)->head), (op) = (PyObject *)(*(iter)->ptr); \ + (op) != NULL; \ + worklist_iter_init((iter), (iter)->next), (op) = (PyObject *)(*(iter)->ptr)) + +static void +worklist_push(struct worklist *worklist, PyObject *op) +{ + assert(op->ob_tid == 0); + op->ob_tid = worklist->head; + worklist->head = (uintptr_t)op; +} + +static PyObject * +worklist_pop(struct worklist *worklist) +{ + PyObject *op = (PyObject *)worklist->head; + if (op != NULL) { + worklist->head = op->ob_tid; + op->ob_tid = 0; + } + return op; +} + +static void +worklist_iter_init(struct worklist_iter *iter, uintptr_t *next) +{ + iter->ptr = next; + PyObject *op = (PyObject *)*(iter->ptr); + if (op) { + iter->next = &op->ob_tid; + } +} + +static void +worklist_remove(struct worklist_iter *iter) +{ + PyObject *op = (PyObject *)*(iter->ptr); + *(iter->ptr) = op->ob_tid; + op->ob_tid = 0; + iter->next = iter->ptr; +} + +static inline int +gc_is_unreachable(PyObject *op) +{ + return (op->ob_gc_bits & _PyGC_BITS_UNREACHABLE) != 0; +} + +static void +gc_set_unreachable(PyObject *op) +{ + op->ob_gc_bits |= _PyGC_BITS_UNREACHABLE; +} + +static void +gc_clear_unreachable(PyObject *op) +{ + op->ob_gc_bits &= ~_PyGC_BITS_UNREACHABLE; +} + +// Initialize the `ob_tid` field to zero if the object is not already +// initialized as unreachable. +static void +gc_maybe_init_refs(PyObject *op) +{ + if (!gc_is_unreachable(op)) { + gc_set_unreachable(op); + op->ob_tid = 0; + } +} + +static inline Py_ssize_t +gc_get_refs(PyObject *op) +{ + return (Py_ssize_t)op->ob_tid; +} + +static inline void +gc_add_refs(PyObject *op, Py_ssize_t refs) +{ + assert(_PyObject_GC_IS_TRACKED(op)); + op->ob_tid += refs; +} + +static inline void +gc_decref(PyObject *op) +{ + op->ob_tid -= 1; +} + +// Merge refcounts while the world is stopped. +static void +merge_refcount(PyObject *op, Py_ssize_t extra) +{ + assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); + + Py_ssize_t refcount = Py_REFCNT(op); + refcount += extra; + +#ifdef Py_REF_DEBUG + _Py_AddRefTotal(_PyInterpreterState_GET(), extra); +#endif + + // No atomics necessary; all other threads in this interpreter are paused. + op->ob_tid = 0; + op->ob_ref_local = 0; + op->ob_ref_shared = _Py_REF_SHARED(refcount, _Py_REF_MERGED); +} + +static void +gc_restore_tid(PyObject *op) +{ + mi_segment_t *segment = _mi_ptr_segment(op); + if (_Py_REF_IS_MERGED(op->ob_ref_shared)) { + op->ob_tid = 0; + } + else { + // NOTE: may change ob_tid if the object was re-initialized by + // a different thread or its segment was abandoned and reclaimed. + // The segment thread id might be zero, in which case we should + // ensure the refcounts are now merged. + op->ob_tid = segment->thread_id; + if (op->ob_tid == 0) { + merge_refcount(op, 0); + } + } +} + +static void +gc_restore_refs(PyObject *op) +{ + if (gc_is_unreachable(op)) { + gc_restore_tid(op); + gc_clear_unreachable(op); + } +} + +// Given a mimalloc memory block return the PyObject stored in it or NULL if +// the block is not allocated or the object is not tracked or is immortal. +static PyObject * +op_from_block(void *block, void *arg, bool include_frozen) +{ + struct visitor_args *a = arg; + if (block == NULL) { + return NULL; + } + PyObject *op = (PyObject *)((char*)block + a->offset); + assert(PyObject_IS_GC(op)); + if (!_PyObject_GC_IS_TRACKED(op)) { + return NULL; + } + if (!include_frozen && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) { + return NULL; + } + return op; +} + +static int +gc_visit_heaps_lock_held(PyInterpreterState *interp, mi_block_visit_fun *visitor, + struct visitor_args *arg) +{ + // Offset of PyObject header from start of memory block. + Py_ssize_t offset_base = sizeof(PyGC_Head); + if (_PyMem_DebugEnabled()) { + // The debug allocator adds two words at the beginning of each block. + offset_base += 2 * sizeof(size_t); + } + + // Objects with Py_TPFLAGS_PREHEADER have two extra fields + Py_ssize_t offset_pre = offset_base + 2 * sizeof(PyObject*); + + // visit each thread's heaps for GC objects + for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { + struct _mimalloc_thread_state *m = &((_PyThreadStateImpl *)p)->mimalloc; + + arg->offset = offset_base; + if (!mi_heap_visit_blocks(&m->heaps[_Py_MIMALLOC_HEAP_GC], true, + visitor, arg)) { + return -1; + } + arg->offset = offset_pre; + if (!mi_heap_visit_blocks(&m->heaps[_Py_MIMALLOC_HEAP_GC_PRE], true, + visitor, arg)) { + return -1; + } + } + + // visit blocks in the per-interpreter abandoned pool (from dead threads) + mi_abandoned_pool_t *pool = &interp->mimalloc.abandoned_pool; + arg->offset = offset_base; + if (!_mi_abandoned_pool_visit_blocks(pool, _Py_MIMALLOC_HEAP_GC, true, + visitor, arg)) { + return -1; + } + arg->offset = offset_pre; + if (!_mi_abandoned_pool_visit_blocks(pool, _Py_MIMALLOC_HEAP_GC_PRE, true, + visitor, arg)) { + return -1; + } + return 0; +} + +// Visits all GC objects in the interpreter's heaps. +// NOTE: It is not safe to allocate or free any mimalloc managed memory while +// this function is running. +static int +gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor, + struct visitor_args *arg) +{ + // Other threads in the interpreter must be paused so that we can safely + // traverse their heaps. + assert(interp->stoptheworld.world_stopped); + + int err; + HEAD_LOCK(&_PyRuntime); + err = gc_visit_heaps_lock_held(interp, visitor, arg); + HEAD_UNLOCK(&_PyRuntime); + return err; +} + +// Subtract an incoming reference from the computed "gc_refs" refcount. +static int +visit_decref(PyObject *op, void *arg) +{ + if (_PyObject_GC_IS_TRACKED(op) && !_Py_IsImmortal(op)) { + // If update_refs hasn't reached this object yet, mark it + // as (tentatively) unreachable and initialize ob_tid to zero. + gc_maybe_init_refs(op); + gc_decref(op); + } + return 0; +} + +// Compute the number of external references to objects in the heap +// by subtracting internal references from the refcount. The difference is +// computed in the ob_tid field (we restore it later). +static bool +update_refs(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, false); + if (op == NULL) { + return true; + } + + // Exclude immortal objects from garbage collection + if (_Py_IsImmortal(op)) { + op->ob_tid = 0; + _PyObject_GC_UNTRACK(op); + gc_clear_unreachable(op); + return true; + } + + // Untrack tuples and dicts as necessary in this pass. + if (PyTuple_CheckExact(op)) { + _PyTuple_MaybeUntrack(op); + if (!_PyObject_GC_IS_TRACKED(op)) { + gc_restore_refs(op); + return true; + } + } + else if (PyDict_CheckExact(op)) { + _PyDict_MaybeUntrack(op); + if (!_PyObject_GC_IS_TRACKED(op)) { + gc_restore_refs(op); + return true; + } + } + + Py_ssize_t refcount = Py_REFCNT(op); + _PyObject_ASSERT(op, refcount >= 0); + + // Add the actual refcount to ob_tid. + gc_maybe_init_refs(op); + gc_add_refs(op, refcount); + + // Subtract internal references from ob_tid. Objects with ob_tid > 0 + // are directly reachable from outside containers, and so can't be + // collected. + Py_TYPE(op)->tp_traverse(op, visit_decref, NULL); + return true; +} + +static int +visit_clear_unreachable(PyObject *op, _PyObjectStack *stack) +{ + if (gc_is_unreachable(op)) { + _PyObject_ASSERT(op, _PyObject_GC_IS_TRACKED(op)); + gc_clear_unreachable(op); + return _PyObjectStack_Push(stack, op); + } + return 0; +} + +// Transitively clear the unreachable bit on all objects reachable from op. +static int +mark_reachable(PyObject *op) +{ + _PyObjectStack stack = { NULL }; + do { + traverseproc traverse = Py_TYPE(op)->tp_traverse; + if (traverse(op, (visitproc)&visit_clear_unreachable, &stack) < 0) { + _PyObjectStack_Clear(&stack); + return -1; + } + op = _PyObjectStack_Pop(&stack); + } while (op != NULL); + return 0; +} + +#ifdef GC_DEBUG +static bool +validate_gc_objects(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, false); + if (op == NULL) { + return true; + } + + _PyObject_ASSERT(op, gc_is_unreachable(op)); + _PyObject_ASSERT_WITH_MSG(op, gc_get_refs(op) >= 0, + "refcount is too small"); + return true; +} +#endif + +static bool +mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, false); + if (op == NULL) { + return true; + } + + if (gc_is_unreachable(op) && gc_get_refs(op) != 0) { + // Object is reachable but currently marked as unreachable. + // Mark it as reachable and traverse its pointers to find + // any other object that may be directly reachable from it. + gc_clear_unreachable(op); + + // Transitively mark reachable objects by clearing the unreachable flag. + if (mark_reachable(op) < 0) { + return false; + } + } + + return true; +} + +/* Return true if object has a pre-PEP 442 finalization method. */ +static int +has_legacy_finalizer(PyObject *op) +{ + return Py_TYPE(op)->tp_del != NULL; +} + +static bool +scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, false); + if (op == NULL) { + return true; + } + + struct collection_state *state = (struct collection_state *)args; + if (gc_is_unreachable(op)) { + // Merge and add one to the refcount to prevent deallocation while we + // are holding on to it in a worklist. + merge_refcount(op, 1); + + if (has_legacy_finalizer(op)) { + // would be unreachable, but has legacy finalizer + gc_clear_unreachable(op); + worklist_push(&state->legacy_finalizers, op); + } + else { + worklist_push(&state->unreachable, op); + } + } + else { + // object is reachable, restore `ob_tid`; we're done with these objects + gc_restore_tid(op); + state->gcstate->long_lived_total++; + } + + return true; +} + +static int +move_legacy_finalizer_reachable(struct collection_state *state); + +static int +deduce_unreachable_heap(PyInterpreterState *interp, + struct collection_state *state) +{ + // Identify objects that are directly reachable from outside the GC heap + // by computing the difference between the refcount and the number of + // incoming references. + gc_visit_heaps(interp, &update_refs, &state->base); + +#ifdef GC_DEBUG + // Check that all objects are marked as unreachable and that the computed + // reference count difference (stored in `ob_tid`) is non-negative. + gc_visit_heaps(interp, &validate_gc_objects, &state->base); +#endif + + // Transitively mark reachable objects by clearing the + // _PyGC_BITS_UNREACHABLE flag. + if (gc_visit_heaps(interp, &mark_heap_visitor, &state->base) < 0) { + return -1; + } + + // Identify remaining unreachable objects and push them onto a stack. + // Restores ob_tid for reachable objects. + gc_visit_heaps(interp, &scan_heap_visitor, &state->base); + + if (state->legacy_finalizers.head) { + // There may be objects reachable from legacy finalizers that are in + // the unreachable set. We need to mark them as reachable. + if (move_legacy_finalizer_reachable(state) < 0) { + return -1; + } + } + + return 0; +} + +static int +move_legacy_finalizer_reachable(struct collection_state *state) +{ + // Clear the reachable bit on all objects transitively reachable + // from the objects with legacy finalizers. + PyObject *op; + WORKSTACK_FOR_EACH(&state->legacy_finalizers, op) { + if (mark_reachable(op) < 0) { + return -1; + } + } + + // Move the reachable objects from the unreachable worklist to the legacy + // finalizer worklist. + struct worklist_iter iter; + WORKSTACK_FOR_EACH_ITER(&state->unreachable, &iter, op) { + if (!gc_is_unreachable(op)) { + worklist_remove(&iter); + worklist_push(&state->legacy_finalizers, op); + } + } + + return 0; +} + +// Clear all weakrefs to unreachable objects. Weakrefs with callbacks are +// enqueued in `wrcb_to_call`, but not invoked yet. +static void +clear_weakrefs(struct collection_state *state) +{ + PyObject *op; + WORKSTACK_FOR_EACH(&state->unreachable, op) { + if (PyWeakref_Check(op)) { + // Clear weakrefs that are themselves unreachable to ensure their + // callbacks will not be executed later from a `tp_clear()` + // inside delete_garbage(). That would be unsafe: it could + // resurrect a dead object or access a an already cleared object. + // See bpo-38006 for one example. + _PyWeakref_ClearRef((PyWeakReference *)op); + } + + if (!_PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) { + continue; + } + + // NOTE: This is never triggered for static types so we can avoid the + // (slightly) more costly _PyObject_GET_WEAKREFS_LISTPTR(). + PyWeakReference **wrlist = _PyObject_GET_WEAKREFS_LISTPTR_FROM_OFFSET(op); + + // `op` may have some weakrefs. March over the list, clear + // all the weakrefs, and enqueue the weakrefs with callbacks + // that must be called into wrcb_to_call. + for (PyWeakReference *wr = *wrlist; wr != NULL; wr = *wrlist) { + // _PyWeakref_ClearRef clears the weakref but leaves + // the callback pointer intact. Obscure: it also + // changes *wrlist. + _PyObject_ASSERT((PyObject *)wr, wr->wr_object == op); + _PyWeakref_ClearRef(wr); + _PyObject_ASSERT((PyObject *)wr, wr->wr_object == Py_None); + + // We do not invoke callbacks for weakrefs that are themselves + // unreachable. This is partly for historical reasons: weakrefs + // predate safe object finalization, and a weakref that is itself + // unreachable may have a callback that resurrects other + // unreachable objects. + if (wr->wr_callback == NULL || gc_is_unreachable((PyObject *)wr)) { + continue; + } + + // Create a new reference so that wr can't go away before we can + // process it again. + merge_refcount((PyObject *)wr, 1); + + // Enqueue weakref to be called later. + worklist_push(&state->wrcb_to_call, (PyObject *)wr); + } + } +} + +static void +call_weakref_callbacks(struct collection_state *state) +{ + // Invoke the callbacks we decided to honor. + PyObject *op; + while ((op = worklist_pop(&state->wrcb_to_call)) != NULL) { + _PyObject_ASSERT(op, PyWeakref_Check(op)); + + PyWeakReference *wr = (PyWeakReference *)op; + PyObject *callback = wr->wr_callback; + _PyObject_ASSERT(op, callback != NULL); + + /* copy-paste of weakrefobject.c's handle_callback() */ + PyObject *temp = PyObject_CallOneArg(callback, (PyObject *)wr); + if (temp == NULL) { + PyErr_WriteUnraisable(callback); + } + else { + Py_DECREF(temp); + } + + gc_restore_tid(op); + Py_DECREF(op); // drop worklist reference + } +} + + +static GCState * +get_gc_state(void) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + return &interp->gc; +} + + +void +_PyGC_InitState(GCState *gcstate) +{ +} + + +PyStatus +_PyGC_Init(PyInterpreterState *interp) +{ + GCState *gcstate = &interp->gc; + + gcstate->garbage = PyList_New(0); + if (gcstate->garbage == NULL) { + return _PyStatus_NO_MEMORY(); + } + + gcstate->callbacks = PyList_New(0); + if (gcstate->callbacks == NULL) { + return _PyStatus_NO_MEMORY(); + } + + return _PyStatus_OK(); +} + +static void +debug_cycle(const char *msg, PyObject *op) +{ + PySys_FormatStderr("gc: %s <%s %p>\n", + msg, Py_TYPE(op)->tp_name, op); +} + +/* Run first-time finalizers (if any) on all the objects in collectable. + * Note that this may remove some (or even all) of the objects from the + * list, due to refcounts falling to 0. + */ +static void +finalize_garbage(struct collection_state *state) +{ + // NOTE: the unreachable worklist holds a strong reference to the object + // to prevent it from being deallocated while we are holding on to it. + PyObject *op; + WORKSTACK_FOR_EACH(&state->unreachable, op) { + if (!_PyGC_FINALIZED(op)) { + destructor finalize = Py_TYPE(op)->tp_finalize; + if (finalize != NULL) { + _PyGC_SET_FINALIZED(op); + finalize(op); + assert(!_PyErr_Occurred(_PyThreadState_GET())); + } + } + } +} + +// Break reference cycles by clearing the containers involved. +static void +delete_garbage(struct collection_state *state) +{ + PyThreadState *tstate = _PyThreadState_GET(); + GCState *gcstate = state->gcstate; + + assert(!_PyErr_Occurred(tstate)); + + PyObject *op; + while ((op = worklist_pop(&state->objs_to_decref)) != NULL) { + Py_DECREF(op); + } + + while ((op = worklist_pop(&state->unreachable)) != NULL) { + _PyObject_ASSERT(op, gc_is_unreachable(op)); + + // Clear the unreachable flag. + gc_clear_unreachable(op); + + if (!_PyObject_GC_IS_TRACKED(op)) { + // Object might have been untracked by some other tp_clear() call. + Py_DECREF(op); // drop the reference from the worklist + continue; + } + + state->collected++; + + if (gcstate->debug & _PyGC_DEBUG_SAVEALL) { + assert(gcstate->garbage != NULL); + if (PyList_Append(gcstate->garbage, op) < 0) { + _PyErr_Clear(tstate); + } + } + else { + inquiry clear = Py_TYPE(op)->tp_clear; + if (clear != NULL) { + (void) clear(op); + if (_PyErr_Occurred(tstate)) { + PyErr_FormatUnraisable("Exception ignored in tp_clear of %s", + Py_TYPE(op)->tp_name); + } + } + } + + Py_DECREF(op); // drop the reference from the worklist + } +} + +static void +handle_legacy_finalizers(struct collection_state *state) +{ + GCState *gcstate = state->gcstate; + assert(gcstate->garbage != NULL); + + PyObject *op; + while ((op = worklist_pop(&state->legacy_finalizers)) != NULL) { + state->uncollectable++; + + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { + debug_cycle("uncollectable", op); + } + + if ((gcstate->debug & _PyGC_DEBUG_SAVEALL) || has_legacy_finalizer(op)) { + if (PyList_Append(gcstate->garbage, op) < 0) { + PyErr_Clear(); + } + } + Py_DECREF(op); // drop worklist reference + } +} + +// Show stats for objects in each generations +static void +show_stats_each_generations(GCState *gcstate) +{ + // TODO +} + +// Traversal callback for handle_resurrected_objects. +static int +visit_decref_unreachable(PyObject *op, void *data) +{ + if (gc_is_unreachable(op) && _PyObject_GC_IS_TRACKED(op)) { + op->ob_ref_local -= 1; + } + return 0; +} + +// Handle objects that may have resurrected after a call to 'finalize_garbage'. +static int +handle_resurrected_objects(struct collection_state *state) +{ + // First, find externally reachable objects by computing the reference + // count difference in ob_ref_local. We can't use ob_tid here because + // that's already used to store the unreachable worklist. + PyObject *op; + struct worklist_iter iter; + WORKSTACK_FOR_EACH_ITER(&state->unreachable, &iter, op) { + assert(gc_is_unreachable(op)); + assert(_Py_REF_IS_MERGED(op->ob_ref_shared)); + + if (!_PyObject_GC_IS_TRACKED(op)) { + // Object was untracked by a finalizer. Schedule it for a Py_DECREF + // after we finish with the stop-the-world pause. + gc_clear_unreachable(op); + worklist_remove(&iter); + worklist_push(&state->objs_to_decref, op); + continue; + } + + Py_ssize_t refcount = (op->ob_ref_shared >> _Py_REF_SHARED_SHIFT); + if (refcount > INT32_MAX) { + // The refcount is too big to fit in `ob_ref_local`. Mark the + // object as immortal and bail out. + gc_clear_unreachable(op); + worklist_remove(&iter); + _Py_SetImmortal(op); + continue; + } + + op->ob_ref_local += (uint32_t)refcount; + + // Subtract one to account for the reference from the worklist. + op->ob_ref_local -= 1; + + traverseproc traverse = Py_TYPE(op)->tp_traverse; + (void) traverse(op, + (visitproc)visit_decref_unreachable, + NULL); + } + + // Find resurrected objects + bool any_resurrected = false; + WORKSTACK_FOR_EACH(&state->unreachable, op) { + int32_t gc_refs = (int32_t)op->ob_ref_local; + op->ob_ref_local = 0; // restore ob_ref_local + + _PyObject_ASSERT(op, gc_refs >= 0); + + if (gc_is_unreachable(op) && gc_refs > 0) { + // Clear the unreachable flag on any transitively reachable objects + // from this one. + any_resurrected = true; + gc_clear_unreachable(op); + if (mark_reachable(op) < 0) { + return -1; + } + } + } + + if (any_resurrected) { + // Remove resurrected objects from the unreachable list. + WORKSTACK_FOR_EACH_ITER(&state->unreachable, &iter, op) { + if (!gc_is_unreachable(op)) { + _PyObject_ASSERT(op, Py_REFCNT(op) > 1); + worklist_remove(&iter); + merge_refcount(op, -1); // remove worklist reference + } + } + } + +#ifdef GC_DEBUG + WORKSTACK_FOR_EACH(&state->unreachable, op) { + _PyObject_ASSERT(op, gc_is_unreachable(op)); + _PyObject_ASSERT(op, _PyObject_GC_IS_TRACKED(op)); + _PyObject_ASSERT(op, op->ob_ref_local == 0); + _PyObject_ASSERT(op, _Py_REF_IS_MERGED(op->ob_ref_shared)); + } +#endif + + return 0; +} + + +/* Invoke progress callbacks to notify clients that garbage collection + * is starting or stopping + */ +static void +invoke_gc_callback(PyThreadState *tstate, const char *phase, + int generation, Py_ssize_t collected, + Py_ssize_t uncollectable) +{ + assert(!_PyErr_Occurred(tstate)); + + /* we may get called very early */ + GCState *gcstate = &tstate->interp->gc; + if (gcstate->callbacks == NULL) { + return; + } + + /* The local variable cannot be rebound, check it for sanity */ + assert(PyList_CheckExact(gcstate->callbacks)); + PyObject *info = NULL; + if (PyList_GET_SIZE(gcstate->callbacks) != 0) { + info = Py_BuildValue("{sisnsn}", + "generation", generation, + "collected", collected, + "uncollectable", uncollectable); + if (info == NULL) { + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; + } + } + + PyObject *phase_obj = PyUnicode_FromString(phase); + if (phase_obj == NULL) { + Py_XDECREF(info); + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; + } + + PyObject *stack[] = {phase_obj, info}; + for (Py_ssize_t i=0; icallbacks); i++) { + PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); + Py_INCREF(cb); /* make sure cb doesn't go away */ + r = PyObject_Vectorcall(cb, stack, 2, NULL); + if (r == NULL) { + PyErr_WriteUnraisable(cb); + } + else { + Py_DECREF(r); + } + Py_DECREF(cb); + } + Py_DECREF(phase_obj); + Py_XDECREF(info); + assert(!_PyErr_Occurred(tstate)); +} + + +/* Find the oldest generation (highest numbered) where the count + * exceeds the threshold. Objects in the that generation and + * generations younger than it will be collected. */ +static int +gc_select_generation(GCState *gcstate) +{ + for (int i = NUM_GENERATIONS-1; i >= 0; i--) { + if (gcstate->generations[i].count > gcstate->generations[i].threshold) { + /* Avoid quadratic performance degradation in number + of tracked objects (see also issue #4074): + + To limit the cost of garbage collection, there are two strategies; + - make each collection faster, e.g. by scanning fewer objects + - do less collections + This heuristic is about the latter strategy. + + In addition to the various configurable thresholds, we only trigger a + full collection if the ratio + + long_lived_pending / long_lived_total + + is above a given value (hardwired to 25%). + + The reason is that, while "non-full" collections (i.e., collections of + the young and middle generations) will always examine roughly the same + number of objects -- determined by the aforementioned thresholds --, + the cost of a full collection is proportional to the total number of + long-lived objects, which is virtually unbounded. + + Indeed, it has been remarked that doing a full collection every + of object creations entails a dramatic performance + degradation in workloads which consist in creating and storing lots of + long-lived objects (e.g. building a large list of GC-tracked objects would + show quadratic performance, instead of linear as expected: see issue #4074). + + Using the above ratio, instead, yields amortized linear performance in + the total number of objects (the effect of which can be summarized + thusly: "each full garbage collection is more and more costly as the + number of objects grows, but we do fewer and fewer of them"). + + This heuristic was suggested by Martin von Löwis on python-dev in + June 2008. His original analysis and proposal can be found at: + http://mail.python.org/pipermail/python-dev/2008-June/080579.html + */ + if (i == NUM_GENERATIONS - 1 + && gcstate->long_lived_pending < gcstate->long_lived_total / 4) + { + continue; + } + return i; + } + } + return -1; +} + +static void +cleanup_worklist(struct worklist *worklist) +{ + PyObject *op; + while ((op = worklist_pop(worklist)) != NULL) { + gc_restore_tid(op); + gc_clear_unreachable(op); + Py_DECREF(op); + } +} + +static void +gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) +{ + _PyEval_StopTheWorld(interp); + // Find unreachable objects + int err = deduce_unreachable_heap(interp, state); + if (err < 0) { + _PyEval_StartTheWorld(interp); + goto error; + } + + // Print debugging information. + if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) { + PyObject *op; + WORKSTACK_FOR_EACH(&state->unreachable, op) { + debug_cycle("collectable", op); + } + } + + // Clear weakrefs and enqueue callbacks (but do not call them). + clear_weakrefs(state); + _PyEval_StartTheWorld(interp); + + // Call weakref callbacks and finalizers after unpausing other threads to + // avoid potential deadlocks. + call_weakref_callbacks(state); + finalize_garbage(state); + + // Handle any objects that may have resurrected after the finalization. + _PyEval_StopTheWorld(interp); + err = handle_resurrected_objects(state); + _PyEval_StartTheWorld(interp); + + if (err < 0) { + goto error; + } + + // Call tp_clear on objects in the unreachable set. This will cause + // the reference cycles to be broken. It may also cause some objects + // to be freed. + delete_garbage(state); + + // Append objects with legacy finalizers to the "gc.garbage" list. + handle_legacy_finalizers(state); + return; + +error: + cleanup_worklist(&state->unreachable); + cleanup_worklist(&state->legacy_finalizers); + cleanup_worklist(&state->wrcb_to_call); + cleanup_worklist(&state->objs_to_decref); + PyErr_NoMemory(); + PyErr_FormatUnraisable("Out of memory during garbage collection"); +} + +/* This is the main function. Read this to understand how the + * collection process works. */ +static Py_ssize_t +gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) +{ + int i; + Py_ssize_t m = 0; /* # objects collected */ + Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ + _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ + GCState *gcstate = &tstate->interp->gc; + + // gc_collect_main() must not be called before _PyGC_Init + // or after _PyGC_Fini() + assert(gcstate->garbage != NULL); + assert(!_PyErr_Occurred(tstate)); + + int expected = 0; + if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { + // Don't start a garbage collection if one is already in progress. + return 0; + } + + if (generation == GENERATION_AUTO) { + // Select the oldest generation that needs collecting. We will collect + // objects from that generation and all generations younger than it. + generation = gc_select_generation(gcstate); + if (generation < 0) { + // No generation needs to be collected. + _Py_atomic_store_int(&gcstate->collecting, 0); + return 0; + } + } + + assert(generation >= 0 && generation < NUM_GENERATIONS); + +#ifdef Py_STATS + if (_Py_stats) { + _Py_stats->object_stats.object_visits = 0; + } +#endif + GC_STAT_ADD(generation, collections, 1); + + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "start", generation, 0, 0); + } + + if (gcstate->debug & _PyGC_DEBUG_STATS) { + PySys_WriteStderr("gc: collecting generation %d...\n", generation); + show_stats_each_generations(gcstate); + t1 = _PyTime_GetPerfCounter(); + } + + if (PyDTrace_GC_START_ENABLED()) { + PyDTrace_GC_START(generation); + } + + /* update collection and allocation counters */ + if (generation+1 < NUM_GENERATIONS) { + gcstate->generations[generation+1].count += 1; + } + for (i = 0; i <= generation; i++) { + gcstate->generations[i].count = 0; + } + + PyInterpreterState *interp = tstate->interp; + + struct collection_state state = { + .interp = interp, + .gcstate = gcstate, + }; + + gc_collect_internal(interp, &state); + + m = state.collected; + n = state.uncollectable; + + if (gcstate->debug & _PyGC_DEBUG_STATS) { + double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + PySys_WriteStderr( + "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", + n+m, n, d); + } + + // Clear free lists in all threads + _PyGC_ClearAllFreeLists(interp); + + if (_PyErr_Occurred(tstate)) { + if (reason == _Py_GC_REASON_SHUTDOWN) { + _PyErr_Clear(tstate); + } + else { + PyErr_FormatUnraisable("Exception ignored in garbage collection"); + } + } + + /* Update stats */ + struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; + stats->collections++; + stats->collected += m; + stats->uncollectable += n; + + GC_STAT_ADD(generation, objects_collected, m); +#ifdef Py_STATS + if (_Py_stats) { + GC_STAT_ADD(generation, object_visits, + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; + } +#endif + + if (PyDTrace_GC_DONE_ENABLED()) { + PyDTrace_GC_DONE(n + m); + } + + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "stop", generation, m, n); + } + + assert(!_PyErr_Occurred(tstate)); + _Py_atomic_store_int(&gcstate->collecting, 0); + return n + m; +} + +struct get_referrers_args { + struct visitor_args base; + PyObject *objs; + struct worklist results; +}; + +static int +referrersvisit(PyObject* obj, void *arg) +{ + PyObject *objs = arg; + Py_ssize_t i; + for (i = 0; i < PyTuple_GET_SIZE(objs); i++) { + if (PyTuple_GET_ITEM(objs, i) == obj) { + return 1; + } + } + return 0; +} + +static bool +visit_get_referrers(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, true); + if (op == NULL) { + return true; + } + + struct get_referrers_args *arg = (struct get_referrers_args *)args; + if (Py_TYPE(op)->tp_traverse(op, referrersvisit, arg->objs)) { + op->ob_tid = 0; // we will restore the refcount later + worklist_push(&arg->results, op); + } + + return true; +} + +PyObject * +_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs) +{ + PyObject *result = PyList_New(0); + if (!result) { + return NULL; + } + + _PyEval_StopTheWorld(interp); + + // Append all objects to a worklist. This abuses ob_tid. We will restore + // it later. NOTE: We can't append to the PyListObject during + // gc_visit_heaps() because PyList_Append() may reclaim an abandoned + // mimalloc segments while we are traversing them. + struct get_referrers_args args = { .objs = objs }; + gc_visit_heaps(interp, &visit_get_referrers, &args.base); + + bool error = false; + PyObject *op; + while ((op = worklist_pop(&args.results)) != NULL) { + gc_restore_tid(op); + if (op != objs && PyList_Append(result, op) < 0) { + error = true; + break; + } + } + + // In case of error, clear the remaining worklist + while ((op = worklist_pop(&args.results)) != NULL) { + gc_restore_tid(op); + } + + _PyEval_StartTheWorld(interp); + + if (error) { + Py_DECREF(result); + return NULL; + } + + return result; +} + +struct get_objects_args { + struct visitor_args base; + struct worklist objects; +}; + +static bool +visit_get_objects(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, true); + if (op == NULL) { + return true; + } + + struct get_objects_args *arg = (struct get_objects_args *)args; + op->ob_tid = 0; // we will restore the refcount later + worklist_push(&arg->objects, op); + + return true; +} + +PyObject * +_PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) +{ + PyObject *result = PyList_New(0); + if (!result) { + return NULL; + } + + _PyEval_StopTheWorld(interp); + + // Append all objects to a worklist. This abuses ob_tid. We will restore + // it later. NOTE: We can't append to the list during gc_visit_heaps() + // because PyList_Append() may reclaim an abandoned mimalloc segment + // while we are traversing it. + struct get_objects_args args = { 0 }; + gc_visit_heaps(interp, &visit_get_objects, &args.base); + + bool error = false; + PyObject *op; + while ((op = worklist_pop(&args.objects)) != NULL) { + gc_restore_tid(op); + if (op != result && PyList_Append(result, op) < 0) { + error = true; + break; + } + } + + // In case of error, clear the remaining worklist + while ((op = worklist_pop(&args.objects)) != NULL) { + gc_restore_tid(op); + } + + _PyEval_StartTheWorld(interp); + + if (error) { + Py_DECREF(result); + return NULL; + } + + return result; +} + +static bool +visit_freeze(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, true); + if (op != NULL) { + op->ob_gc_bits |= _PyGC_BITS_FROZEN; + } + return true; +} + +void +_PyGC_Freeze(PyInterpreterState *interp) +{ + struct visitor_args args; + _PyEval_StopTheWorld(interp); + gc_visit_heaps(interp, &visit_freeze, &args); + _PyEval_StartTheWorld(interp); +} + +static bool +visit_unfreeze(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, true); + if (op != NULL) { + op->ob_gc_bits &= ~_PyGC_BITS_FROZEN; + } + return true; +} + +void +_PyGC_Unfreeze(PyInterpreterState *interp) +{ + struct visitor_args args; + _PyEval_StopTheWorld(interp); + gc_visit_heaps(interp, &visit_unfreeze, &args); + _PyEval_StartTheWorld(interp); +} + +struct count_frozen_args { + struct visitor_args base; + Py_ssize_t count; +}; + +static bool +visit_count_frozen(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, true); + if (op != NULL && (op->ob_gc_bits & _PyGC_BITS_FROZEN) != 0) { + struct count_frozen_args *arg = (struct count_frozen_args *)args; + arg->count++; + } + return true; +} + +Py_ssize_t +_PyGC_GetFreezeCount(PyInterpreterState *interp) +{ + struct count_frozen_args args = { .count = 0 }; + _PyEval_StopTheWorld(interp); + gc_visit_heaps(interp, &visit_count_frozen, &args.base); + _PyEval_StartTheWorld(interp); + return args.count; +} + +/* C API for controlling the state of the garbage collector */ +int +PyGC_Enable(void) +{ + GCState *gcstate = get_gc_state(); + int old_state = gcstate->enabled; + gcstate->enabled = 1; + return old_state; +} + +int +PyGC_Disable(void) +{ + GCState *gcstate = get_gc_state(); + int old_state = gcstate->enabled; + gcstate->enabled = 0; + return old_state; +} + +int +PyGC_IsEnabled(void) +{ + GCState *gcstate = get_gc_state(); + return gcstate->enabled; +} + +/* Public API to invoke gc.collect() from C */ +Py_ssize_t +PyGC_Collect(void) +{ + PyThreadState *tstate = _PyThreadState_GET(); + GCState *gcstate = &tstate->interp->gc; + + if (!gcstate->enabled) { + return 0; + } + + Py_ssize_t n; + PyObject *exc = _PyErr_GetRaisedException(tstate); + n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); + _PyErr_SetRaisedException(tstate, exc); + + return n; +} + +Py_ssize_t +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +{ + return gc_collect_main(tstate, generation, reason); +} + +Py_ssize_t +_PyGC_CollectNoFail(PyThreadState *tstate) +{ + /* Ideally, this function is only called on interpreter shutdown, + and therefore not recursively. Unfortunately, when there are daemon + threads, a daemon thread can start a cyclic garbage collection + during interpreter shutdown (and then never finish it). + See http://bugs.python.org/issue8713#msg195178 for an example. + */ + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); +} + +void +_PyGC_DumpShutdownStats(PyInterpreterState *interp) +{ + GCState *gcstate = &interp->gc; + if (!(gcstate->debug & _PyGC_DEBUG_SAVEALL) + && gcstate->garbage != NULL && PyList_GET_SIZE(gcstate->garbage) > 0) { + const char *message; + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { + message = "gc: %zd uncollectable objects at shutdown"; + } + else { + message = "gc: %zd uncollectable objects at shutdown; " \ + "use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them"; + } + /* PyErr_WarnFormat does too many things and we are at shutdown, + the warnings module's dependencies (e.g. linecache) may be gone + already. */ + if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0, + "gc", NULL, message, + PyList_GET_SIZE(gcstate->garbage))) + { + PyErr_WriteUnraisable(NULL); + } + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { + PyObject *repr = NULL, *bytes = NULL; + repr = PyObject_Repr(gcstate->garbage); + if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) { + PyErr_WriteUnraisable(gcstate->garbage); + } + else { + PySys_WriteStderr( + " %s\n", + PyBytes_AS_STRING(bytes) + ); + } + Py_XDECREF(repr); + Py_XDECREF(bytes); + } + } +} + + +void +_PyGC_Fini(PyInterpreterState *interp) +{ + GCState *gcstate = &interp->gc; + Py_CLEAR(gcstate->garbage); + Py_CLEAR(gcstate->callbacks); + + /* We expect that none of this interpreters objects are shared + with other interpreters. + See https://github.com/python/cpython/issues/90228. */ +} + +/* for debugging */ + +#ifdef Py_DEBUG +static int +visit_validate(PyObject *op, void *parent_raw) +{ + PyObject *parent = _PyObject_CAST(parent_raw); + if (_PyObject_IsFreed(op)) { + _PyObject_ASSERT_FAILED_MSG(parent, + "PyObject_GC_Track() object is not valid"); + } + return 0; +} +#endif + + +/* extension modules might be compiled with GC support so these + functions must always be available */ + +void +PyObject_GC_Track(void *op_raw) +{ + PyObject *op = _PyObject_CAST(op_raw); + if (_PyObject_GC_IS_TRACKED(op)) { + _PyObject_ASSERT_FAILED_MSG(op, + "object already tracked " + "by the garbage collector"); + } + _PyObject_GC_TRACK(op); + +#ifdef Py_DEBUG + /* Check that the object is valid: validate objects traversed + by tp_traverse() */ + traverseproc traverse = Py_TYPE(op)->tp_traverse; + (void)traverse(op, visit_validate, op); +#endif +} + +void +PyObject_GC_UnTrack(void *op_raw) +{ + PyObject *op = _PyObject_CAST(op_raw); + /* Obscure: the Py_TRASHCAN mechanism requires that we be able to + * call PyObject_GC_UnTrack twice on an object. + */ + if (_PyObject_GC_IS_TRACKED(op)) { + _PyObject_GC_UNTRACK(op); + } +} + +int +PyObject_IS_GC(PyObject *obj) +{ + return _PyObject_IS_GC(obj); +} + +void +_Py_ScheduleGC(PyInterpreterState *interp) +{ + _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 1); +} + +void +_PyObject_GC_Link(PyObject *op) +{ + PyThreadState *tstate = _PyThreadState_GET(); + GCState *gcstate = &tstate->interp->gc; + gcstate->generations[0].count++; /* number of allocated GC objects */ + if (gcstate->generations[0].count > gcstate->generations[0].threshold && + gcstate->enabled && + gcstate->generations[0].threshold && + !_Py_atomic_load_int_relaxed(&gcstate->collecting) && + !_PyErr_Occurred(tstate)) + { + _Py_ScheduleGC(tstate->interp); + } +} + +void +_Py_RunGC(PyThreadState *tstate) +{ + gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); +} + +static PyObject * +gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize) +{ + PyThreadState *tstate = _PyThreadState_GET(); + if (basicsize > PY_SSIZE_T_MAX - presize) { + return _PyErr_NoMemory(tstate); + } + size_t size = presize + basicsize; + char *mem = _PyObject_MallocWithType(tp, size); + if (mem == NULL) { + return _PyErr_NoMemory(tstate); + } + ((PyObject **)mem)[0] = NULL; + ((PyObject **)mem)[1] = NULL; + PyObject *op = (PyObject *)(mem + presize); + _PyObject_GC_Link(op); + return op; +} + +PyObject * +_PyObject_GC_New(PyTypeObject *tp) +{ + size_t presize = _PyType_PreHeaderSize(tp); + PyObject *op = gc_alloc(tp, _PyObject_SIZE(tp), presize); + if (op == NULL) { + return NULL; + } + _PyObject_Init(op, tp); + return op; +} + +PyVarObject * +_PyObject_GC_NewVar(PyTypeObject *tp, Py_ssize_t nitems) +{ + PyVarObject *op; + + if (nitems < 0) { + PyErr_BadInternalCall(); + return NULL; + } + size_t presize = _PyType_PreHeaderSize(tp); + size_t size = _PyObject_VAR_SIZE(tp, nitems); + op = (PyVarObject *)gc_alloc(tp, size, presize); + if (op == NULL) { + return NULL; + } + _PyObject_InitVar(op, tp, nitems); + return op; +} + +PyObject * +PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *tp, size_t extra_size) +{ + size_t presize = _PyType_PreHeaderSize(tp); + PyObject *op = gc_alloc(tp, _PyObject_SIZE(tp) + extra_size, presize); + if (op == NULL) { + return NULL; + } + memset(op, 0, _PyObject_SIZE(tp) + extra_size); + _PyObject_Init(op, tp); + return op; +} + +PyVarObject * +_PyObject_GC_Resize(PyVarObject *op, Py_ssize_t nitems) +{ + const size_t basicsize = _PyObject_VAR_SIZE(Py_TYPE(op), nitems); + const size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type); + _PyObject_ASSERT((PyObject *)op, !_PyObject_GC_IS_TRACKED(op)); + if (basicsize > (size_t)PY_SSIZE_T_MAX - presize) { + return (PyVarObject *)PyErr_NoMemory(); + } + char *mem = (char *)op - presize; + mem = (char *)_PyObject_ReallocWithType(Py_TYPE(op), mem, presize + basicsize); + if (mem == NULL) { + return (PyVarObject *)PyErr_NoMemory(); + } + op = (PyVarObject *) (mem + presize); + Py_SET_SIZE(op, nitems); + return op; +} + +void +PyObject_GC_Del(void *op) +{ + size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type); + if (_PyObject_GC_IS_TRACKED(op)) { +#ifdef Py_DEBUG + PyObject *exc = PyErr_GetRaisedException(); + if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0, + "gc", NULL, "Object of type %s is not untracked before destruction", + ((PyObject*)op)->ob_type->tp_name)) { + PyErr_WriteUnraisable(NULL); + } + PyErr_SetRaisedException(exc); +#endif + } + GCState *gcstate = get_gc_state(); + if (gcstate->generations[0].count > 0) { + gcstate->generations[0].count--; + } + PyObject_Free(((char *)op)-presize); +} + +int +PyObject_GC_IsTracked(PyObject* obj) +{ + if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) { + return 1; + } + return 0; +} + +int +PyObject_GC_IsFinalized(PyObject *obj) +{ + if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { + return 1; + } + return 0; +} + +struct custom_visitor_args { + struct visitor_args base; + gcvisitobjects_t callback; + void *arg; +}; + +static bool +custom_visitor_wrapper(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block(block, args, false); + if (op == NULL) { + return true; + } + + struct custom_visitor_args *wrapper = (struct custom_visitor_args *)args; + if (!wrapper->callback(op, wrapper->arg)) { + return false; + } + + return true; +} + +void +PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct custom_visitor_args wrapper = { + .callback = callback, + .arg = arg, + }; + + _PyEval_StopTheWorld(interp); + gc_visit_heaps(interp, &custom_visitor_wrapper, &wrapper.base); + _PyEval_StartTheWorld(interp); +} + /* Clear all free lists * All free lists are cleared during the collection of the highest generation. * Allocated items in the free list may keep a pymalloc arena occupied. @@ -25,4 +1720,4 @@ _PyGC_ClearAllFreeLists(PyInterpreterState *interp) HEAD_UNLOCK(&_PyRuntime); } -#endif +#endif // Py_GIL_DISABLED diff --git a/Python/object_stack.c b/Python/object_stack.c new file mode 100644 index 000000000000000..66b37bcbb444758 --- /dev/null +++ b/Python/object_stack.c @@ -0,0 +1,87 @@ +// Stack of Python objects + +#include "Python.h" +#include "pycore_freelist.h" +#include "pycore_pystate.h" +#include "pycore_object_stack.h" + +extern _PyObjectStackChunk *_PyObjectStackChunk_New(void); +extern void _PyObjectStackChunk_Free(_PyObjectStackChunk *); + +static struct _Py_object_stack_state * +get_state(void) +{ + _PyFreeListState *state = _PyFreeListState_GET(); + return &state->object_stack_state; +} + +_PyObjectStackChunk * +_PyObjectStackChunk_New(void) +{ + _PyObjectStackChunk *buf; + struct _Py_object_stack_state *state = get_state(); + if (state->numfree > 0) { + buf = state->free_list; + state->free_list = buf->prev; + state->numfree--; + } + else { + // NOTE: we use PyMem_RawMalloc() here because this is used by the GC + // during mimalloc heap traversal. In that context, it is not safe to + // allocate mimalloc memory, such as via PyMem_Malloc(). + buf = PyMem_RawMalloc(sizeof(_PyObjectStackChunk)); + if (buf == NULL) { + return NULL; + } + } + buf->prev = NULL; + buf->n = 0; + return buf; +} + +void +_PyObjectStackChunk_Free(_PyObjectStackChunk *buf) +{ + assert(buf->n == 0); + struct _Py_object_stack_state *state = get_state(); + if (state->numfree >= 0 && + state->numfree < _PyObjectStackChunk_MAXFREELIST) + { + buf->prev = state->free_list; + state->free_list = buf; + state->numfree++; + } + else { + PyMem_RawFree(buf); + } +} + +void +_PyObjectStack_Clear(_PyObjectStack *queue) +{ + while (queue->head != NULL) { + _PyObjectStackChunk *buf = queue->head; + buf->n = 0; + queue->head = buf->prev; + _PyObjectStackChunk_Free(buf); + } +} + +void +_PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalization) +{ + if (!is_finalization) { + // Ignore requests to clear the free list during GC. We use object + // stacks during GC, so emptying the free-list is counterproductive. + return; + } + + struct _Py_object_stack_state *state = &free_lists->object_stack_state; + while (state->numfree > 0) { + _PyObjectStackChunk *buf = state->free_list; + state->free_list = buf->prev; + state->numfree--; + PyMem_RawFree(buf); + } + state->numfree = -1; +} diff --git a/Python/pystate.c b/Python/pystate.c index c9b521351444a71..5ad5b6f3fcc6345 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -10,6 +10,7 @@ #include "pycore_frame.h" #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_object.h" // _PyType_InitCache() +#include "pycore_object_stack.h" // _PyObjectStackChunk_ClearFreeList() #include "pycore_parking_lot.h" // _PyParkingLot_AfterFork() #include "pycore_pyerrors.h" // _PyErr_Clear() #include "pycore_pylifecycle.h" // _PyAST_Fini() @@ -1468,6 +1469,7 @@ _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization) _PyList_ClearFreeList(state, is_finalization); _PyContext_ClearFreeList(state, is_finalization); _PyAsyncGen_ClearFreeLists(state, is_finalization); + _PyObjectStackChunk_ClearFreeList(state, is_finalization); } void @@ -2055,7 +2057,6 @@ start_the_world(struct _stoptheworld_state *stw) HEAD_LOCK(runtime); stw->requested = 0; stw->world_stopped = 0; - stw->requester = NULL; // Switch threads back to the detached state. PyInterpreterState *i; PyThreadState *t; @@ -2066,6 +2067,7 @@ start_the_world(struct _stoptheworld_state *stw) _PyParkingLot_UnparkAll(&t->state); } } + stw->requester = NULL; HEAD_UNLOCK(runtime); if (stw->is_global) { _PyRWMutex_Unlock(&runtime->stoptheworld_mutex); From d96358ff9de646dbf64dfdfed46d510da7ec4803 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 25 Jan 2024 22:46:32 +0300 Subject: [PATCH 027/263] gh-114315: Make `threading.Lock` a real class, not a factory function (#114479) `threading.Lock` is now the underlying class and is constructable rather than the old factory function. This allows for type annotations to refer to it which had no non-ugly way to be expressed prior to this. --------- Co-authored-by: Alex Waygood Co-authored-by: Gregory P. Smith --- Doc/library/threading.rst | 7 ++-- Lib/test/test_threading.py | 20 ++++++++--- Lib/threading.py | 4 +-- ...-01-23-14-11-49.gh-issue-114315.KeVdzl.rst | 2 ++ Modules/_threadmodule.c | 33 ++++++++++++++++--- 5 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index b85b7f008d1594c..5fbf9379b8202c0 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -534,9 +534,10 @@ All methods are executed atomically. lock, subsequent attempts to acquire it block, until it is released; any thread may release it. - Note that ``Lock`` is actually a factory function which returns an instance - of the most efficient version of the concrete Lock class that is supported - by the platform. + .. versionchanged:: 3.13 + ``Lock`` is now a class. In earlier Pythons, ``Lock`` was a factory + function which returned an instance of the underlying private lock + type. .. method:: acquire(blocking=True, timeout=-1) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index dbdc46fff1e313c..1ab223b81e939e5 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -171,11 +171,21 @@ def test_args_argument(self): t.start() t.join() - @cpython_only - def test_disallow_instantiation(self): - # Ensure that the type disallows instantiation (bpo-43916) - lock = threading.Lock() - test.support.check_disallow_instantiation(self, type(lock)) + def test_lock_no_args(self): + threading.Lock() # works + self.assertRaises(TypeError, threading.Lock, 1) + self.assertRaises(TypeError, threading.Lock, a=1) + self.assertRaises(TypeError, threading.Lock, 1, 2, a=1, b=2) + + def test_lock_no_subclass(self): + # Intentionally disallow subclasses of threading.Lock because they have + # never been allowed, so why start now just because the type is public? + with self.assertRaises(TypeError): + class MyLock(threading.Lock): pass + + def test_lock_or_none(self): + import types + self.assertIsInstance(threading.Lock | None, types.UnionType) # Create a bunch of threads, let each do some work, wait until all are # done. diff --git a/Lib/threading.py b/Lib/threading.py index ecf799bc26ab061..00b95f8d92a1f06 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -5,7 +5,6 @@ import _thread import functools import warnings -import _weakref from time import monotonic as _time from _weakrefset import WeakSet @@ -37,6 +36,7 @@ _start_joinable_thread = _thread.start_joinable_thread _daemon_threads_allowed = _thread.daemon_threads_allowed _allocate_lock = _thread.allocate_lock +_LockType = _thread.LockType _set_sentinel = _thread._set_sentinel get_ident = _thread.get_ident _is_main_interpreter = _thread._is_main_interpreter @@ -115,7 +115,7 @@ def gettrace(): # Synchronization classes -Lock = _allocate_lock +Lock = _LockType def RLock(*args, **kwargs): """Factory function that returns a new reentrant lock. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst b/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst new file mode 100644 index 000000000000000..a8a19fc525d0199 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-14-11-49.gh-issue-114315.KeVdzl.rst @@ -0,0 +1,2 @@ +Make :class:`threading.Lock` a real class, not a factory function. Add +``__new__`` to ``_thread.lock`` type. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 99f97eb6d0adcce..5cceb84658deb78 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -5,6 +5,7 @@ #include "Python.h" #include "pycore_interp.h" // _PyInterpreterState.threads.count #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_pylifecycle.h" #include "pycore_pystate.h" // _PyThreadState_SetCurrent() #include "pycore_sysmodule.h" // _PySys_GetAttr() @@ -349,6 +350,27 @@ lock__at_fork_reinit(lockobject *self, PyObject *Py_UNUSED(args)) } #endif /* HAVE_FORK */ +static lockobject *newlockobject(PyObject *module); + +static PyObject * +lock_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + // convert to AC? + if (!_PyArg_NoKeywords("lock", kwargs)) { + goto error; + } + if (!_PyArg_CheckPositional("lock", PyTuple_GET_SIZE(args), 0, 0)) { + goto error; + } + + PyObject *module = PyType_GetModuleByDef(type, &thread_module); + assert(module != NULL); + return (PyObject *)newlockobject(module); + +error: + return NULL; +} + static PyMethodDef lock_methods[] = { {"acquire_lock", _PyCFunction_CAST(lock_PyThread_acquire_lock), @@ -398,6 +420,7 @@ static PyType_Slot lock_type_slots[] = { {Py_tp_methods, lock_methods}, {Py_tp_traverse, lock_traverse}, {Py_tp_members, lock_type_members}, + {Py_tp_new, lock_new}, {0, 0} }; @@ -405,7 +428,7 @@ static PyType_Spec lock_type_spec = { .name = "_thread.lock", .basicsize = sizeof(lockobject), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), + Py_TPFLAGS_IMMUTABLETYPE), .slots = lock_type_slots, }; @@ -1442,8 +1465,6 @@ A subthread can use this function to interrupt the main thread.\n\ Note: the default signal handler for SIGINT raises ``KeyboardInterrupt``." ); -static lockobject *newlockobject(PyObject *module); - static PyObject * thread_PyThread_allocate_lock(PyObject *module, PyObject *Py_UNUSED(ignored)) { @@ -1841,10 +1862,14 @@ thread_module_exec(PyObject *module) } // Lock - state->lock_type = (PyTypeObject *)PyType_FromSpec(&lock_type_spec); + state->lock_type = (PyTypeObject *)PyType_FromModuleAndSpec(module, &lock_type_spec, NULL); if (state->lock_type == NULL) { return -1; } + if (PyModule_AddType(module, state->lock_type) < 0) { + return -1; + } + // Old alias: lock -> LockType if (PyDict_SetItemString(d, "LockType", (PyObject *)state->lock_type) < 0) { return -1; } From 33ae9895d4ac0d88447e529038bc4725ddd8c291 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Thu, 25 Jan 2024 23:00:52 +0300 Subject: [PATCH 028/263] gh-114561: Mark some tests in ``test_wincosoleio`` with `requires_resource('console')` decorator (GH-114565) --- Lib/test/test_winconsoleio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_winconsoleio.py b/Lib/test/test_winconsoleio.py index 72ff9606908ed5a..209e4464e1a5c0c 100644 --- a/Lib/test/test_winconsoleio.py +++ b/Lib/test/test_winconsoleio.py @@ -6,7 +6,7 @@ import sys import tempfile import unittest -from test.support import os_helper +from test.support import os_helper, requires_resource if sys.platform != 'win32': raise unittest.SkipTest("test only relevant on win32") @@ -150,6 +150,7 @@ def assertStdinRoundTrip(self, text): sys.stdin = old_stdin self.assertEqual(actual, text) + @requires_resource('console') def test_input(self): # ASCII self.assertStdinRoundTrip('abc123') @@ -164,6 +165,7 @@ def test_input_nonbmp(self): # Non-BMP self.assertStdinRoundTrip('\U00100000\U0010ffff\U0010fffd') + @requires_resource('console') def test_partial_reads(self): # Test that reading less than 1 full character works when stdin # contains multibyte UTF-8 sequences @@ -199,6 +201,7 @@ def test_partial_surrogate_reads(self): self.assertEqual(actual, expected, 'stdin.read({})'.format(read_count)) + @requires_resource('console') def test_ctrl_z(self): with open('CONIN$', 'rb', buffering=0) as stdin: source = '\xC4\x1A\r\n'.encode('utf-16-le') From ac5e53e15057bc0326a03f56e400ce345d1cebeb Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:06:48 +0000 Subject: [PATCH 029/263] gh-107901: compiler replaces POP_BLOCK instruction by NOPs before optimisations (#114530) --- Python/flowgraph.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index de831358eb9ac83..96610b3cb11a43d 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -903,6 +903,7 @@ label_exception_targets(basicblock *entryblock) { } else if (instr->i_opcode == POP_BLOCK) { handler = pop_except_block(except_stack); + INSTR_SET_OP0(instr, NOP); } else if (is_jump(instr)) { instr->i_except = handler; @@ -2313,7 +2314,7 @@ convert_pseudo_ops(cfg_builder *g) for (basicblock *b = entryblock; b != NULL; b = b->b_next) { for (int i = 0; i < b->b_iused; i++) { cfg_instr *instr = &b->b_instr[i]; - if (is_block_push(instr) || instr->i_opcode == POP_BLOCK) { + if (is_block_push(instr)) { INSTR_SET_OP0(instr, NOP); } else if (instr->i_opcode == LOAD_CLOSURE) { From 841eacd07646e643f87d7f063106633a25315910 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Fri, 26 Jan 2024 05:49:37 +0900 Subject: [PATCH 030/263] Add CODEOWNERS for dbm (gh-114555) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9587b3996a9ac2a..4984170f0d17ff7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -42,6 +42,9 @@ Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum Tools/c-analyzer/ @ericsnowcurrently +# dbm +**/*dbm* @corona10 @erlend-aasland @serhiy-storchaka + # Exceptions Lib/traceback.py @iritkatriel Lib/test/test_except*.py @iritkatriel From b69548a0f52418b8a2cf7c7a885fdd7d3bfb1b0b Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 26 Jan 2024 01:12:46 +0000 Subject: [PATCH 031/263] GH-73435: Add `pathlib.PurePath.full_match()` (#114350) In 49f90ba we added support for the recursive wildcard `**` in `pathlib.PurePath.match()`. This should allow arbitrary prefix and suffix matching, like `p.match('foo/**')` or `p.match('**/foo')`, but there's a problem: for relative patterns only, `match()` implicitly inserts a `**` token on the left hand side, causing all patterns to match from the right. As a result, it's impossible to match relative patterns from the left: `PurePath('foo/bar').match('bar/**')` is true! This commit reverts the changes to `match()`, and instead adds a new `full_match()` method that: - Allows empty patterns - Supports the recursive wildcard `**` - Matches the *entire* path when given a relative pattern --- Doc/library/glob.rst | 5 +- Doc/library/pathlib.rst | 60 +++++++------- Doc/whatsnew/3.13.rst | 3 +- Lib/pathlib/__init__.py | 7 ++ Lib/pathlib/_abc.py | 54 +++++++++---- Lib/test/test_pathlib/test_pathlib_abc.py | 98 +++++++++++++++++------ 6 files changed, 155 insertions(+), 72 deletions(-) diff --git a/Doc/library/glob.rst b/Doc/library/glob.rst index 6e4f72c19ff4c97..19a0bbba8966bae 100644 --- a/Doc/library/glob.rst +++ b/Doc/library/glob.rst @@ -147,8 +147,9 @@ The :mod:`glob` module defines the following functions: .. seealso:: - :meth:`pathlib.PurePath.match` and :meth:`pathlib.Path.glob` methods, - which call this function to implement pattern matching and globbing. + :meth:`pathlib.PurePath.full_match` and :meth:`pathlib.Path.glob` + methods, which call this function to implement pattern matching and + globbing. .. versionadded:: 3.13 diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index fcbc0bf489b3445..2f4ff4efec47f8c 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -559,55 +559,55 @@ Pure paths provide the following methods and properties: PureWindowsPath('c:/Program Files') -.. method:: PurePath.match(pattern, *, case_sensitive=None) +.. method:: PurePath.full_match(pattern, *, case_sensitive=None) Match this path against the provided glob-style pattern. Return ``True`` - if matching is successful, ``False`` otherwise. - - If *pattern* is relative, the path can be either relative or absolute, - and matching is done from the right:: + if matching is successful, ``False`` otherwise. For example:: - >>> PurePath('a/b.py').match('*.py') - True - >>> PurePath('/a/b/c.py').match('b/*.py') + >>> PurePath('a/b.py').full_match('a/*.py') True - >>> PurePath('/a/b/c.py').match('a/*.py') + >>> PurePath('a/b.py').full_match('*.py') False + >>> PurePath('/a/b/c.py').full_match('/a/**') + True + >>> PurePath('/a/b/c.py').full_match('**/*.py') + True - If *pattern* is absolute, the path must be absolute, and the whole path - must match:: + As with other methods, case-sensitivity follows platform defaults:: - >>> PurePath('/a.py').match('/*.py') - True - >>> PurePath('a/b.py').match('/*.py') + >>> PurePosixPath('b.py').full_match('*.PY') False + >>> PureWindowsPath('b.py').full_match('*.PY') + True - The *pattern* may be another path object; this speeds up matching the same - pattern against multiple files:: + Set *case_sensitive* to ``True`` or ``False`` to override this behaviour. - >>> pattern = PurePath('*.py') - >>> PurePath('a/b.py').match(pattern) - True + .. versionadded:: 3.13 - .. versionchanged:: 3.12 - Accepts an object implementing the :class:`os.PathLike` interface. - As with other methods, case-sensitivity follows platform defaults:: +.. method:: PurePath.match(pattern, *, case_sensitive=None) - >>> PurePosixPath('b.py').match('*.PY') - False - >>> PureWindowsPath('b.py').match('*.PY') + Match this path against the provided non-recursive glob-style pattern. + Return ``True`` if matching is successful, ``False`` otherwise. + + This method is similar to :meth:`~PurePath.full_match`, but empty patterns + aren't allowed (:exc:`ValueError` is raised), the recursive wildcard + "``**``" isn't supported (it acts like non-recursive "``*``"), and if a + relative pattern is provided, then matching is done from the right:: + + >>> PurePath('a/b.py').match('*.py') + True + >>> PurePath('/a/b/c.py').match('b/*.py') True + >>> PurePath('/a/b/c.py').match('a/*.py') + False - Set *case_sensitive* to ``True`` or ``False`` to override this behaviour. + .. versionchanged:: 3.12 + The *pattern* parameter accepts a :term:`path-like object`. .. versionchanged:: 3.12 The *case_sensitive* parameter was added. - .. versionchanged:: 3.13 - Support for the recursive wildcard "``**``" was added. In previous - versions, it acted like the non-recursive wildcard "``*``". - .. method:: PurePath.relative_to(other, walk_up=False) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 40f0cd37fe93185..8c2bb05920d5b60 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -336,7 +336,8 @@ pathlib object from a 'file' URI (``file:/``). (Contributed by Barney Gale in :gh:`107465`.) -* Add support for recursive wildcards in :meth:`pathlib.PurePath.match`. +* Add :meth:`pathlib.PurePath.full_match` for matching paths with + shell-style wildcards, including the recursive wildcard "``**``". (Contributed by Barney Gale in :gh:`73435`.) * Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`, diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index b043aed12b3849e..eee82ef26bc7e7c 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -490,6 +490,13 @@ def _pattern_stack(self): parts.reverse() return parts + @property + def _pattern_str(self): + """The path expressed as a string, for use in pattern-matching.""" + # The string representation of an empty path is a single dot ('.'). Empty + # paths shouldn't match wildcards, so we change it to the empty string. + path_str = str(self) + return '' if path_str == '.' else path_str # Subclassing os.PathLike makes isinstance() checks slower, # which in turn makes Path construction slower. Register instead! diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 553e1a399061d3d..6303a18680befc4 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -47,8 +47,8 @@ def _is_case_sensitive(pathmod): re = glob = None -@functools.lru_cache(maxsize=256) -def _compile_pattern(pat, sep, case_sensitive): +@functools.lru_cache(maxsize=512) +def _compile_pattern(pat, sep, case_sensitive, recursive=True): """Compile given glob pattern to a re.Pattern object (observing case sensitivity).""" global re, glob @@ -56,10 +56,7 @@ def _compile_pattern(pat, sep, case_sensitive): import re, glob flags = re.NOFLAG if case_sensitive else re.IGNORECASE - regex = glob.translate(pat, recursive=True, include_hidden=True, seps=sep) - # The string representation of an empty path is a single dot ('.'). Empty - # paths shouldn't match wildcards, so we consume it with an atomic group. - regex = r'(\.\Z)?+' + regex + regex = glob.translate(pat, recursive=recursive, include_hidden=True, seps=sep) return re.compile(regex, flags=flags).match @@ -441,23 +438,48 @@ def _pattern_stack(self): raise NotImplementedError("Non-relative patterns are unsupported") return parts + @property + def _pattern_str(self): + """The path expressed as a string, for use in pattern-matching.""" + return str(self) + def match(self, path_pattern, *, case_sensitive=None): """ - Return True if this path matches the given pattern. + Return True if this path matches the given pattern. If the pattern is + relative, matching is done from the right; otherwise, the entire path + is matched. The recursive wildcard '**' is *not* supported by this + method. """ if not isinstance(path_pattern, PurePathBase): path_pattern = self.with_segments(path_pattern) if case_sensitive is None: case_sensitive = _is_case_sensitive(self.pathmod) sep = path_pattern.pathmod.sep - if path_pattern.anchor: - pattern_str = str(path_pattern) - elif path_pattern.parts: - pattern_str = str('**' / path_pattern) - else: + path_parts = self.parts[::-1] + pattern_parts = path_pattern.parts[::-1] + if not pattern_parts: raise ValueError("empty pattern") - match = _compile_pattern(pattern_str, sep, case_sensitive) - return match(str(self)) is not None + if len(path_parts) < len(pattern_parts): + return False + if len(path_parts) > len(pattern_parts) and path_pattern.anchor: + return False + for path_part, pattern_part in zip(path_parts, pattern_parts): + match = _compile_pattern(pattern_part, sep, case_sensitive, recursive=False) + if match(path_part) is None: + return False + return True + + def full_match(self, pattern, *, case_sensitive=None): + """ + Return True if this path matches the given glob-style pattern. The + pattern is matched against the entire path. + """ + if not isinstance(pattern, PurePathBase): + pattern = self.with_segments(pattern) + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self.pathmod) + match = _compile_pattern(pattern._pattern_str, pattern.pathmod.sep, case_sensitive) + return match(self._pattern_str) is not None @@ -781,8 +803,8 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): if filter_paths: # Filter out paths that don't match pattern. prefix_len = len(str(self._make_child_relpath('_'))) - 1 - match = _compile_pattern(str(pattern), sep, case_sensitive) - paths = (path for path in paths if match(str(path), prefix_len)) + match = _compile_pattern(pattern._pattern_str, sep, case_sensitive) + paths = (path for path in paths if match(path._pattern_str, prefix_len)) return paths def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 199718a8a69c5ad..364f776dbb1413a 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -249,29 +249,8 @@ def test_match_common(self): self.assertFalse(P('/ab.py').match('/a/*.py')) self.assertFalse(P('/a/b/c.py').match('/a/*.py')) # Multi-part glob-style pattern. - self.assertTrue(P('a').match('**')) - self.assertTrue(P('c.py').match('**')) - self.assertTrue(P('a/b/c.py').match('**')) - self.assertTrue(P('/a/b/c.py').match('**')) - self.assertTrue(P('/a/b/c.py').match('/**')) - self.assertTrue(P('/a/b/c.py').match('/a/**')) - self.assertTrue(P('/a/b/c.py').match('**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/**/*.py')) + self.assertFalse(P('/a/b/c.py').match('/**/*.py')) self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py')) - self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py')) - self.assertFalse(P('c.py').match('**/a.py')) - self.assertFalse(P('c.py').match('c/**')) - self.assertFalse(P('a/b/c.py').match('**/a')) - self.assertFalse(P('a/b/c.py').match('**/a/b')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c.')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) - self.assertFalse(P('a/b/c.py').match('**/a/b/c./**')) - self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**')) - self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py')) - self.assertRaises(ValueError, P('a').match, '**a/b/c') - self.assertRaises(ValueError, P('a').match, 'a/b/c**') # Case-sensitive flag self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) @@ -279,9 +258,82 @@ def test_match_common(self): self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) # Matching against empty path self.assertFalse(P('').match('*')) - self.assertTrue(P('').match('**')) + self.assertFalse(P('').match('**')) self.assertFalse(P('').match('**/*')) + def test_full_match_common(self): + P = self.cls + # Simple relative pattern. + self.assertTrue(P('b.py').full_match('b.py')) + self.assertFalse(P('a/b.py').full_match('b.py')) + self.assertFalse(P('/a/b.py').full_match('b.py')) + self.assertFalse(P('a.py').full_match('b.py')) + self.assertFalse(P('b/py').full_match('b.py')) + self.assertFalse(P('/a.py').full_match('b.py')) + self.assertFalse(P('b.py/c').full_match('b.py')) + # Wildcard relative pattern. + self.assertTrue(P('b.py').full_match('*.py')) + self.assertFalse(P('a/b.py').full_match('*.py')) + self.assertFalse(P('/a/b.py').full_match('*.py')) + self.assertFalse(P('b.pyc').full_match('*.py')) + self.assertFalse(P('b./py').full_match('*.py')) + self.assertFalse(P('b.py/c').full_match('*.py')) + # Multi-part relative pattern. + self.assertTrue(P('ab/c.py').full_match('a*/*.py')) + self.assertFalse(P('/d/ab/c.py').full_match('a*/*.py')) + self.assertFalse(P('a.py').full_match('a*/*.py')) + self.assertFalse(P('/dab/c.py').full_match('a*/*.py')) + self.assertFalse(P('ab/c.py/d').full_match('a*/*.py')) + # Absolute pattern. + self.assertTrue(P('/b.py').full_match('/*.py')) + self.assertFalse(P('b.py').full_match('/*.py')) + self.assertFalse(P('a/b.py').full_match('/*.py')) + self.assertFalse(P('/a/b.py').full_match('/*.py')) + # Multi-part absolute pattern. + self.assertTrue(P('/a/b.py').full_match('/a/*.py')) + self.assertFalse(P('/ab.py').full_match('/a/*.py')) + self.assertFalse(P('/a/b/c.py').full_match('/a/*.py')) + # Multi-part glob-style pattern. + self.assertTrue(P('a').full_match('**')) + self.assertTrue(P('c.py').full_match('**')) + self.assertTrue(P('a/b/c.py').full_match('**')) + self.assertTrue(P('/a/b/c.py').full_match('**')) + self.assertTrue(P('/a/b/c.py').full_match('/**')) + self.assertTrue(P('/a/b/c.py').full_match('/a/**')) + self.assertTrue(P('/a/b/c.py').full_match('**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/a/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/a/b/**/*.py')) + self.assertTrue(P('/a/b/c.py').full_match('/**/**/**/**/*.py')) + self.assertFalse(P('c.py').full_match('**/a.py')) + self.assertFalse(P('c.py').full_match('c/**')) + self.assertFalse(P('a/b/c.py').full_match('**/a')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c.')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**')) + self.assertFalse(P('a/b/c.py').full_match('/a/b/c.py/**')) + self.assertFalse(P('a/b/c.py').full_match('/**/a/b/c.py')) + self.assertRaises(ValueError, P('a').full_match, '**a/b/c') + self.assertRaises(ValueError, P('a').full_match, 'a/b/c**') + # Case-sensitive flag + self.assertFalse(P('A.py').full_match('a.PY', case_sensitive=True)) + self.assertTrue(P('A.py').full_match('a.PY', case_sensitive=False)) + self.assertFalse(P('c:/a/B.Py').full_match('C:/A/*.pY', case_sensitive=True)) + self.assertTrue(P('/a/b/c.py').full_match('/A/*/*.Py', case_sensitive=False)) + # Matching against empty path + self.assertFalse(P('').full_match('*')) + self.assertTrue(P('').full_match('**')) + self.assertFalse(P('').full_match('**/*')) + # Matching with empty pattern + self.assertTrue(P('').full_match('')) + self.assertTrue(P('.').full_match('.')) + self.assertFalse(P('/').full_match('')) + self.assertFalse(P('/').full_match('.')) + self.assertFalse(P('foo').full_match('')) + self.assertFalse(P('foo').full_match('.')) + def test_parts_common(self): # `parts` returns a tuple. sep = self.sep From 456e274578dc9863f42ab24d62adc0d8c511b50f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 26 Jan 2024 09:33:13 +0100 Subject: [PATCH 032/263] gh-112451: Prohibit subclassing of datetime.timezone. (#114190) This is consistent with C-extension datetime.timezone. --- Lib/_pydatetime.py | 3 +++ Lib/test/datetimetester.py | 4 ++++ .../Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index bca2acf1fc88cf9..355145387e355bb 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -2347,6 +2347,9 @@ def __new__(cls, offset, name=_Omitted): "timedelta(hours=24).") return cls._create(offset, name) + def __init_subclass__(cls): + raise TypeError("type 'datetime.timezone' is not an acceptable base type") + @classmethod def _create(cls, offset, name=None): self = tzinfo.__new__(cls) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 8bda17358db87f9..53ad5e57ada0178 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -301,6 +301,10 @@ def test_inheritance(self): self.assertIsInstance(timezone.utc, tzinfo) self.assertIsInstance(self.EST, tzinfo) + def test_cannot_subclass(self): + with self.assertRaises(TypeError): + class MyTimezone(timezone): pass + def test_utcoffset(self): dummy = self.DT for h in [0, 1.5, 12]: diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst new file mode 100644 index 000000000000000..126ca36a3b7cb1a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-20-11-46.gh-issue-112451.7YrG4p.rst @@ -0,0 +1,2 @@ +Prohibit subclassing pure-Python :class:`datetime.timezone`. This is consistent +with C-extension implementation. Patch by Mariusz Felisiak. From 582d95e8bb0b78bf1b6b9a12371108b9993d3b84 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Fri, 26 Jan 2024 03:48:13 -0600 Subject: [PATCH 033/263] gh-114250: Fetch metadata for pip and its vendored dependencies from PyPI (#114450) --- Misc/sbom.spdx.json | 624 +++++++++++++++++++++++++++++++++++ Tools/build/generate_sbom.py | 263 ++++++++++++--- 2 files changed, 837 insertions(+), 50 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 5b3cd04ffa7f74b..94566772338b108 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1695,6 +1695,510 @@ "primaryPackagePurpose": "SOURCE", "versionInfo": "2.5.1" }, + { + "SPDXID": "SPDXRef-PACKAGE-cachecontrol", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "95dedbec849f46dda3137866dc28b9d133fc9af55f5b805ab1291833e4457aa4" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/1d/e3/a22348e6226dcd585d5a4b5f0175b3a16dabfd3912cbeb02f321d00e56c7/cachecontrol-0.13.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/cachecontrol@0.13.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "cachecontrol", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.13.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-colorama", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/colorama@0.4.6", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "colorama", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.4.6" + }, + { + "SPDXID": "SPDXRef-PACKAGE-distlib", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/76/cb/6bbd2b10170ed991cf64e8c8b85e01f2fb38f95d1bc77617569e0b0b26ac/distlib-0.3.6-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/distlib@0.3.6", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "distlib", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.3.6" + }, + { + "SPDXID": "SPDXRef-PACKAGE-distro", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/f4/2c/c90a3adaf0ddb70afe193f5ebfb539612af57cffe677c3126be533df3098/distro-1.8.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/distro@1.8.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "distro", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.8.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-msgpack", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/9f/4a/36d936e54cf71e23ad276564465f6a54fb129e3d61520b76e13e0bb29167/msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/msgpack@1.0.5", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "msgpack", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.0.5" + }, + { + "SPDXID": "SPDXRef-PACKAGE-packaging", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/packaging@21.3", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "packaging", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "21.3" + }, + { + "SPDXID": "SPDXRef-PACKAGE-platformdirs", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/9e/d8/563a9fc17153c588c8c2042d2f0f84a89057cdb1c30270f589c88b42d62c/platformdirs-3.8.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/platformdirs@3.8.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "platformdirs", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.8.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-pyparsing", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/a4/24/6ae4c9c45cf99d96b06b5d99e25526c060303171fb0aea9da2bfd7dbde93/pyparsing-3.1.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/pyparsing@3.1.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "pyparsing", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.1.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-pyproject-hooks", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/pyproject-hooks@1.0.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "pyproject-hooks", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.0.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-requests", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/requests@2.31.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "requests", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2.31.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-certifi", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/certifi@2023.7.22", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "certifi", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2023.7.22" + }, + { + "SPDXID": "SPDXRef-PACKAGE-chardet", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/74/8f/8fc49109009e8d2169d94d72e6b1f4cd45c13d147ba7d6170fb41f22b08f/chardet-5.1.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/chardet@5.1.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "chardet", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "5.1.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-idna", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/idna@3.4", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "idna", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.4" + }, + { + "SPDXID": "SPDXRef-PACKAGE-rich", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/fc/1e/482e5eec0b89b593e81d78f819a9412849814e22225842b598908e7ac560/rich-13.4.2-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/rich@13.4.2", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "rich", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "13.4.2" + }, + { + "SPDXID": "SPDXRef-PACKAGE-pygments", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/34/a7/37c8d68532ba71549db4212cb036dbd6161b40e463aba336770e80c72f84/Pygments-2.15.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/pygments@2.15.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "pygments", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2.15.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-typing-extensions", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/typing_extensions@4.7.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "typing_extensions", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "4.7.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-resolvelib", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/d2/fc/e9ccf0521607bcd244aa0b3fbd574f71b65e9ce6a112c83af988bbbe2e23/resolvelib-1.0.1-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/resolvelib@1.0.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "resolvelib", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.0.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-setuptools", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/c7/42/be1c7bbdd83e1bfb160c94b9cafd8e25efc7400346cf7ccdbdb452c467fa/setuptools-68.0.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/setuptools@68.0.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "setuptools", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "68.0.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-six", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/six@1.16.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "six", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.16.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-tenacity", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/e7/b0/c23bd61e1b32c9b96fbca996c87784e196a812da8d621d8d04851f6c8181/tenacity-8.2.2-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/tenacity@8.2.2", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "tenacity", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "8.2.2" + }, + { + "SPDXID": "SPDXRef-PACKAGE-tomli", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/tomli@2.0.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "tomli", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2.0.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-truststore", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "e37a5642ae9fc48caa8f120b6283d77225d600d224965a672c9e8ef49ce4bb4c" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/20/56/7811d5439b6a56374f274a8672d8f18b4deadadeb3a9f0c86424b98b6f96/truststore-0.8.0-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/truststore@0.8.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "truststore", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.8.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-webencodings", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/webencodings@0.5.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "webencodings", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.5.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-urllib3", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/48/fe/a5c6cc46e9fe9171d7ecf0f33ee7aae14642f8d74baa7af4d7840f9358be/urllib3-1.26.17-py2.py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/urllib3@1.26.17", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "urllib3", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.26.17" + }, { "SPDXID": "SPDXRef-PACKAGE-pip", "checksums": [ @@ -1724,6 +2228,126 @@ } ], "relationships": [ + { + "relatedSpdxElement": "SPDXRef-PACKAGE-cachecontrol", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-certifi", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-chardet", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-colorama", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-distlib", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-distro", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-idna", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-msgpack", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-packaging", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-platformdirs", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-pygments", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-pyparsing", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-pyproject-hooks", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-requests", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-resolvelib", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-rich", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-setuptools", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-six", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-tenacity", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-tomli", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-truststore", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-typing-extensions", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-urllib3", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, + { + "relatedSpdxElement": "SPDXRef-PACKAGE-webencodings", + "relationshipType": "DEPENDS_ON", + "spdxElementId": "SPDXRef-PACKAGE-pip" + }, { "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-COPYING", "relationshipType": "CONTAINS", diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index 317d48fee3a9d40..aceb13f141cba40 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -8,6 +8,7 @@ import subprocess import sys import typing +import zipfile from urllib.request import urlopen CPYTHON_ROOT_DIR = pathlib.Path(__file__).parent.parent.parent @@ -16,10 +17,16 @@ # the license expression is a valid SPDX license expression: # See: https://spdx.org/licenses ALLOWED_LICENSE_EXPRESSIONS = { - "MIT", - "CC0-1.0", "Apache-2.0", + "Apache-2.0 OR BSD-2-Clause", "BSD-2-Clause", + "BSD-3-Clause", + "CC0-1.0", + "ISC", + "LGPL-2.1-only", + "MIT", + "MPL-2.0", + "Python-2.0.1", } # Properties which are required for our purposes. @@ -31,14 +38,13 @@ "checksums", "licenseConcluded", "externalRefs", - "originator", "primaryPackagePurpose", ]) class PackageFiles(typing.NamedTuple): """Structure for describing the files of a package""" - include: list[str] + include: list[str] | None exclude: list[str] | None = None @@ -118,62 +124,209 @@ def filter_gitignored_paths(paths: list[str]) -> list[str]: return sorted([line.split()[-1] for line in git_check_ignore_lines if line.startswith("::")]) +def fetch_package_metadata_from_pypi(project: str, version: str, filename: str | None = None) -> tuple[str, str] | None: + """ + Fetches the SHA256 checksum and download location from PyPI. + If we're given a filename then we match with that, otherwise we use wheels. + """ + # Get pip's download location from PyPI. Check that the checksum is correct too. + try: + raw_text = urlopen(f"https://pypi.org/pypi/{project}/{version}/json").read() + release_metadata = json.loads(raw_text) + url: dict[str, typing.Any] + + # Look for a matching artifact filename and then check + # its remote checksum to the local one. + for url in release_metadata["urls"]: + # pip can only use Python-only dependencies, so there's + # no risk of picking the 'incorrect' wheel here. + if ( + (filename is None and url["packagetype"] == "bdist_wheel") + or (filename is not None and url["filename"] == filename) + ): + break + else: + raise ValueError(f"No matching filename on PyPI for '{filename}'") + + # Successfully found the download URL for the matching artifact. + download_url = url["url"] + checksum_sha256 = url["digests"]["sha256"] + return download_url, checksum_sha256 + + except (OSError, ValueError) as e: + # Fail if we're running in CI where we should have an internet connection. + error_if( + "CI" in os.environ, + f"Couldn't fetch metadata for project '{project}' from PyPI: {e}" + ) + return None + + +def find_ensurepip_pip_wheel() -> pathlib.Path | None: + """Try to find the pip wheel bundled in ensurepip. If missing return None""" + + ensurepip_bundled_dir = CPYTHON_ROOT_DIR / "Lib/ensurepip/_bundled" + + pip_wheels = [] + try: + for wheel_filename in os.listdir(ensurepip_bundled_dir): + if wheel_filename.startswith("pip-"): + pip_wheels.append(wheel_filename) + else: + print(f"Unexpected wheel in ensurepip: '{wheel_filename}'") + sys.exit(1) + + # Ignore this error, likely caused by downstream distributors + # deleting the 'ensurepip/_bundled' directory. + except FileNotFoundError: + pass + + if len(pip_wheels) == 0: + return None + elif len(pip_wheels) > 1: + print("Multiple pip wheels detected in 'Lib/ensurepip/_bundled'") + sys.exit(1) + # Otherwise return the one pip wheel. + return ensurepip_bundled_dir / pip_wheels[0] + + +def maybe_remove_pip_and_deps_from_sbom(sbom_data: dict[str, typing.Any]) -> None: + """ + Removes pip and its dependencies from the SBOM data + if the pip wheel is removed from ensurepip. This is done + by redistributors of Python and pip. + """ + + # If there's a wheel we don't remove anything. + if find_ensurepip_pip_wheel() is not None: + return + + # Otherwise we traverse the relationships + # to find dependent packages to remove. + sbom_pip_spdx_id = spdx_id("SPDXRef-PACKAGE-pip") + sbom_spdx_ids_to_remove = {sbom_pip_spdx_id} + + # Find all package SPDXIDs that pip depends on. + for sbom_relationship in sbom_data["relationships"]: + if ( + sbom_relationship["relationshipType"] == "DEPENDS_ON" + and sbom_relationship["spdxElementId"] == sbom_pip_spdx_id + ): + sbom_spdx_ids_to_remove.add(sbom_relationship["relatedSpdxElement"]) + + # Remove all the packages and relationships. + sbom_data["packages"] = [ + sbom_package for sbom_package in sbom_data["packages"] + if sbom_package["SPDXID"] not in sbom_spdx_ids_to_remove + ] + sbom_data["relationships"] = [ + sbom_relationship for sbom_relationship in sbom_data["relationships"] + if sbom_relationship["relatedSpdxElement"] not in sbom_spdx_ids_to_remove + ] + + def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: """pip is a part of a packaging ecosystem (Python, surprise!) so it's actually automatable to discover the metadata we need like the version and checksums - so let's do that on behalf of our friends at the PyPA. + so let's do that on behalf of our friends at the PyPA. This function also + discovers vendored packages within pip and fetches their metadata. """ global PACKAGE_TO_FILES - ensurepip_bundled_dir = CPYTHON_ROOT_DIR / "Lib/ensurepip/_bundled" - pip_wheels = [] - - # Find the hopefully one pip wheel in the bundled directory. - for wheel_filename in os.listdir(ensurepip_bundled_dir): - if wheel_filename.startswith("pip-"): - pip_wheels.append(wheel_filename) - if len(pip_wheels) != 1: - print("Zero or multiple pip wheels detected in 'Lib/ensurepip/_bundled'") - sys.exit(1) - pip_wheel_filename = pip_wheels[0] + pip_wheel_filepath = find_ensurepip_pip_wheel() + if pip_wheel_filepath is None: + return # There's no pip wheel, nothing to discover. # Add the wheel filename to the list of files so the SBOM file # and relationship generator can work its magic on the wheel too. PACKAGE_TO_FILES["pip"] = PackageFiles( - include=[f"Lib/ensurepip/_bundled/{pip_wheel_filename}"] + include=[str(pip_wheel_filepath.relative_to(CPYTHON_ROOT_DIR))] ) # Wheel filename format puts the version right after the project name. - pip_version = pip_wheel_filename.split("-")[1] + pip_version = pip_wheel_filepath.name.split("-")[1] pip_checksum_sha256 = hashlib.sha256( - (ensurepip_bundled_dir / pip_wheel_filename).read_bytes() + pip_wheel_filepath.read_bytes() ).hexdigest() - # Get pip's download location from PyPI. Check that the checksum is correct too. - try: - raw_text = urlopen(f"https://pypi.org/pypi/pip/{pip_version}/json").read() - pip_release_metadata = json.loads(raw_text) - url: dict[str, typing.Any] + pip_metadata = fetch_package_metadata_from_pypi( + project="pip", + version=pip_version, + filename=pip_wheel_filepath.name, + ) + # We couldn't fetch any metadata from PyPI, + # so we give up on verifying if we're not in CI. + if pip_metadata is None: + return + + pip_download_url, pip_actual_sha256 = pip_metadata + if pip_actual_sha256 != pip_checksum_sha256: + raise ValueError("Unexpected") + + # Parse 'pip/_vendor/vendor.txt' from the wheel for sub-dependencies. + with zipfile.ZipFile(pip_wheel_filepath) as whl: + vendor_txt_data = whl.read("pip/_vendor/vendor.txt").decode() + + # With this version regex we're assuming that pip isn't using pre-releases. + # If any version doesn't match we get a failure below, so we're safe doing this. + version_pin_re = re.compile(r"^([a-zA-Z0-9_.-]+)==([0-9.]*[0-9])$") + sbom_pip_dependency_spdx_ids = set() + for line in vendor_txt_data.splitlines(): + line = line.partition("#")[0].strip() # Strip comments and whitespace. + if not line: # Skip empty lines. + continue + + # Non-empty lines we must be able to match. + match = version_pin_re.match(line) + error_if(match is None, f"Couldn't parse line from pip vendor.txt: '{line}'") + assert match is not None # Make mypy happy. + + # Parse out and normalize the project name. + project_name, project_version = match.groups() + project_name = project_name.lower() + + # At this point if pip's metadata fetch succeeded we should + # expect this request to also succeed. + project_metadata = ( + fetch_package_metadata_from_pypi(project_name, project_version) + ) + assert project_metadata is not None + project_download_url, project_checksum_sha256 = project_metadata + + # Update our SBOM data with what we received from PyPI. + # Don't overwrite any existing values. + sbom_project_spdx_id = spdx_id(f"SPDXRef-PACKAGE-{project_name}") + sbom_pip_dependency_spdx_ids.add(sbom_project_spdx_id) + for package in sbom_data["packages"]: + if package["SPDXID"] != sbom_project_spdx_id: + continue - # Look for a matching artifact filename and then check - # its remote checksum to the local one. - for url in pip_release_metadata["urls"]: - if url["filename"] == pip_wheel_filename: + # Only thing missing from this blob is the `licenseConcluded`, + # that needs to be triaged by human maintainers if the list changes. + package.update({ + "SPDXID": sbom_project_spdx_id, + "name": project_name, + "versionInfo": project_version, + "downloadLocation": project_download_url, + "checksums": [ + {"algorithm": "SHA256", "checksumValue": project_checksum_sha256} + ], + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": f"pkg:pypi/{project_name}@{project_version}", + "referenceType": "purl", + }, + ], + "primaryPackagePurpose": "SOURCE" + }) break - else: - raise ValueError(f"No matching filename on PyPI for '{pip_wheel_filename}'") - if url["digests"]["sha256"] != pip_checksum_sha256: - raise ValueError(f"Local pip checksum doesn't match artifact on PyPI") - - # Successfully found the download URL for the matching artifact. - pip_download_url = url["url"] - except (OSError, ValueError) as e: - print(f"Couldn't fetch pip's metadata from PyPI: {e}") - sys.exit(1) + PACKAGE_TO_FILES[project_name] = PackageFiles(include=None) # Remove pip from the existing SBOM packages if it's there # and then overwrite its entry with our own generated one. + sbom_pip_spdx_id = spdx_id("SPDXRef-PACKAGE-pip") sbom_data["packages"] = [ sbom_package for sbom_package in sbom_data["packages"] @@ -181,7 +334,7 @@ def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: ] sbom_data["packages"].append( { - "SPDXID": spdx_id("SPDXRef-PACKAGE-pip"), + "SPDXID": sbom_pip_spdx_id, "name": "pip", "versionInfo": pip_version, "originator": "Organization: Python Packaging Authority", @@ -205,12 +358,27 @@ def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: "primaryPackagePurpose": "SOURCE", } ) + for sbom_dep_spdx_id in sorted(sbom_pip_dependency_spdx_ids): + sbom_data["relationships"].append({ + "spdxElementId": sbom_pip_spdx_id, + "relatedSpdxElement": sbom_dep_spdx_id, + "relationshipType": "DEPENDS_ON" + }) def main() -> None: sbom_path = CPYTHON_ROOT_DIR / "Misc/sbom.spdx.json" sbom_data = json.loads(sbom_path.read_bytes()) + # Check if pip should be removed if the wheel is missing. + # We can't reset the SBOM relationship data until checking this. + maybe_remove_pip_and_deps_from_sbom(sbom_data) + + # We regenerate all of this information. Package information + # should be preserved though since that is edited by humans. + sbom_data["files"] = [] + sbom_data["relationships"] = [] + # Insert pip's SBOM metadata from the wheel. discover_pip_sbom_package(sbom_data) @@ -227,9 +395,10 @@ def main() -> None: "name" not in package, "Package is missing the 'name' field" ) + missing_required_keys = REQUIRED_PROPERTIES_PACKAGE - set(package.keys()) error_if( - set(package.keys()) != REQUIRED_PROPERTIES_PACKAGE, - f"Package '{package['name']}' is missing required fields", + bool(missing_required_keys), + f"Package '{package['name']}' is missing required fields: {missing_required_keys}", ) error_if( package["SPDXID"] != spdx_id(f"SPDXRef-PACKAGE-{package['name']}"), @@ -257,15 +426,11 @@ def main() -> None: f"License identifier '{license_concluded}' not in SBOM tool allowlist" ) - # Regenerate file information from current data. - sbom_files = [] - sbom_relationships = [] - # We call 'sorted()' here a lot to avoid filesystem scan order issues. for name, files in sorted(PACKAGE_TO_FILES.items()): package_spdx_id = spdx_id(f"SPDXRef-PACKAGE-{name}") exclude = files.exclude or () - for include in sorted(files.include): + for include in sorted(files.include or ()): # Find all the paths and then filter them through .gitignore. paths = glob.glob(include, root_dir=CPYTHON_ROOT_DIR, recursive=True) paths = filter_gitignored_paths(paths) @@ -285,7 +450,7 @@ def main() -> None: checksum_sha256 = hashlib.sha256(data).hexdigest() file_spdx_id = spdx_id(f"SPDXRef-FILE-{path}") - sbom_files.append({ + sbom_data["files"].append({ "SPDXID": file_spdx_id, "fileName": path, "checksums": [ @@ -295,15 +460,13 @@ def main() -> None: }) # Tie each file back to its respective package. - sbom_relationships.append({ + sbom_data["relationships"].append({ "spdxElementId": package_spdx_id, "relatedSpdxElement": file_spdx_id, "relationshipType": "CONTAINS", }) # Update the SBOM on disk - sbom_data["files"] = sbom_files - sbom_data["relationships"] = sbom_relationships sbom_path.write_text(json.dumps(sbom_data, indent=2, sort_keys=True)) From 01d970c1b8acf3ccf199d5de151a635ffd9d8c61 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 26 Jan 2024 12:55:22 +0300 Subject: [PATCH 034/263] gh-101100: Fix sphinx warnings in `c-api/file.rst` (#114546) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/c-api/file.rst | 9 +++++++-- Doc/c-api/object.rst | 8 ++++++++ Doc/tools/.nitignore | 2 -- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/file.rst b/Doc/c-api/file.rst index b36c800e00444ac..0a03841e467cad9 100644 --- a/Doc/c-api/file.rst +++ b/Doc/c-api/file.rst @@ -65,8 +65,13 @@ the :mod:`io` APIs instead. Overrides the normal behavior of :func:`io.open_code` to pass its parameter through the provided handler. - The handler is a function of type :c:expr:`PyObject *(\*)(PyObject *path, - void *userData)`, where *path* is guaranteed to be :c:type:`PyUnicodeObject`. + The handler is a function of type: + + .. c:type:: Py_OpenCodeHookFunction + + Equivalent of :c:expr:`PyObject *(\*)(PyObject *path, + void *userData)`, where *path* is guaranteed to be + :c:type:`PyUnicodeObject`. The *userData* pointer is passed into the hook function. Since hook functions may be called from different runtimes, this pointer should not diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 8a179690d048e39..4f656779c80b1a2 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -19,6 +19,14 @@ Object Protocol to NotImplemented and return it). +.. c:macro:: Py_PRINT_RAW + + Flag to be used with multiple functions that print the object (like + :c:func:`PyObject_Print` and :c:func:`PyFile_WriteObject`). + If passed, these function would use the :func:`str` of the object + instead of the :func:`repr`. + + .. c:function:: int PyObject_Print(PyObject *o, FILE *fp, int flags) Print an object *o*, on file *fp*. Returns ``-1`` on error. The flags argument diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 00b4b6919ff14a1..d56a44ad09a6f88 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -4,7 +4,6 @@ Doc/c-api/descriptor.rst Doc/c-api/exceptions.rst -Doc/c-api/file.rst Doc/c-api/float.rst Doc/c-api/gcsupport.rst Doc/c-api/init.rst @@ -12,7 +11,6 @@ Doc/c-api/init_config.rst Doc/c-api/intro.rst Doc/c-api/memoryview.rst Doc/c-api/module.rst -Doc/c-api/object.rst Doc/c-api/stable.rst Doc/c-api/sys.rst Doc/c-api/type.rst From 06c5de36f222b926bbc94831536096b974bd5e77 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 11:05:08 +0100 Subject: [PATCH 035/263] Docs: reword dbm.gnu introduction (#114548) Also... - consistently spell GDBM as GDBM - silence gdbm class refs - improve accuracy of dbm.gdbm.open() spec --- Doc/library/dbm.rst | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index eca1c25602a018e..0a9d28a41c6d7a1 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -137,27 +137,28 @@ then prints out the contents of the database:: The individual submodules are described in the following sections. -:mod:`dbm.gnu` --- GNU's reinterpretation of dbm ------------------------------------------------- +:mod:`dbm.gnu` --- GNU database manager +--------------------------------------- .. module:: dbm.gnu :platform: Unix - :synopsis: GNU's reinterpretation of dbm. + :synopsis: GNU database manager **Source code:** :source:`Lib/dbm/gnu.py` -------------- -This module is quite similar to the :mod:`dbm` module, but uses the GNU library -``gdbm`` instead to provide some additional functionality. Please note that the -file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible. +The :mod:`dbm.gnu` module provides an interface to the :abbr:`GDBM (GNU dbm)` +library, similar to the :mod:`dbm.ndbm` module, but with additional +functionality like crash tolerance. -The :mod:`dbm.gnu` module provides an interface to the GNU DBM library. -``dbm.gnu.gdbm`` objects behave like mappings (dictionaries), except that keys and -values are always converted to bytes before storing. Printing a ``gdbm`` -object doesn't print the -keys and values, and the :meth:`items` and :meth:`values` methods are not -supported. +:class:`!gdbm` objects behave similar to :term:`mappings `, +except that keys and values are always converted to :class:`bytes` before storing, +and the :meth:`!items` and :meth:`!values` methods are not supported. + +.. note:: + The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are + incompatible and can not be used interchangeably. .. exception:: error @@ -165,9 +166,9 @@ supported. raised for general mapping errors like specifying an incorrect key. -.. function:: open(filename[, flag[, mode]]) +.. function:: open(filename, flag="r", mode=0o666, /) - Open a ``gdbm`` database and return a :class:`gdbm` object. The *filename* + Open a GDBM database and return a :class:`!gdbm` object. The *filename* argument is the name of the database file. The optional *flag* argument can be: @@ -196,14 +197,14 @@ supported. | ``'u'`` | Do not lock database. | +---------+--------------------------------------------+ - Not all flags are valid for all versions of ``gdbm``. The module constant + Not all flags are valid for all versions of GDBM. The module constant :const:`open_flags` is a string of supported flag characters. The exception :exc:`error` is raised if an invalid flag is specified. The optional *mode* argument is the Unix mode of the file, used only when the database has to be created. It defaults to octal ``0o666``. - In addition to the dictionary-like methods, ``gdbm`` objects have the + In addition to the dictionary-like methods, :class:`gdbm` objects have the following methods: .. versionchanged:: 3.11 @@ -212,7 +213,7 @@ supported. .. method:: gdbm.firstkey() It's possible to loop over every key in the database using this method and the - :meth:`nextkey` method. The traversal is ordered by ``gdbm``'s internal + :meth:`nextkey` method. The traversal is ordered by GDBM's internal hash values, and won't be sorted by the key values. This method returns the starting key. @@ -230,7 +231,7 @@ supported. .. method:: gdbm.reorganize() If you have carried out a lot of deletions and would like to shrink the space - used by the ``gdbm`` file, this routine will reorganize the database. ``gdbm`` + used by the GDBM file, this routine will reorganize the database. :class:`!gdbm` objects will not shorten the length of a database file except by using this reorganization; otherwise, deleted file space will be kept and reused as new (key, value) pairs are added. @@ -242,11 +243,11 @@ supported. .. method:: gdbm.close() - Close the ``gdbm`` database. + Close the GDBM database. .. method:: gdbm.clear() - Remove all items from the ``gdbm`` database. + Remove all items from the GDBM database. .. versionadded:: 3.13 From d0f7f5c41d71758c59f9372a192e927d73cf7c27 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 26 Jan 2024 05:10:03 -0500 Subject: [PATCH 036/263] gh-114312: Fix rare event counter tests on aarch64 (GH-114554) --- Modules/_testinternalcapi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 2c32c691afa5837..c4a648a1816392f 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1642,11 +1642,11 @@ get_rare_event_counters(PyObject *self, PyObject *type) return Py_BuildValue( "{sksksksksk}", - "set_class", interp->rare_events.set_class, - "set_bases", interp->rare_events.set_bases, - "set_eval_frame_func", interp->rare_events.set_eval_frame_func, - "builtin_dict", interp->rare_events.builtin_dict, - "func_modification", interp->rare_events.func_modification + "set_class", (unsigned long)interp->rare_events.set_class, + "set_bases", (unsigned long)interp->rare_events.set_bases, + "set_eval_frame_func", (unsigned long)interp->rare_events.set_eval_frame_func, + "builtin_dict", (unsigned long)interp->rare_events.builtin_dict, + "func_modification", (unsigned long)interp->rare_events.func_modification ); } From dcd28b5c35dda8e2cb7c5f66450f2aff0948c001 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 11:11:35 +0100 Subject: [PATCH 037/263] gh-114569: Use PyMem_* APIs for most non-PyObject uses (#114574) Fix usage in Modules, Objects, and Parser subdirectories. --- Modules/_elementtree.c | 23 +++++++++++++---------- Modules/_sre/sre_lib.h | 4 ++-- Modules/mathmodule.c | 12 ++++++------ Modules/pyexpat.c | 2 +- Objects/bytearrayobject.c | 14 +++++++------- Objects/typeobject.c | 10 +++++----- Parser/lexer/lexer.c | 4 ++-- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index b574c96d3f96255..544510812116541 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -267,7 +267,7 @@ typedef struct { LOCAL(int) create_extra(ElementObject* self, PyObject* attrib) { - self->extra = PyObject_Malloc(sizeof(ElementObjectExtra)); + self->extra = PyMem_Malloc(sizeof(ElementObjectExtra)); if (!self->extra) { PyErr_NoMemory(); return -1; @@ -295,10 +295,11 @@ dealloc_extra(ElementObjectExtra *extra) for (i = 0; i < extra->length; i++) Py_DECREF(extra->children[i]); - if (extra->children != extra->_children) - PyObject_Free(extra->children); + if (extra->children != extra->_children) { + PyMem_Free(extra->children); + } - PyObject_Free(extra); + PyMem_Free(extra); } LOCAL(void) @@ -495,14 +496,16 @@ element_resize(ElementObject* self, Py_ssize_t extra) * "children", which needs at least 4 bytes. Although it's a * false alarm always assume at least one child to be safe. */ - children = PyObject_Realloc(self->extra->children, - size * sizeof(PyObject*)); - if (!children) + children = PyMem_Realloc(self->extra->children, + size * sizeof(PyObject*)); + if (!children) { goto nomemory; + } } else { - children = PyObject_Malloc(size * sizeof(PyObject*)); - if (!children) + children = PyMem_Malloc(size * sizeof(PyObject*)); + if (!children) { goto nomemory; + } /* copy existing children from static area to malloc buffer */ memcpy(children, self->extra->children, self->extra->length * sizeof(PyObject*)); @@ -3044,7 +3047,7 @@ _elementtree_TreeBuilder_start_impl(TreeBuilderObject *self, PyObject *tag, #define EXPAT(st, func) ((st)->expat_capi->func) static XML_Memory_Handling_Suite ExpatMemoryHandler = { - PyObject_Malloc, PyObject_Realloc, PyObject_Free}; + PyMem_Malloc, PyMem_Realloc, PyMem_Free}; typedef struct { PyObject_HEAD diff --git a/Modules/_sre/sre_lib.h b/Modules/_sre/sre_lib.h index f5497d9ff2b93fd..97fbb0a75e54b65 100644 --- a/Modules/_sre/sre_lib.h +++ b/Modules/_sre/sre_lib.h @@ -1122,7 +1122,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) /* install new repeat context */ /* TODO(https://github.com/python/cpython/issues/67877): Fix this * potential memory leak. */ - ctx->u.rep = (SRE_REPEAT*) PyObject_Malloc(sizeof(*ctx->u.rep)); + ctx->u.rep = (SRE_REPEAT*) PyMem_Malloc(sizeof(*ctx->u.rep)); if (!ctx->u.rep) { PyErr_NoMemory(); RETURN_FAILURE; @@ -1136,7 +1136,7 @@ SRE(match)(SRE_STATE* state, const SRE_CODE* pattern, int toplevel) state->ptr = ptr; DO_JUMP(JUMP_REPEAT, jump_repeat, pattern+pattern[0]); state->repeat = ctx->u.rep->prev; - PyObject_Free(ctx->u.rep); + PyMem_Free(ctx->u.rep); if (ret) { RETURN_ON_ERROR(ret); diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 2a796c1c55d2f0a..0be46b1574c1feb 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2570,7 +2570,7 @@ math_dist_impl(PyObject *module, PyObject *p, PyObject *q) goto error_exit; } if (n > NUM_STACK_ELEMS) { - diffs = (double *) PyObject_Malloc(n * sizeof(double)); + diffs = (double *) PyMem_Malloc(n * sizeof(double)); if (diffs == NULL) { PyErr_NoMemory(); goto error_exit; @@ -2590,7 +2590,7 @@ math_dist_impl(PyObject *module, PyObject *p, PyObject *q) } result = vector_norm(n, diffs, max, found_nan); if (diffs != diffs_on_stack) { - PyObject_Free(diffs); + PyMem_Free(diffs); } if (p_allocated) { Py_DECREF(p); @@ -2602,7 +2602,7 @@ math_dist_impl(PyObject *module, PyObject *p, PyObject *q) error_exit: if (diffs != diffs_on_stack) { - PyObject_Free(diffs); + PyMem_Free(diffs); } if (p_allocated) { Py_DECREF(p); @@ -2626,7 +2626,7 @@ math_hypot(PyObject *self, PyObject *const *args, Py_ssize_t nargs) double *coordinates = coord_on_stack; if (nargs > NUM_STACK_ELEMS) { - coordinates = (double *) PyObject_Malloc(nargs * sizeof(double)); + coordinates = (double *) PyMem_Malloc(nargs * sizeof(double)); if (coordinates == NULL) { return PyErr_NoMemory(); } @@ -2643,13 +2643,13 @@ math_hypot(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } result = vector_norm(nargs, coordinates, max, found_nan); if (coordinates != coord_on_stack) { - PyObject_Free(coordinates); + PyMem_Free(coordinates); } return PyFloat_FromDouble(result); error_exit: if (coordinates != coord_on_stack) { - PyObject_Free(coordinates); + PyMem_Free(coordinates); } return NULL; } diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index ec44892d101e44e..7c08eda83e66b2a 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -21,7 +21,7 @@ module pyexpat #define XML_COMBINED_VERSION (10000*XML_MAJOR_VERSION+100*XML_MINOR_VERSION+XML_MICRO_VERSION) static XML_Memory_Handling_Suite ExpatMemoryHandler = { - PyObject_Malloc, PyObject_Realloc, PyObject_Free}; + PyMem_Malloc, PyMem_Realloc, PyMem_Free}; enum HandlerTypes { StartElement, diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 659de7d3dd5a994..acc59b926448ca4 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -132,7 +132,7 @@ PyByteArray_FromStringAndSize(const char *bytes, Py_ssize_t size) } else { alloc = size + 1; - new->ob_bytes = PyObject_Malloc(alloc); + new->ob_bytes = PyMem_Malloc(alloc); if (new->ob_bytes == NULL) { Py_DECREF(new); return PyErr_NoMemory(); @@ -221,17 +221,17 @@ PyByteArray_Resize(PyObject *self, Py_ssize_t requested_size) } if (logical_offset > 0) { - sval = PyObject_Malloc(alloc); + sval = PyMem_Malloc(alloc); if (sval == NULL) { PyErr_NoMemory(); return -1; } memcpy(sval, PyByteArray_AS_STRING(self), Py_MIN((size_t)requested_size, (size_t)Py_SIZE(self))); - PyObject_Free(obj->ob_bytes); + PyMem_Free(obj->ob_bytes); } else { - sval = PyObject_Realloc(obj->ob_bytes, alloc); + sval = PyMem_Realloc(obj->ob_bytes, alloc); if (sval == NULL) { PyErr_NoMemory(); return -1; @@ -951,7 +951,7 @@ bytearray_repr(PyByteArrayObject *self) } newsize += 6 + length * 4; - buffer = PyObject_Malloc(newsize); + buffer = PyMem_Malloc(newsize); if (buffer == NULL) { PyErr_NoMemory(); return NULL; @@ -1008,7 +1008,7 @@ bytearray_repr(PyByteArrayObject *self) } v = PyUnicode_FromStringAndSize(buffer, p - buffer); - PyObject_Free(buffer); + PyMem_Free(buffer); return v; } @@ -1088,7 +1088,7 @@ bytearray_dealloc(PyByteArrayObject *self) PyErr_Print(); } if (self->ob_bytes != 0) { - PyObject_Free(self->ob_bytes); + PyMem_Free(self->ob_bytes); } Py_TYPE(self)->tp_free((PyObject *)self); } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a8c3b8896d36eb4..114cf21f95e744b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3493,7 +3493,7 @@ type_new_set_doc(PyTypeObject *type) // Silently truncate the docstring if it contains a null byte Py_ssize_t size = strlen(doc_str) + 1; - char *tp_doc = (char *)PyObject_Malloc(size); + char *tp_doc = (char *)PyMem_Malloc(size); if (tp_doc == NULL) { PyErr_NoMemory(); return -1; @@ -4166,12 +4166,12 @@ _PyType_FromMetaclass_impl( goto finally; } if (slot->pfunc == NULL) { - PyObject_Free(tp_doc); + PyMem_Free(tp_doc); tp_doc = NULL; } else { size_t len = strlen(slot->pfunc)+1; - tp_doc = PyObject_Malloc(len); + tp_doc = PyMem_Malloc(len); if (tp_doc == NULL) { PyErr_NoMemory(); goto finally; @@ -4501,7 +4501,7 @@ _PyType_FromMetaclass_impl( Py_CLEAR(res); } Py_XDECREF(bases); - PyObject_Free(tp_doc); + PyMem_Free(tp_doc); Py_XDECREF(ht_name); PyMem_Free(_ht_tpname); return (PyObject*)res; @@ -5099,7 +5099,7 @@ type_dealloc(PyObject *self) /* A type's tp_doc is heap allocated, unlike the tp_doc slots * of most other objects. It's okay to cast it to char *. */ - PyObject_Free((char *)type->tp_doc); + PyMem_Free((char *)type->tp_doc); PyHeapTypeObject *et = (PyHeapTypeObject *)type; Py_XDECREF(et->ht_name); diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c index ebf7686773ff45c..82b0e4ee352d622 100644 --- a/Parser/lexer/lexer.c +++ b/Parser/lexer/lexer.c @@ -129,7 +129,7 @@ set_fstring_expr(struct tok_state* tok, struct token *token, char c) { if (hash_detected) { Py_ssize_t input_length = tok_mode->last_expr_size - tok_mode->last_expr_end; - char *result = (char *)PyObject_Malloc((input_length + 1) * sizeof(char)); + char *result = (char *)PyMem_Malloc((input_length + 1) * sizeof(char)); if (!result) { return -1; } @@ -154,7 +154,7 @@ set_fstring_expr(struct tok_state* tok, struct token *token, char c) { result[j] = '\0'; // Null-terminate the result string res = PyUnicode_DecodeUTF8(result, j, NULL); - PyObject_Free(result); + PyMem_Free(result); } else { res = PyUnicode_DecodeUTF8( tok_mode->last_expr_buffer, From 65cf5dce11a38e327b9b0abfca279d650452b34f Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 11:15:34 +0100 Subject: [PATCH 038/263] Docs: rework dbm introduction (#114551) - add refs to other parts of the docs (dict, bytes, etc.) - clarify whichdb() return value by using list markup - silence refs to example or generic submodule methods (keys, get, etc.) --- Doc/library/dbm.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 0a9d28a41c6d7a1..1a8e0158fcdbd5e 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -28,10 +28,11 @@ the Oracle Berkeley DB. available --- :mod:`dbm.gnu`, :mod:`dbm.ndbm` or :mod:`dbm.dumb` --- should be used to open a given file. - Returns one of the following values: ``None`` if the file can't be opened - because it's unreadable or doesn't exist; the empty string (``''``) if the - file's format can't be guessed; or a string containing the required module - name, such as ``'dbm.ndbm'`` or ``'dbm.gnu'``. + Return one of the following values: + + * ``None`` if the file can't be opened because it's unreadable or doesn't exist + * the empty string (``''``) if the file's format can't be guessed + * a string containing the required module name, such as ``'dbm.ndbm'`` or ``'dbm.gnu'`` .. versionchanged:: 3.11 Accepts :term:`path-like object` for filename. @@ -74,13 +75,13 @@ the Oracle Berkeley DB. modified by the prevailing umask). -The object returned by :func:`.open` supports the same basic functionality as -dictionaries; keys and their corresponding values can be stored, retrieved, and -deleted, and the :keyword:`in` operator and the :meth:`keys` method are -available, as well as :meth:`get` and :meth:`setdefault`. +The object returned by :func:`open` supports the same basic functionality as a +:class:`dict`; keys and their corresponding values can be stored, retrieved, and +deleted, and the :keyword:`in` operator and the :meth:`!keys` method are +available, as well as :meth:`!get` and :meth:`!setdefault`. .. versionchanged:: 3.2 - :meth:`get` and :meth:`setdefault` are now available in all database modules. + :meth:`!get` and :meth:`!setdefault` are now available in all database modules. .. versionchanged:: 3.8 Deleting a key from a read-only database raises database module specific error @@ -89,7 +90,7 @@ available, as well as :meth:`get` and :meth:`setdefault`. .. versionchanged:: 3.11 Accepts :term:`path-like object` for file. -Key and values are always stored as bytes. This means that when +Key and values are always stored as :class:`bytes`. This means that when strings are used they are implicitly converted to the default encoding before being stored. From 4cf068ed0879cccf86a45f06fb274b350b89e911 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 13:35:56 +0100 Subject: [PATCH 039/263] Docs: reword dbm.ndbm introduction (#114549) - add abbreviation directives for NDBM and GDBM - consistently spell NDBM as NDBM - silence broken ndbm class refs - improve accuracy of dbm.ndbm.open() spec - use replacement text for NDBM/GDBM file format incompatibility note --- Doc/library/dbm.rst | 47 +++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 1a8e0158fcdbd5e..bc5bb0cec0cef76 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -52,6 +52,10 @@ the Oracle Berkeley DB. .. |flag_n| replace:: Always create a new, empty database, open for reading and writing. +.. |incompat_note| replace:: + The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible + and can not be used interchangeably. + .. function:: open(file, flag='r', mode=0o666) Open the database file *file* and return a corresponding object. @@ -157,9 +161,7 @@ functionality like crash tolerance. except that keys and values are always converted to :class:`bytes` before storing, and the :meth:`!items` and :meth:`!values` methods are not supported. -.. note:: - The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are - incompatible and can not be used interchangeably. +.. note:: |incompat_note| .. exception:: error @@ -253,29 +255,31 @@ and the :meth:`!items` and :meth:`!values` methods are not supported. .. versionadded:: 3.13 -:mod:`dbm.ndbm` --- Interface based on ndbm -------------------------------------------- +:mod:`dbm.ndbm` --- New Database Manager +---------------------------------------- .. module:: dbm.ndbm :platform: Unix - :synopsis: The standard "database" interface, based on ndbm. + :synopsis: The New Database Manager **Source code:** :source:`Lib/dbm/ndbm.py` -------------- -The :mod:`dbm.ndbm` module provides an interface to the Unix "(n)dbm" library. -Dbm objects behave like mappings (dictionaries), except that keys and values are -always stored as bytes. Printing a ``dbm`` object doesn't print the keys and -values, and the :meth:`items` and :meth:`values` methods are not supported. +The :mod:`dbm.ndbm` module provides an interface to the +:abbr:`NDBM (New Database Manager)` library. +:class:`!ndbm` objects behave similar to :term:`mappings `, +except that keys and values are always stored as :class:`bytes`, +and the :meth:`!items` and :meth:`!values` methods are not supported. + +This module can be used with the "classic" NDBM interface or the +:abbr:`GDBM (GNU dbm)` compatibility interface. -This module can be used with the "classic" ndbm interface or the GNU GDBM -compatibility interface. On Unix, the :program:`configure` script will attempt -to locate the appropriate header file to simplify building this module. +.. note:: |incompat_note| .. warning:: - The ndbm library shipped as part of macOS has an undocumented limitation on the + The NDBM library shipped as part of macOS has an undocumented limitation on the size of values, which can result in corrupted database files when storing values larger than this limit. Reading such corrupted files can result in a hard crash (segmentation fault). @@ -288,13 +292,14 @@ to locate the appropriate header file to simplify building this module. .. data:: library - Name of the ``ndbm`` implementation library used. + Name of the NDBM implementation library used. -.. function:: open(filename[, flag[, mode]]) +.. function:: open(filename, flag="r", mode=0o666, /) - Open a dbm database and return a ``ndbm`` object. The *filename* argument is the - name of the database file (without the :file:`.dir` or :file:`.pag` extensions). + Open an NDBM database and return an :class:`!ndbm` object. + The *filename* argument is the name of the database file + (without the :file:`.dir` or :file:`.pag` extensions). The optional *flag* argument must be one of these values: @@ -310,7 +315,7 @@ to locate the appropriate header file to simplify building this module. database has to be created. It defaults to octal ``0o666`` (and will be modified by the prevailing umask). - In addition to the dictionary-like methods, ``ndbm`` objects + In addition to the dictionary-like methods, :class:`!ndbm` objects provide the following method: .. versionchanged:: 3.11 @@ -318,11 +323,11 @@ to locate the appropriate header file to simplify building this module. .. method:: ndbm.close() - Close the ``ndbm`` database. + Close the NDBM database. .. method:: ndbm.clear() - Remove all items from the ``ndbm`` database. + Remove all items from the NDBM database. .. versionadded:: 3.13 From 8710faeac28e65c65862359413e8341492f529af Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 13:36:37 +0100 Subject: [PATCH 040/263] Docs: fix versionchanged directives for dbm.open() and dbm.whichdb() (#114594) --- Doc/library/dbm.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index bc5bb0cec0cef76..55846e996b5d266 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -34,8 +34,8 @@ the Oracle Berkeley DB. * the empty string (``''``) if the file's format can't be guessed * a string containing the required module name, such as ``'dbm.ndbm'`` or ``'dbm.gnu'`` -.. versionchanged:: 3.11 - Accepts :term:`path-like object` for filename. + .. versionchanged:: 3.11 + *filename* accepts a :term:`path-like object`. .. Substitutions for the open() flag param docs; all submodules use the same text. @@ -78,6 +78,9 @@ the Oracle Berkeley DB. database has to be created. It defaults to octal ``0o666`` (and will be modified by the prevailing umask). + .. versionchanged:: 3.11 + *file* accepts a :term:`path-like object`. + The object returned by :func:`open` supports the same basic functionality as a :class:`dict`; keys and their corresponding values can be stored, retrieved, and @@ -91,9 +94,6 @@ available, as well as :meth:`!get` and :meth:`!setdefault`. Deleting a key from a read-only database raises database module specific error instead of :exc:`KeyError`. -.. versionchanged:: 3.11 - Accepts :term:`path-like object` for file. - Key and values are always stored as :class:`bytes`. This means that when strings are used they are implicitly converted to the default encoding before being stored. From 30b7b4f73cd876732244de06d079a2caf2a7b95c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 26 Jan 2024 08:42:49 -0500 Subject: [PATCH 041/263] Docs: 'still' is a better word than 'nonetheless' (#114598) --- Doc/library/dataclasses.rst | 2 +- Doc/library/imaplib.rst | 2 +- Doc/using/cmdline.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index cde147d1cbb501b..88f2e0251b1e519 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -141,7 +141,7 @@ Module contents then :func:`dataclass` *may* add an implicit :meth:`~object.__hash__` method. Although not recommended, you can force :func:`dataclass` to create a :meth:`~object.__hash__` method with ``unsafe_hash=True``. This might be the case - if your class is logically immutable but can nonetheless be mutated. + if your class is logically immutable but can still be mutated. This is a specialized use case and should be considered carefully. Here are the rules governing implicit creation of a :meth:`~object.__hash__` diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst index 1f774e64b0eae33..d5c868def3b64f7 100644 --- a/Doc/library/imaplib.rst +++ b/Doc/library/imaplib.rst @@ -531,7 +531,7 @@ An :class:`IMAP4` instance has the following methods: allowed creation of such tags, and popular IMAP servers, such as Gmail, accept and produce such flags. There are non-Python programs which also create such tags. Although it is an RFC violation and IMAP clients and - servers are supposed to be strict, imaplib nonetheless continues to allow + servers are supposed to be strict, imaplib still continues to allow such tags to be created for backward compatibility reasons, and as of Python 3.6, handles them if they are sent from the server, since this improves real-world compatibility. diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index df8b07c61185998..53c95ca1a05c9b4 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -623,7 +623,7 @@ Setting the environment variable ``TERM`` to ``dumb`` will disable color. If the environment variable ``FORCE_COLOR`` is set, then color will be enabled regardless of the value of TERM. This is useful on CI systems which -aren’t terminals but can none-the-less display ANSI escape sequences. +aren’t terminals but can still display ANSI escape sequences. If the environment variable ``NO_COLOR`` is set, Python will disable all color in the output. This takes precedence over ``FORCE_COLOR``. From 442a299af06d0dfe89484a841451666503479e2e Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 26 Jan 2024 14:38:24 +0000 Subject: [PATCH 042/263] gh-114272: Allow _wmi audit test to succeed even if it times out (GH-114602) --- Lib/test/audit-tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index ce4a11b119c9007..de7d0da560a1c79 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -487,7 +487,13 @@ def hook(event, args): print(event, args[0]) sys.addaudithook(hook) - _wmi.exec_query("SELECT * FROM Win32_OperatingSystem") + try: + _wmi.exec_query("SELECT * FROM Win32_OperatingSystem") + except WindowsError as e: + # gh-112278: WMI may be slow response when first called, but we still + # get the audit event, so just ignore the timeout + if e.winerror != 258: + raise def test_syslog(): import syslog From 0bd8297a2208125f76807cdf01f72abe5c94136b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 16:11:45 +0100 Subject: [PATCH 043/263] Docs: mark up dbm.open() with param list (#114601) Also consolidate following paragraphs regarding database objects. --- Doc/library/dbm.rst | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 55846e996b5d266..3a7dad1a0736a09 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -58,41 +58,33 @@ the Oracle Berkeley DB. .. function:: open(file, flag='r', mode=0o666) - Open the database file *file* and return a corresponding object. + Open a database and return the corresponding database object. - If the database file already exists, the :func:`whichdb` function is used to - determine its type and the appropriate module is used; if it does not exist, - the first module listed above that can be imported is used. + :param file: + The database file to open. - The optional *flag* argument can be: + If the database file already exists, the :func:`whichdb` function is used to + determine its type and the appropriate module is used; if it does not exist, + the first submodule listed above that can be imported is used. + :type file: :term:`path-like object` - .. csv-table:: - :header: "Value", "Meaning" + :param str flag: + * ``'r'`` (default), |flag_r| + * ``'w'``, |flag_w| + * ``'c'``, |flag_c| + * ``'n'``, |flag_n| - ``'r'`` (default), |flag_r| - ``'w'``, |flag_w| - ``'c'``, |flag_c| - ``'n'``, |flag_n| - - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666`` (and will be - modified by the prevailing umask). + :param int mode: + The Unix file access mode of the file (default: octal ``0o666``), + used only when the database has to be created. .. versionchanged:: 3.11 *file* accepts a :term:`path-like object`. - -The object returned by :func:`open` supports the same basic functionality as a +The object returned by :func:`~dbm.open` supports the same basic functionality as a :class:`dict`; keys and their corresponding values can be stored, retrieved, and deleted, and the :keyword:`in` operator and the :meth:`!keys` method are -available, as well as :meth:`!get` and :meth:`!setdefault`. - -.. versionchanged:: 3.2 - :meth:`!get` and :meth:`!setdefault` are now available in all database modules. - -.. versionchanged:: 3.8 - Deleting a key from a read-only database raises database module specific error - instead of :exc:`KeyError`. +available, as well as :meth:`!get` and :meth:`!setdefault` methods. Key and values are always stored as :class:`bytes`. This means that when strings are used they are implicitly converted to the default encoding before @@ -101,9 +93,17 @@ being stored. These objects also support being used in a :keyword:`with` statement, which will automatically close them when done. +.. versionchanged:: 3.2 + :meth:`!get` and :meth:`!setdefault` methods are now available for all + :mod:`dbm` backends. + .. versionchanged:: 3.4 Added native support for the context management protocol to the objects - returned by :func:`.open`. + returned by :func:`~dbm.open`. + +.. versionchanged:: 3.8 + Deleting a key from a read-only database raises a database module specific exception + instead of :exc:`KeyError`. The following example records some hostnames and a corresponding title, and then prints out the contents of the database:: From 504334c7be5a56237df2598d338cd494a42fca4c Mon Sep 17 00:00:00 2001 From: Rito Takeuchi Date: Sat, 27 Jan 2024 00:19:41 +0900 Subject: [PATCH 044/263] gh-77749: Fix inconsistent behavior of non-ASCII handling in EmailPolicy.fold() (GH-6986) It now always encodes non-ASCII characters in headers if utf8 is false. Co-authored-by: Serhiy Storchaka --- Lib/email/policy.py | 9 ++++++++- Lib/test/test_email/test_policy.py | 17 +++++++++++++++++ ...024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 611deb50bb5290e..8816c84ed175a70 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -210,8 +210,15 @@ def _fold(self, name, value, refold_binary=False): self.refold_source == 'long' and (lines and len(lines[0])+len(name)+2 > maxlen or any(len(x) > maxlen for x in lines[1:]))) - if refold or refold_binary and _has_surrogates(value): + + if not refold: + if not self.utf8: + refold = not value.isascii() + elif refold_binary: + refold = _has_surrogates(value) + if refold: return self.header_factory(name, ''.join(lines)).fold(policy=self) + return name + ': ' + self.linesep.join(lines) + self.linesep diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py index e87c275549406d1..c6b9c80efe1b54a 100644 --- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -135,6 +135,23 @@ def test_policy_addition(self): for attr, value in expected.items(): self.assertEqual(getattr(added, attr), value) + def test_fold_utf8(self): + expected_ascii = 'Subject: =?utf-8?q?=C3=A1?=\n' + expected_utf8 = 'Subject: á\n' + + msg = email.message.EmailMessage() + s = 'á' + msg['Subject'] = s + + p_ascii = email.policy.default.clone() + p_utf8 = email.policy.default.clone(utf8=True) + + self.assertEqual(p_ascii.fold('Subject', msg['Subject']), expected_ascii) + self.assertEqual(p_utf8.fold('Subject', msg['Subject']), expected_utf8) + + self.assertEqual(p_ascii.fold('Subject', s), expected_ascii) + self.assertEqual(p_utf8.fold('Subject', s), expected_utf8) + def test_fold_zero_max_line_length(self): expected = 'Subject: =?utf-8?q?=C3=A1?=\n' diff --git a/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst b/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst new file mode 100644 index 000000000000000..f1c99c09d2dfe1c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-26-16-46-21.gh-issue-77749.NY_7TS.rst @@ -0,0 +1,2 @@ +:meth:`email.policy.EmailPolicy.fold` now always encodes non-ASCII characters +in headers if :attr:`~email.policy.EmailPolicy.utf8` is false. From 699779256ec4d4b8afb8211de08ef1382c78c370 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Sat, 27 Jan 2024 00:25:16 +0900 Subject: [PATCH 045/263] gh-111968: Unify freelist naming schema to Eric's suggestion (gh-114581) --- Include/internal/pycore_freelist.h | 14 +++++++------- Objects/floatobject.c | 4 ++-- Objects/genobject.c | 4 ++-- Objects/listobject.c | 4 ++-- Objects/sliceobject.c | 14 +++++++------- Objects/tupleobject.c | 2 +- Python/context.c | 4 ++-- Python/object_stack.c | 4 ++-- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index dfb12839affedfc..b91d2bc066b783b 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -103,13 +103,13 @@ struct _Py_object_stack_state { }; typedef struct _Py_freelist_state { - struct _Py_float_state float_state; - struct _Py_tuple_state tuple_state; - struct _Py_list_state list_state; - struct _Py_slice_state slice_state; - struct _Py_context_state context_state; - struct _Py_async_gen_state async_gen_state; - struct _Py_object_stack_state object_stack_state; + struct _Py_float_state floats; + struct _Py_tuple_state tuples; + struct _Py_list_state lists; + struct _Py_slice_state slices; + struct _Py_context_state contexts; + struct _Py_async_gen_state async_gens; + struct _Py_object_stack_state object_stacks; } _PyFreeListState; #ifdef __cplusplus diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 912c450a5e1055c..b7611d5f96ac3be 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -32,7 +32,7 @@ get_float_state(void) { _PyFreeListState *state = _PyFreeListState_GET(); assert(state != NULL); - return &state->float_state; + return &state->floats; } #endif @@ -1993,7 +1993,7 @@ void _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_float_state *state = &freelist_state->float_state; + struct _Py_float_state *state = &freelist_state->floats; PyFloatObject *f = state->free_list; while (f != NULL) { PyFloatObject *next = (PyFloatObject*) Py_TYPE(f); diff --git a/Objects/genobject.c b/Objects/genobject.c index e9aeb7ab9a9fa83..f47197330fdd801 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1633,7 +1633,7 @@ static struct _Py_async_gen_state * get_async_gen_state(void) { _PyFreeListState *state = _PyFreeListState_GET(); - return &state->async_gen_state; + return &state->async_gens; } #endif @@ -1659,7 +1659,7 @@ void _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_async_gen_state *state = &freelist_state->async_gen_state; + struct _Py_async_gen_state *state = &freelist_state->async_gens; while (state->value_numfree > 0) { _PyAsyncGenWrappedValue *o; diff --git a/Objects/listobject.c b/Objects/listobject.c index 401d1026133f4e1..1e885f9cb80c4ce 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -26,7 +26,7 @@ get_list_state(void) { _PyFreeListState *state = _PyFreeListState_GET(); assert(state != NULL); - return &state->list_state; + return &state->lists; } #endif @@ -124,7 +124,7 @@ void _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_list_state *state = &freelist_state->list_state; + struct _Py_list_state *state = &freelist_state->lists; while (state->numfree > 0) { PyListObject *op = state->free_list[--state->numfree]; assert(PyList_CheckExact(op)); diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 440c1da30620c38..8b9d6bbfd858b75 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -106,9 +106,9 @@ PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type); void _PySlice_ClearCache(_PyFreeListState *state) { #ifdef WITH_FREELISTS - PySliceObject *obj = state->slice_state.slice_cache; + PySliceObject *obj = state->slices.slice_cache; if (obj != NULL) { - state->slice_state.slice_cache = NULL; + state->slices.slice_cache = NULL; PyObject_GC_Del(obj); } #endif @@ -132,9 +132,9 @@ _PyBuildSlice_Consume2(PyObject *start, PyObject *stop, PyObject *step) PySliceObject *obj; #ifdef WITH_FREELISTS _PyFreeListState *state = _PyFreeListState_GET(); - if (state->slice_state.slice_cache != NULL) { - obj = state->slice_state.slice_cache; - state->slice_state.slice_cache = NULL; + if (state->slices.slice_cache != NULL) { + obj = state->slices.slice_cache; + state->slices.slice_cache = NULL; _Py_NewReference((PyObject *)obj); } else @@ -370,8 +370,8 @@ slice_dealloc(PySliceObject *r) Py_DECREF(r->stop); #ifdef WITH_FREELISTS _PyFreeListState *state = _PyFreeListState_GET(); - if (state->slice_state.slice_cache == NULL) { - state->slice_state.slice_cache = r; + if (state->slices.slice_cache == NULL) { + state->slices.slice_cache = r; } else #endif diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index e1b8e4004c61638..b9bf6cd48f61292 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -1125,7 +1125,7 @@ tuple_iter(PyObject *seq) * freelists * *************/ -#define STATE (state->tuple_state) +#define STATE (state->tuples) #define FREELIST_FINALIZED (STATE.numfree[0] < 0) static inline PyTupleObject * diff --git a/Python/context.c b/Python/context.c index 1e90811c374ec63..294485e5b407dfe 100644 --- a/Python/context.c +++ b/Python/context.c @@ -69,7 +69,7 @@ static struct _Py_context_state * get_context_state(void) { _PyFreeListState *state = _PyFreeListState_GET(); - return &state->context_state; + return &state->contexts; } #endif @@ -1270,7 +1270,7 @@ void _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) { #ifdef WITH_FREELISTS - struct _Py_context_state *state = &freelist_state->context_state; + struct _Py_context_state *state = &freelist_state->contexts; for (; state->numfree > 0; state->numfree--) { PyContext *ctx = state->freelist; state->freelist = (PyContext *)ctx->ctx_weakreflist; diff --git a/Python/object_stack.c b/Python/object_stack.c index 66b37bcbb444758..8544892eb71dcb6 100644 --- a/Python/object_stack.c +++ b/Python/object_stack.c @@ -12,7 +12,7 @@ static struct _Py_object_stack_state * get_state(void) { _PyFreeListState *state = _PyFreeListState_GET(); - return &state->object_stack_state; + return &state->object_stacks; } _PyObjectStackChunk * @@ -76,7 +76,7 @@ _PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalizat return; } - struct _Py_object_stack_state *state = &free_lists->object_stack_state; + struct _Py_object_stack_state *state = &free_lists->object_stacks; while (state->numfree > 0) { _PyObjectStackChunk *buf = state->free_list; state->free_list = buf->prev; From f9c505698a1ac27f5a380780767665ffd2fb8ebc Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Sat, 27 Jan 2024 01:20:21 +0900 Subject: [PATCH 046/263] gh-112087: Make list_repr and list_length to be thread-safe (gh-114582) --- Include/cpython/listobject.h | 4 ++++ Objects/listobject.c | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Include/cpython/listobject.h b/Include/cpython/listobject.h index 8ade1b164681f9e..49f5e8d6d1a0d6c 100644 --- a/Include/cpython/listobject.h +++ b/Include/cpython/listobject.h @@ -29,7 +29,11 @@ typedef struct { static inline Py_ssize_t PyList_GET_SIZE(PyObject *op) { PyListObject *list = _PyList_CAST(op); +#ifdef Py_GIL_DISABLED + return _Py_atomic_load_ssize_relaxed(&(_PyVarObject_CAST(list)->ob_size)); +#else return Py_SIZE(list); +#endif } #define PyList_GET_SIZE(op) PyList_GET_SIZE(_PyObject_CAST(op)) diff --git a/Objects/listobject.c b/Objects/listobject.c index 1e885f9cb80c4ce..56785e5f37a450f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -383,18 +383,11 @@ list_dealloc(PyObject *self) } static PyObject * -list_repr(PyObject *self) +list_repr_impl(PyListObject *v) { - PyListObject *v = (PyListObject *)self; - Py_ssize_t i; PyObject *s; _PyUnicodeWriter writer; - - if (Py_SIZE(v) == 0) { - return PyUnicode_FromString("[]"); - } - - i = Py_ReprEnter((PyObject*)v); + Py_ssize_t i = Py_ReprEnter((PyObject*)v); if (i != 0) { return i > 0 ? PyUnicode_FromString("[...]") : NULL; } @@ -439,10 +432,24 @@ list_repr(PyObject *self) return NULL; } +static PyObject * +list_repr(PyObject *self) +{ + if (PyList_GET_SIZE(self) == 0) { + return PyUnicode_FromString("[]"); + } + PyListObject *v = (PyListObject *)self; + PyObject *ret = NULL; + Py_BEGIN_CRITICAL_SECTION(v); + ret = list_repr_impl(v); + Py_END_CRITICAL_SECTION(); + return ret; +} + static Py_ssize_t list_length(PyObject *a) { - return Py_SIZE(a); + return PyList_GET_SIZE(a); } static int From 102569d150b690efe94c13921e93da66081ba1cf Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 26 Jan 2024 17:27:29 +0000 Subject: [PATCH 047/263] Use Unicode unconditionally for _winapi.CreateFile (GH-114611) Currently it switches based on build settings, but argument clinic does not handle it correctly. --- Modules/_winapi.c | 17 +++++++++-------- Modules/clinic/_winapi.c.h | 13 ++++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 26302b559817b39..5e5eb123c4ccfff 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -441,7 +441,7 @@ _winapi_ConnectNamedPipe_impl(PyObject *module, HANDLE handle, /*[clinic input] _winapi.CreateFile -> HANDLE - file_name: LPCTSTR + file_name: LPCWSTR desired_access: DWORD share_mode: DWORD security_attributes: LPSECURITY_ATTRIBUTES @@ -452,12 +452,12 @@ _winapi.CreateFile -> HANDLE [clinic start generated code]*/ static HANDLE -_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, +_winapi_CreateFile_impl(PyObject *module, LPCWSTR file_name, DWORD desired_access, DWORD share_mode, LPSECURITY_ATTRIBUTES security_attributes, DWORD creation_disposition, DWORD flags_and_attributes, HANDLE template_file) -/*[clinic end generated code: output=417ddcebfc5a3d53 input=6423c3e40372dbd5]*/ +/*[clinic end generated code: output=818c811e5e04d550 input=1fa870ed1c2e3d69]*/ { HANDLE handle; @@ -468,14 +468,15 @@ _winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, } Py_BEGIN_ALLOW_THREADS - handle = CreateFile(file_name, desired_access, - share_mode, security_attributes, - creation_disposition, - flags_and_attributes, template_file); + handle = CreateFileW(file_name, desired_access, + share_mode, security_attributes, + creation_disposition, + flags_and_attributes, template_file); Py_END_ALLOW_THREADS - if (handle == INVALID_HANDLE_VALUE) + if (handle == INVALID_HANDLE_VALUE) { PyErr_SetFromWindowsErr(0); + } return handle; } diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index 3a3231c051ef713..d1052f38919ddef 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -162,7 +162,7 @@ PyDoc_STRVAR(_winapi_CreateFile__doc__, {"CreateFile", _PyCFunction_CAST(_winapi_CreateFile), METH_FASTCALL, _winapi_CreateFile__doc__}, static HANDLE -_winapi_CreateFile_impl(PyObject *module, LPCTSTR file_name, +_winapi_CreateFile_impl(PyObject *module, LPCWSTR file_name, DWORD desired_access, DWORD share_mode, LPSECURITY_ATTRIBUTES security_attributes, DWORD creation_disposition, @@ -172,7 +172,7 @@ static PyObject * _winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) { PyObject *return_value = NULL; - LPCTSTR file_name; + LPCWSTR file_name = NULL; DWORD desired_access; DWORD share_mode; LPSECURITY_ATTRIBUTES security_attributes; @@ -181,8 +181,8 @@ _winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) HANDLE template_file; HANDLE _return_value; - if (!_PyArg_ParseStack(args, nargs, "skk" F_POINTER "kk" F_HANDLE ":CreateFile", - &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { + if (!_PyArg_ParseStack(args, nargs, "O&kk" F_POINTER "kk" F_HANDLE ":CreateFile", + _PyUnicode_WideCharString_Converter, &file_name, &desired_access, &share_mode, &security_attributes, &creation_disposition, &flags_and_attributes, &template_file)) { goto exit; } _return_value = _winapi_CreateFile_impl(module, file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file); @@ -195,6 +195,9 @@ _winapi_CreateFile(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return_value = HANDLE_TO_PYNUM(_return_value); exit: + /* Cleanup for file_name */ + PyMem_Free((void *)file_name); + return return_value; } @@ -1479,4 +1482,4 @@ _winapi_CopyFile2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO return return_value; } -/*[clinic end generated code: output=e1a9908bb82a6379 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2350d4f2275d3a6f input=a9049054013a1b77]*/ From d91ddff5de61447844f1dac575d2e670c8d7e26b Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 26 Jan 2024 17:33:44 +0000 Subject: [PATCH 048/263] gh-114435: Allow test_stat_inaccessible_file() to have matching ino/dev (GH-114571) This may occur if Windows allows reading stat information from a file even if the current user does not have access. --- Lib/test/test_os.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index ed1f304c6c8cac7..e6f0dfde8cb4aea 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3129,10 +3129,9 @@ def cleanup(): if support.verbose: print(" without access:", stat2) - # We cannot get st_dev/st_ino, so ensure those are 0 or else our test - # is not set up correctly - self.assertEqual(0, stat2.st_dev) - self.assertEqual(0, stat2.st_ino) + # We may not get st_dev/st_ino, so ensure those are 0 or match + self.assertIn(stat2.st_dev, (0, stat1.st_dev)) + self.assertIn(stat2.st_ino, (0, stat1.st_ino)) # st_mode and st_size should match (for a normal file, at least) self.assertEqual(stat1.st_mode, stat2.st_mode) From 3f62bf32caf04cedb2c59579a0ce835d1e793d4d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 26 Jan 2024 20:44:45 +0300 Subject: [PATCH 049/263] Document PyOS_strtoul and PyOS_strtol (GH-114048) --- Doc/c-api/conversion.rst | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Doc/c-api/conversion.rst b/Doc/c-api/conversion.rst index c5350123dfdfdcd..4aaf3905e81c8a6 100644 --- a/Doc/c-api/conversion.rst +++ b/Doc/c-api/conversion.rst @@ -48,6 +48,42 @@ The return value (*rv*) for these functions should be interpreted as follows: The following functions provide locale-independent string to number conversions. +.. c:function:: unsigned long PyOS_strtoul(const char *str, char **ptr, int base) + + Convert the initial part of the string in ``str`` to an :c:expr:`unsigned + long` value according to the given ``base``, which must be between ``2`` and + ``36`` inclusive, or be the special value ``0``. + + Leading white space and case of characters are ignored. If ``base`` is zero + it looks for a leading ``0b``, ``0o`` or ``0x`` to tell which base. If + these are absent it defaults to ``10``. Base must be 0 or between 2 and 36 + (inclusive). If ``ptr`` is non-``NULL`` it will contain a pointer to the + end of the scan. + + If the converted value falls out of range of corresponding return type, + range error occurs (:c:data:`errno` is set to :c:macro:`!ERANGE`) and + :c:macro:`!ULONG_MAX` is returned. If no conversion can be performed, ``0`` + is returned. + + See also the Unix man page :manpage:`strtoul(3)`. + + .. versionadded:: 3.2 + + +.. c:function:: long PyOS_strtol(const char *str, char **ptr, int base) + + Convert the initial part of the string in ``str`` to an :c:expr:`long` value + according to the given ``base``, which must be between ``2`` and ``36`` + inclusive, or be the special value ``0``. + + Same as :c:func:`PyOS_strtoul`, but return a :c:expr:`long` value instead + and :c:macro:`LONG_MAX` on overflows. + + See also the Unix man page :manpage:`strtol(3)`. + + .. versionadded:: 3.2 + + .. c:function:: double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception) Convert a string ``s`` to a :c:expr:`double`, raising a Python From 6c2b419fb91c8d7daa769d39f73768114b5eb45a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 26 Jan 2024 19:12:48 +0100 Subject: [PATCH 050/263] Docs: rework the dbm.dumb introduction (#114550) - consistently use correct parameter markup - consistently use submodule name as database name - improve accuracy of the dbm.dumb.open() spec - remove dumbdbm class refs and replace them with generic "database object" - use parameter list for dbm.dumb.open() --- Doc/library/dbm.rst | 66 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 3a7dad1a0736a09..076e86143d06a6f 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -351,13 +351,14 @@ This module can be used with the "classic" NDBM interface or the -------------- -The :mod:`dbm.dumb` module provides a persistent dictionary-like interface which -is written entirely in Python. Unlike other modules such as :mod:`dbm.gnu` no -external library is required. As with other persistent mappings, the keys and -values are always stored as bytes. - -The module defines the following: +The :mod:`dbm.dumb` module provides a persistent :class:`dict`-like +interface which is written entirely in Python. +Unlike other :mod:`dbm` backends, such as :mod:`dbm.gnu`, no +external library is required. +As with other :mod:`dbm` backends, +the keys and values are always stored as :class:`bytes`. +The :mod:`!dbm.dumb` module defines the following: .. exception:: error @@ -365,26 +366,33 @@ The module defines the following: raised for general mapping errors like specifying an incorrect key. -.. function:: open(filename[, flag[, mode]]) +.. function:: open(filename, flag="c", mode=0o666) - Open a ``dumbdbm`` database and return a dumbdbm object. The *filename* argument is - the basename of the database file (without any specific extensions). When a - dumbdbm database is created, files with :file:`.dat` and :file:`.dir` extensions - are created. + Open a :mod:`!dbm.dumb` database. + The returned database object behaves similar to a :term:`mapping`, + in addition to providing :meth:`~dumbdbm.sync` and :meth:`~dumbdbm.close` + methods. - The optional *flag* argument can be: + :param filename: + The basename of the database file (without extensions). + A new database creates the following files: - .. csv-table:: - :header: "Value", "Meaning" + - :file:`{filename}.dat` + - :file:`{filename}.dir` + :type database: :term:`path-like object` - ``'r'``, |flag_r| - ``'w'``, |flag_w| - ``'c'`` (default), |flag_c| - ``'n'``, |flag_n| + :param str flag: + .. csv-table:: + :header: "Value", "Meaning" - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666`` (and will be modified - by the prevailing umask). + ``'r'``, |flag_r| + ``'w'``, |flag_w| + ``'c'`` (default), |flag_c| + ``'n'``, |flag_n| + + :param int mode: + The Unix file access mode of the file (default: ``0o666``), + used only when the database has to be created. .. warning:: It is possible to crash the Python interpreter when loading a database @@ -392,20 +400,18 @@ The module defines the following: Python's AST compiler. .. versionchanged:: 3.5 - :func:`.open` always creates a new database when the flag has the value - ``'n'``. + :func:`open` always creates a new database when *flag* is ``'n'``. .. versionchanged:: 3.8 - A database opened with flags ``'r'`` is now read-only. Opening with - flags ``'r'`` and ``'w'`` no longer creates a database if it does not - exist. + A database opened read-only if *flag* is ``'r'``. + A database is not created if it does not exist if *flag* is ``'r'`` or ``'w'``. .. versionchanged:: 3.11 - Accepts :term:`path-like object` for filename. + *filename* accepts a :term:`path-like object`. In addition to the methods provided by the - :class:`collections.abc.MutableMapping` class, :class:`dumbdbm` objects - provide the following methods: + :class:`collections.abc.MutableMapping` class, + the following methods are provided: .. method:: dumbdbm.sync() @@ -414,5 +420,5 @@ The module defines the following: .. method:: dumbdbm.close() - Close the ``dumbdbm`` database. + Close the database. From 7e31d6dea276ac91402aefb023c58d239dfd9246 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 26 Jan 2024 18:14:24 +0000 Subject: [PATCH 051/263] gh-88569: add `ntpath.isreserved()` (#95486) Add `ntpath.isreserved()`, which identifies reserved pathnames such as "NUL", "AUX" and "CON". Deprecate `pathlib.PurePath.is_reserved()`. --------- Co-authored-by: Eryk Sun Co-authored-by: Brett Cannon Co-authored-by: Steve Dower --- Doc/library/os.path.rst | 22 ++++++++ Doc/library/pathlib.rst | 13 ++--- Doc/whatsnew/3.13.rst | 15 +++++ Lib/ntpath.py | 40 ++++++++++++- Lib/pathlib/__init__.py | 28 +++------- Lib/test/test_ntpath.py | 56 +++++++++++++++++++ Lib/test/test_pathlib/test_pathlib.py | 48 ++-------------- ...2-07-31-01-24-40.gh-issue-88569.eU0--b.rst | 4 ++ 8 files changed, 154 insertions(+), 72 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 3cab7a260df0084..34bc76b231de921 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -326,6 +326,28 @@ the :mod:`glob` module.) .. versionadded:: 3.12 +.. function:: isreserved(path) + + Return ``True`` if *path* is a reserved pathname on the current system. + + On Windows, reserved filenames include those that end with a space or dot; + those that contain colons (i.e. file streams such as "name:stream"), + wildcard characters (i.e. ``'*?"<>'``), pipe, or ASCII control characters; + as well as DOS device names such as "NUL", "CON", "CONIN$", "CONOUT$", + "AUX", "PRN", "COM1", and "LPT1". + + .. note:: + + This function approximates rules for reserved paths on most Windows + systems. These rules change over time in various Windows releases. + This function may be updated in future Python releases as changes to + the rules become broadly available. + + .. availability:: Windows. + + .. versionadded:: 3.13 + + .. function:: join(path, *paths) Join one or more path segments intelligently. The return value is the diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 2f4ff4efec47f8c..f1aba793fda03e7 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -535,14 +535,13 @@ Pure paths provide the following methods and properties: reserved under Windows, ``False`` otherwise. With :class:`PurePosixPath`, ``False`` is always returned. - >>> PureWindowsPath('nul').is_reserved() - True - >>> PurePosixPath('nul').is_reserved() - False - - File system calls on reserved paths can fail mysteriously or have - unintended effects. + .. versionchanged:: 3.13 + Windows path names that contain a colon, or end with a dot or a space, + are considered reserved. UNC paths may be reserved. + .. deprecated-removed:: 3.13 3.15 + This method is deprecated; use :func:`os.path.isreserved` to detect + reserved paths on Windows. .. method:: PurePath.joinpath(*pathsegments) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8c2bb05920d5b60..985e34b453f63a5 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -321,6 +321,9 @@ os os.path ------- +* Add :func:`os.path.isreserved` to check if a path is reserved on the current + system. This function is only available on Windows. + (Contributed by Barney Gale in :gh:`88569`.) * On Windows, :func:`os.path.isabs` no longer considers paths starting with exactly one (back)slash to be absolute. (Contributed by Barney Gale and Jon Foster in :gh:`44626`.) @@ -498,6 +501,12 @@ Deprecated security and functionality bugs. This includes removal of the ``--cgi`` flag to the ``python -m http.server`` command line in 3.15. +* :mod:`pathlib`: + + * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. + * :mod:`sys`: :func:`sys._enablelegacywindowsfsencoding` function. Replace it with :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. (Contributed by Inada Naoki in :gh:`73427`.) @@ -709,6 +718,12 @@ Pending Removal in Python 3.15 :func:`locale.getlocale()` instead. (Contributed by Hugo van Kemenade in :gh:`111187`.) +* :mod:`pathlib`: + + * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. + * :class:`typing.NamedTuple`: * The undocumented keyword argument syntax for creating NamedTuple classes diff --git a/Lib/ntpath.py b/Lib/ntpath.py index aa0e018eb668c2e..e7cbfe17ecb3c84 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -26,8 +26,8 @@ __all__ = ["normcase","isabs","join","splitdrive","splitroot","split","splitext", "basename","dirname","commonprefix","getsize","getmtime", "getatime","getctime", "islink","exists","lexists","isdir","isfile", - "ismount", "expanduser","expandvars","normpath","abspath", - "curdir","pardir","sep","pathsep","defpath","altsep", + "ismount","isreserved","expanduser","expandvars","normpath", + "abspath","curdir","pardir","sep","pathsep","defpath","altsep", "extsep","devnull","realpath","supports_unicode_filenames","relpath", "samefile", "sameopenfile", "samestat", "commonpath", "isjunction"] @@ -330,6 +330,42 @@ def ismount(path): return False +_reserved_chars = frozenset( + {chr(i) for i in range(32)} | + {'"', '*', ':', '<', '>', '?', '|', '/', '\\'} +) + +_reserved_names = frozenset( + {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | + {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | + {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} +) + +def isreserved(path): + """Return true if the pathname is reserved by the system.""" + # Refer to "Naming Files, Paths, and Namespaces": + # https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + path = os.fsdecode(splitroot(path)[2]).replace(altsep, sep) + return any(_isreservedname(name) for name in reversed(path.split(sep))) + +def _isreservedname(name): + """Return true if the filename is reserved by the system.""" + # Trailing dots and spaces are reserved. + if name.endswith(('.', ' ')) and name not in ('.', '..'): + return True + # Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved. + # ASCII control characters (0-31) are reserved. + # Colon is reserved for file streams (e.g. "name:stream[:type]"). + if _reserved_chars.intersection(name): + return True + # DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules + # are complex and vary across Windows versions. On the side of + # caution, return True for names that may not be reserved. + if name.partition('.')[0].rstrip(' ').upper() in _reserved_names: + return True + return False + + # Expand paths beginning with '~' or '~user'. # '~' means $HOME; '~user' means that user's home directory. # If the path doesn't begin with '~', or if the user or $HOME is unknown, diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index eee82ef26bc7e7c..cc159edab5796f5 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -33,15 +33,6 @@ ] -# Reference for Windows paths can be found at -# https://learn.microsoft.com/en-gb/windows/win32/fileio/naming-a-file . -_WIN_RESERVED_NAMES = frozenset( - {'CON', 'PRN', 'AUX', 'NUL', 'CONIN$', 'CONOUT$'} | - {f'COM{c}' for c in '123456789\xb9\xb2\xb3'} | - {f'LPT{c}' for c in '123456789\xb9\xb2\xb3'} -) - - class _PathParents(Sequence): """This object provides sequence-like access to the logical ancestors of a path. Don't try to construct it yourself.""" @@ -433,18 +424,13 @@ def is_absolute(self): def is_reserved(self): """Return True if the path contains one of the special names reserved by the system, if any.""" - if self.pathmod is not ntpath or not self.name: - return False - - # NOTE: the rules for reserved names seem somewhat complicated - # (e.g. r"..\NUL" is reserved but not r"foo\NUL" if "foo" does not - # exist). We err on the side of caution and return True for paths - # which are not considered reserved by Windows. - if self.drive.startswith('\\\\'): - # UNC paths are never reserved. - return False - name = self.name.partition('.')[0].partition(':')[0].rstrip(' ') - return name.upper() in _WIN_RESERVED_NAMES + msg = ("pathlib.PurePath.is_reserved() is deprecated and scheduled " + "for removal in Python 3.15. Use os.path.isreserved() to " + "detect reserved paths on Windows.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + if self.pathmod is ntpath: + return self.pathmod.isreserved(self) + return False def as_uri(self): """Return the path as a URI.""" diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index aefcb98f1c30eb0..9cb03e3cd5de8d7 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -981,6 +981,62 @@ def test_ismount(self): self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$")) self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\")) + def test_isreserved(self): + self.assertFalse(ntpath.isreserved('')) + self.assertFalse(ntpath.isreserved('.')) + self.assertFalse(ntpath.isreserved('..')) + self.assertFalse(ntpath.isreserved('/')) + self.assertFalse(ntpath.isreserved('/foo/bar')) + # A name that ends with a space or dot is reserved. + self.assertTrue(ntpath.isreserved('foo.')) + self.assertTrue(ntpath.isreserved('foo ')) + # ASCII control characters are reserved. + self.assertTrue(ntpath.isreserved('\foo')) + # Wildcard characters, colon, and pipe are reserved. + self.assertTrue(ntpath.isreserved('foo*bar')) + self.assertTrue(ntpath.isreserved('foo?bar')) + self.assertTrue(ntpath.isreserved('foo"bar')) + self.assertTrue(ntpath.isreserved('foobar')) + self.assertTrue(ntpath.isreserved('foo:bar')) + self.assertTrue(ntpath.isreserved('foo|bar')) + # Case-insensitive DOS-device names are reserved. + self.assertTrue(ntpath.isreserved('nul')) + self.assertTrue(ntpath.isreserved('aux')) + self.assertTrue(ntpath.isreserved('prn')) + self.assertTrue(ntpath.isreserved('con')) + self.assertTrue(ntpath.isreserved('conin$')) + self.assertTrue(ntpath.isreserved('conout$')) + # COM/LPT + 1-9 or + superscript 1-3 are reserved. + self.assertTrue(ntpath.isreserved('COM1')) + self.assertTrue(ntpath.isreserved('LPT9')) + self.assertTrue(ntpath.isreserved('com\xb9')) + self.assertTrue(ntpath.isreserved('com\xb2')) + self.assertTrue(ntpath.isreserved('lpt\xb3')) + # DOS-device name matching ignores characters after a dot or + # a colon and also ignores trailing spaces. + self.assertTrue(ntpath.isreserved('NUL.txt')) + self.assertTrue(ntpath.isreserved('PRN ')) + self.assertTrue(ntpath.isreserved('AUX .txt')) + self.assertTrue(ntpath.isreserved('COM1:bar')) + self.assertTrue(ntpath.isreserved('LPT9 :bar')) + # DOS-device names are only matched at the beginning + # of a path component. + self.assertFalse(ntpath.isreserved('bar.com9')) + self.assertFalse(ntpath.isreserved('bar.lpt9')) + # The entire path is checked, except for the drive. + self.assertTrue(ntpath.isreserved('c:/bar/baz/NUL')) + self.assertTrue(ntpath.isreserved('c:/NUL/bar/baz')) + self.assertFalse(ntpath.isreserved('//./NUL')) + # Bytes are supported. + self.assertFalse(ntpath.isreserved(b'')) + self.assertFalse(ntpath.isreserved(b'.')) + self.assertFalse(ntpath.isreserved(b'..')) + self.assertFalse(ntpath.isreserved(b'/')) + self.assertFalse(ntpath.isreserved(b'/foo/bar')) + self.assertTrue(ntpath.isreserved(b'foo.')) + self.assertTrue(ntpath.isreserved(b'nul')) + def assertEqualCI(self, s1, s2): """Assert that two strings are equal ignoring case differences.""" self.assertEqual(s1.lower(), s2.lower()) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index bdbe92369639ef8..2da3afdd1980154 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -349,6 +349,12 @@ def test_is_relative_to_several_args(self): with self.assertWarns(DeprecationWarning): p.is_relative_to('a', 'b') + def test_is_reserved_deprecated(self): + P = self.cls + p = P('a/b') + with self.assertWarns(DeprecationWarning): + p.is_reserved() + def test_match_empty(self): P = self.cls self.assertRaises(ValueError, P('a').match, '') @@ -414,13 +420,6 @@ def test_is_absolute(self): self.assertTrue(P('//a').is_absolute()) self.assertTrue(P('//a/b').is_absolute()) - def test_is_reserved(self): - P = self.cls - self.assertIs(False, P('').is_reserved()) - self.assertIs(False, P('/').is_reserved()) - self.assertIs(False, P('/foo/bar').is_reserved()) - self.assertIs(False, P('/dev/con/PRN/NUL').is_reserved()) - def test_join(self): P = self.cls p = P('//a') @@ -1082,41 +1081,6 @@ def test_div(self): self.assertEqual(p / P('./dd:s'), P('C:/a/b/dd:s')) self.assertEqual(p / P('E:d:s'), P('E:d:s')) - def test_is_reserved(self): - P = self.cls - self.assertIs(False, P('').is_reserved()) - self.assertIs(False, P('/').is_reserved()) - self.assertIs(False, P('/foo/bar').is_reserved()) - # UNC paths are never reserved. - self.assertIs(False, P('//my/share/nul/con/aux').is_reserved()) - # Case-insensitive DOS-device names are reserved. - self.assertIs(True, P('nul').is_reserved()) - self.assertIs(True, P('aux').is_reserved()) - self.assertIs(True, P('prn').is_reserved()) - self.assertIs(True, P('con').is_reserved()) - self.assertIs(True, P('conin$').is_reserved()) - self.assertIs(True, P('conout$').is_reserved()) - # COM/LPT + 1-9 or + superscript 1-3 are reserved. - self.assertIs(True, P('COM1').is_reserved()) - self.assertIs(True, P('LPT9').is_reserved()) - self.assertIs(True, P('com\xb9').is_reserved()) - self.assertIs(True, P('com\xb2').is_reserved()) - self.assertIs(True, P('lpt\xb3').is_reserved()) - # DOS-device name mataching ignores characters after a dot or - # a colon and also ignores trailing spaces. - self.assertIs(True, P('NUL.txt').is_reserved()) - self.assertIs(True, P('PRN ').is_reserved()) - self.assertIs(True, P('AUX .txt').is_reserved()) - self.assertIs(True, P('COM1:bar').is_reserved()) - self.assertIs(True, P('LPT9 :bar').is_reserved()) - # DOS-device names are only matched at the beginning - # of a path component. - self.assertIs(False, P('bar.com9').is_reserved()) - self.assertIs(False, P('bar.lpt9').is_reserved()) - # Only the last path component matters. - self.assertIs(True, P('c:/baz/con/NUL').is_reserved()) - self.assertIs(False, P('c:/NUL/con/baz').is_reserved()) - class PurePathSubclassTest(PurePathTest): class cls(pathlib.PurePath): diff --git a/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst b/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst new file mode 100644 index 000000000000000..31dd985bb5c3b6c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-31-01-24-40.gh-issue-88569.eU0--b.rst @@ -0,0 +1,4 @@ +Add :func:`os.path.isreserved`, which identifies reserved pathnames such +as "NUL", "AUX" and "CON". This function is only available on Windows. + +Deprecate :meth:`pathlib.PurePath.is_reserved`. From df17b5264378f38f49b16343b5016a8882212a8a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 26 Jan 2024 11:33:02 -0700 Subject: [PATCH 052/263] Add More Entries to CODEOWNERS (#114617) --- .github/CODEOWNERS | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4984170f0d17ff7..ae915423ece9551 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -45,6 +45,30 @@ Tools/c-analyzer/ @ericsnowcurrently # dbm **/*dbm* @corona10 @erlend-aasland @serhiy-storchaka +# runtime state/lifecycle +**/*pylifecycle* @ericsnowcurrently +**/*pystate* @ericsnowcurrently +**/*preconfig* @ericsnowcurrently +**/*initconfig* @ericsnowcurrently +**/*pathconfig* @ericsnowcurrently +**/*sysmodule* @ericsnowcurrently +**/*bltinmodule* @ericsnowcurrently +**/*gil* @ericsnowcurrently +Include/internal/pycore_runtime.h @ericsnowcurrently +Include/internal/pycore_interp.h @ericsnowcurrently +Include/internal/pycore_tstate.h @ericsnowcurrently +Include/internal/pycore_*_state.h @ericsnowcurrently +Include/internal/pycore_*_init.h @ericsnowcurrently +Include/internal/pycore_atexit.h @ericsnowcurrently +Include/internal/pycore_freelist.h @ericsnowcurrently +Include/internal/pycore_global_objects.h @ericsnowcurrently +Include/internal/pycore_obmalloc.h @ericsnowcurrently +Include/internal/pycore_pymem.h @ericsnowcurrently +Modules/main.c @ericsnowcurrently +Programs/_bootstrap_python.c @ericsnowcurrently +Programs/python.c @ericsnowcurrently +Tools/build/generate_global_objects.py @ericsnowcurrently + # Exceptions Lib/traceback.py @iritkatriel Lib/test/test_except*.py @iritkatriel @@ -79,7 +103,20 @@ Modules/_hacl/** @gpshead # Import (including importlib). **/*import* @brettcannon @ericsnowcurrently @ncoghlan @warsaw /Python/import.c @kumaraditya303 -**/*importlib/resources/* @jaraco @warsaw @FFY00 +Python/dynload_*.c @ericsnowcurrently +**/*freeze* @ericsnowcurrently +**/*frozen* @ericsnowcurrently +**/*modsupport* @ericsnowcurrently +**/*modulefinder* @ericsnowcurrently +**/*moduleobject* @ericsnowcurrently +**/*multiphase* @ericsnowcurrently +**/*pkgutil* @ericsnowcurrently +**/*pythonrun* @ericsnowcurrently +**/*runpy* @ericsnowcurrently +**/*singlephase* @ericsnowcurrently +Lib/test/test_module/ @ericsnowcurrently +Doc/c-api/module.rst @ericsnowcurrently +**/*importlib/resources/* @jaraco @warsaw @FFY00 **/importlib/metadata/* @jaraco @warsaw # Dates and times @@ -198,6 +235,8 @@ Doc/c-api/stable.rst @encukou Doc/howto/clinic.rst @erlend-aasland # Subinterpreters +**/*interpreteridobject.* @ericsnowcurrently +**/*crossinterp* @ericsnowcurrently Lib/test/support/interpreters/ @ericsnowcurrently Modules/_xx*interp*module.c @ericsnowcurrently Lib/test/test_interpreters/ @ericsnowcurrently From 07236f5b39a2e534cf190cd4f7c73300d209520b Mon Sep 17 00:00:00 2001 From: Tristan Pank Date: Fri, 26 Jan 2024 14:48:22 -0500 Subject: [PATCH 053/263] gh-114494: Change logging docstring to bool for exec_info (GH=114558) --- Lib/logging/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index eb7e020d1edfc04..684b58d5548f91f 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1493,7 +1493,7 @@ def debug(self, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.debug("Houston, we have a %s", "thorny problem", exc_info=1) + logger.debug("Houston, we have a %s", "thorny problem", exc_info=True) """ if self.isEnabledFor(DEBUG): self._log(DEBUG, msg, args, **kwargs) @@ -1505,7 +1505,7 @@ def info(self, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.info("Houston, we have a %s", "notable problem", exc_info=1) + logger.info("Houston, we have a %s", "notable problem", exc_info=True) """ if self.isEnabledFor(INFO): self._log(INFO, msg, args, **kwargs) @@ -1517,7 +1517,7 @@ def warning(self, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1) + logger.warning("Houston, we have a %s", "bit of a problem", exc_info=True) """ if self.isEnabledFor(WARNING): self._log(WARNING, msg, args, **kwargs) @@ -1529,7 +1529,7 @@ def error(self, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.error("Houston, we have a %s", "major problem", exc_info=1) + logger.error("Houston, we have a %s", "major problem", exc_info=True) """ if self.isEnabledFor(ERROR): self._log(ERROR, msg, args, **kwargs) @@ -1547,7 +1547,7 @@ def critical(self, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.critical("Houston, we have a %s", "major disaster", exc_info=1) + logger.critical("Houston, we have a %s", "major disaster", exc_info=True) """ if self.isEnabledFor(CRITICAL): self._log(CRITICAL, msg, args, **kwargs) @@ -1565,7 +1565,7 @@ def log(self, level, msg, *args, **kwargs): To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.log(level, "We have a %s", "mysterious problem", exc_info=1) + logger.log(level, "We have a %s", "mysterious problem", exc_info=True) """ if not isinstance(level, int): if raiseExceptions: From b5c7c84673b96bfdd7c877521a970f7a4beafece Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 26 Jan 2024 15:36:50 -0500 Subject: [PATCH 054/263] gh-114490: Add check for Mach-O linkage in Lib/platform.py (#114491) ``platform.architecture()`` now returns the format of binaries (e.g. Mach-O) instead of the default empty string. Co-authored-by: AN Long --- Lib/platform.py | 2 ++ .../next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst | 1 + 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst diff --git a/Lib/platform.py b/Lib/platform.py index 75aa55510858fd4..b56472235ee9e42 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -752,6 +752,8 @@ def architecture(executable=sys.executable, bits='', linkage=''): # Linkage if 'ELF' in fileout: linkage = 'ELF' + elif 'Mach-O' in fileout: + linkage = "Mach-O" elif 'PE' in fileout: # E.g. Windows uses this format if 'Windows' in fileout: diff --git a/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst b/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst new file mode 100644 index 000000000000000..abd296f86085180 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-01-23-11-35-26.gh-issue-114490.FrQOQ0.rst @@ -0,0 +1 @@ +Add Mach-O linkage support for :func:`platform.architecture()`. From 7a9727e10c14a82e8e20f5b85e368a6f937db203 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 26 Jan 2024 22:29:28 +0000 Subject: [PATCH 055/263] pathlib tests: annotate tests needing symlinks with decorator (#114625) Add `@needs_symlinks` decorator for tests that require symlink support in the path class. Also add `@needs_windows` and `@needs_posix` decorators for tests that require a specific a specific path flavour. These aren't much used yet, but will be later. --- Lib/test/test_pathlib/test_pathlib.py | 17 ++--- Lib/test/test_pathlib/test_pathlib_abc.py | 86 +++++++++++++---------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 2da3afdd1980154..2cef3b295559d8c 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -19,6 +19,7 @@ from test.support import os_helper from test.support.os_helper import TESTFN, FakePath from test.test_pathlib import test_pathlib_abc +from test.test_pathlib.test_pathlib_abc import needs_posix, needs_windows, needs_symlinks try: import grp, pwd @@ -26,11 +27,6 @@ grp = pwd = None -only_nt = unittest.skipIf(os.name != 'nt', - 'test requires a Windows-compatible system') -only_posix = unittest.skipIf(os.name == 'nt', - 'test requires a POSIX-compatible system') - root_in_posix = False if hasattr(os, 'geteuid'): root_in_posix = (os.geteuid() == 0) @@ -1268,7 +1264,7 @@ def test_chmod(self): self.assertEqual(p.stat().st_mode, new_mode) # On Windows, os.chmod does not follow symlinks (issue #15411) - @only_posix + @needs_posix @os_helper.skip_unless_working_chmod def test_chmod_follow_symlinks_true(self): p = self.cls(self.base) / 'linkA' @@ -1537,7 +1533,7 @@ def test_mkdir_exist_ok_root(self): self.cls('/').resolve().mkdir(exist_ok=True) self.cls('/').resolve().mkdir(parents=True, exist_ok=True) - @only_nt # XXX: not sure how to test this on POSIX. + @needs_windows # XXX: not sure how to test this on POSIX. def test_mkdir_with_unknown_drive(self): for d in 'ZYXWVUTSRQPONMLKJIHGFEDCBA': p = self.cls(d + ':\\') @@ -1602,9 +1598,8 @@ def my_mkdir(path, mode=0o777): self.assertNotIn(str(p12), concurrently_created) self.assertTrue(p.exists()) + @needs_symlinks def test_symlink_to(self): - if not self.can_symlink: - self.skipTest("symlinks required") P = self.cls(self.base) target = P / 'fileA' # Symlinking a path target. @@ -1848,7 +1843,7 @@ def test_rglob_pathlike(self): self.assertEqual(expect, set(p.rglob(FakePath(pattern)))) -@only_posix +@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system') class PosixPathTest(PathTest, PurePosixPathTest): cls = pathlib.PosixPath @@ -2024,7 +2019,7 @@ def test_from_uri_pathname2url(self): self.assertEqual(P.from_uri('file:' + pathname2url('//foo/bar')), P('//foo/bar')) -@only_nt +@unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') class WindowsPathTest(PathTest, PureWindowsPathTest): cls = pathlib.WindowsPath diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 364f776dbb1413a..b19e9b40419c7ae 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -11,6 +11,27 @@ from test.support.os_helper import TESTFN +_tests_needing_posix = set() +_tests_needing_windows = set() +_tests_needing_symlinks = set() + + +def needs_posix(fn): + """Decorator that marks a test as requiring a POSIX-flavoured path class.""" + _tests_needing_posix.add(fn.__name__) + return fn + +def needs_windows(fn): + """Decorator that marks a test as requiring a Windows-flavoured path class.""" + _tests_needing_windows.add(fn.__name__) + return fn + +def needs_symlinks(fn): + """Decorator that marks a test as requiring a path class that supports symlinks.""" + _tests_needing_symlinks.add(fn.__name__) + return fn + + class UnsupportedOperationTest(unittest.TestCase): def test_is_notimplemented(self): self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError)) @@ -115,6 +136,11 @@ class DummyPurePathTest(unittest.TestCase): base = f'/this/path/kills/fascists/{TESTFN}' def setUp(self): + name = self.id().split('.')[-1] + if name in _tests_needing_posix and self.cls.pathmod is not posixpath: + self.skipTest('requires POSIX-flavoured path class') + if name in _tests_needing_windows and self.cls.pathmod is posixpath: + self.skipTest('requires Windows-flavoured path class') p = self.cls('a') self.pathmod = p.pathmod self.sep = self.pathmod.sep @@ -888,6 +914,9 @@ class DummyPathTest(DummyPurePathTest): def setUp(self): super().setUp() + name = self.id().split('.')[-1] + if name in _tests_needing_symlinks and not self.can_symlink: + self.skipTest('requires symlinks') pathmod = self.cls.pathmod p = self.cls(self.base) p.mkdir(parents=True) @@ -1045,9 +1074,8 @@ def test_iterdir(self): expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] self.assertEqual(paths, { P(self.base, q) for q in expected }) + @needs_symlinks def test_iterdir_symlink(self): - if not self.can_symlink: - self.skipTest("symlinks required") # __iter__ on a symlink to a directory. P = self.cls p = P(self.base, 'linkB') @@ -1116,9 +1144,8 @@ def _check(path, pattern, case_sensitive, expected): _check(path, "dirb/file*", True, []) _check(path, "dirb/file*", False, ["dirB/fileB"]) + @needs_symlinks def test_glob_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=True) if path.parts.count("linkD") <= 1} # exclude symlink loop. @@ -1144,9 +1171,8 @@ def _check(path, glob, expected): _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) + @needs_symlinks def test_glob_no_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=False)} self.assertEqual(actual, { P(self.base, q) for q in expected }) @@ -1210,9 +1236,8 @@ def _check(glob, expected): _check(p.rglob("*.txt"), ["dirC/novel.txt"]) _check(p.rglob("*.*"), ["dirC/novel.txt"]) + @needs_symlinks def test_rglob_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=True) if path.parts.count("linkD") <= 1} # exclude symlink loop. @@ -1243,9 +1268,8 @@ def _check(path, glob, expected): _check(p, "*.txt", ["dirC/novel.txt"]) _check(p, "*.*", ["dirC/novel.txt"]) + @needs_symlinks def test_rglob_no_follow_symlinks_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=False)} self.assertEqual(actual, { P(self.base, q) for q in expected }) @@ -1269,10 +1293,9 @@ def _check(path, glob, expected): _check(p, "*.txt", ["dirC/novel.txt"]) _check(p, "*.*", ["dirC/novel.txt"]) + @needs_symlinks def test_rglob_symlink_loop(self): # Don't get fooled by symlink loops (Issue #26012). - if not self.can_symlink: - self.skipTest("symlinks required") P = self.cls p = P(self.base) given = set(p.rglob('*')) @@ -1302,10 +1325,9 @@ def test_glob_dotdot(self): self.assertEqual(set(p.glob("xyzzy/..")), set()) self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)}) + @needs_symlinks def test_glob_permissions(self): # See bpo-38894 - if not self.can_symlink: - self.skipTest("symlinks required") P = self.cls base = P(self.base) / 'permissions' base.mkdir() @@ -1322,19 +1344,17 @@ def test_glob_permissions(self): self.assertEqual(len(set(base.glob("*/fileC"))), 50) self.assertEqual(len(set(base.glob("*/file*"))), 50) + @needs_symlinks def test_glob_long_symlink(self): # See gh-87695 - if not self.can_symlink: - self.skipTest("symlinks required") base = self.cls(self.base) / 'long_symlink' base.mkdir() bad_link = base / 'bad_link' bad_link.symlink_to("bad" * 200) self.assertEqual(sorted(base.glob('**/*')), [bad_link]) + @needs_symlinks def test_readlink(self): - if not self.can_symlink: - self.skipTest("symlinks required") P = self.cls(self.base) self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) self.assertEqual((P / 'brokenLink').readlink(), @@ -1358,9 +1378,8 @@ def _check_resolve(self, p, expected, strict=True): # This can be used to check both relative and absolute resolutions. _check_resolve_relative = _check_resolve_absolute = _check_resolve + @needs_symlinks def test_resolve_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") P = self.cls p = P(self.base, 'foo') with self.assertRaises(OSError) as cm: @@ -1419,10 +1438,9 @@ def test_resolve_common(self): # resolves to 'dirB/..' first before resolving to parent of dirB. self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) + @needs_symlinks def test_resolve_dot(self): # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/ - if not self.can_symlink: - self.skipTest("symlinks required") pathmod = self.pathmod p = self.cls(self.base) p.joinpath('0').symlink_to('.', target_is_directory=True) @@ -1441,11 +1459,9 @@ def _check_symlink_loop(self, *args): path.resolve(strict=True) self.assertEqual(cm.exception.errno, errno.ELOOP) + @needs_posix + @needs_symlinks def test_resolve_loop(self): - if not self.can_symlink: - self.skipTest("symlinks required") - if self.cls.pathmod is not posixpath: - self.skipTest("symlink loops work differently with concrete Windows paths") # Loops with relative symlinks. self.cls(self.base, 'linkX').symlink_to('linkX/inside') self._check_symlink_loop(self.base, 'linkX') @@ -1487,9 +1503,8 @@ def test_stat(self): self.assertEqual(statA.st_dev, statC.st_dev) # other attributes not used by pathlib. + @needs_symlinks def test_stat_no_follow_symlinks(self): - if not self.can_symlink: - self.skipTest("symlinks required") p = self.cls(self.base) / 'linkA' st = p.stat() self.assertNotEqual(st, p.stat(follow_symlinks=False)) @@ -1499,9 +1514,8 @@ def test_stat_no_follow_symlinks_nosymlink(self): st = p.stat() self.assertEqual(st, p.stat(follow_symlinks=False)) + @needs_symlinks def test_lstat(self): - if not self.can_symlink: - self.skipTest("symlinks required") p = self.cls(self.base)/ 'linkA' st = p.stat() self.assertNotEqual(st, p.lstat()) @@ -1634,9 +1648,6 @@ def test_is_char_device_false(self): self.assertIs((P / 'fileA\x00').is_char_device(), False) def _check_complex_symlinks(self, link0_target): - if not self.can_symlink: - self.skipTest("symlinks required") - # Test solving a non-looping chain of symlinks (issue #19887). pathmod = self.pathmod P = self.cls(self.base) @@ -1682,12 +1693,15 @@ def _check_complex_symlinks(self, link0_target): finally: os.chdir(old_path) + @needs_symlinks def test_complex_symlinks_absolute(self): self._check_complex_symlinks(self.base) + @needs_symlinks def test_complex_symlinks_relative(self): self._check_complex_symlinks('.') + @needs_symlinks def test_complex_symlinks_relative_dot_dot(self): self._check_complex_symlinks(self.pathmod.join('dirA', '..')) @@ -1803,9 +1817,8 @@ def test_walk_bottom_up(self): raise AssertionError(f"Unexpected path: {path}") self.assertTrue(seen_testfn) + @needs_symlinks def test_walk_follow_symlinks(self): - if not self.can_symlink: - self.skipTest("symlinks required") self.setUpWalk() walk_it = self.walk_path.walk(follow_symlinks=True) for root, dirs, files in walk_it: @@ -1816,9 +1829,8 @@ def test_walk_follow_symlinks(self): else: self.fail("Didn't follow symlink with follow_symlinks=True") + @needs_symlinks def test_walk_symlink_location(self): - if not self.can_symlink: - self.skipTest("symlinks required") self.setUpWalk() # Tests whether symlinks end up in filenames or dirnames depending # on the `follow_symlinks` argument. From fe5905e21ac90a2fb5ebd62779f56bcd87b5f7a0 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 27 Jan 2024 01:30:25 +0000 Subject: [PATCH 056/263] Cover OS-specific behaviour in `PurePath` and `Path` tests (#114632) Test Posix- and Windows-specific behaviour from `PurePathTest` and `PathTest`. --- Lib/test/test_pathlib/test_pathlib.py | 198 +++++++++++++++++--------- 1 file changed, 128 insertions(+), 70 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 2cef3b295559d8c..b0067c25d208b97 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -234,8 +234,10 @@ def test_eq_common(self): self.assertNotEqual(P(), {}) self.assertNotEqual(P(), int) - def test_equivalences(self): - for k, tuples in self.equivalences.items(): + def test_equivalences(self, equivalences=None): + if equivalences is None: + equivalences = self.equivalences + for k, tuples in equivalences.items(): canon = k.replace('/', self.sep) posix = k.replace(self.sep, '/') if canon != posix: @@ -356,11 +358,8 @@ def test_match_empty(self): self.assertRaises(ValueError, P('a').match, '') self.assertRaises(ValueError, P('a').match, '.') - -class PurePosixPathTest(PurePathTest): - cls = pathlib.PurePosixPath - - def test_parse_path(self): + @needs_posix + def test_parse_path_posix(self): check = self._check_parse_path # Collapsing of excess leading slashes, except for the double-slash # special case. @@ -372,25 +371,29 @@ def test_parse_path(self): check('c:\\a', '', '', ['c:\\a',]) check('\\a', '', '', ['\\a',]) - def test_root(self): + @needs_posix + def test_root_posix(self): P = self.cls self.assertEqual(P('/a/b').root, '/') self.assertEqual(P('///a/b').root, '/') # POSIX special case for two leading slashes. self.assertEqual(P('//a/b').root, '//') - def test_eq(self): + @needs_posix + def test_eq_posix(self): P = self.cls self.assertNotEqual(P('a/b'), P('A/b')) self.assertEqual(P('/a'), P('///a')) self.assertNotEqual(P('/a'), P('//a')) - def test_as_uri(self): + @needs_posix + def test_as_uri_posix(self): P = self.cls self.assertEqual(P('/').as_uri(), 'file:///') self.assertEqual(P('/a/b.c').as_uri(), 'file:///a/b.c') self.assertEqual(P('/a/b%#c').as_uri(), 'file:///a/b%25%23c') + @needs_posix def test_as_uri_non_ascii(self): from urllib.parse import quote_from_bytes P = self.cls @@ -401,11 +404,13 @@ def test_as_uri_non_ascii(self): self.assertEqual(P('/a/b\xe9').as_uri(), 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9'))) - def test_match(self): + @needs_posix + def test_match_posix(self): P = self.cls self.assertFalse(P('A.py').match('a.PY')) - def test_is_absolute(self): + @needs_posix + def test_is_absolute_posix(self): P = self.cls self.assertFalse(P().is_absolute()) self.assertFalse(P('a').is_absolute()) @@ -416,7 +421,8 @@ def test_is_absolute(self): self.assertTrue(P('//a').is_absolute()) self.assertTrue(P('//a/b').is_absolute()) - def test_join(self): + @needs_posix + def test_join_posix(self): P = self.cls p = P('//a') pp = p.joinpath('b') @@ -426,7 +432,8 @@ def test_join(self): pp = P('//a').joinpath('/c') self.assertEqual(pp, P('/c')) - def test_div(self): + @needs_posix + def test_div_posix(self): # Basically the same as joinpath(). P = self.cls p = P('//a') @@ -437,18 +444,14 @@ def test_div(self): pp = P('//a') / '/c' self.assertEqual(pp, P('/c')) + @needs_posix def test_parse_windows_path(self): P = self.cls p = P('c:', 'a', 'b') pp = P(pathlib.PureWindowsPath('c:\\a\\b')) self.assertEqual(p, pp) - -class PureWindowsPathTest(PurePathTest): - cls = pathlib.PureWindowsPath - - equivalences = PurePathTest.equivalences.copy() - equivalences.update({ + windows_equivalences = { './a:b': [ ('./a:b',) ], 'c:a': [ ('c:', 'a'), ('c:', 'a/'), ('.', 'c:', 'a') ], 'c:/a': [ @@ -459,9 +462,14 @@ class PureWindowsPathTest(PurePathTest): '//a/b/c': [ ('//a/b', 'c'), ('//a/b/', 'c'), ], - }) + } + + @needs_windows + def test_equivalences_windows(self): + self.test_equivalences(self.windows_equivalences) - def test_parse_path(self): + @needs_windows + def test_parse_path_windows(self): check = self._check_parse_path # First part is anchored. check('c:', 'c:', '', []) @@ -509,7 +517,8 @@ def test_parse_path(self): check('D:a/c:b', 'D:', '', ['a', 'c:b']) check('D:/a/c:b', 'D:', '\\', ['a', 'c:b']) - def test_str(self): + @needs_windows + def test_str_windows(self): p = self.cls('a/b/c') self.assertEqual(str(p), 'a\\b\\c') p = self.cls('c:/a/b/c') @@ -521,7 +530,8 @@ def test_str(self): p = self.cls('//a/b/c/d') self.assertEqual(str(p), '\\\\a\\b\\c\\d') - def test_str_subclass(self): + @needs_windows + def test_str_subclass_windows(self): self._check_str_subclass('.\\a:b') self._check_str_subclass('c:') self._check_str_subclass('c:a') @@ -533,7 +543,8 @@ def test_str_subclass(self): self._check_str_subclass('\\\\some\\share\\a') self._check_str_subclass('\\\\some\\share\\a\\b.txt') - def test_eq(self): + @needs_windows + def test_eq_windows(self): P = self.cls self.assertEqual(P('c:a/b'), P('c:a/b')) self.assertEqual(P('c:a/b'), P('c:', 'a', 'b')) @@ -546,7 +557,8 @@ def test_eq(self): self.assertEqual(P('//Some/SHARE/a/B'), P('//somE/share/A/b')) self.assertEqual(P('\u0130'), P('i\u0307')) - def test_as_uri(self): + @needs_windows + def test_as_uri_windows(self): P = self.cls with self.assertRaises(ValueError): P('/a/b').as_uri() @@ -562,7 +574,8 @@ def test_as_uri(self): self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), 'file://some/share/a/b%25%23c%C3%A9') - def test_match(self): + @needs_windows + def test_match_windows(self): P = self.cls # Absolute patterns. self.assertTrue(P('c:/b.py').match('*:/*.py')) @@ -589,7 +602,8 @@ def test_match(self): self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:' self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/' - def test_ordering_common(self): + @needs_windows + def test_ordering_windows(self): # Case-insensitivity. def assertOrderedEqual(a, b): self.assertLessEqual(a, b) @@ -606,7 +620,8 @@ def assertOrderedEqual(a, b): self.assertFalse(p < q) self.assertFalse(p > q) - def test_parts(self): + @needs_windows + def test_parts_windows(self): P = self.cls p = P('c:a/b') parts = p.parts @@ -618,7 +633,8 @@ def test_parts(self): parts = p.parts self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd')) - def test_parent(self): + @needs_windows + def test_parent_windows(self): # Anchored P = self.cls p = P('z:a/b/c') @@ -636,7 +652,8 @@ def test_parent(self): self.assertEqual(p.parent.parent, P('//a/b')) self.assertEqual(p.parent.parent.parent, P('//a/b')) - def test_parents(self): + @needs_windows + def test_parents_windows(self): # Anchored P = self.cls p = P('z:a/b/') @@ -682,7 +699,8 @@ def test_parents(self): with self.assertRaises(IndexError): par[2] - def test_drive(self): + @needs_windows + def test_drive_windows(self): P = self.cls self.assertEqual(P('c:').drive, 'c:') self.assertEqual(P('c:a/b').drive, 'c:') @@ -693,7 +711,8 @@ def test_drive(self): self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b') self.assertEqual(P('./c:a').drive, '') - def test_root(self): + @needs_windows + def test_root_windows(self): P = self.cls self.assertEqual(P('c:').root, '') self.assertEqual(P('c:a/b').root, '') @@ -703,7 +722,8 @@ def test_root(self): self.assertEqual(P('//a/b/').root, '\\') self.assertEqual(P('//a/b/c/d').root, '\\') - def test_anchor(self): + @needs_windows + def test_anchor_windows(self): P = self.cls self.assertEqual(P('c:').anchor, 'c:') self.assertEqual(P('c:a/b').anchor, 'c:') @@ -713,7 +733,8 @@ def test_anchor(self): self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\') self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\') - def test_name(self): + @needs_windows + def test_name_windows(self): P = self.cls self.assertEqual(P('c:').name, '') self.assertEqual(P('c:/').name, '') @@ -724,7 +745,8 @@ def test_name(self): self.assertEqual(P('//My.py/Share.php').name, '') self.assertEqual(P('//My.py/Share.php/a/b').name, 'b') - def test_suffix(self): + @needs_windows + def test_suffix_windows(self): P = self.cls self.assertEqual(P('c:').suffix, '') self.assertEqual(P('c:/').suffix, '') @@ -743,7 +765,8 @@ def test_suffix(self): self.assertEqual(P('//My.py/Share.php').suffix, '') self.assertEqual(P('//My.py/Share.php/a/b').suffix, '') - def test_suffixes(self): + @needs_windows + def test_suffixes_windows(self): P = self.cls self.assertEqual(P('c:').suffixes, []) self.assertEqual(P('c:/').suffixes, []) @@ -762,7 +785,8 @@ def test_suffixes(self): self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, []) self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, []) - def test_stem(self): + @needs_windows + def test_stem_windows(self): P = self.cls self.assertEqual(P('c:').stem, '') self.assertEqual(P('c:.').stem, '') @@ -776,7 +800,8 @@ def test_stem(self): self.assertEqual(P('c:a/Some name. Ending with a dot.').stem, 'Some name. Ending with a dot.') - def test_with_name(self): + @needs_windows + def test_with_name_windows(self): P = self.cls self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml')) self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml')) @@ -792,7 +817,8 @@ def test_with_name(self): self.assertRaises(ValueError, P('c:a/b').with_name, 'd:/e') self.assertRaises(ValueError, P('c:a/b').with_name, '//My/Share') - def test_with_stem(self): + @needs_windows + def test_with_stem_windows(self): P = self.cls self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d')) self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d')) @@ -808,7 +834,8 @@ def test_with_stem(self): self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e') self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share') - def test_with_suffix(self): + @needs_windows + def test_with_suffix_windows(self): P = self.cls self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz')) self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz')) @@ -832,7 +859,8 @@ def test_with_suffix(self): self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d') self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d') - def test_relative_to(self): + @needs_windows + def test_relative_to_windows(self): P = self.cls p = P('C:Foo/Bar') self.assertEqual(p.relative_to(P('c:')), P('Foo/Bar')) @@ -937,7 +965,8 @@ def test_relative_to(self): self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'), walk_up=True) self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'), walk_up=True) - def test_is_relative_to(self): + @needs_windows + def test_is_relative_to_windows(self): P = self.cls p = P('C:Foo/Bar') self.assertTrue(p.is_relative_to(P('c:'))) @@ -990,7 +1019,8 @@ def test_is_relative_to(self): self.assertFalse(p.is_relative_to(P('//z/Share/Foo'))) self.assertFalse(p.is_relative_to(P('//Server/z/Foo'))) - def test_is_absolute(self): + @needs_windows + def test_is_absolute_windows(self): P = self.cls # Under NT, only paths with both a drive and a root are absolute. self.assertFalse(P().is_absolute()) @@ -1015,7 +1045,8 @@ def test_is_absolute(self): self.assertTrue(P('//?/UNC/').is_absolute()) self.assertTrue(P('//?/UNC/spam').is_absolute()) - def test_join(self): + @needs_windows + def test_join_windows(self): P = self.cls p = P('C:/a/b') pp = p.joinpath('x/y') @@ -1052,7 +1083,8 @@ def test_join(self): pp = P('//./BootPartition').joinpath('Windows') self.assertEqual(pp, P('//./BootPartition/Windows')) - def test_div(self): + @needs_windows + def test_div_windows(self): # Basically the same as joinpath(). P = self.cls p = P('C:/a/b') @@ -1078,6 +1110,14 @@ def test_div(self): self.assertEqual(p / P('E:d:s'), P('E:d:s')) +class PurePosixPathTest(PurePathTest): + cls = pathlib.PurePosixPath + + +class PureWindowsPathTest(PurePathTest): + cls = pathlib.PureWindowsPath + + class PurePathSubclassTest(PurePathTest): class cls(pathlib.PurePath): pass @@ -1842,12 +1882,8 @@ def test_rglob_pathlike(self): self.assertEqual(expect, set(p.rglob(P(pattern)))) self.assertEqual(expect, set(p.rglob(FakePath(pattern)))) - -@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system') -class PosixPathTest(PathTest, PurePosixPathTest): - cls = pathlib.PosixPath - - def test_absolute(self): + @needs_posix + def test_absolute_posix(self): P = self.cls self.assertEqual(str(P('/').absolute()), '/') self.assertEqual(str(P('/a').absolute()), '/a') @@ -1862,6 +1898,7 @@ def test_absolute(self): is_emscripten or is_wasi, "umask is not implemented on Emscripten/WASI." ) + @needs_posix def test_open_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) @@ -1876,6 +1913,7 @@ def test_open_mode(self): st = os.stat(self.pathmod.join(self.base, 'other_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) + @needs_posix def test_resolve_root(self): current_directory = os.getcwd() try: @@ -1889,6 +1927,7 @@ def test_resolve_root(self): is_emscripten or is_wasi, "umask is not implemented on Emscripten/WASI." ) + @needs_posix def test_touch_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) @@ -1904,7 +1943,8 @@ def test_touch_mode(self): st = os.stat(self.pathmod.join(self.base, 'masked_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) - def test_glob(self): + @needs_posix + def test_glob_posix(self): P = self.cls p = P(self.base) given = set(p.glob("FILEa")) @@ -1912,7 +1952,8 @@ def test_glob(self): self.assertEqual(given, expect) self.assertEqual(set(p.glob("FILEa*")), set()) - def test_rglob(self): + @needs_posix + def test_rglob_posix(self): P = self.cls p = P(self.base, "dirC") given = set(p.rglob("FILEd")) @@ -1924,7 +1965,8 @@ def test_rglob(self): 'pwd module does not expose getpwall()') @unittest.skipIf(sys.platform == "vxworks", "no home directory on VxWorks") - def test_expanduser(self): + @needs_posix + def test_expanduser_posix(self): P = self.cls import_helper.import_module('pwd') import pwd @@ -1979,6 +2021,7 @@ def test_expanduser(self): @unittest.skipIf(sys.platform != "darwin", "Bad file descriptor in /dev/fd affects only macOS") + @needs_posix def test_handling_bad_descriptor(self): try: file_descriptors = list(pathlib.Path('/dev/fd').rglob("*"))[3:] @@ -2000,7 +2043,8 @@ def test_handling_bad_descriptor(self): self.fail("Bad file descriptor not handled.") raise - def test_from_uri(self): + @needs_posix + def test_from_uri_posix(self): P = self.cls self.assertEqual(P.from_uri('file:/foo/bar'), P('/foo/bar')) self.assertEqual(P.from_uri('file://foo/bar'), P('//foo/bar')) @@ -2013,17 +2057,14 @@ def test_from_uri(self): self.assertRaises(ValueError, P.from_uri, 'file:foo/bar') self.assertRaises(ValueError, P.from_uri, 'http://foo/bar') - def test_from_uri_pathname2url(self): + @needs_posix + def test_from_uri_pathname2url_posix(self): P = self.cls self.assertEqual(P.from_uri('file:' + pathname2url('/foo/bar')), P('/foo/bar')) self.assertEqual(P.from_uri('file:' + pathname2url('//foo/bar')), P('//foo/bar')) - -@unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') -class WindowsPathTest(PathTest, PureWindowsPathTest): - cls = pathlib.WindowsPath - - def test_absolute(self): + @needs_windows + def test_absolute_windows(self): P = self.cls # Simple absolute paths. @@ -2068,7 +2109,8 @@ def test_absolute(self): self.assertEqual(str(P(other_drive).absolute()), other_cwd) self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo') - def test_glob(self): + @needs_windows + def test_glob_windows(self): P = self.cls p = P(self.base) self.assertEqual(set(p.glob("FILEa")), { P(self.base, "fileA") }) @@ -2077,14 +2119,16 @@ def test_glob(self): self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"}) self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) - def test_rglob(self): + @needs_windows + def test_rglob_windows(self): P = self.cls p = P(self.base, "dirC") self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") }) self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) - def test_expanduser(self): + @needs_windows + def test_expanduser_windows(self): P = self.cls with os_helper.EnvironmentVarGuard() as env: env.pop('HOME', None) @@ -2137,7 +2181,8 @@ def check(): env['HOME'] = 'C:\\Users\\eve' check() - def test_from_uri(self): + @needs_windows + def test_from_uri_windows(self): P = self.cls # DOS drive paths self.assertEqual(P.from_uri('file:c:/path/to/file'), P('c:/path/to/file')) @@ -2158,22 +2203,35 @@ def test_from_uri(self): self.assertRaises(ValueError, P.from_uri, 'file:foo/bar') self.assertRaises(ValueError, P.from_uri, 'http://foo/bar') - def test_from_uri_pathname2url(self): + @needs_windows + def test_from_uri_pathname2url_windows(self): P = self.cls self.assertEqual(P.from_uri('file:' + pathname2url(r'c:\path\to\file')), P('c:/path/to/file')) self.assertEqual(P.from_uri('file:' + pathname2url(r'\\server\path\to\file')), P('//server/path/to/file')) - def test_owner(self): + @needs_windows + def test_owner_windows(self): P = self.cls with self.assertRaises(pathlib.UnsupportedOperation): P('c:/').owner() - def test_group(self): + @needs_windows + def test_group_windows(self): P = self.cls with self.assertRaises(pathlib.UnsupportedOperation): P('c:/').group() +@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system') +class PosixPathTest(PathTest, PurePosixPathTest): + cls = pathlib.PosixPath + + +@unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') +class WindowsPathTest(PathTest, PureWindowsPathTest): + cls = pathlib.WindowsPath + + class PathSubclassTest(PathTest): class cls(pathlib.Path): pass From 2d08af34b873d5e6b4df5082dfc30a37ef59c346 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 27 Jan 2024 02:16:17 +0000 Subject: [PATCH 057/263] Cover OS-specific behaviour in `PurePathBase` and `PathBase` tests. (#114633) Wherever possible, move tests for OS-specific behaviour from `PurePathTest` and `PathTest` to `DummyPurePathTest` and `DummyPathTest`. --- Lib/test/test_pathlib/test_pathlib.py | 627 ---------------------- Lib/test/test_pathlib/test_pathlib_abc.py | 625 +++++++++++++++++++++ 2 files changed, 625 insertions(+), 627 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index b0067c25d208b97..9c2b26d41d73f8e 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -371,14 +371,6 @@ def test_parse_path_posix(self): check('c:\\a', '', '', ['c:\\a',]) check('\\a', '', '', ['\\a',]) - @needs_posix - def test_root_posix(self): - P = self.cls - self.assertEqual(P('/a/b').root, '/') - self.assertEqual(P('///a/b').root, '/') - # POSIX special case for two leading slashes. - self.assertEqual(P('//a/b').root, '//') - @needs_posix def test_eq_posix(self): P = self.cls @@ -404,46 +396,6 @@ def test_as_uri_non_ascii(self): self.assertEqual(P('/a/b\xe9').as_uri(), 'file:///a/b' + quote_from_bytes(os.fsencode('\xe9'))) - @needs_posix - def test_match_posix(self): - P = self.cls - self.assertFalse(P('A.py').match('a.PY')) - - @needs_posix - def test_is_absolute_posix(self): - P = self.cls - self.assertFalse(P().is_absolute()) - self.assertFalse(P('a').is_absolute()) - self.assertFalse(P('a/b/').is_absolute()) - self.assertTrue(P('/').is_absolute()) - self.assertTrue(P('/a').is_absolute()) - self.assertTrue(P('/a/b/').is_absolute()) - self.assertTrue(P('//a').is_absolute()) - self.assertTrue(P('//a/b').is_absolute()) - - @needs_posix - def test_join_posix(self): - P = self.cls - p = P('//a') - pp = p.joinpath('b') - self.assertEqual(pp, P('//a/b')) - pp = P('/a').joinpath('//c') - self.assertEqual(pp, P('//c')) - pp = P('//a').joinpath('/c') - self.assertEqual(pp, P('/c')) - - @needs_posix - def test_div_posix(self): - # Basically the same as joinpath(). - P = self.cls - p = P('//a') - pp = p / 'b' - self.assertEqual(pp, P('//a/b')) - pp = P('/a') / '//c' - self.assertEqual(pp, P('//c')) - pp = P('//a') / '/c' - self.assertEqual(pp, P('/c')) - @needs_posix def test_parse_windows_path(self): P = self.cls @@ -517,32 +469,6 @@ def test_parse_path_windows(self): check('D:a/c:b', 'D:', '', ['a', 'c:b']) check('D:/a/c:b', 'D:', '\\', ['a', 'c:b']) - @needs_windows - def test_str_windows(self): - p = self.cls('a/b/c') - self.assertEqual(str(p), 'a\\b\\c') - p = self.cls('c:/a/b/c') - self.assertEqual(str(p), 'c:\\a\\b\\c') - p = self.cls('//a/b') - self.assertEqual(str(p), '\\\\a\\b\\') - p = self.cls('//a/b/c') - self.assertEqual(str(p), '\\\\a\\b\\c') - p = self.cls('//a/b/c/d') - self.assertEqual(str(p), '\\\\a\\b\\c\\d') - - @needs_windows - def test_str_subclass_windows(self): - self._check_str_subclass('.\\a:b') - self._check_str_subclass('c:') - self._check_str_subclass('c:a') - self._check_str_subclass('c:a\\b.txt') - self._check_str_subclass('c:\\') - self._check_str_subclass('c:\\a') - self._check_str_subclass('c:\\a\\b.txt') - self._check_str_subclass('\\\\some\\share') - self._check_str_subclass('\\\\some\\share\\a') - self._check_str_subclass('\\\\some\\share\\a\\b.txt') - @needs_windows def test_eq_windows(self): P = self.cls @@ -574,34 +500,6 @@ def test_as_uri_windows(self): self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), 'file://some/share/a/b%25%23c%C3%A9') - @needs_windows - def test_match_windows(self): - P = self.cls - # Absolute patterns. - self.assertTrue(P('c:/b.py').match('*:/*.py')) - self.assertTrue(P('c:/b.py').match('c:/*.py')) - self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive - self.assertFalse(P('b.py').match('/*.py')) - self.assertFalse(P('b.py').match('c:*.py')) - self.assertFalse(P('b.py').match('c:/*.py')) - self.assertFalse(P('c:b.py').match('/*.py')) - self.assertFalse(P('c:b.py').match('c:/*.py')) - self.assertFalse(P('/b.py').match('c:*.py')) - self.assertFalse(P('/b.py').match('c:/*.py')) - # UNC patterns. - self.assertTrue(P('//some/share/a.py').match('//*/*/*.py')) - self.assertTrue(P('//some/share/a.py').match('//some/share/*.py')) - self.assertFalse(P('//other/share/a.py').match('//some/share/*.py')) - self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py')) - # Case-insensitivity. - self.assertTrue(P('B.py').match('b.PY')) - self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY')) - self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY')) - # Path anchor doesn't match pattern anchor - self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/' - self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:' - self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/' - @needs_windows def test_ordering_windows(self): # Case-insensitivity. @@ -620,495 +518,6 @@ def assertOrderedEqual(a, b): self.assertFalse(p < q) self.assertFalse(p > q) - @needs_windows - def test_parts_windows(self): - P = self.cls - p = P('c:a/b') - parts = p.parts - self.assertEqual(parts, ('c:', 'a', 'b')) - p = P('c:/a/b') - parts = p.parts - self.assertEqual(parts, ('c:\\', 'a', 'b')) - p = P('//a/b/c/d') - parts = p.parts - self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd')) - - @needs_windows - def test_parent_windows(self): - # Anchored - P = self.cls - p = P('z:a/b/c') - self.assertEqual(p.parent, P('z:a/b')) - self.assertEqual(p.parent.parent, P('z:a')) - self.assertEqual(p.parent.parent.parent, P('z:')) - self.assertEqual(p.parent.parent.parent.parent, P('z:')) - p = P('z:/a/b/c') - self.assertEqual(p.parent, P('z:/a/b')) - self.assertEqual(p.parent.parent, P('z:/a')) - self.assertEqual(p.parent.parent.parent, P('z:/')) - self.assertEqual(p.parent.parent.parent.parent, P('z:/')) - p = P('//a/b/c/d') - self.assertEqual(p.parent, P('//a/b/c')) - self.assertEqual(p.parent.parent, P('//a/b')) - self.assertEqual(p.parent.parent.parent, P('//a/b')) - - @needs_windows - def test_parents_windows(self): - # Anchored - P = self.cls - p = P('z:a/b/') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:a')) - self.assertEqual(par[1], P('z:')) - self.assertEqual(par[0:1], (P('z:a'),)) - self.assertEqual(par[:-1], (P('z:a'),)) - self.assertEqual(par[:2], (P('z:a'), P('z:'))) - self.assertEqual(par[1:], (P('z:'),)) - self.assertEqual(par[::2], (P('z:a'),)) - self.assertEqual(par[::-1], (P('z:'), P('z:a'))) - self.assertEqual(list(par), [P('z:a'), P('z:')]) - with self.assertRaises(IndexError): - par[2] - p = P('z:/a/b/') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('z:/a')) - self.assertEqual(par[1], P('z:/')) - self.assertEqual(par[0:1], (P('z:/a'),)) - self.assertEqual(par[0:-1], (P('z:/a'),)) - self.assertEqual(par[:2], (P('z:/a'), P('z:/'))) - self.assertEqual(par[1:], (P('z:/'),)) - self.assertEqual(par[::2], (P('z:/a'),)) - self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),)) - self.assertEqual(list(par), [P('z:/a'), P('z:/')]) - with self.assertRaises(IndexError): - par[2] - p = P('//a/b/c/d') - par = p.parents - self.assertEqual(len(par), 2) - self.assertEqual(par[0], P('//a/b/c')) - self.assertEqual(par[1], P('//a/b')) - self.assertEqual(par[0:1], (P('//a/b/c'),)) - self.assertEqual(par[0:-1], (P('//a/b/c'),)) - self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b'))) - self.assertEqual(par[1:], (P('//a/b'),)) - self.assertEqual(par[::2], (P('//a/b/c'),)) - self.assertEqual(par[::-1], (P('//a/b'), P('//a/b/c'))) - self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')]) - with self.assertRaises(IndexError): - par[2] - - @needs_windows - def test_drive_windows(self): - P = self.cls - self.assertEqual(P('c:').drive, 'c:') - self.assertEqual(P('c:a/b').drive, 'c:') - self.assertEqual(P('c:/').drive, 'c:') - self.assertEqual(P('c:/a/b/').drive, 'c:') - self.assertEqual(P('//a/b').drive, '\\\\a\\b') - self.assertEqual(P('//a/b/').drive, '\\\\a\\b') - self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b') - self.assertEqual(P('./c:a').drive, '') - - @needs_windows - def test_root_windows(self): - P = self.cls - self.assertEqual(P('c:').root, '') - self.assertEqual(P('c:a/b').root, '') - self.assertEqual(P('c:/').root, '\\') - self.assertEqual(P('c:/a/b/').root, '\\') - self.assertEqual(P('//a/b').root, '\\') - self.assertEqual(P('//a/b/').root, '\\') - self.assertEqual(P('//a/b/c/d').root, '\\') - - @needs_windows - def test_anchor_windows(self): - P = self.cls - self.assertEqual(P('c:').anchor, 'c:') - self.assertEqual(P('c:a/b').anchor, 'c:') - self.assertEqual(P('c:/').anchor, 'c:\\') - self.assertEqual(P('c:/a/b/').anchor, 'c:\\') - self.assertEqual(P('//a/b').anchor, '\\\\a\\b\\') - self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\') - self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\') - - @needs_windows - def test_name_windows(self): - P = self.cls - self.assertEqual(P('c:').name, '') - self.assertEqual(P('c:/').name, '') - self.assertEqual(P('c:a/b').name, 'b') - self.assertEqual(P('c:/a/b').name, 'b') - self.assertEqual(P('c:a/b.py').name, 'b.py') - self.assertEqual(P('c:/a/b.py').name, 'b.py') - self.assertEqual(P('//My.py/Share.php').name, '') - self.assertEqual(P('//My.py/Share.php/a/b').name, 'b') - - @needs_windows - def test_suffix_windows(self): - P = self.cls - self.assertEqual(P('c:').suffix, '') - self.assertEqual(P('c:/').suffix, '') - self.assertEqual(P('c:a/b').suffix, '') - self.assertEqual(P('c:/a/b').suffix, '') - self.assertEqual(P('c:a/b.py').suffix, '.py') - self.assertEqual(P('c:/a/b.py').suffix, '.py') - self.assertEqual(P('c:a/.hgrc').suffix, '') - self.assertEqual(P('c:/a/.hgrc').suffix, '') - self.assertEqual(P('c:a/.hg.rc').suffix, '.rc') - self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc') - self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('c:a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('//My.py/Share.php').suffix, '') - self.assertEqual(P('//My.py/Share.php/a/b').suffix, '') - - @needs_windows - def test_suffixes_windows(self): - P = self.cls - self.assertEqual(P('c:').suffixes, []) - self.assertEqual(P('c:/').suffixes, []) - self.assertEqual(P('c:a/b').suffixes, []) - self.assertEqual(P('c:/a/b').suffixes, []) - self.assertEqual(P('c:a/b.py').suffixes, ['.py']) - self.assertEqual(P('c:/a/b.py').suffixes, ['.py']) - self.assertEqual(P('c:a/.hgrc').suffixes, []) - self.assertEqual(P('c:/a/.hgrc').suffixes, []) - self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('//My.py/Share.php').suffixes, []) - self.assertEqual(P('//My.py/Share.php/a/b').suffixes, []) - self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, []) - self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, []) - - @needs_windows - def test_stem_windows(self): - P = self.cls - self.assertEqual(P('c:').stem, '') - self.assertEqual(P('c:.').stem, '') - self.assertEqual(P('c:..').stem, '..') - self.assertEqual(P('c:/').stem, '') - self.assertEqual(P('c:a/b').stem, 'b') - self.assertEqual(P('c:a/b.py').stem, 'b') - self.assertEqual(P('c:a/.hgrc').stem, '.hgrc') - self.assertEqual(P('c:a/.hg.rc').stem, '.hg') - self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar') - self.assertEqual(P('c:a/Some name. Ending with a dot.').stem, - 'Some name. Ending with a dot.') - - @needs_windows - def test_with_name_windows(self): - P = self.cls - self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml')) - self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml')) - self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml')) - self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml')) - self.assertRaises(ValueError, P('c:').with_name, 'd.xml') - self.assertRaises(ValueError, P('c:/').with_name, 'd.xml') - self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml') - self.assertEqual(str(P('a').with_name('d:')), '.\\d:') - self.assertEqual(str(P('a').with_name('d:e')), '.\\d:e') - self.assertEqual(P('c:a/b').with_name('d:'), P('c:a/d:')) - self.assertEqual(P('c:a/b').with_name('d:e'), P('c:a/d:e')) - self.assertRaises(ValueError, P('c:a/b').with_name, 'd:/e') - self.assertRaises(ValueError, P('c:a/b').with_name, '//My/Share') - - @needs_windows - def test_with_stem_windows(self): - P = self.cls - self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d')) - self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d')) - self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d')) - self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d')) - self.assertRaises(ValueError, P('c:').with_stem, 'd') - self.assertRaises(ValueError, P('c:/').with_stem, 'd') - self.assertRaises(ValueError, P('//My/Share').with_stem, 'd') - self.assertEqual(str(P('a').with_stem('d:')), '.\\d:') - self.assertEqual(str(P('a').with_stem('d:e')), '.\\d:e') - self.assertEqual(P('c:a/b').with_stem('d:'), P('c:a/d:')) - self.assertEqual(P('c:a/b').with_stem('d:e'), P('c:a/d:e')) - self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e') - self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share') - - @needs_windows - def test_with_suffix_windows(self): - P = self.cls - self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz')) - self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz')) - self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz')) - self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz')) - # Path doesn't have a "filename" component. - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz') - # Invalid suffix. - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '/') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '/.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:.gz') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c/d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c\\d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d') - self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d') - - @needs_windows - def test_relative_to_windows(self): - P = self.cls - p = P('C:Foo/Bar') - self.assertEqual(p.relative_to(P('c:')), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:foO')), P('Bar')) - self.assertEqual(p.relative_to('c:foO'), P('Bar')) - self.assertEqual(p.relative_to('c:foO/'), P('Bar')) - self.assertEqual(p.relative_to(P('c:foO/baR')), P()) - self.assertEqual(p.relative_to('c:foO/baR'), P()) - self.assertEqual(p.relative_to(P('c:'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:foO'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:foO', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:foO/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('c:foO/baR'), walk_up=True), P()) - self.assertEqual(p.relative_to('c:foO/baR', walk_up=True), P()) - self.assertEqual(p.relative_to(P('C:Foo/Bar/Baz'), walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('C:Foo/Baz'), walk_up=True), P('../Bar')) - self.assertEqual(p.relative_to(P('C:Baz/Bar'), walk_up=True), P('../../Foo/Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P()) - self.assertRaises(ValueError, p.relative_to, '') - self.assertRaises(ValueError, p.relative_to, P('d:')) - self.assertRaises(ValueError, p.relative_to, P('/')) - self.assertRaises(ValueError, p.relative_to, P('Foo')) - self.assertRaises(ValueError, p.relative_to, P('/Foo')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo/Bar/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo/Baz')) - self.assertRaises(ValueError, p.relative_to, P(), walk_up=True) - self.assertRaises(ValueError, p.relative_to, '', walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True) - p = P('C:/Foo/Bar') - self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:/'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:/foO')), P('Bar')) - self.assertEqual(p.relative_to('c:/foO'), P('Bar')) - self.assertEqual(p.relative_to('c:/foO/'), P('Bar')) - self.assertEqual(p.relative_to(P('c:/foO/baR')), P()) - self.assertEqual(p.relative_to('c:/foO/baR'), P()) - self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:/foO', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('c:/foO/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('c:/foO/baR'), walk_up=True), P()) - self.assertEqual(p.relative_to('c:/foO/baR', walk_up=True), P()) - self.assertEqual(p.relative_to('C:/Baz', walk_up=True), P('../Foo/Bar')) - self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..')) - self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, 'c:') - self.assertRaises(ValueError, p.relative_to, P('c:')) - self.assertRaises(ValueError, p.relative_to, P('C:/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Baz')) - self.assertRaises(ValueError, p.relative_to, P('C:Foo')) - self.assertRaises(ValueError, p.relative_to, P('d:')) - self.assertRaises(ValueError, p.relative_to, P('d:/')) - self.assertRaises(ValueError, p.relative_to, P('/')) - self.assertRaises(ValueError, p.relative_to, P('/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//C/Foo')) - self.assertRaises(ValueError, p.relative_to, 'c:', walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('c:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//C/Foo'), walk_up=True) - # UNC paths. - p = P('//Server/Share/Foo/Bar') - self.assertEqual(p.relative_to(P('//sErver/sHare')), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare'), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/'), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo')), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo'), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/'), P('Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar')), P()) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar'), P()) - self.assertEqual(p.relative_to(P('//sErver/sHare'), walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/', walk_up=True), P('Foo/Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo'), walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/', walk_up=True), P('Bar')) - self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar'), walk_up=True), P()) - self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar', walk_up=True), P()) - self.assertEqual(p.relative_to(P('//sErver/sHare/bar'), walk_up=True), P('../Foo/Bar')) - self.assertEqual(p.relative_to('//sErver/sHare/bar', walk_up=True), P('../Foo/Bar')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo')) - self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo')) - self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'), walk_up=True) - - @needs_windows - def test_is_relative_to_windows(self): - P = self.cls - p = P('C:Foo/Bar') - self.assertTrue(p.is_relative_to(P('c:'))) - self.assertTrue(p.is_relative_to('c:')) - self.assertTrue(p.is_relative_to(P('c:foO'))) - self.assertTrue(p.is_relative_to('c:foO')) - self.assertTrue(p.is_relative_to('c:foO/')) - self.assertTrue(p.is_relative_to(P('c:foO/baR'))) - self.assertTrue(p.is_relative_to('c:foO/baR')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P())) - self.assertFalse(p.is_relative_to('')) - self.assertFalse(p.is_relative_to(P('d:'))) - self.assertFalse(p.is_relative_to(P('/'))) - self.assertFalse(p.is_relative_to(P('Foo'))) - self.assertFalse(p.is_relative_to(P('/Foo'))) - self.assertFalse(p.is_relative_to(P('C:/Foo'))) - self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz'))) - self.assertFalse(p.is_relative_to(P('C:Foo/Baz'))) - p = P('C:/Foo/Bar') - self.assertTrue(p.is_relative_to(P('c:/'))) - self.assertTrue(p.is_relative_to(P('c:/foO'))) - self.assertTrue(p.is_relative_to('c:/foO/')) - self.assertTrue(p.is_relative_to(P('c:/foO/baR'))) - self.assertTrue(p.is_relative_to('c:/foO/baR')) - # Unrelated paths. - self.assertFalse(p.is_relative_to('c:')) - self.assertFalse(p.is_relative_to(P('C:/Baz'))) - self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz'))) - self.assertFalse(p.is_relative_to(P('C:/Foo/Baz'))) - self.assertFalse(p.is_relative_to(P('C:Foo'))) - self.assertFalse(p.is_relative_to(P('d:'))) - self.assertFalse(p.is_relative_to(P('d:/'))) - self.assertFalse(p.is_relative_to(P('/'))) - self.assertFalse(p.is_relative_to(P('/Foo'))) - self.assertFalse(p.is_relative_to(P('//C/Foo'))) - # UNC paths. - p = P('//Server/Share/Foo/Bar') - self.assertTrue(p.is_relative_to(P('//sErver/sHare'))) - self.assertTrue(p.is_relative_to('//sErver/sHare')) - self.assertTrue(p.is_relative_to('//sErver/sHare/')) - self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo'))) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo')) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/')) - self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo/Bar'))) - self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/Bar')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('/Server/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('c:/Server/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('//z/Share/Foo'))) - self.assertFalse(p.is_relative_to(P('//Server/z/Foo'))) - - @needs_windows - def test_is_absolute_windows(self): - P = self.cls - # Under NT, only paths with both a drive and a root are absolute. - self.assertFalse(P().is_absolute()) - self.assertFalse(P('a').is_absolute()) - self.assertFalse(P('a/b/').is_absolute()) - self.assertFalse(P('/').is_absolute()) - self.assertFalse(P('/a').is_absolute()) - self.assertFalse(P('/a/b/').is_absolute()) - self.assertFalse(P('c:').is_absolute()) - self.assertFalse(P('c:a').is_absolute()) - self.assertFalse(P('c:a/b/').is_absolute()) - self.assertTrue(P('c:/').is_absolute()) - self.assertTrue(P('c:/a').is_absolute()) - self.assertTrue(P('c:/a/b/').is_absolute()) - # UNC paths are absolute by definition. - self.assertTrue(P('//').is_absolute()) - self.assertTrue(P('//a').is_absolute()) - self.assertTrue(P('//a/b').is_absolute()) - self.assertTrue(P('//a/b/').is_absolute()) - self.assertTrue(P('//a/b/c').is_absolute()) - self.assertTrue(P('//a/b/c/d').is_absolute()) - self.assertTrue(P('//?/UNC/').is_absolute()) - self.assertTrue(P('//?/UNC/spam').is_absolute()) - - @needs_windows - def test_join_windows(self): - P = self.cls - p = P('C:/a/b') - pp = p.joinpath('x/y') - self.assertEqual(pp, P('C:/a/b/x/y')) - pp = p.joinpath('/x/y') - self.assertEqual(pp, P('C:/x/y')) - # Joining with a different drive => the first path is ignored, even - # if the second path is relative. - pp = p.joinpath('D:x/y') - self.assertEqual(pp, P('D:x/y')) - pp = p.joinpath('D:/x/y') - self.assertEqual(pp, P('D:/x/y')) - pp = p.joinpath('//host/share/x/y') - self.assertEqual(pp, P('//host/share/x/y')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - pp = p.joinpath('c:x/y') - self.assertEqual(pp, P('C:/a/b/x/y')) - pp = p.joinpath('c:/x/y') - self.assertEqual(pp, P('C:/x/y')) - # Joining with files with NTFS data streams => the filename should - # not be parsed as a drive letter - pp = p.joinpath(P('./d:s')) - self.assertEqual(pp, P('C:/a/b/d:s')) - pp = p.joinpath(P('./dd:s')) - self.assertEqual(pp, P('C:/a/b/dd:s')) - pp = p.joinpath(P('E:d:s')) - self.assertEqual(pp, P('E:d:s')) - # Joining onto a UNC path with no root - pp = P('//').joinpath('server') - self.assertEqual(pp, P('//server')) - pp = P('//server').joinpath('share') - self.assertEqual(pp, P('//server/share')) - pp = P('//./BootPartition').joinpath('Windows') - self.assertEqual(pp, P('//./BootPartition/Windows')) - - @needs_windows - def test_div_windows(self): - # Basically the same as joinpath(). - P = self.cls - p = P('C:/a/b') - self.assertEqual(p / 'x/y', P('C:/a/b/x/y')) - self.assertEqual(p / 'x' / 'y', P('C:/a/b/x/y')) - self.assertEqual(p / '/x/y', P('C:/x/y')) - self.assertEqual(p / '/x' / 'y', P('C:/x/y')) - # Joining with a different drive => the first path is ignored, even - # if the second path is relative. - self.assertEqual(p / 'D:x/y', P('D:x/y')) - self.assertEqual(p / 'D:' / 'x/y', P('D:x/y')) - self.assertEqual(p / 'D:/x/y', P('D:/x/y')) - self.assertEqual(p / 'D:' / '/x/y', P('D:/x/y')) - self.assertEqual(p / '//host/share/x/y', P('//host/share/x/y')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - self.assertEqual(p / 'c:x/y', P('C:/a/b/x/y')) - self.assertEqual(p / 'c:/x/y', P('C:/x/y')) - # Joining with files with NTFS data streams => the filename should - # not be parsed as a drive letter - self.assertEqual(p / P('./d:s'), P('C:/a/b/d:s')) - self.assertEqual(p / P('./dd:s'), P('C:/a/b/dd:s')) - self.assertEqual(p / P('E:d:s'), P('E:d:s')) - class PurePosixPathTest(PurePathTest): cls = pathlib.PurePosixPath @@ -1943,24 +1352,6 @@ def test_touch_mode(self): st = os.stat(self.pathmod.join(self.base, 'masked_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) - @needs_posix - def test_glob_posix(self): - P = self.cls - p = P(self.base) - given = set(p.glob("FILEa")) - expect = set() if not os_helper.fs_is_case_insensitive(self.base) else given - self.assertEqual(given, expect) - self.assertEqual(set(p.glob("FILEa*")), set()) - - @needs_posix - def test_rglob_posix(self): - P = self.cls - p = P(self.base, "dirC") - given = set(p.rglob("FILEd")) - expect = set() if not os_helper.fs_is_case_insensitive(self.base) else given - self.assertEqual(given, expect) - self.assertEqual(set(p.rglob("FILEd*")), set()) - @unittest.skipUnless(hasattr(pwd, 'getpwall'), 'pwd module does not expose getpwall()') @unittest.skipIf(sys.platform == "vxworks", @@ -2109,24 +1500,6 @@ def test_absolute_windows(self): self.assertEqual(str(P(other_drive).absolute()), other_cwd) self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo') - @needs_windows - def test_glob_windows(self): - P = self.cls - p = P(self.base) - self.assertEqual(set(p.glob("FILEa")), { P(self.base, "fileA") }) - self.assertEqual(set(p.glob("*a\\")), { P(self.base, "dirA/") }) - self.assertEqual(set(p.glob("F*a")), { P(self.base, "fileA") }) - self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"}) - self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) - - @needs_windows - def test_rglob_windows(self): - P = self.cls - p = P(self.base, "dirC") - self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") }) - self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) - self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) - @needs_windows def test_expanduser_windows(self): P = self.cls diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index b19e9b40419c7ae..ea70931eaa2c7e6 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -174,6 +174,19 @@ def test_str_subclass_common(self): self._check_str_subclass('a/b.txt') self._check_str_subclass('/a/b.txt') + @needs_windows + def test_str_subclass_windows(self): + self._check_str_subclass('.\\a:b') + self._check_str_subclass('c:') + self._check_str_subclass('c:a') + self._check_str_subclass('c:a\\b.txt') + self._check_str_subclass('c:\\') + self._check_str_subclass('c:\\a') + self._check_str_subclass('c:\\a\\b.txt') + self._check_str_subclass('\\\\some\\share') + self._check_str_subclass('\\\\some\\share\\a') + self._check_str_subclass('\\\\some\\share\\a\\b.txt') + def test_with_segments_common(self): class P(self.cls): def __init__(self, *pathsegments, session_id): @@ -206,6 +219,55 @@ def test_join_common(self): pp = p.joinpath('/c') self.assertEqual(pp, P('/c')) + @needs_posix + def test_join_posix(self): + P = self.cls + p = P('//a') + pp = p.joinpath('b') + self.assertEqual(pp, P('//a/b')) + pp = P('/a').joinpath('//c') + self.assertEqual(pp, P('//c')) + pp = P('//a').joinpath('/c') + self.assertEqual(pp, P('/c')) + + @needs_windows + def test_join_windows(self): + P = self.cls + p = P('C:/a/b') + pp = p.joinpath('x/y') + self.assertEqual(pp, P('C:/a/b/x/y')) + pp = p.joinpath('/x/y') + self.assertEqual(pp, P('C:/x/y')) + # Joining with a different drive => the first path is ignored, even + # if the second path is relative. + pp = p.joinpath('D:x/y') + self.assertEqual(pp, P('D:x/y')) + pp = p.joinpath('D:/x/y') + self.assertEqual(pp, P('D:/x/y')) + pp = p.joinpath('//host/share/x/y') + self.assertEqual(pp, P('//host/share/x/y')) + # Joining with the same drive => the first path is appended to if + # the second path is relative. + pp = p.joinpath('c:x/y') + self.assertEqual(pp, P('C:/a/b/x/y')) + pp = p.joinpath('c:/x/y') + self.assertEqual(pp, P('C:/x/y')) + # Joining with files with NTFS data streams => the filename should + # not be parsed as a drive letter + pp = p.joinpath(P('./d:s')) + self.assertEqual(pp, P('C:/a/b/d:s')) + pp = p.joinpath(P('./dd:s')) + self.assertEqual(pp, P('C:/a/b/dd:s')) + pp = p.joinpath(P('E:d:s')) + self.assertEqual(pp, P('E:d:s')) + # Joining onto a UNC path with no root + pp = P('//').joinpath('server') + self.assertEqual(pp, P('//server')) + pp = P('//server').joinpath('share') + self.assertEqual(pp, P('//server/share')) + pp = P('//./BootPartition').joinpath('Windows') + self.assertEqual(pp, P('//./BootPartition/Windows')) + def test_div_common(self): # Basically the same as joinpath(). P = self.cls @@ -222,6 +284,44 @@ def test_div_common(self): pp = p/ '/c' self.assertEqual(pp, P('/c')) + @needs_posix + def test_div_posix(self): + # Basically the same as joinpath(). + P = self.cls + p = P('//a') + pp = p / 'b' + self.assertEqual(pp, P('//a/b')) + pp = P('/a') / '//c' + self.assertEqual(pp, P('//c')) + pp = P('//a') / '/c' + self.assertEqual(pp, P('/c')) + + @needs_windows + def test_div_windows(self): + # Basically the same as joinpath(). + P = self.cls + p = P('C:/a/b') + self.assertEqual(p / 'x/y', P('C:/a/b/x/y')) + self.assertEqual(p / 'x' / 'y', P('C:/a/b/x/y')) + self.assertEqual(p / '/x/y', P('C:/x/y')) + self.assertEqual(p / '/x' / 'y', P('C:/x/y')) + # Joining with a different drive => the first path is ignored, even + # if the second path is relative. + self.assertEqual(p / 'D:x/y', P('D:x/y')) + self.assertEqual(p / 'D:' / 'x/y', P('D:x/y')) + self.assertEqual(p / 'D:/x/y', P('D:/x/y')) + self.assertEqual(p / 'D:' / '/x/y', P('D:/x/y')) + self.assertEqual(p / '//host/share/x/y', P('//host/share/x/y')) + # Joining with the same drive => the first path is appended to if + # the second path is relative. + self.assertEqual(p / 'c:x/y', P('C:/a/b/x/y')) + self.assertEqual(p / 'c:/x/y', P('C:/x/y')) + # Joining with files with NTFS data streams => the filename should + # not be parsed as a drive letter + self.assertEqual(p / P('./d:s'), P('C:/a/b/d:s')) + self.assertEqual(p / P('./dd:s'), P('C:/a/b/dd:s')) + self.assertEqual(p / P('E:d:s'), P('E:d:s')) + def _check_str(self, expected, args): p = self.cls(*args) self.assertEqual(str(p), expected.replace('/', self.sep)) @@ -232,6 +332,19 @@ def test_str_common(self): self._check_str(pathstr, (pathstr,)) # Other tests for str() are in test_equivalences(). + @needs_windows + def test_str_windows(self): + p = self.cls('a/b/c') + self.assertEqual(str(p), 'a\\b\\c') + p = self.cls('c:/a/b/c') + self.assertEqual(str(p), 'c:\\a\\b\\c') + p = self.cls('//a/b') + self.assertEqual(str(p), '\\\\a\\b\\') + p = self.cls('//a/b/c') + self.assertEqual(str(p), '\\\\a\\b\\c') + p = self.cls('//a/b/c/d') + self.assertEqual(str(p), '\\\\a\\b\\c\\d') + def test_as_posix_common(self): P = self.cls for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): @@ -287,6 +400,39 @@ def test_match_common(self): self.assertFalse(P('').match('**')) self.assertFalse(P('').match('**/*')) + @needs_posix + def test_match_posix(self): + P = self.cls + self.assertFalse(P('A.py').match('a.PY')) + + @needs_windows + def test_match_windows(self): + P = self.cls + # Absolute patterns. + self.assertTrue(P('c:/b.py').match('*:/*.py')) + self.assertTrue(P('c:/b.py').match('c:/*.py')) + self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive + self.assertFalse(P('b.py').match('/*.py')) + self.assertFalse(P('b.py').match('c:*.py')) + self.assertFalse(P('b.py').match('c:/*.py')) + self.assertFalse(P('c:b.py').match('/*.py')) + self.assertFalse(P('c:b.py').match('c:/*.py')) + self.assertFalse(P('/b.py').match('c:*.py')) + self.assertFalse(P('/b.py').match('c:/*.py')) + # UNC patterns. + self.assertTrue(P('//some/share/a.py').match('//*/*/*.py')) + self.assertTrue(P('//some/share/a.py').match('//some/share/*.py')) + self.assertFalse(P('//other/share/a.py').match('//some/share/*.py')) + self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py')) + # Case-insensitivity. + self.assertTrue(P('B.py').match('b.PY')) + self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY')) + self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY')) + # Path anchor doesn't match pattern anchor + self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/' + self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:' + self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/' + def test_full_match_common(self): P = self.cls # Simple relative pattern. @@ -372,6 +518,19 @@ def test_parts_common(self): parts = p.parts self.assertEqual(parts, (sep, 'a', 'b')) + @needs_windows + def test_parts_windows(self): + P = self.cls + p = P('c:a/b') + parts = p.parts + self.assertEqual(parts, ('c:', 'a', 'b')) + p = P('c:/a/b') + parts = p.parts + self.assertEqual(parts, ('c:\\', 'a', 'b')) + p = P('//a/b/c/d') + parts = p.parts + self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd')) + def test_parent_common(self): # Relative P = self.cls @@ -387,6 +546,25 @@ def test_parent_common(self): self.assertEqual(p.parent.parent.parent, P('/')) self.assertEqual(p.parent.parent.parent.parent, P('/')) + @needs_windows + def test_parent_windows(self): + # Anchored + P = self.cls + p = P('z:a/b/c') + self.assertEqual(p.parent, P('z:a/b')) + self.assertEqual(p.parent.parent, P('z:a')) + self.assertEqual(p.parent.parent.parent, P('z:')) + self.assertEqual(p.parent.parent.parent.parent, P('z:')) + p = P('z:/a/b/c') + self.assertEqual(p.parent, P('z:/a/b')) + self.assertEqual(p.parent.parent, P('z:/a')) + self.assertEqual(p.parent.parent.parent, P('z:/')) + self.assertEqual(p.parent.parent.parent.parent, P('z:/')) + p = P('//a/b/c/d') + self.assertEqual(p.parent, P('//a/b/c')) + self.assertEqual(p.parent.parent, P('//a/b')) + self.assertEqual(p.parent.parent.parent, P('//a/b')) + def test_parents_common(self): # Relative P = self.cls @@ -434,12 +612,71 @@ def test_parents_common(self): with self.assertRaises(IndexError): par[3] + @needs_windows + def test_parents_windows(self): + # Anchored + P = self.cls + p = P('z:a/b/') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('z:a')) + self.assertEqual(par[1], P('z:')) + self.assertEqual(par[0:1], (P('z:a'),)) + self.assertEqual(par[:-1], (P('z:a'),)) + self.assertEqual(par[:2], (P('z:a'), P('z:'))) + self.assertEqual(par[1:], (P('z:'),)) + self.assertEqual(par[::2], (P('z:a'),)) + self.assertEqual(par[::-1], (P('z:'), P('z:a'))) + self.assertEqual(list(par), [P('z:a'), P('z:')]) + with self.assertRaises(IndexError): + par[2] + p = P('z:/a/b/') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('z:/a')) + self.assertEqual(par[1], P('z:/')) + self.assertEqual(par[0:1], (P('z:/a'),)) + self.assertEqual(par[0:-1], (P('z:/a'),)) + self.assertEqual(par[:2], (P('z:/a'), P('z:/'))) + self.assertEqual(par[1:], (P('z:/'),)) + self.assertEqual(par[::2], (P('z:/a'),)) + self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),)) + self.assertEqual(list(par), [P('z:/a'), P('z:/')]) + with self.assertRaises(IndexError): + par[2] + p = P('//a/b/c/d') + par = p.parents + self.assertEqual(len(par), 2) + self.assertEqual(par[0], P('//a/b/c')) + self.assertEqual(par[1], P('//a/b')) + self.assertEqual(par[0:1], (P('//a/b/c'),)) + self.assertEqual(par[0:-1], (P('//a/b/c'),)) + self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b'))) + self.assertEqual(par[1:], (P('//a/b'),)) + self.assertEqual(par[::2], (P('//a/b/c'),)) + self.assertEqual(par[::-1], (P('//a/b'), P('//a/b/c'))) + self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')]) + with self.assertRaises(IndexError): + par[2] + def test_drive_common(self): P = self.cls self.assertEqual(P('a/b').drive, '') self.assertEqual(P('/a/b').drive, '') self.assertEqual(P('').drive, '') + @needs_windows + def test_drive_windows(self): + P = self.cls + self.assertEqual(P('c:').drive, 'c:') + self.assertEqual(P('c:a/b').drive, 'c:') + self.assertEqual(P('c:/').drive, 'c:') + self.assertEqual(P('c:/a/b/').drive, 'c:') + self.assertEqual(P('//a/b').drive, '\\\\a\\b') + self.assertEqual(P('//a/b/').drive, '\\\\a\\b') + self.assertEqual(P('//a/b/c/d').drive, '\\\\a\\b') + self.assertEqual(P('./c:a').drive, '') + def test_root_common(self): P = self.cls sep = self.sep @@ -448,6 +685,24 @@ def test_root_common(self): self.assertEqual(P('/').root, sep) self.assertEqual(P('/a/b').root, sep) + @needs_posix + def test_root_posix(self): + P = self.cls + self.assertEqual(P('/a/b').root, '/') + # POSIX special case for two leading slashes. + self.assertEqual(P('//a/b').root, '//') + + @needs_windows + def test_root_windows(self): + P = self.cls + self.assertEqual(P('c:').root, '') + self.assertEqual(P('c:a/b').root, '') + self.assertEqual(P('c:/').root, '\\') + self.assertEqual(P('c:/a/b/').root, '\\') + self.assertEqual(P('//a/b').root, '\\') + self.assertEqual(P('//a/b/').root, '\\') + self.assertEqual(P('//a/b/c/d').root, '\\') + def test_anchor_common(self): P = self.cls sep = self.sep @@ -456,6 +711,17 @@ def test_anchor_common(self): self.assertEqual(P('/').anchor, sep) self.assertEqual(P('/a/b').anchor, sep) + @needs_windows + def test_anchor_windows(self): + P = self.cls + self.assertEqual(P('c:').anchor, 'c:') + self.assertEqual(P('c:a/b').anchor, 'c:') + self.assertEqual(P('c:/').anchor, 'c:\\') + self.assertEqual(P('c:/a/b/').anchor, 'c:\\') + self.assertEqual(P('//a/b').anchor, '\\\\a\\b\\') + self.assertEqual(P('//a/b/').anchor, '\\\\a\\b\\') + self.assertEqual(P('//a/b/c/d').anchor, '\\\\a\\b\\') + def test_name_empty(self): P = self.cls self.assertEqual(P('').name, '') @@ -470,6 +736,18 @@ def test_name_common(self): self.assertEqual(P('a/b.py').name, 'b.py') self.assertEqual(P('/a/b.py').name, 'b.py') + @needs_windows + def test_name_windows(self): + P = self.cls + self.assertEqual(P('c:').name, '') + self.assertEqual(P('c:/').name, '') + self.assertEqual(P('c:a/b').name, 'b') + self.assertEqual(P('c:/a/b').name, 'b') + self.assertEqual(P('c:a/b.py').name, 'b.py') + self.assertEqual(P('c:/a/b.py').name, 'b.py') + self.assertEqual(P('//My.py/Share.php').name, '') + self.assertEqual(P('//My.py/Share.php/a/b').name, 'b') + def test_suffix_common(self): P = self.cls self.assertEqual(P('').suffix, '') @@ -490,6 +768,26 @@ def test_suffix_common(self): self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') + @needs_windows + def test_suffix_windows(self): + P = self.cls + self.assertEqual(P('c:').suffix, '') + self.assertEqual(P('c:/').suffix, '') + self.assertEqual(P('c:a/b').suffix, '') + self.assertEqual(P('c:/a/b').suffix, '') + self.assertEqual(P('c:a/b.py').suffix, '.py') + self.assertEqual(P('c:/a/b.py').suffix, '.py') + self.assertEqual(P('c:a/.hgrc').suffix, '') + self.assertEqual(P('c:/a/.hgrc').suffix, '') + self.assertEqual(P('c:a/.hg.rc').suffix, '.rc') + self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc') + self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('c:a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('//My.py/Share.php').suffix, '') + self.assertEqual(P('//My.py/Share.php/a/b').suffix, '') + def test_suffixes_common(self): P = self.cls self.assertEqual(P('').suffixes, []) @@ -509,6 +807,26 @@ def test_suffixes_common(self): self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) + @needs_windows + def test_suffixes_windows(self): + P = self.cls + self.assertEqual(P('c:').suffixes, []) + self.assertEqual(P('c:/').suffixes, []) + self.assertEqual(P('c:a/b').suffixes, []) + self.assertEqual(P('c:/a/b').suffixes, []) + self.assertEqual(P('c:a/b.py').suffixes, ['.py']) + self.assertEqual(P('c:/a/b.py').suffixes, ['.py']) + self.assertEqual(P('c:a/.hgrc').suffixes, []) + self.assertEqual(P('c:/a/.hgrc').suffixes, []) + self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('//My.py/Share.php').suffixes, []) + self.assertEqual(P('//My.py/Share.php/a/b').suffixes, []) + self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, []) + self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, []) + def test_stem_empty(self): P = self.cls self.assertEqual(P('').stem, '') @@ -526,6 +844,20 @@ def test_stem_common(self): self.assertEqual(P('a/Some name. Ending with a dot.').stem, 'Some name. Ending with a dot.') + @needs_windows + def test_stem_windows(self): + P = self.cls + self.assertEqual(P('c:').stem, '') + self.assertEqual(P('c:.').stem, '') + self.assertEqual(P('c:..').stem, '..') + self.assertEqual(P('c:/').stem, '') + self.assertEqual(P('c:a/b').stem, 'b') + self.assertEqual(P('c:a/b.py').stem, 'b') + self.assertEqual(P('c:a/.hgrc').stem, '.hgrc') + self.assertEqual(P('c:a/.hg.rc').stem, '.hg') + self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar') + self.assertEqual(P('c:a/Some name. Ending with a dot.').stem, + 'Some name. Ending with a dot.') def test_with_name_common(self): P = self.cls self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml')) @@ -535,6 +867,23 @@ def test_with_name_common(self): self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml')) self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml')) + @needs_windows + def test_with_name_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml')) + self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml')) + self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml')) + self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml')) + self.assertRaises(ValueError, P('c:').with_name, 'd.xml') + self.assertRaises(ValueError, P('c:/').with_name, 'd.xml') + self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml') + self.assertEqual(str(P('a').with_name('d:')), '.\\d:') + self.assertEqual(str(P('a').with_name('d:e')), '.\\d:e') + self.assertEqual(P('c:a/b').with_name('d:'), P('c:a/d:')) + self.assertEqual(P('c:a/b').with_name('d:e'), P('c:a/d:e')) + self.assertRaises(ValueError, P('c:a/b').with_name, 'd:/e') + self.assertRaises(ValueError, P('c:a/b').with_name, '//My/Share') + def test_with_name_empty(self): P = self.cls self.assertEqual(P('').with_name('d.xml'), P('d.xml')) @@ -559,6 +908,23 @@ def test_with_stem_common(self): self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) + @needs_windows + def test_with_stem_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d')) + self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d')) + self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d')) + self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d')) + self.assertRaises(ValueError, P('c:').with_stem, 'd') + self.assertRaises(ValueError, P('c:/').with_stem, 'd') + self.assertRaises(ValueError, P('//My/Share').with_stem, 'd') + self.assertEqual(str(P('a').with_stem('d:')), '.\\d:') + self.assertEqual(str(P('a').with_stem('d:e')), '.\\d:e') + self.assertEqual(P('c:a/b').with_stem('d:'), P('c:a/d:')) + self.assertEqual(P('c:a/b').with_stem('d:e'), P('c:a/d:e')) + self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e') + self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share') + def test_with_stem_empty(self): P = self.cls self.assertEqual(P('').with_stem('d'), P('d')) @@ -583,6 +949,31 @@ def test_with_suffix_common(self): self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) + @needs_windows + def test_with_suffix_windows(self): + P = self.cls + self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz')) + self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz')) + self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz')) + self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz')) + # Path doesn't have a "filename" component. + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('.').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') + self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz') + # Invalid suffix. + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '/') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '/.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:.gz') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c/d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c\\d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d') + self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d') + def test_with_suffix_empty(self): P = self.cls # Path doesn't have a "filename" component. @@ -677,6 +1068,112 @@ def test_relative_to_common(self): self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + @needs_windows + def test_relative_to_windows(self): + P = self.cls + p = P('C:Foo/Bar') + self.assertEqual(p.relative_to(P('c:')), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:foO')), P('Bar')) + self.assertEqual(p.relative_to('c:foO'), P('Bar')) + self.assertEqual(p.relative_to('c:foO/'), P('Bar')) + self.assertEqual(p.relative_to(P('c:foO/baR')), P()) + self.assertEqual(p.relative_to('c:foO/baR'), P()) + self.assertEqual(p.relative_to(P('c:'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:foO'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:foO', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:foO/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('c:foO/baR'), walk_up=True), P()) + self.assertEqual(p.relative_to('c:foO/baR', walk_up=True), P()) + self.assertEqual(p.relative_to(P('C:Foo/Bar/Baz'), walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('C:Foo/Baz'), walk_up=True), P('../Bar')) + self.assertEqual(p.relative_to(P('C:Baz/Bar'), walk_up=True), P('../../Foo/Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P()) + self.assertRaises(ValueError, p.relative_to, '') + self.assertRaises(ValueError, p.relative_to, P('d:')) + self.assertRaises(ValueError, p.relative_to, P('/')) + self.assertRaises(ValueError, p.relative_to, P('Foo')) + self.assertRaises(ValueError, p.relative_to, P('/Foo')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo/Bar/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo/Baz')) + self.assertRaises(ValueError, p.relative_to, P(), walk_up=True) + self.assertRaises(ValueError, p.relative_to, '', walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo'), walk_up=True) + p = P('C:/Foo/Bar') + self.assertEqual(p.relative_to(P('c:/')), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:/'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:/foO')), P('Bar')) + self.assertEqual(p.relative_to('c:/foO'), P('Bar')) + self.assertEqual(p.relative_to('c:/foO/'), P('Bar')) + self.assertEqual(p.relative_to(P('c:/foO/baR')), P()) + self.assertEqual(p.relative_to('c:/foO/baR'), P()) + self.assertEqual(p.relative_to(P('c:/'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('c:/', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('c:/foO'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:/foO', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('c:/foO/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('c:/foO/baR'), walk_up=True), P()) + self.assertEqual(p.relative_to('c:/foO/baR', walk_up=True), P()) + self.assertEqual(p.relative_to('C:/Baz', walk_up=True), P('../Foo/Bar')) + self.assertEqual(p.relative_to('C:/Foo/Bar/Baz', walk_up=True), P('..')) + self.assertEqual(p.relative_to('C:/Foo/Baz', walk_up=True), P('../Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, 'c:') + self.assertRaises(ValueError, p.relative_to, P('c:')) + self.assertRaises(ValueError, p.relative_to, P('C:/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Bar/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:/Foo/Baz')) + self.assertRaises(ValueError, p.relative_to, P('C:Foo')) + self.assertRaises(ValueError, p.relative_to, P('d:')) + self.assertRaises(ValueError, p.relative_to, P('d:/')) + self.assertRaises(ValueError, p.relative_to, P('/')) + self.assertRaises(ValueError, p.relative_to, P('/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//C/Foo')) + self.assertRaises(ValueError, p.relative_to, 'c:', walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('c:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('C:Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('d:/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//C/Foo'), walk_up=True) + # UNC paths. + p = P('//Server/Share/Foo/Bar') + self.assertEqual(p.relative_to(P('//sErver/sHare')), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare'), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/'), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo')), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo'), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/'), P('Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar')), P()) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar'), P()) + self.assertEqual(p.relative_to(P('//sErver/sHare'), walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/', walk_up=True), P('Foo/Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo'), walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/', walk_up=True), P('Bar')) + self.assertEqual(p.relative_to(P('//sErver/sHare/Foo/Bar'), walk_up=True), P()) + self.assertEqual(p.relative_to('//sErver/sHare/Foo/Bar', walk_up=True), P()) + self.assertEqual(p.relative_to(P('//sErver/sHare/bar'), walk_up=True), P('../Foo/Bar')) + self.assertEqual(p.relative_to('//sErver/sHare/bar', walk_up=True), P('../Foo/Bar')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo')) + self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo')) + self.assertRaises(ValueError, p.relative_to, P('/Server/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('c:/Server/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//z/Share/Foo'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('//Server/z/Foo'), walk_up=True) + def test_is_relative_to_common(self): P = self.cls p = P('a/b') @@ -709,6 +1206,98 @@ def test_is_relative_to_common(self): self.assertFalse(p.is_relative_to('')) self.assertFalse(p.is_relative_to(P('a'))) + @needs_windows + def test_is_relative_to_windows(self): + P = self.cls + p = P('C:Foo/Bar') + self.assertTrue(p.is_relative_to(P('c:'))) + self.assertTrue(p.is_relative_to('c:')) + self.assertTrue(p.is_relative_to(P('c:foO'))) + self.assertTrue(p.is_relative_to('c:foO')) + self.assertTrue(p.is_relative_to('c:foO/')) + self.assertTrue(p.is_relative_to(P('c:foO/baR'))) + self.assertTrue(p.is_relative_to('c:foO/baR')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P())) + self.assertFalse(p.is_relative_to('')) + self.assertFalse(p.is_relative_to(P('d:'))) + self.assertFalse(p.is_relative_to(P('/'))) + self.assertFalse(p.is_relative_to(P('Foo'))) + self.assertFalse(p.is_relative_to(P('/Foo'))) + self.assertFalse(p.is_relative_to(P('C:/Foo'))) + self.assertFalse(p.is_relative_to(P('C:Foo/Bar/Baz'))) + self.assertFalse(p.is_relative_to(P('C:Foo/Baz'))) + p = P('C:/Foo/Bar') + self.assertTrue(p.is_relative_to(P('c:/'))) + self.assertTrue(p.is_relative_to(P('c:/foO'))) + self.assertTrue(p.is_relative_to('c:/foO/')) + self.assertTrue(p.is_relative_to(P('c:/foO/baR'))) + self.assertTrue(p.is_relative_to('c:/foO/baR')) + # Unrelated paths. + self.assertFalse(p.is_relative_to('c:')) + self.assertFalse(p.is_relative_to(P('C:/Baz'))) + self.assertFalse(p.is_relative_to(P('C:/Foo/Bar/Baz'))) + self.assertFalse(p.is_relative_to(P('C:/Foo/Baz'))) + self.assertFalse(p.is_relative_to(P('C:Foo'))) + self.assertFalse(p.is_relative_to(P('d:'))) + self.assertFalse(p.is_relative_to(P('d:/'))) + self.assertFalse(p.is_relative_to(P('/'))) + self.assertFalse(p.is_relative_to(P('/Foo'))) + self.assertFalse(p.is_relative_to(P('//C/Foo'))) + # UNC paths. + p = P('//Server/Share/Foo/Bar') + self.assertTrue(p.is_relative_to(P('//sErver/sHare'))) + self.assertTrue(p.is_relative_to('//sErver/sHare')) + self.assertTrue(p.is_relative_to('//sErver/sHare/')) + self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo'))) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo')) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/')) + self.assertTrue(p.is_relative_to(P('//sErver/sHare/Foo/Bar'))) + self.assertTrue(p.is_relative_to('//sErver/sHare/Foo/Bar')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('/Server/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('c:/Server/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('//z/Share/Foo'))) + self.assertFalse(p.is_relative_to(P('//Server/z/Foo'))) + + @needs_posix + def test_is_absolute_posix(self): + P = self.cls + self.assertFalse(P('').is_absolute()) + self.assertFalse(P('a').is_absolute()) + self.assertFalse(P('a/b/').is_absolute()) + self.assertTrue(P('/').is_absolute()) + self.assertTrue(P('/a').is_absolute()) + self.assertTrue(P('/a/b/').is_absolute()) + self.assertTrue(P('//a').is_absolute()) + self.assertTrue(P('//a/b').is_absolute()) + + @needs_windows + def test_is_absolute_windows(self): + P = self.cls + # Under NT, only paths with both a drive and a root are absolute. + self.assertFalse(P().is_absolute()) + self.assertFalse(P('a').is_absolute()) + self.assertFalse(P('a/b/').is_absolute()) + self.assertFalse(P('/').is_absolute()) + self.assertFalse(P('/a').is_absolute()) + self.assertFalse(P('/a/b/').is_absolute()) + self.assertFalse(P('c:').is_absolute()) + self.assertFalse(P('c:a').is_absolute()) + self.assertFalse(P('c:a/b/').is_absolute()) + self.assertTrue(P('c:/').is_absolute()) + self.assertTrue(P('c:/a').is_absolute()) + self.assertTrue(P('c:/a/b/').is_absolute()) + # UNC paths are absolute by definition. + self.assertTrue(P('//').is_absolute()) + self.assertTrue(P('//a').is_absolute()) + self.assertTrue(P('//a/b').is_absolute()) + self.assertTrue(P('//a/b/').is_absolute()) + self.assertTrue(P('//a/b/c').is_absolute()) + self.assertTrue(P('//a/b/c/d').is_absolute()) + self.assertTrue(P('//?/UNC/').is_absolute()) + self.assertTrue(P('//?/UNC/spam').is_absolute()) + # # Tests for the virtual classes. @@ -1124,6 +1713,25 @@ def _check(glob, expected): else: _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + @needs_posix + def test_glob_posix(self): + P = self.cls + p = P(self.base) + given = set(p.glob("FILEa")) + expect = set() + self.assertEqual(given, expect) + self.assertEqual(set(p.glob("FILEa*")), set()) + + @needs_windows + def test_glob_windows(self): + P = self.cls + p = P(self.base) + self.assertEqual(set(p.glob("FILEa")), { P(self.base, "fileA") }) + self.assertEqual(set(p.glob("*a\\")), { P(self.base, "dirA/") }) + self.assertEqual(set(p.glob("F*a")), { P(self.base, "fileA") }) + self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"}) + self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) + def test_glob_empty_pattern(self): def _check(glob, expected): self.assertEqual(set(glob), { P(self.base, q) for q in expected }) @@ -1236,6 +1844,23 @@ def _check(glob, expected): _check(p.rglob("*.txt"), ["dirC/novel.txt"]) _check(p.rglob("*.*"), ["dirC/novel.txt"]) + @needs_posix + def test_rglob_posix(self): + P = self.cls + p = P(self.base, "dirC") + given = set(p.rglob("FILEd")) + expect = set() + self.assertEqual(given, expect) + self.assertEqual(set(p.rglob("FILEd*")), set()) + + @needs_windows + def test_rglob_windows(self): + P = self.cls + p = P(self.base, "dirC") + self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") }) + self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") }) + self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) + @needs_symlinks def test_rglob_follow_symlinks_common(self): def _check(path, glob, expected): From 7a7bce5a0ab249407e866a1e955d21fa2b0c8506 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Fri, 26 Jan 2024 19:38:14 -0800 Subject: [PATCH 058/263] gh-113055: Use pointer for interp->obmalloc state (gh-113412) For interpreters that share state with the main interpreter, this points to the same static memory structure. For interpreters with their own obmalloc state, it is heap allocated. Add free_obmalloc_arenas() which will free the obmalloc arenas and radix tree structures for interpreters with their own obmalloc state. Co-authored-by: Eric Snow --- Include/internal/pycore_interp.h | 12 +- Include/internal/pycore_obmalloc.h | 2 + Include/internal/pycore_obmalloc_init.h | 7 - Include/internal/pycore_runtime_init.h | 1 - ...-12-22-13-21-39.gh-issue-113055.47xBMF.rst | 5 + Objects/obmalloc.c | 121 +++++++++++++++++- Python/pylifecycle.c | 16 +++ Python/pystate.c | 14 +- Tools/c-analyzer/cpython/ignored.tsv | 3 +- 9 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 662a18d93f329d2..04e75940dcb5734 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -203,7 +203,17 @@ struct _is { struct _mimalloc_interp_state mimalloc; #endif - struct _obmalloc_state obmalloc; + // Per-interpreter state for the obmalloc allocator. For the main + // interpreter and for all interpreters that don't have their + // own obmalloc state, this points to the static structure in + // obmalloc.c obmalloc_state_main. For other interpreters, it is + // heap allocated by _PyMem_init_obmalloc() and freed when the + // interpreter structure is freed. In the case of a heap allocated + // obmalloc state, it is not safe to hold on to or use memory after + // the interpreter is freed. The obmalloc state corresponding to + // that allocated memory is gone. See free_obmalloc_arenas() for + // more comments. + struct _obmalloc_state *obmalloc; PyObject *audit_hooks; PyType_WatchCallback type_watchers[TYPE_MAX_WATCHERS]; diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h index 17572dba65487d3..9140d8f08f0af1e 100644 --- a/Include/internal/pycore_obmalloc.h +++ b/Include/internal/pycore_obmalloc.h @@ -686,6 +686,8 @@ extern Py_ssize_t _Py_GetGlobalAllocatedBlocks(void); _Py_GetGlobalAllocatedBlocks() extern Py_ssize_t _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *); extern void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *); +extern int _PyMem_init_obmalloc(PyInterpreterState *interp); +extern bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp); #ifdef WITH_PYMALLOC diff --git a/Include/internal/pycore_obmalloc_init.h b/Include/internal/pycore_obmalloc_init.h index 8ee72ff2d4126f9..e6811b7aeca73c1 100644 --- a/Include/internal/pycore_obmalloc_init.h +++ b/Include/internal/pycore_obmalloc_init.h @@ -59,13 +59,6 @@ extern "C" { .dump_debug_stats = -1, \ } -#define _obmalloc_state_INIT(obmalloc) \ - { \ - .pools = { \ - .used = _obmalloc_pools_INIT(obmalloc.pools), \ - }, \ - } - #ifdef __cplusplus } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index b4806ab09fd145f..0a5c92bb84b524f 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -155,7 +155,6 @@ extern PyTypeObject _PyExc_MemoryError; { \ .id_refcount = -1, \ .imports = IMPORTS_INIT, \ - .obmalloc = _obmalloc_state_INIT(INTERP.obmalloc), \ .ceval = { \ .recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ }, \ diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst new file mode 100644 index 000000000000000..90f49272218c960 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-22-13-21-39.gh-issue-113055.47xBMF.rst @@ -0,0 +1,5 @@ +Make interp->obmalloc a pointer. For interpreters that share state with the +main interpreter, this points to the same static memory structure. For +interpreters with their own obmalloc state, it is heap allocated. Add +free_obmalloc_arenas() which will free the obmalloc arenas and radix tree +structures for interpreters with their own obmalloc state. diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 16d5bcb53e7eb75..bea4ea85332bdda 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -7,6 +7,7 @@ #include "pycore_pyerrors.h" // _Py_FatalErrorFormat() #include "pycore_pymem.h" #include "pycore_pystate.h" // _PyInterpreterState_GET +#include "pycore_obmalloc_init.h" #include // malloc() #include @@ -1016,6 +1017,13 @@ static int running_on_valgrind = -1; typedef struct _obmalloc_state OMState; +/* obmalloc state for main interpreter and shared by all interpreters without + * their own obmalloc state. By not explicitly initalizing this structure, it + * will be allocated in the BSS which is a small performance win. The radix + * tree arrays are fairly large but are sparsely used. */ +static struct _obmalloc_state obmalloc_state_main; +static bool obmalloc_state_initialized; + static inline int has_own_state(PyInterpreterState *interp) { @@ -1028,10 +1036,8 @@ static inline OMState * get_state(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); - if (!has_own_state(interp)) { - interp = _PyInterpreterState_Main(); - } - return &interp->obmalloc; + assert(interp->obmalloc != NULL); // otherwise not initialized or freed + return interp->obmalloc; } // These macros all rely on a local "state" variable. @@ -1094,7 +1100,11 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) "the interpreter doesn't have its own allocator"); } #endif - OMState *state = &interp->obmalloc; + OMState *state = interp->obmalloc; + + if (state == NULL) { + return 0; + } Py_ssize_t n = raw_allocated_blocks; /* add up allocated blocks for used pools */ @@ -1116,6 +1126,8 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) return n; } +static void free_obmalloc_arenas(PyInterpreterState *interp); + void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) { @@ -1124,10 +1136,20 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) return; } #endif - if (has_own_state(interp)) { + if (has_own_state(interp) && interp->obmalloc != NULL) { Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp); assert(has_own_state(interp) || leaked == 0); interp->runtime->obmalloc.interpreter_leaks += leaked; + if (_PyMem_obmalloc_state_on_heap(interp) && leaked == 0) { + // free the obmalloc arenas and radix tree nodes. If leaked > 0 + // then some of the memory allocated by obmalloc has not been + // freed. It might be safe to free the arenas in that case but + // it's possible that extension modules are still using that + // memory. So, it is safer to not free and to leak. Perhaps there + // should be warning when this happens. It should be possible to + // use a tool like "-fsanitize=address" to track down these leaks. + free_obmalloc_arenas(interp); + } } } @@ -2717,9 +2739,96 @@ _PyDebugAllocatorStats(FILE *out, (void)printone(out, buf2, num_blocks * sizeof_block); } +// Return true if the obmalloc state structure is heap allocated, +// by PyMem_RawCalloc(). For the main interpreter, this structure +// allocated in the BSS. Allocating that way gives some memory savings +// and a small performance win (at least on a demand paged OS). On +// 64-bit platforms, the obmalloc structure is 256 kB. Most of that +// memory is for the arena_map_top array. Since normally only one entry +// of that array is used, only one page of resident memory is actually +// used, rather than the full 256 kB. +bool _PyMem_obmalloc_state_on_heap(PyInterpreterState *interp) +{ +#if WITH_PYMALLOC + return interp->obmalloc && interp->obmalloc != &obmalloc_state_main; +#else + return false; +#endif +} + +#ifdef WITH_PYMALLOC +static void +init_obmalloc_pools(PyInterpreterState *interp) +{ + // initialize the obmalloc->pools structure. This must be done + // before the obmalloc alloc/free functions can be called. + poolp temp[OBMALLOC_USED_POOLS_SIZE] = + _obmalloc_pools_INIT(interp->obmalloc->pools); + memcpy(&interp->obmalloc->pools.used, temp, sizeof(temp)); +} +#endif /* WITH_PYMALLOC */ + +int _PyMem_init_obmalloc(PyInterpreterState *interp) +{ +#ifdef WITH_PYMALLOC + /* Initialize obmalloc, but only for subinterpreters, + since the main interpreter is initialized statically. */ + if (_Py_IsMainInterpreter(interp) + || _PyInterpreterState_HasFeature(interp, + Py_RTFLAGS_USE_MAIN_OBMALLOC)) { + interp->obmalloc = &obmalloc_state_main; + if (!obmalloc_state_initialized) { + init_obmalloc_pools(interp); + obmalloc_state_initialized = true; + } + } else { + interp->obmalloc = PyMem_RawCalloc(1, sizeof(struct _obmalloc_state)); + if (interp->obmalloc == NULL) { + return -1; + } + init_obmalloc_pools(interp); + } +#endif /* WITH_PYMALLOC */ + return 0; // success +} + #ifdef WITH_PYMALLOC +static void +free_obmalloc_arenas(PyInterpreterState *interp) +{ + OMState *state = interp->obmalloc; + for (uint i = 0; i < maxarenas; ++i) { + // free each obmalloc memory arena + struct arena_object *ao = &allarenas[i]; + _PyObject_Arena.free(_PyObject_Arena.ctx, + (void *)ao->address, ARENA_SIZE); + } + // free the array containing pointers to all arenas + PyMem_RawFree(allarenas); +#if WITH_PYMALLOC_RADIX_TREE +#ifdef USE_INTERIOR_NODES + // Free the middle and bottom nodes of the radix tree. These are allocated + // by arena_map_mark_used() but not freed when arenas are freed. + for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { + arena_map_mid_t *mid = arena_map_root.ptrs[i1]; + if (mid == NULL) { + continue; + } + for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { + arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; + if (bot == NULL) { + continue; + } + PyMem_RawFree(bot); + } + PyMem_RawFree(mid); + } +#endif +#endif +} + #ifdef Py_DEBUG /* Is target in the list? The list is traversed via the nextpool pointers. * The list may be NULL-terminated, or circular. Return 1 if target is in diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 261622adc4cc771..fff64dd63d6b219 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -32,6 +32,7 @@ #include "pycore_typevarobject.h" // _Py_clear_generic_types() #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include "pycore_obmalloc.h" // _PyMem_init_obmalloc() #include "opcode.h" @@ -645,6 +646,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } + // initialize the interp->obmalloc state. This must be done after + // the settings are loaded (so that feature_flags are set) but before + // any calls are made to obmalloc functions. + if (_PyMem_init_obmalloc(interp) < 0) { + return _PyStatus_NO_MEMORY(); + } + PyThreadState *tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { @@ -2144,6 +2152,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) goto error; } + // initialize the interp->obmalloc state. This must be done after + // the settings are loaded (so that feature_flags are set) but before + // any calls are made to obmalloc functions. + if (_PyMem_init_obmalloc(interp) < 0) { + status = _PyStatus_NO_MEMORY(); + goto error; + } + tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { status = _PyStatus_NO_MEMORY(); diff --git a/Python/pystate.c b/Python/pystate.c index 5ad5b6f3fcc6345..8e097c848cf4a18 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -18,6 +18,7 @@ #include "pycore_pystate.h" #include "pycore_runtime_init.h" // _PyRuntimeState_INIT #include "pycore_sysmodule.h" // _PySys_Audit() +#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap() /* -------------------------------------------------------------------------- CAUTION @@ -553,6 +554,11 @@ free_interpreter(PyInterpreterState *interp) // The main interpreter is statically allocated so // should not be freed. if (interp != &_PyRuntime._main_interpreter) { + if (_PyMem_obmalloc_state_on_heap(interp)) { + // interpreter has its own obmalloc state, free it + PyMem_RawFree(interp->obmalloc); + interp->obmalloc = NULL; + } PyMem_RawFree(interp); } } @@ -595,14 +601,6 @@ init_interpreter(PyInterpreterState *interp, assert(next != NULL || (interp == runtime->interpreters.main)); interp->next = next; - /* Initialize obmalloc, but only for subinterpreters, - since the main interpreter is initialized statically. */ - if (interp != &runtime->_main_interpreter) { - poolp temp[OBMALLOC_USED_POOLS_SIZE] = \ - _obmalloc_pools_INIT(interp->obmalloc.pools); - memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp)); - } - PyStatus status = _PyObject_InitState(interp); if (_PyStatus_EXCEPTION(status)) { return status; diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 2f9e80d6ab67371..c75aff8c1723c10 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -325,7 +325,8 @@ Objects/obmalloc.c - _PyMem_Debug - Objects/obmalloc.c - _PyMem_Raw - Objects/obmalloc.c - _PyObject - Objects/obmalloc.c - last_final_leaks - -Objects/obmalloc.c - usedpools - +Objects/obmalloc.c - obmalloc_state_main - +Objects/obmalloc.c - obmalloc_state_initialized - Objects/typeobject.c - name_op - Objects/typeobject.c - slotdefs - Objects/unicodeobject.c - stripfuncnames - From 926881dc10ebf77069e02e66eea3e0d3ba500fe5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 27 Jan 2024 10:55:33 +0300 Subject: [PATCH 059/263] gh-113445: Amend PyObject_RichCompareBool() docs (GH-113891) --- Doc/c-api/object.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 4f656779c80b1a2..12476412799a4f1 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -229,12 +229,8 @@ Object Protocol .. c:function:: int PyObject_RichCompareBool(PyObject *o1, PyObject *o2, int opid) Compare the values of *o1* and *o2* using the operation specified by *opid*, - which must be one of :c:macro:`Py_LT`, :c:macro:`Py_LE`, :c:macro:`Py_EQ`, - :c:macro:`Py_NE`, :c:macro:`Py_GT`, or :c:macro:`Py_GE`, corresponding to ``<``, - ``<=``, ``==``, ``!=``, ``>``, or ``>=`` respectively. Returns ``-1`` on error, - ``0`` if the result is false, ``1`` otherwise. This is the equivalent of the - Python expression ``o1 op o2``, where ``op`` is the operator corresponding to - *opid*. + like :c:func:`PyObject_RichCompare`, but returns ``-1`` on error, ``0`` if + the result is false, ``1`` otherwise. .. note:: If *o1* and *o2* are the same object, :c:func:`PyObject_RichCompareBool` From 547c135d70760f974ed0476a32a6809e708bfe4d Mon Sep 17 00:00:00 2001 From: NewUserHa <32261870+NewUserHa@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:29:38 +0800 Subject: [PATCH 060/263] Simplify concurrent.futures.process code by using itertools.batched() (GH-114221) --- Lib/concurrent/futures/process.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index ffaffdb8b3d0aad..ca843e11eeb83dc 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -190,16 +190,6 @@ def _on_queue_feeder_error(self, e, obj): super()._on_queue_feeder_error(e, obj) -def _get_chunks(*iterables, chunksize): - """ Iterates over zip()ed iterables in chunks. """ - it = zip(*iterables) - while True: - chunk = tuple(itertools.islice(it, chunksize)) - if not chunk: - return - yield chunk - - def _process_chunk(fn, chunk): """ Processes a chunk of an iterable passed to map. @@ -847,7 +837,7 @@ def map(self, fn, *iterables, timeout=None, chunksize=1): raise ValueError("chunksize must be >= 1.") results = super().map(partial(_process_chunk, fn), - _get_chunks(*iterables, chunksize=chunksize), + itertools.batched(zip(*iterables), chunksize), timeout=timeout) return _chain_from_iterable_of_lists(results) From 23fb9f0777b054526b3b32f58e60b2a03132bf45 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 27 Jan 2024 11:45:07 +0300 Subject: [PATCH 061/263] Fix `c-api/file.rst` indexes (GH-114608) --- Doc/c-api/file.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/file.rst b/Doc/c-api/file.rst index 0a03841e467cad9..d3a78c588454e8e 100644 --- a/Doc/c-api/file.rst +++ b/Doc/c-api/file.rst @@ -65,9 +65,10 @@ the :mod:`io` APIs instead. Overrides the normal behavior of :func:`io.open_code` to pass its parameter through the provided handler. - The handler is a function of type: + The *handler* is a function of type: - .. c:type:: Py_OpenCodeHookFunction + .. c:namespace:: NULL + .. c:type:: PyObject * (*Py_OpenCodeHookFunction)(PyObject *, void *) Equivalent of :c:expr:`PyObject *(\*)(PyObject *path, void *userData)`, where *path* is guaranteed to be From 6a8944acb61d0a2c210ab8066cdcec8602110e2f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 27 Jan 2024 11:45:40 +0300 Subject: [PATCH 062/263] gh-101100: Fix sphinx warnings in `library/email.mime.rst` (GH-114635) --- Doc/library/email.mime.rst | 16 ++++++++-------- Doc/tools/.nitignore | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Doc/library/email.mime.rst b/Doc/library/email.mime.rst index d7c0d203d191f86..dc0dd3b9eebde6e 100644 --- a/Doc/library/email.mime.rst +++ b/Doc/library/email.mime.rst @@ -28,7 +28,7 @@ make things easier. Here are the classes: -.. currentmodule:: email.mime.base +.. module:: email.mime.base .. class:: MIMEBase(_maintype, _subtype, *, policy=compat32, **_params) @@ -58,7 +58,7 @@ Here are the classes: Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.nonmultipart +.. module:: email.mime.nonmultipart .. class:: MIMENonMultipart() @@ -72,7 +72,7 @@ Here are the classes: is called, a :exc:`~email.errors.MultipartConversionError` exception is raised. -.. currentmodule:: email.mime.multipart +.. module:: email.mime.multipart .. class:: MIMEMultipart(_subtype='mixed', boundary=None, _subparts=None, \ *, policy=compat32, **_params) @@ -104,7 +104,7 @@ Here are the classes: .. versionchanged:: 3.6 Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.application +.. module:: email.mime.application .. class:: MIMEApplication(_data, _subtype='octet-stream', \ _encoder=email.encoders.encode_base64, \ @@ -135,7 +135,7 @@ Here are the classes: .. versionchanged:: 3.6 Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.audio +.. module:: email.mime.audio .. class:: MIMEAudio(_audiodata, _subtype=None, \ _encoder=email.encoders.encode_base64, \ @@ -169,7 +169,7 @@ Here are the classes: .. versionchanged:: 3.6 Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.image +.. module:: email.mime.image .. class:: MIMEImage(_imagedata, _subtype=None, \ _encoder=email.encoders.encode_base64, \ @@ -205,7 +205,7 @@ Here are the classes: .. versionchanged:: 3.6 Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.message +.. module:: email.mime.message .. class:: MIMEMessage(_msg, _subtype='rfc822', *, policy=compat32) @@ -225,7 +225,7 @@ Here are the classes: .. versionchanged:: 3.6 Added *policy* keyword-only parameter. -.. currentmodule:: email.mime.text +.. module:: email.mime.text .. class:: MIMEText(_text, _subtype='plain', _charset=None, *, policy=compat32) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index d56a44ad09a6f88..f48506e3a21df58 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -33,7 +33,6 @@ Doc/library/decimal.rst Doc/library/email.charset.rst Doc/library/email.compat32-message.rst Doc/library/email.errors.rst -Doc/library/email.mime.rst Doc/library/email.parser.rst Doc/library/email.policy.rst Doc/library/enum.rst From 11c582235d86b6020710eff282eeb381a7bf7bb7 Mon Sep 17 00:00:00 2001 From: Charlie Zhao Date: Sat, 27 Jan 2024 17:53:01 +0800 Subject: [PATCH 063/263] gh-113560: Improve docstrings for set.issubset() and set.issuperset() (GH-113562) --- Objects/setobject.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 88d20019bfb4a7f..93de8e84f2ddf99 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -1770,7 +1770,11 @@ set_issubset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issubset_doc, "Report whether another set contains this set."); +PyDoc_STRVAR(issubset_doc, +"issubset($self, other, /)\n\ +--\n\ +\n\ +Test whether every element in the set is in other."); static PyObject * set_issuperset(PySetObject *so, PyObject *other) @@ -1802,7 +1806,11 @@ set_issuperset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issuperset_doc, "Report whether this set contains another set."); +PyDoc_STRVAR(issuperset_doc, +"issuperset($self, other, /)\n\ +--\n\ +\n\ +Test whether every element in other is in the set."); static PyObject * set_richcompare(PySetObject *v, PyObject *w, int op) From b6623d61d4e07aefd15d910ef67837c66bceaf32 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:06:59 +0200 Subject: [PATCH 064/263] gh-101100: Fix Sphinx warnings in `whatsnew/3.11.rst` and related (#114531) --- Doc/whatsnew/2.7.rst | 2 +- Doc/whatsnew/3.11.rst | 96 ++++++++++++++++++++-------------------- Doc/whatsnew/3.8.rst | 4 +- Misc/NEWS.d/3.11.0a2.rst | 2 +- Misc/NEWS.d/3.11.0a4.rst | 6 +-- Misc/NEWS.d/3.11.0a6.rst | 2 +- Misc/NEWS.d/3.11.0a7.rst | 4 +- Misc/NEWS.d/3.11.0b1.rst | 4 +- Misc/NEWS.d/3.12.0a4.rst | 4 +- Misc/NEWS.d/3.12.0a6.rst | 2 +- Misc/NEWS.d/3.12.0a7.rst | 2 +- Misc/NEWS.d/3.12.0b1.rst | 2 +- Misc/NEWS.d/3.8.0a1.rst | 4 +- Misc/NEWS.d/3.9.0a1.rst | 4 +- Misc/NEWS.d/3.9.0a5.rst | 2 +- 15 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 241d58720399aff..524967b45242344 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -2130,7 +2130,7 @@ Changes to Python's build process and to the C API include: only the filename, function name, and first line number are required. This is useful for extension modules that are attempting to construct a more useful traceback stack. Previously such - extensions needed to call :c:func:`PyCode_New`, which had many + extensions needed to call :c:func:`!PyCode_New`, which had many more arguments. (Added by Jeffrey Yasskin.) * New function: :c:func:`PyErr_NewExceptionWithDoc` creates a new diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index cb646a54df36070..a8a7dfda0c5309b 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -672,7 +672,7 @@ enum * Changed :meth:`Enum.__format__() ` (the default for :func:`format`, :meth:`str.format` and :term:`f-string`\s) to always produce - the same result as :meth:`Enum.__str__()`: for enums inheriting from + the same result as :meth:`Enum.__str__() `: for enums inheriting from :class:`~enum.ReprEnum` it will be the member's value; for all other enums it will be the enum and member name (e.g. ``Color.RED``). @@ -1604,7 +1604,7 @@ raw, adaptive bytecode containing quickened data. New opcodes ----------- -* :opcode:`ASYNC_GEN_WRAP`, :opcode:`RETURN_GENERATOR` and :opcode:`SEND`, +* :opcode:`!ASYNC_GEN_WRAP`, :opcode:`RETURN_GENERATOR` and :opcode:`SEND`, used in generators and co-routines. * :opcode:`COPY_FREE_VARS`, @@ -1615,7 +1615,7 @@ New opcodes * :opcode:`MAKE_CELL`, to create :ref:`cell-objects`. -* :opcode:`CHECK_EG_MATCH` and :opcode:`PREP_RERAISE_STAR`, +* :opcode:`CHECK_EG_MATCH` and :opcode:`!PREP_RERAISE_STAR`, to handle the :ref:`new exception groups and except* ` added in :pep:`654`. @@ -1630,38 +1630,38 @@ New opcodes Replaced opcodes ---------------- -+------------------------------------+-----------------------------------+-----------------------------------------+ -| Replaced Opcode(s) | New Opcode(s) | Notes | -+====================================+===================================+=========================================+ -| | :opcode:`!BINARY_*` | :opcode:`BINARY_OP` | Replaced all numeric binary/in-place | -| | :opcode:`!INPLACE_*` | | opcodes with a single opcode | -+------------------------------------+-----------------------------------+-----------------------------------------+ -| | :opcode:`!CALL_FUNCTION` | | :opcode:`CALL` | Decouples argument shifting for methods | -| | :opcode:`!CALL_FUNCTION_KW` | | :opcode:`KW_NAMES` | from handling of keyword arguments; | -| | :opcode:`!CALL_METHOD` | | :opcode:`PRECALL` | allows better specialization of calls | -| | | :opcode:`PUSH_NULL` | | -+------------------------------------+-----------------------------------+-----------------------------------------+ -| | :opcode:`!DUP_TOP` | | :opcode:`COPY` | Stack manipulation instructions | -| | :opcode:`!DUP_TOP_TWO` | | :opcode:`SWAP` | | -| | :opcode:`!ROT_TWO` | | | -| | :opcode:`!ROT_THREE` | | | -| | :opcode:`!ROT_FOUR` | | | -| | :opcode:`!ROT_N` | | | -+------------------------------------+-----------------------------------+-----------------------------------------+ -| | :opcode:`!JUMP_IF_NOT_EXC_MATCH` | | :opcode:`CHECK_EXC_MATCH` | Now performs check but doesn't jump | -+------------------------------------+-----------------------------------+-----------------------------------------+ -| | :opcode:`!JUMP_ABSOLUTE` | | :opcode:`JUMP_BACKWARD` | See [#bytecode-jump]_; | -| | :opcode:`!POP_JUMP_IF_FALSE` | | :opcode:`POP_JUMP_BACKWARD_IF_* | ``TRUE``, ``FALSE``, | -| | :opcode:`!POP_JUMP_IF_TRUE` | ` | ``NONE`` and ``NOT_NONE`` variants | -| | | :opcode:`POP_JUMP_FORWARD_IF_* | for each direction | -| | ` | | -+------------------------------------+-----------------------------------+-----------------------------------------+ -| | :opcode:`!SETUP_WITH` | :opcode:`BEFORE_WITH` | :keyword:`with` block setup | -| | :opcode:`!SETUP_ASYNC_WITH` | | | -+------------------------------------+-----------------------------------+-----------------------------------------+ ++------------------------------------+------------------------------------+-----------------------------------------+ +| Replaced Opcode(s) | New Opcode(s) | Notes | ++====================================+====================================+=========================================+ +| | :opcode:`!BINARY_*` | :opcode:`BINARY_OP` | Replaced all numeric binary/in-place | +| | :opcode:`!INPLACE_*` | | opcodes with a single opcode | ++------------------------------------+------------------------------------+-----------------------------------------+ +| | :opcode:`!CALL_FUNCTION` | | :opcode:`CALL` | Decouples argument shifting for methods | +| | :opcode:`!CALL_FUNCTION_KW` | | :opcode:`!KW_NAMES` | from handling of keyword arguments; | +| | :opcode:`!CALL_METHOD` | | :opcode:`!PRECALL` | allows better specialization of calls | +| | | :opcode:`PUSH_NULL` | | ++------------------------------------+------------------------------------+-----------------------------------------+ +| | :opcode:`!DUP_TOP` | | :opcode:`COPY` | Stack manipulation instructions | +| | :opcode:`!DUP_TOP_TWO` | | :opcode:`SWAP` | | +| | :opcode:`!ROT_TWO` | | | +| | :opcode:`!ROT_THREE` | | | +| | :opcode:`!ROT_FOUR` | | | +| | :opcode:`!ROT_N` | | | ++------------------------------------+------------------------------------+-----------------------------------------+ +| | :opcode:`!JUMP_IF_NOT_EXC_MATCH` | | :opcode:`CHECK_EXC_MATCH` | Now performs check but doesn't jump | ++------------------------------------+------------------------------------+-----------------------------------------+ +| | :opcode:`!JUMP_ABSOLUTE` | | :opcode:`JUMP_BACKWARD` | See [#bytecode-jump]_; | +| | :opcode:`!POP_JUMP_IF_FALSE` | | :opcode:`!POP_JUMP_BACKWARD_IF_*`| ``TRUE``, ``FALSE``, | +| | :opcode:`!POP_JUMP_IF_TRUE` | | :opcode:`!POP_JUMP_FORWARD_IF_*` | ``NONE`` and ``NOT_NONE`` variants | +| | | for each direction | +| | | | ++------------------------------------+------------------------------------+-----------------------------------------+ +| | :opcode:`!SETUP_WITH` | :opcode:`BEFORE_WITH` | :keyword:`with` block setup | +| | :opcode:`!SETUP_ASYNC_WITH` | | | ++------------------------------------+------------------------------------+-----------------------------------------+ .. [#bytecode-jump] All jump opcodes are now relative, including the - existing :opcode:`JUMP_IF_TRUE_OR_POP` and :opcode:`JUMP_IF_FALSE_OR_POP`. + existing :opcode:`!JUMP_IF_TRUE_OR_POP` and :opcode:`!JUMP_IF_FALSE_OR_POP`. The argument is now an offset from the current instruction rather than an absolute location. @@ -1789,13 +1789,13 @@ Standard Library and will be removed in a future Python version, due to not supporting resources located within package subdirectories: - * :func:`importlib.resources.contents` - * :func:`importlib.resources.is_resource` - * :func:`importlib.resources.open_binary` - * :func:`importlib.resources.open_text` - * :func:`importlib.resources.read_binary` - * :func:`importlib.resources.read_text` - * :func:`importlib.resources.path` + * :func:`!importlib.resources.contents` + * :func:`!importlib.resources.is_resource` + * :func:`!importlib.resources.open_binary` + * :func:`!importlib.resources.open_text` + * :func:`!importlib.resources.read_binary` + * :func:`!importlib.resources.read_text` + * :func:`!importlib.resources.path` * The :func:`locale.getdefaultlocale` function is deprecated and will be removed in Python 3.15. Use :func:`locale.setlocale`, @@ -1803,7 +1803,7 @@ Standard Library :func:`locale.getlocale` functions instead. (Contributed by Victor Stinner in :gh:`90817`.) -* The :func:`locale.resetlocale` function is deprecated and will be +* The :func:`!locale.resetlocale` function is deprecated and will be removed in Python 3.13. Use ``locale.setlocale(locale.LC_ALL, "")`` instead. (Contributed by Victor Stinner in :gh:`90817`.) @@ -1967,7 +1967,7 @@ Removed C APIs are :ref:`listed separately `. (Contributed by Victor Stinner in :issue:`45085`.) -* Removed the :mod:`distutils` ``bdist_msi`` command deprecated in Python 3.9. +* Removed the :mod:`!distutils` ``bdist_msi`` command deprecated in Python 3.9. Use ``bdist_wheel`` (wheel packages) instead. (Contributed by Hugo van Kemenade in :issue:`45124`.) @@ -2295,7 +2295,7 @@ Porting to Python 3.11 as its second parameter, instead of ``PyFrameObject*``. See :pep:`523` for more details of how to use this function pointer type. -* :c:func:`PyCode_New` and :c:func:`PyCode_NewWithPosOnlyArgs` now take +* :c:func:`!PyCode_New` and :c:func:`!PyCode_NewWithPosOnlyArgs` now take an additional ``exception_table`` argument. Using these functions should be avoided, if at all possible. To get a custom code object: create a code object using the compiler, @@ -2402,7 +2402,7 @@ Porting to Python 3.11 been included directly, consider including ``Python.h`` instead. (Contributed by Victor Stinner in :issue:`35134`.) -* The :c:func:`PyUnicode_CHECK_INTERNED` macro has been excluded from the +* The :c:func:`!PyUnicode_CHECK_INTERNED` macro has been excluded from the limited C API. It was never usable there, because it used internal structures which are not available in the limited C API. (Contributed by Victor Stinner in :issue:`46007`.) @@ -2465,7 +2465,7 @@ Porting to Python 3.11 Debuggers that accessed the :attr:`~frame.f_locals` directly *must* call :c:func:`PyFrame_GetLocals` instead. They no longer need to call - :c:func:`PyFrame_FastToLocalsWithError` or :c:func:`PyFrame_LocalsToFast`, + :c:func:`!PyFrame_FastToLocalsWithError` or :c:func:`!PyFrame_LocalsToFast`, in fact they should not call those functions. The necessary updating of the frame is now managed by the virtual machine. @@ -2604,8 +2604,8 @@ and will be removed in Python 3.12. * :c:func:`!PyUnicode_GET_DATA_SIZE` * :c:func:`!PyUnicode_GET_SIZE` * :c:func:`!PyUnicode_GetSize` -* :c:func:`PyUnicode_IS_COMPACT` -* :c:func:`PyUnicode_IS_READY` +* :c:func:`!PyUnicode_IS_COMPACT` +* :c:func:`!PyUnicode_IS_READY` * :c:func:`PyUnicode_READY` * :c:func:`!PyUnicode_WSTR_LENGTH` * :c:func:`!_PyUnicode_AsUnicode` @@ -2660,7 +2660,7 @@ Removed (Contributed by Victor Stinner in :issue:`45474`.) * Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never - worked since the :c:type:`PyWeakReference` structure is opaque in the + worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. (Contributed by Victor Stinner in :issue:`35134`.) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index d373fa163ff737d..304d1b4ef4efe80 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -1623,8 +1623,8 @@ Build and C API Changes allocation or deallocation may need to be adjusted. (Contributed by Eddie Elizondo in :issue:`35810`.) -* The new function :c:func:`PyCode_NewWithPosOnlyArgs` allows to create - code objects like :c:func:`PyCode_New`, but with an extra *posonlyargcount* +* The new function :c:func:`!PyCode_NewWithPosOnlyArgs` allows to create + code objects like :c:func:`!PyCode_New`, but with an extra *posonlyargcount* parameter for indicating the number of positional-only arguments. (Contributed by Pablo Galindo in :issue:`37221`.) diff --git a/Misc/NEWS.d/3.11.0a2.rst b/Misc/NEWS.d/3.11.0a2.rst index eb1456f1bcf3536..a6b5fe54b391c53 100644 --- a/Misc/NEWS.d/3.11.0a2.rst +++ b/Misc/NEWS.d/3.11.0a2.rst @@ -1189,7 +1189,7 @@ context objects can now be disabled. .. section: C API Exclude :c:func:`PyWeakref_GET_OBJECT` from the limited C API. It never -worked since the :c:type:`PyWeakReference` structure is opaque in the +worked since the :c:type:`!PyWeakReference` structure is opaque in the limited C API. .. diff --git a/Misc/NEWS.d/3.11.0a4.rst b/Misc/NEWS.d/3.11.0a4.rst index 5abacd8473f3940..78b682f7a22cc61 100644 --- a/Misc/NEWS.d/3.11.0a4.rst +++ b/Misc/NEWS.d/3.11.0a4.rst @@ -161,7 +161,7 @@ faster due to reference-counting optimizations. Patch by Dennis Sweeney. .. nonce: 7oGp-I .. section: Core and Builtins -:opcode:`PREP_RERAISE_STAR` no longer pushes ``lasti`` to the stack. +:opcode:`!PREP_RERAISE_STAR` no longer pushes ``lasti`` to the stack. .. @@ -170,7 +170,7 @@ faster due to reference-counting optimizations. Patch by Dennis Sweeney. .. nonce: IKx4v6 .. section: Core and Builtins -Remove :opcode:`POP_EXCEPT_AND_RERAISE` and replace it by an equivalent +Remove :opcode:`!POP_EXCEPT_AND_RERAISE` and replace it by an equivalent sequence of other opcodes. .. @@ -1171,7 +1171,7 @@ Replaced deprecated usage of :c:func:`PyImport_ImportModuleNoBlock` with .. nonce: sMgDLz .. section: C API -The :c:func:`PyUnicode_CHECK_INTERNED` macro has been excluded from the +The :c:func:`!PyUnicode_CHECK_INTERNED` macro has been excluded from the limited C API. It was never usable there, because it used internal structures which are not available in the limited C API. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/3.11.0a6.rst b/Misc/NEWS.d/3.11.0a6.rst index 2b50b7773492cbd..2fdceef7746d4e9 100644 --- a/Misc/NEWS.d/3.11.0a6.rst +++ b/Misc/NEWS.d/3.11.0a6.rst @@ -248,7 +248,7 @@ Don't un-adapt :opcode:`COMPARE_OP` when collecting specialization stats. .. nonce: RX_AzJ .. section: Core and Builtins -Fix specialization stats gathering for :opcode:`PRECALL` instructions. +Fix specialization stats gathering for :opcode:`!PRECALL` instructions. .. diff --git a/Misc/NEWS.d/3.11.0a7.rst b/Misc/NEWS.d/3.11.0a7.rst index 76699632db223a5..ec99bd0294ceca2 100644 --- a/Misc/NEWS.d/3.11.0a7.rst +++ b/Misc/NEWS.d/3.11.0a7.rst @@ -138,7 +138,7 @@ Replaced :opcode:`JUMP_ABSOLUTE` by the relative jump .. nonce: SwrrFO .. section: Core and Builtins -:c:func:`PyFrame_FastToLocalsWithError` and :c:func:`PyFrame_LocalsToFast` +:c:func:`!PyFrame_FastToLocalsWithError` and :c:func:`!PyFrame_LocalsToFast` are no longer called during profiling nor tracing. C code can access the ``f_locals`` attribute of :c:type:`PyFrameObject` by calling :c:func:`PyFrame_GetLocals`. @@ -295,7 +295,7 @@ oparg) as an adaptive counter. .. nonce: O12Pba .. section: Core and Builtins -Use inline caching for :opcode:`PRECALL` and :opcode:`CALL`, and remove the +Use inline caching for :opcode:`!PRECALL` and :opcode:`CALL`, and remove the internal machinery for managing the (now unused) non-inline caches. .. diff --git a/Misc/NEWS.d/3.11.0b1.rst b/Misc/NEWS.d/3.11.0b1.rst index 2c30dc6e084bfb9..f92966796555733 100644 --- a/Misc/NEWS.d/3.11.0b1.rst +++ b/Misc/NEWS.d/3.11.0b1.rst @@ -403,8 +403,8 @@ so this led to crashes. The problem is now fixed. .. nonce: 6S_uoU .. section: Core and Builtins -Make opcodes :opcode:`JUMP_IF_TRUE_OR_POP` and -:opcode:`JUMP_IF_FALSE_OR_POP` relative rather than absolute. +Make opcodes :opcode:`!JUMP_IF_TRUE_OR_POP` and +:opcode:`!JUMP_IF_FALSE_OR_POP` relative rather than absolute. .. diff --git a/Misc/NEWS.d/3.12.0a4.rst b/Misc/NEWS.d/3.12.0a4.rst index ce2814bbe2e5ab2..82faa5ad0b2031a 100644 --- a/Misc/NEWS.d/3.12.0a4.rst +++ b/Misc/NEWS.d/3.12.0a4.rst @@ -13,8 +13,8 @@ Fix misleading default value in :func:`input`'s ``__text_signature__``. .. nonce: cmGwxv .. section: Core and Builtins -Remove :opcode:`UNARY_POSITIVE`, :opcode:`ASYNC_GEN_WRAP` and -:opcode:`LIST_TO_TUPLE`, replacing them with intrinsics. +Remove :opcode:`!UNARY_POSITIVE`, :opcode:`!ASYNC_GEN_WRAP` and +:opcode:`!LIST_TO_TUPLE`, replacing them with intrinsics. .. diff --git a/Misc/NEWS.d/3.12.0a6.rst b/Misc/NEWS.d/3.12.0a6.rst index 5bd600cd8b6fc0e..cf28bdb92588206 100644 --- a/Misc/NEWS.d/3.12.0a6.rst +++ b/Misc/NEWS.d/3.12.0a6.rst @@ -170,7 +170,7 @@ all as not all platform C libraries generate an error. .. section: Core and Builtins Add :opcode:`CALL_INTRINSIC_2` and use it instead of -:opcode:`PREP_RERAISE_STAR`. +:opcode:`!PREP_RERAISE_STAR`. .. diff --git a/Misc/NEWS.d/3.12.0a7.rst b/Misc/NEWS.d/3.12.0a7.rst index f22050b0dc377b2..a859be8a0474561 100644 --- a/Misc/NEWS.d/3.12.0a7.rst +++ b/Misc/NEWS.d/3.12.0a7.rst @@ -24,7 +24,7 @@ Reduce the number of inline :opcode:`CACHE` entries for .. nonce: PRkGca .. section: Core and Builtins -Removed :opcode:`JUMP_IF_FALSE_OR_POP` and :opcode:`JUMP_IF_TRUE_OR_POP` +Removed :opcode:`!JUMP_IF_FALSE_OR_POP` and :opcode:`!JUMP_IF_TRUE_OR_POP` instructions. .. diff --git a/Misc/NEWS.d/3.12.0b1.rst b/Misc/NEWS.d/3.12.0b1.rst index 007a6ad4ffd4d42..211513d05d00407 100644 --- a/Misc/NEWS.d/3.12.0b1.rst +++ b/Misc/NEWS.d/3.12.0b1.rst @@ -1008,7 +1008,7 @@ Update the bundled copy of pip to version 23.1.2. .. nonce: pst8iT .. section: Library -Make :mod:`dis` display the value of oparg of :opcode:`KW_NAMES`. +Make :mod:`dis` display the value of oparg of :opcode:`!KW_NAMES`. .. diff --git a/Misc/NEWS.d/3.8.0a1.rst b/Misc/NEWS.d/3.8.0a1.rst index 11b303e89ad04f7..bd9061601fe1905 100644 --- a/Misc/NEWS.d/3.8.0a1.rst +++ b/Misc/NEWS.d/3.8.0a1.rst @@ -3395,8 +3395,8 @@ Zackery Spytz. .. nonce: S0Irst .. section: Library -Fix parsing non-ASCII identifiers in :mod:`lib2to3.pgen2.tokenize` (PEP -3131). +Fix parsing non-ASCII identifiers in :mod:`!lib2to3.pgen2.tokenize` +(:pep:`3131`). .. diff --git a/Misc/NEWS.d/3.9.0a1.rst b/Misc/NEWS.d/3.9.0a1.rst index 0772a0fed206527..66d7fc1f32e705d 100644 --- a/Misc/NEWS.d/3.9.0a1.rst +++ b/Misc/NEWS.d/3.9.0a1.rst @@ -5715,8 +5715,8 @@ The :c:macro:`METH_FASTCALL` calling convention has been documented. .. nonce: 4tClQT .. section: C API -The new function :c:func:`PyCode_NewWithPosOnlyArgs` allows to create code -objects like :c:func:`PyCode_New`, but with an extra *posonlyargcount* +The new function :c:func:`!PyCode_NewWithPosOnlyArgs` allows to create code +objects like :c:func:`!PyCode_New`, but with an extra *posonlyargcount* parameter for indicating the number of positonal-only arguments. .. diff --git a/Misc/NEWS.d/3.9.0a5.rst b/Misc/NEWS.d/3.9.0a5.rst index b4594aade3b3ed8..f0015ac54df307a 100644 --- a/Misc/NEWS.d/3.9.0a5.rst +++ b/Misc/NEWS.d/3.9.0a5.rst @@ -1122,7 +1122,7 @@ a different condition than the GIL. .. nonce: Nbl7lF .. section: Tools/Demos -Added support to fix ``getproxies`` in the :mod:`lib2to3.fixes.fix_urllib` +Added support to fix ``getproxies`` in the :mod:`!lib2to3.fixes.fix_urllib` module. Patch by José Roberto Meza Cabrera. .. From a384b20c0ce5aa520fa91ae0233d53642925525b Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 27 Jan 2024 17:30:21 +0300 Subject: [PATCH 065/263] gh-101100: Fix sphinx warnings in `reference/import.rst` (#114646) --- Doc/reference/import.rst | 7 ++++--- Doc/tools/.nitignore | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst index a7beeea29b45567..f8c9724114da9ed 100644 --- a/Doc/reference/import.rst +++ b/Doc/reference/import.rst @@ -327,14 +327,15 @@ modules, and one that knows how to import modules from an :term:`import path` finders replaced :meth:`!find_module`, which is now deprecated. While it will continue to work without change, the import machinery will try it only if the finder does not implement - ``find_spec()``. + :meth:`~importlib.abc.MetaPathFinder.find_spec`. .. versionchanged:: 3.10 Use of :meth:`!find_module` by the import system now raises :exc:`ImportWarning`. .. versionchanged:: 3.12 - ``find_module()`` has been removed. Use :meth:`find_spec` instead. + :meth:`!find_module` has been removed. + Use :meth:`~importlib.abc.MetaPathFinder.find_spec` instead. Loading @@ -812,7 +813,7 @@ attributes on package objects are also used. These provide additional ways that the import machinery can be customized. :data:`sys.path` contains a list of strings providing search locations for -modules and packages. It is initialized from the :data:`PYTHONPATH` +modules and packages. It is initialized from the :envvar:`PYTHONPATH` environment variable and various other installation- and implementation-specific defaults. Entries in :data:`sys.path` can name directories on the file system, zip files, and potentially other "locations" diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index f48506e3a21df58..8b6847ef2a7d76a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -87,7 +87,6 @@ Doc/library/xmlrpc.server.rst Doc/library/zlib.rst Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst -Doc/reference/import.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.0.rst From 7a470541e2bbc6f3e87a6d813e2ec42cf726de7a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 27 Jan 2024 18:38:17 +0200 Subject: [PATCH 066/263] gh-114100: Remove superfluous writing to fd 1 in test_pty (GH-114647) --- Lib/test/test_pty.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index f31a68c5d84e037..51e3a46d0df1780 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,4 +1,5 @@ from test.support import verbose, reap_children +from test.support.os_helper import TESTFN, unlink from test.support.import_helper import import_module # Skip these tests if termios or fcntl are not available @@ -292,7 +293,26 @@ def test_master_read(self): self.assertEqual(data, b"") def test_spawn_doesnt_hang(self): - pty.spawn([sys.executable, '-c', 'print("hi there")']) + self.addCleanup(unlink, TESTFN) + with open(TESTFN, 'wb') as f: + STDOUT_FILENO = 1 + dup_stdout = os.dup(STDOUT_FILENO) + os.dup2(f.fileno(), STDOUT_FILENO) + buf = b'' + def master_read(fd): + nonlocal buf + data = os.read(fd, 1024) + buf += data + return data + try: + pty.spawn([sys.executable, '-c', 'print("hi there")'], + master_read) + finally: + os.dup2(dup_stdout, STDOUT_FILENO) + os.close(dup_stdout) + self.assertEqual(buf, b'hi there\r\n') + with open(TESTFN, 'rb') as f: + self.assertEqual(f.read(), b'hi there\r\n') class SmallPtyTests(unittest.TestCase): """These tests don't spawn children or hang.""" From 823a38a960c245cbf309ef29120d3690ba1bcd2c Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 27 Jan 2024 19:59:51 +0000 Subject: [PATCH 067/263] GH-79634: Speed up pathlib globbing by removing `joinpath()` call. (#114623) Remove `self.joinpath('')` call that should have been removed in 6313cdde. This makes `PathBase.glob('')` yield itself *without* adding a trailing slash. It's hard to say whether this is more or less correct, but at least everything else is faster, and there's no behaviour change in the public classes where empty glob patterns are disallowed. --- Lib/pathlib/_abc.py | 2 +- Lib/test/test_pathlib/test_pathlib.py | 2 ++ Lib/test/test_pathlib/test_pathlib_abc.py | 7 +++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 6303a18680befc4..ad5684829ebc800 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -771,7 +771,7 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): filter_paths = False deduplicate_paths = False sep = self.pathmod.sep - paths = iter([self.joinpath('')] if self.is_dir() else []) + paths = iter([self] if self.is_dir() else []) while stack: part = stack.pop() if part in specials: diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 9c2b26d41d73f8e..5ce3b605c58e637 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1232,6 +1232,8 @@ def test_glob_empty_pattern(self): list(p.glob('')) with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): list(p.glob('.')) + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('./')) def test_glob_many_open_files(self): depth = 30 diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index ea70931eaa2c7e6..ab989cb5503f99d 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1733,12 +1733,11 @@ def test_glob_windows(self): self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"}) def test_glob_empty_pattern(self): - def _check(glob, expected): - self.assertEqual(set(glob), { P(self.base, q) for q in expected }) P = self.cls p = P(self.base) - _check(p.glob(""), [""]) - _check(p.glob("."), ["."]) + self.assertEqual(list(p.glob("")), [p]) + self.assertEqual(list(p.glob(".")), [p / "."]) + self.assertEqual(list(p.glob("./")), [p / "./"]) def test_glob_case_sensitive(self): P = self.cls From a768e12f094a9b14a9a1680fb50330e1050716c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 27 Jan 2024 23:47:55 +0200 Subject: [PATCH 068/263] Use bool in fileinput.input() docstring and tests for the inplace argument (GH-111998) The `.rst` docs, most tests, and typeshed already use bool for it. --- Lib/fileinput.py | 2 +- Lib/test/test_fileinput.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/fileinput.py b/Lib/fileinput.py index 1b25f28f3d34326..3dba3d2fbfa967b 100644 --- a/Lib/fileinput.py +++ b/Lib/fileinput.py @@ -53,7 +53,7 @@ sequence must be accessed in strictly sequential order; sequence access and readline() cannot be mixed. -Optional in-place filtering: if the keyword argument inplace=1 is +Optional in-place filtering: if the keyword argument inplace=True is passed to input() or to the FileInput constructor, the file is moved to a backup file and standard output is directed to the input file. This makes it possible to write a filter that rewrites its input file diff --git a/Lib/test/test_fileinput.py b/Lib/test/test_fileinput.py index 786d9186634305b..b3ad41d2588c4cb 100644 --- a/Lib/test/test_fileinput.py +++ b/Lib/test/test_fileinput.py @@ -151,7 +151,7 @@ def test_buffer_sizes(self): print('6. Inplace') savestdout = sys.stdout try: - fi = FileInput(files=(t1, t2, t3, t4), inplace=1, encoding="utf-8") + fi = FileInput(files=(t1, t2, t3, t4), inplace=True, encoding="utf-8") for line in fi: line = line[:-1].upper() print(line) @@ -256,7 +256,7 @@ def test_detached_stdin_binary_mode(self): def test_file_opening_hook(self): try: # cannot use openhook and inplace mode - fi = FileInput(inplace=1, openhook=lambda f, m: None) + fi = FileInput(inplace=True, openhook=lambda f, m: None) self.fail("FileInput should raise if both inplace " "and openhook arguments are given") except ValueError: From 5ecfd750b4f511f270c38f0d748da9cffa279295 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Sun, 28 Jan 2024 08:51:25 -0600 Subject: [PATCH 069/263] Correction Skip Montanaro's email address (#114677) --- Doc/library/atexit.rst | 4 ++-- Doc/library/csv.rst | 2 +- Doc/library/readline.rst | 2 +- Doc/library/urllib.robotparser.rst | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/atexit.rst b/Doc/library/atexit.rst index 3dbef69580d9b30..43a8bd2d7cd1339 100644 --- a/Doc/library/atexit.rst +++ b/Doc/library/atexit.rst @@ -4,8 +4,8 @@ .. module:: atexit :synopsis: Register and execute cleanup functions. -.. moduleauthor:: Skip Montanaro -.. sectionauthor:: Skip Montanaro +.. moduleauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro -------------- diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 7a5589e68b30525..07f38f5690bb540 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -4,7 +4,7 @@ .. module:: csv :synopsis: Write and read tabular data to and from delimited files. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro **Source code:** :source:`Lib/csv.py` diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 1adafcaa02eab97..54c6d9f3b32b1aa 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -5,7 +5,7 @@ :platform: Unix :synopsis: GNU readline support for Python. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro -------------- diff --git a/Doc/library/urllib.robotparser.rst b/Doc/library/urllib.robotparser.rst index f063e463753e0b8..b5a49d9c5923876 100644 --- a/Doc/library/urllib.robotparser.rst +++ b/Doc/library/urllib.robotparser.rst @@ -5,7 +5,7 @@ :synopsis: Load a robots.txt file and answer questions about fetchability of other URLs. -.. sectionauthor:: Skip Montanaro +.. sectionauthor:: Skip Montanaro **Source code:** :source:`Lib/urllib/robotparser.py` From d00fbed68ffcd5823acbb32a0e47e2e5f9732ff7 Mon Sep 17 00:00:00 2001 From: Bhushan Mohanraj <50306448+bhushan-mohanraj@users.noreply.github.com> Date: Sun, 28 Jan 2024 15:10:32 -0500 Subject: [PATCH 070/263] Fix indentation in `__post_init__` documentation. (gh-114666) --- Doc/library/dataclasses.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index 88f2e0251b1e519..4ada69d63abada1 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -538,8 +538,8 @@ that has to be called, it is common to call this method in a class Rectangle: def __init__(self, height, width): - self.height = height - self.width = width + self.height = height + self.width = width @dataclass class Square(Rectangle): From 3bb6912d8832e6e0a98c74de360dc1b23906c4b3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 28 Jan 2024 22:28:25 +0200 Subject: [PATCH 071/263] gh-100734: Add 'Notable change in 3.11.x' to `whatsnew/3.11.rst` (#114657) Co-authored-by: Serhiy Storchaka --- Doc/whatsnew/3.11.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index a8a7dfda0c5309b..4f4c1de8d8d5964 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2701,4 +2701,30 @@ Removed (Contributed by Inada Naoki in :issue:`44029`.) +Notable changes in 3.11.4 +========================= + +tarfile +------- + +* The extraction methods in :mod:`tarfile`, and :func:`shutil.unpack_archive`, + have a new a *filter* argument that allows limiting tar features than may be + surprising or dangerous, such as creating files outside the destination + directory. + See :ref:`tarfile-extraction-filter` for details. + In Python 3.12, use without the *filter* argument will show a + :exc:`DeprecationWarning`. + In Python 3.14, the default will switch to ``'data'``. + (Contributed by Petr Viktorin in :pep:`706`.) + + +Notable changes in 3.11.5 +========================= + +OpenSSL +------- + +* Windows builds and macOS installers from python.org now use OpenSSL 3.0. + + .. _libb2: https://www.blake2.net/ From f7c05d7ad3075a1dbeed86b6b12903032e4afba6 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Mon, 29 Jan 2024 02:05:29 +0300 Subject: [PATCH 072/263] gh-55664: Add warning when creating a type using a namespace dictionary with non-string keys. (GH-105338) Co-authored-by: Daniel Urban --- Lib/test/test_descr.py | 17 ++++++++++++++++- ...023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst | 1 + Objects/typeobject.c | 11 +++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index fd0af9b30a0a71a..beeab6cb7f254c1 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4734,6 +4734,20 @@ class X(object): with self.assertRaises(AttributeError): del X.__abstractmethods__ + def test_gh55664(self): + # gh-55664: issue a warning when the + # __dict__ of a class contains non-string keys + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = type('MyClass', (), {1: 2}) + + class meta(type): + def __new__(mcls, name, bases, ns): + ns[1] = 2 + return super().__new__(mcls, name, bases, ns) + + with self.assertWarnsRegex(RuntimeWarning, 'MyClass'): + MyClass = meta('MyClass', (), {}) + def test_proxy_call(self): class FakeStr: __class__ = str @@ -5151,7 +5165,8 @@ class Base2(object): mykey = 'from Base2' mykey2 = 'from Base2' - X = type('X', (Base,), {MyKey(): 5}) + with self.assertWarnsRegex(RuntimeWarning, 'X'): + X = type('X', (Base,), {MyKey(): 5}) # mykey is read from Base self.assertEqual(X.mykey, 'from Base') # mykey2 is read from Base2 because MyKey.__eq__ has set __bases__ diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst b/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst new file mode 100644 index 000000000000000..438be9854966501 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-06-06-19-09-00.gh-issue-55664.vYYl0V.rst @@ -0,0 +1 @@ +Add warning when creating :class:`type` using a namespace dictionary with non-string keys. Patched by Daniel Urban and Furkan Onder. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 114cf21f95e744b..a850473cad813dd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3828,6 +3828,17 @@ type_new_impl(type_new_ctx *ctx) // Put the proper slots in place fixup_slot_dispatchers(type); + if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) { + if (PyErr_WarnFormat( + PyExc_RuntimeWarning, + 1, + "non-string key in the __dict__ of class %.200s", + type->tp_name) == -1) + { + goto error; + } + } + if (type_new_set_names(type) < 0) { goto error; } From f6d9e5926b6138994eaa60d1c36462e36105733d Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Sun, 28 Jan 2024 18:48:48 -0800 Subject: [PATCH 073/263] GH-113464: Add a JIT backend for tier 2 (GH-113465) Add an option (--enable-experimental-jit for configure-based builds or --experimental-jit for PCbuild-based ones) to build an *experimental* just-in-time compiler, based on copy-and-patch (https://fredrikbk.com/publications/copy-and-patch.pdf). See Tools/jit/README.md for more information on how to install the required build-time tooling. --- .github/workflows/jit.yml | 112 +++++ .github/workflows/mypy.yml | 2 + .gitignore | 1 + Include/cpython/optimizer.h | 2 + Include/internal/pycore_jit.h | 25 ++ Include/internal/pycore_object.h | 4 +- Makefile.pre.in | 11 +- ...-12-24-03-25-28.gh-issue-113464.dvjQmA.rst | 4 + PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/build.bat | 3 + PCbuild/pythoncore.vcxproj | 3 + PCbuild/pythoncore.vcxproj.filters | 6 + PCbuild/regen.targets | 23 +- Python/ceval.c | 20 +- Python/jit.c | 369 ++++++++++++++++ Python/optimizer.c | 12 + Python/pylifecycle.c | 7 + Tools/jit/README.md | 46 ++ Tools/jit/_llvm.py | 99 +++++ Tools/jit/_schema.py | 99 +++++ Tools/jit/_stencils.py | 220 ++++++++++ Tools/jit/_targets.py | 394 ++++++++++++++++++ Tools/jit/_writer.py | 95 +++++ Tools/jit/build.py | 28 ++ Tools/jit/mypy.ini | 5 + Tools/jit/template.c | 98 +++++ configure | 31 ++ configure.ac | 20 + 29 files changed, 1738 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/jit.yml create mode 100644 Include/internal/pycore_jit.h create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst create mode 100644 Python/jit.c create mode 100644 Tools/jit/README.md create mode 100644 Tools/jit/_llvm.py create mode 100644 Tools/jit/_schema.py create mode 100644 Tools/jit/_stencils.py create mode 100644 Tools/jit/_targets.py create mode 100644 Tools/jit/_writer.py create mode 100644 Tools/jit/build.py create mode 100644 Tools/jit/mypy.ini create mode 100644 Tools/jit/template.c diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml new file mode 100644 index 000000000000000..e137fd21b0a0dd7 --- /dev/null +++ b/.github/workflows/jit.yml @@ -0,0 +1,112 @@ +name: JIT +on: + pull_request: + paths: '**jit**' + push: + paths: '**jit**' + workflow_dispatch: +jobs: + jit: + name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + target: + - i686-pc-windows-msvc/msvc + - x86_64-pc-windows-msvc/msvc + - x86_64-apple-darwin/clang + - x86_64-unknown-linux-gnu/gcc + - x86_64-unknown-linux-gnu/clang + - aarch64-unknown-linux-gnu/gcc + - aarch64-unknown-linux-gnu/clang + debug: + - true + - false + llvm: + - 16 + include: + - target: i686-pc-windows-msvc/msvc + architecture: Win32 + runner: windows-latest + compiler: msvc + - target: x86_64-pc-windows-msvc/msvc + architecture: x64 + runner: windows-latest + compiler: msvc + - target: x86_64-apple-darwin/clang + architecture: x86_64 + runner: macos-latest + compiler: clang + exclude: test_embed + - target: x86_64-unknown-linux-gnu/gcc + architecture: x86_64 + runner: ubuntu-latest + compiler: gcc + - target: x86_64-unknown-linux-gnu/clang + architecture: x86_64 + runner: ubuntu-latest + compiler: clang + - target: aarch64-unknown-linux-gnu/gcc + architecture: aarch64 + runner: ubuntu-latest + compiler: gcc + # These fail because of emulation, not because of the JIT: + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + - target: aarch64-unknown-linux-gnu/clang + architecture: aarch64 + runner: ubuntu-latest + compiler: clang + # These fail because of emulation, not because of the JIT: + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + env: + CC: ${{ matrix.compiler }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Windows + if: runner.os == 'Windows' + run: | + choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }} + ./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '--pgo' }} -p ${{ matrix.architecture }} + ./PCbuild/rt.bat ${{ matrix.debug && '-d' }} -p ${{ matrix.architecture }} -q --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + + - name: macOS + if: runner.os == 'macOS' + run: | + brew install llvm@${{ matrix.llvm }} + export SDKROOT="$(xcrun --show-sdk-path)" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} + make all --jobs 3 + ./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + + - name: Native Linux + if: runner.os == 'Linux' && matrix.architecture == 'x86_64' + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} + make all --jobs 4 + ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + - name: Emulated Linux + if: runner.os == 'Linux' && matrix.architecture != 'x86_64' + run: | + sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }} + export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH" + ./configure --prefix="$(pwd)/../build" + make install --jobs 4 + make clean --jobs 4 + export HOST=${{ matrix.architecture }}-linux-gnu + sudo apt install --yes "gcc-$HOST" qemu-user + ${{ !matrix.debug && matrix.compiler == 'clang' && './configure --enable-optimizations' || '' }} + ${{ !matrix.debug && matrix.compiler == 'clang' && 'make profile-run-stamp --jobs 4' || '' }} + export CC="${{ matrix.compiler == 'clang' && 'clang --target=$HOST' || '$HOST-gcc' }}" + export CPP="$CC --preprocess" + export HOSTRUNNER=qemu-${{ matrix.architecture }} + export QEMU_LD_PREFIX="/usr/$HOST" + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes + make all --jobs 4 + ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 11928e72b9b43a1..b766785de405d2e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -12,6 +12,7 @@ on: - "Tools/build/generate_sbom.py" - "Tools/cases_generator/**" - "Tools/clinic/**" + - "Tools/jit/**" - "Tools/peg_generator/**" - "Tools/requirements-dev.txt" - "Tools/wasm/**" @@ -38,6 +39,7 @@ jobs: "Tools/build/", "Tools/cases_generator", "Tools/clinic", + "Tools/jit", "Tools/peg_generator", "Tools/wasm", ] diff --git a/.gitignore b/.gitignore index c424a894c2a6e09..18eb2a9f0632ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ Tools/unicode/data/ # hendrikmuhs/ccache-action@v1 /.ccache /cross-build/ +/jit_stencils.h /platform /profile-clean-stamp /profile-run-stamp diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index 96e829f8fbe97db..ecf3cae4cbc3f10 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -39,6 +39,8 @@ typedef struct { typedef struct _PyExecutorObject { PyObject_VAR_HEAD _PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */ + void *jit_code; + size_t jit_size; _PyUOpInstruction trace[1]; } _PyExecutorObject; diff --git a/Include/internal/pycore_jit.h b/Include/internal/pycore_jit.h new file mode 100644 index 000000000000000..0b71eb6f758ac62 --- /dev/null +++ b/Include/internal/pycore_jit.h @@ -0,0 +1,25 @@ +#ifndef Py_INTERNAL_JIT_H +#define Py_INTERNAL_JIT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef _Py_JIT + +typedef _Py_CODEUNIT *(*jit_func)(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState *tstate); + +int _PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length); +void _PyJIT_Free(_PyExecutorObject *executor); + +#endif // _Py_JIT + +#ifdef __cplusplus +} +#endif + +#endif // !Py_INTERNAL_JIT_H diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 4e52ffc77c5956b..e32ea2f528940ae 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -178,7 +178,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) } _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); + _Py_DEC_REFTOTAL(PyInterpreterState_Get()); #endif if (--op->ob_refcnt != 0) { assert(op->ob_refcnt > 0); @@ -199,7 +199,7 @@ _Py_DECREF_NO_DEALLOC(PyObject *op) } _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); + _Py_DEC_REFTOTAL(PyInterpreterState_Get()); #endif op->ob_refcnt--; #ifdef Py_DEBUG diff --git a/Makefile.pre.in b/Makefile.pre.in index 37a8b06987c7107..fff3d3c4914e7a7 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -433,6 +433,7 @@ PYTHON_OBJS= \ Python/initconfig.o \ Python/instrumentation.o \ Python/intrinsics.o \ + Python/jit.o \ Python/legacy_tracing.o \ Python/lock.o \ Python/marshal.o \ @@ -1365,7 +1366,7 @@ regen-unicodedata: regen-all: regen-cases regen-typeslots \ regen-token regen-ast regen-keyword regen-sre regen-frozen \ regen-pegen-metaparser regen-pegen regen-test-frozenmain \ - regen-test-levenshtein regen-global-objects regen-sbom + regen-test-levenshtein regen-global-objects regen-sbom regen-jit @echo @echo "Note: make regen-stdlib-module-names, make regen-limited-abi, " @echo "make regen-configure and make regen-unicodedata should be run manually" @@ -1846,6 +1847,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_initconfig.h \ $(srcdir)/Include/internal/pycore_interp.h \ $(srcdir)/Include/internal/pycore_intrinsics.h \ + $(srcdir)/Include/internal/pycore_jit.h \ $(srcdir)/Include/internal/pycore_list.h \ $(srcdir)/Include/internal/pycore_llist.h \ $(srcdir)/Include/internal/pycore_lock.h \ @@ -2641,6 +2643,12 @@ config.status: $(srcdir)/configure Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< +Python/jit.o: regen-jit + +.PHONY: regen-jit +regen-jit: + @REGEN_JIT_COMMAND@ + # Some make's put the object file in the current directory .c.o: $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< @@ -2733,6 +2741,7 @@ clean-retain-profile: pycremoval -rm -f Python/deepfreeze/*.[co] -rm -f Python/frozen_modules/*.h -rm -f Python/frozen_modules/MANIFEST + -rm -f jit_stencils.h -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' -rm -f Include/pydtrace_probes.h -rm -f profile-gen-stamp diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst new file mode 100644 index 000000000000000..bdee4d645f61c83 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-24-03-25-28.gh-issue-113464.dvjQmA.rst @@ -0,0 +1,4 @@ +Add an option (``--enable-experimental-jit`` for ``configure``-based builds +or ``--experimental-jit`` for ``PCbuild``-based ones) to build an +*experimental* just-in-time compiler, based on `copy-and-patch +`_ diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index dde801fc0fd525e..35788ec4503e8f6 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -224,6 +224,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 90ccb954b424bc1..7a44179e3561059 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -250,6 +250,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/build.bat b/PCbuild/build.bat index e61267b5852a8f2..83b50db44670337 100644 --- a/PCbuild/build.bat +++ b/PCbuild/build.bat @@ -36,6 +36,7 @@ echo. overrides -c and -d echo. --disable-gil Enable experimental support for running without the GIL. echo. --test-marker Enable the test marker within the build. echo. --regen Regenerate all opcodes, grammar and tokens. +echo. --experimental-jit Enable the experimental just-in-time compiler. echo. echo.Available flags to avoid building certain modules. echo.These flags have no effect if '-e' is not given: @@ -85,6 +86,7 @@ if "%~1"=="--disable-gil" (set UseDisableGil=true) & shift & goto CheckOpts if "%~1"=="--test-marker" (set UseTestMarker=true) & shift & goto CheckOpts if "%~1"=="-V" shift & goto Version if "%~1"=="--regen" (set Regen=true) & shift & goto CheckOpts +if "%~1"=="--experimental-jit" (set UseJIT=true) & shift & goto CheckOpts rem These use the actual property names used by MSBuild. We could just let rem them in through the environment, but we specify them on the command line rem anyway for visibility so set defaults after this @@ -176,6 +178,7 @@ echo on /p:IncludeSSL=%IncludeSSL% /p:IncludeTkinter=%IncludeTkinter%^ /p:DisableGil=%UseDisableGil%^ /p:UseTestMarker=%UseTestMarker% %GITProperty%^ + /p:UseJIT=%UseJIT%^ %1 %2 %3 %4 %5 %6 %7 %8 %9 @echo off diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index e0b9fc137457a08..e1ff97659659eea 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -104,6 +104,7 @@ $(zlibDir);%(AdditionalIncludeDirectories) _USRDLL;Py_BUILD_CORE;Py_BUILD_CORE_BUILTIN;Py_ENABLE_SHARED;MS_DLL_ID="$(SysWinVer)";%(PreprocessorDefinitions) _Py_HAVE_ZLIB;%(PreprocessorDefinitions) + _Py_JIT;%(PreprocessorDefinitions) version.lib;ws2_32.lib;pathcch.lib;bcrypt.lib;%(AdditionalDependencies) @@ -247,6 +248,7 @@ + @@ -585,6 +587,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index fd79436f5add970..4c55f23006b2f0c 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -669,6 +669,9 @@ Include\cpython + + Include\internal + Include\internal @@ -1337,6 +1340,9 @@ Source Files + + Python + Source Files diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index cc9469c7ddd726b..a90620d6ca8b7d1 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -28,6 +28,9 @@ <_KeywordSources Include="$(PySourcePath)Grammar\python.gram;$(PySourcePath)Grammar\Tokens" /> <_KeywordOutputs Include="$(PySourcePath)Lib\keyword.py" /> + + <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/> + <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/> @@ -76,10 +79,28 @@ + + + + aarch64-pc-windows-msvc + i686-pc-windows-msvc + x86_64-pc-windows-msvc + $(JITArgs) --debug + + + - + + + diff --git a/Python/ceval.c b/Python/ceval.c index 49388cd20377c0c..4f2080090861914 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -11,6 +11,7 @@ #include "pycore_function.h" #include "pycore_instruments.h" #include "pycore_intrinsics.h" +#include "pycore_jit.h" #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_moduleobject.h" // PyModuleObject #include "pycore_object.h" // _PyObject_GC_TRACK() @@ -955,9 +956,24 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int -// The Tier 2 interpreter is also here! +// Tier 2 is also here! enter_tier_two: +#ifdef _Py_JIT + + ; // ;) + jit_func jitted = current_executor->jit_code; + next_instr = jitted(frame, stack_pointer, tstate); + frame = tstate->current_frame; + Py_DECREF(current_executor); + if (next_instr == NULL) { + goto resume_with_error; + } + stack_pointer = _PyFrame_GetStackPointer(frame); + DISPATCH(); + +#else + #undef LOAD_IP #define LOAD_IP(UNUSED) (void)0 @@ -1073,6 +1089,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int Py_DECREF(current_executor); DISPATCH(); +#endif // _Py_JIT + } #if defined(__GNUC__) # pragma GCC diagnostic pop diff --git a/Python/jit.c b/Python/jit.c new file mode 100644 index 000000000000000..22949c082da05a2 --- /dev/null +++ b/Python/jit.c @@ -0,0 +1,369 @@ +#ifdef _Py_JIT + +#include "Python.h" + +#include "pycore_abstract.h" +#include "pycore_call.h" +#include "pycore_ceval.h" +#include "pycore_dict.h" +#include "pycore_intrinsics.h" +#include "pycore_long.h" +#include "pycore_opcode_metadata.h" +#include "pycore_opcode_utils.h" +#include "pycore_optimizer.h" +#include "pycore_pyerrors.h" +#include "pycore_setobject.h" +#include "pycore_sliceobject.h" +#include "pycore_jit.h" + +#include "jit_stencils.h" + +// Memory management stuff: //////////////////////////////////////////////////// + +#ifndef MS_WINDOWS + #include +#endif + +static size_t +get_page_size(void) +{ +#ifdef MS_WINDOWS + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + return sysconf(_SC_PAGESIZE); +#endif +} + +static void +jit_error(const char *message) +{ +#ifdef MS_WINDOWS + int hint = GetLastError(); +#else + int hint = errno; +#endif + PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); +} + +static char * +jit_alloc(size_t size) +{ + assert(size); + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + int flags = MEM_COMMIT | MEM_RESERVE; + char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); + int failed = memory == NULL; +#else + int flags = MAP_ANONYMOUS | MAP_PRIVATE; + char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + int failed = memory == MAP_FAILED; +#endif + if (failed) { + jit_error("unable to allocate memory"); + return NULL; + } + return memory; +} + +static int +jit_free(char *memory, size_t size) +{ + assert(size); + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + int failed = !VirtualFree(memory, 0, MEM_RELEASE); +#else + int failed = munmap(memory, size); +#endif + if (failed) { + jit_error("unable to free memory"); + return -1; + } + return 0; +} + +static int +mark_executable(char *memory, size_t size) +{ + if (size == 0) { + return 0; + } + assert(size % get_page_size() == 0); + // Do NOT ever leave the memory writable! Also, don't forget to flush the + // i-cache (I cannot begin to tell you how horrible that is to debug): +#ifdef MS_WINDOWS + if (!FlushInstructionCache(GetCurrentProcess(), memory, size)) { + jit_error("unable to flush instruction cache"); + return -1; + } + int old; + int failed = !VirtualProtect(memory, size, PAGE_EXECUTE_READ, &old); +#else + __builtin___clear_cache((char *)memory, (char *)memory + size); + int failed = mprotect(memory, size, PROT_EXEC | PROT_READ); +#endif + if (failed) { + jit_error("unable to protect executable memory"); + return -1; + } + return 0; +} + +static int +mark_readable(char *memory, size_t size) +{ + if (size == 0) { + return 0; + } + assert(size % get_page_size() == 0); +#ifdef MS_WINDOWS + DWORD old; + int failed = !VirtualProtect(memory, size, PAGE_READONLY, &old); +#else + int failed = mprotect(memory, size, PROT_READ); +#endif + if (failed) { + jit_error("unable to protect readable memory"); + return -1; + } + return 0; +} + +// JIT compiler stuff: ///////////////////////////////////////////////////////// + +// Warning! AArch64 requires you to get your hands dirty. These are your gloves: + +// value[value_start : value_start + len] +static uint32_t +get_bits(uint64_t value, uint8_t value_start, uint8_t width) +{ + assert(width <= 32); + return (value >> value_start) & ((1ULL << width) - 1); +} + +// *loc[loc_start : loc_start + width] = value[value_start : value_start + width] +static void +set_bits(uint32_t *loc, uint8_t loc_start, uint64_t value, uint8_t value_start, + uint8_t width) +{ + assert(loc_start + width <= 32); + // Clear the bits we're about to patch: + *loc &= ~(((1ULL << width) - 1) << loc_start); + assert(get_bits(*loc, loc_start, width) == 0); + // Patch the bits: + *loc |= get_bits(value, value_start, width) << loc_start; + assert(get_bits(*loc, loc_start, width) == get_bits(value, value_start, width)); +} + +// See https://developer.arm.com/documentation/ddi0602/2023-09/Base-Instructions +// for instruction encodings: +#define IS_AARCH64_ADD_OR_SUB(I) (((I) & 0x11C00000) == 0x11000000) +#define IS_AARCH64_ADRP(I) (((I) & 0x9F000000) == 0x90000000) +#define IS_AARCH64_BRANCH(I) (((I) & 0x7C000000) == 0x14000000) +#define IS_AARCH64_LDR_OR_STR(I) (((I) & 0x3B000000) == 0x39000000) +#define IS_AARCH64_MOV(I) (((I) & 0x9F800000) == 0x92800000) + +// Fill all of stencil's holes in the memory pointed to by base, using the +// values in patches. +static void +patch(char *base, const Stencil *stencil, uint64_t *patches) +{ + for (uint64_t i = 0; i < stencil->holes_size; i++) { + const Hole *hole = &stencil->holes[i]; + void *location = base + hole->offset; + uint64_t value = patches[hole->value] + (uint64_t)hole->symbol + hole->addend; + uint32_t *loc32 = (uint32_t *)location; + uint64_t *loc64 = (uint64_t *)location; + // LLD is a great reference for performing relocations... just keep in + // mind that Tools/jit/build.py does filtering and preprocessing for us! + // Here's a good place to start for each platform: + // - aarch64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.cpp + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.h + // - aarch64-unknown-linux-gnu: + // - https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/AArch64.cpp + // - i686-pc-windows-msvc: + // - https://github.com/llvm/llvm-project/blob/main/lld/COFF/Chunks.cpp + // - x86_64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/X86_64.cpp + // - x86_64-pc-windows-msvc: + // - https://github.com/llvm/llvm-project/blob/main/lld/COFF/Chunks.cpp + // - x86_64-unknown-linux-gnu: + // - https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/X86_64.cpp + switch (hole->kind) { + case HoleKind_IMAGE_REL_I386_DIR32: + // 32-bit absolute address. + // Check that we're not out of range of 32 unsigned bits: + assert(value < (1ULL << 32)); + *loc32 = (uint32_t)value; + continue; + case HoleKind_ARM64_RELOC_UNSIGNED: + case HoleKind_IMAGE_REL_AMD64_ADDR64: + case HoleKind_R_AARCH64_ABS64: + case HoleKind_X86_64_RELOC_UNSIGNED: + case HoleKind_R_X86_64_64: + // 64-bit absolute address. + *loc64 = value; + continue; + case HoleKind_R_AARCH64_CALL26: + case HoleKind_R_AARCH64_JUMP26: + // 28-bit relative branch. + assert(IS_AARCH64_BRANCH(*loc32)); + value -= (uint64_t)location; + // Check that we're not out of range of 28 signed bits: + assert((int64_t)value >= -(1 << 27)); + assert((int64_t)value < (1 << 27)); + // Since instructions are 4-byte aligned, only use 26 bits: + assert(get_bits(value, 0, 2) == 0); + set_bits(loc32, 0, value, 2, 26); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G0_NC: + // 16-bit low part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 0 of 3"): + assert(get_bits(*loc32, 21, 2) == 0); + set_bits(loc32, 5, value, 0, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G1_NC: + // 16-bit middle-low part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 1 of 3"): + assert(get_bits(*loc32, 21, 2) == 1); + set_bits(loc32, 5, value, 16, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G2_NC: + // 16-bit middle-high part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 2 of 3"): + assert(get_bits(*loc32, 21, 2) == 2); + set_bits(loc32, 5, value, 32, 16); + continue; + case HoleKind_R_AARCH64_MOVW_UABS_G3: + // 16-bit high part of an absolute address. + assert(IS_AARCH64_MOV(*loc32)); + // Check the implicit shift (this is "part 3 of 3"): + assert(get_bits(*loc32, 21, 2) == 3); + set_bits(loc32, 5, value, 48, 16); + continue; + case HoleKind_ARM64_RELOC_GOT_LOAD_PAGE21: + // 21-bit count of pages between this page and an absolute address's + // page... I know, I know, it's weird. Pairs nicely with + // ARM64_RELOC_GOT_LOAD_PAGEOFF12 (below). + assert(IS_AARCH64_ADRP(*loc32)); + // Number of pages between this page and the value's page: + value = (value >> 12) - ((uint64_t)location >> 12); + // Check that we're not out of range of 21 signed bits: + assert((int64_t)value >= -(1 << 20)); + assert((int64_t)value < (1 << 20)); + // value[0:2] goes in loc[29:31]: + set_bits(loc32, 29, value, 0, 2); + // value[2:21] goes in loc[5:26]: + set_bits(loc32, 5, value, 2, 19); + continue; + case HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12: + // 12-bit low part of an absolute address. Pairs nicely with + // ARM64_RELOC_GOT_LOAD_PAGE21 (above). + assert(IS_AARCH64_LDR_OR_STR(*loc32) || IS_AARCH64_ADD_OR_SUB(*loc32)); + // There might be an implicit shift encoded in the instruction: + uint8_t shift = 0; + if (IS_AARCH64_LDR_OR_STR(*loc32)) { + shift = (uint8_t)get_bits(*loc32, 30, 2); + // If both of these are set, the shift is supposed to be 4. + // That's pretty weird, and it's never actually been observed... + assert(get_bits(*loc32, 23, 1) == 0 || get_bits(*loc32, 26, 1) == 0); + } + value = get_bits(value, 0, 12); + assert(get_bits(value, 0, shift) == 0); + set_bits(loc32, 10, value, shift, 12); + continue; + } + Py_UNREACHABLE(); + } +} + +static void +copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) +{ + memcpy(base, stencil->body, stencil->body_size); + patch(base, stencil, patches); +} + +static void +emit(const StencilGroup *group, uint64_t patches[]) +{ + copy_and_patch((char *)patches[HoleValue_CODE], &group->code, patches); + copy_and_patch((char *)patches[HoleValue_DATA], &group->data, patches); +} + +// Compiles executor in-place. Don't forget to call _PyJIT_Free later! +int +_PyJIT_Compile(_PyExecutorObject *executor, _PyUOpInstruction *trace, size_t length) +{ + // Loop once to find the total compiled size: + size_t code_size = 0; + size_t data_size = 0; + for (size_t i = 0; i < length; i++) { + _PyUOpInstruction *instruction = &trace[i]; + const StencilGroup *group = &stencil_groups[instruction->opcode]; + code_size += group->code.body_size; + data_size += group->data.body_size; + } + // Round up to the nearest page (code and data need separate pages): + size_t page_size = get_page_size(); + assert((page_size & (page_size - 1)) == 0); + code_size += page_size - (code_size & (page_size - 1)); + data_size += page_size - (data_size & (page_size - 1)); + char *memory = jit_alloc(code_size + data_size); + if (memory == NULL) { + return -1; + } + // Loop again to emit the code: + char *code = memory; + char *data = memory + code_size; + for (size_t i = 0; i < length; i++) { + _PyUOpInstruction *instruction = &trace[i]; + const StencilGroup *group = &stencil_groups[instruction->opcode]; + // Think of patches as a dictionary mapping HoleValue to uint64_t: + uint64_t patches[] = GET_PATCHES(); + patches[HoleValue_CODE] = (uint64_t)code; + patches[HoleValue_CONTINUE] = (uint64_t)code + group->code.body_size; + patches[HoleValue_DATA] = (uint64_t)data; + patches[HoleValue_EXECUTOR] = (uint64_t)executor; + patches[HoleValue_OPARG] = instruction->oparg; + patches[HoleValue_OPERAND] = instruction->operand; + patches[HoleValue_TARGET] = instruction->target; + patches[HoleValue_TOP] = (uint64_t)memory; + patches[HoleValue_ZERO] = 0; + emit(group, patches); + code += group->code.body_size; + data += group->data.body_size; + } + if (mark_executable(memory, code_size) || + mark_readable(memory + code_size, data_size)) + { + jit_free(memory, code_size + data_size); + return -1; + } + executor->jit_code = memory; + executor->jit_size = code_size + data_size; + return 0; +} + +void +_PyJIT_Free(_PyExecutorObject *executor) +{ + char *memory = (char *)executor->jit_code; + size_t size = executor->jit_size; + if (memory) { + executor->jit_code = NULL; + executor->jit_size = 0; + if (jit_free(memory, size)) { + PyErr_WriteUnraisable(NULL); + } + } +} + +#endif // _Py_JIT diff --git a/Python/optimizer.c b/Python/optimizer.c index db615068ff517f6..0d04b09fef1e846 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -7,6 +7,7 @@ #include "pycore_optimizer.h" // _Py_uop_analyze_and_optimize() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_uop_ids.h" +#include "pycore_jit.h" #include "cpython/optimizer.h" #include #include @@ -227,6 +228,9 @@ static PyMethodDef executor_methods[] = { static void uop_dealloc(_PyExecutorObject *self) { _Py_ExecutorClear(self); +#ifdef _Py_JIT + _PyJIT_Free(self); +#endif PyObject_Free(self); } @@ -789,6 +793,14 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) executor->trace[i].operand); } } +#endif +#ifdef _Py_JIT + executor->jit_code = NULL; + executor->jit_size = 0; + if (_PyJIT_Compile(executor, executor->trace, Py_SIZE(executor))) { + Py_DECREF(executor); + return NULL; + } #endif return executor; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index fff64dd63d6b219..372f60602375b65 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1240,12 +1240,19 @@ init_interp_main(PyThreadState *tstate) // Turn on experimental tier 2 (uops-based) optimizer if (is_main_interp) { +#ifndef _Py_JIT + // No JIT, maybe use the tier two interpreter: char *envvar = Py_GETENV("PYTHON_UOPS"); int enabled = envvar != NULL && *envvar > '0'; if (_Py_get_xoption(&config->xoptions, L"uops") != NULL) { enabled = 1; } if (enabled) { +#else + // Always enable tier two for JIT builds (ignoring the environment + // variable and command-line option above): + if (true) { +#endif PyObject *opt = PyUnstable_Optimizer_NewUOpOptimizer(); if (opt == NULL) { return _PyStatus_ERR("can't initialize optimizer"); diff --git a/Tools/jit/README.md b/Tools/jit/README.md new file mode 100644 index 000000000000000..04a6c0780bf9727 --- /dev/null +++ b/Tools/jit/README.md @@ -0,0 +1,46 @@ +The JIT Compiler +================ + +This version of CPython can be built with an experimental just-in-time compiler. While most everything you already know about building and using CPython is unchanged, you will probably need to install a compatible version of LLVM first. + +## Installing LLVM + +The JIT compiler does not require end users to install any third-party dependencies, but part of it must be *built* using LLVM[^why-llvm]. You are *not* required to build the rest of CPython using LLVM, or even the same version of LLVM (in fact, this is uncommon). + +LLVM version 16 is required. Both `clang` and `llvm-readobj` need to be installed and discoverable (version suffixes, like `clang-16`, are okay). It's highly recommended that you also have `llvm-objdump` available, since this allows the build script to dump human-readable assembly for the generated code. + +It's easy to install all of the required tools: + +### Linux + +Install LLVM 16 on Ubuntu/Debian: + +```sh +wget https://apt.llvm.org/llvm.sh +chmod +x llvm.sh +sudo ./llvm.sh 16 +``` + +### macOS + +Install LLVM 16 with [Homebrew](https://brew.sh): + +```sh +brew install llvm@16 +``` + +Homebrew won't add any of the tools to your `$PATH`. That's okay; the build script knows how to find them. + +### Windows + +Install LLVM 16 [by searching for it on LLVM's GitHub releases page](https://github.com/llvm/llvm-project/releases?q=16), clicking on "Assets", downloading the appropriate Windows installer for your platform (likely the file ending with `-win64.exe`), and running it. **When installing, be sure to select the option labeled "Add LLVM to the system PATH".** + +## Building + +For `PCbuild`-based builds, pass the new `--experimental-jit` option to `build.bat`. + +For all other builds, pass the new `--enable-experimental-jit` option to `configure`. + +Otherwise, just configure and build as you normally would. Cross-compiling "just works", since the JIT is built for the host platform. + +[^why-llvm]: Clang is specifically needed because it's the only C compiler with support for guaranteed tail calls (`musttail`), which are required by CPython's continuation-passing-style approach to JIT compilation. Since LLVM also includes other functionalities we need (namely, object file parsing and disassembly), it's convenient to only support one toolchain at this time. diff --git a/Tools/jit/_llvm.py b/Tools/jit/_llvm.py new file mode 100644 index 000000000000000..603bbef59ba2e63 --- /dev/null +++ b/Tools/jit/_llvm.py @@ -0,0 +1,99 @@ +"""Utilities for invoking LLVM tools.""" +import asyncio +import functools +import os +import re +import shlex +import subprocess +import typing + +_LLVM_VERSION = 16 +_LLVM_VERSION_PATTERN = re.compile(rf"version\s+{_LLVM_VERSION}\.\d+\.\d+\s+") + +_P = typing.ParamSpec("_P") +_R = typing.TypeVar("_R") +_C = typing.Callable[_P, typing.Awaitable[_R]] + + +def _async_cache(f: _C[_P, _R]) -> _C[_P, _R]: + cache = {} + lock = asyncio.Lock() + + @functools.wraps(f) + async def wrapper( + *args: _P.args, **kwargs: _P.kwargs # pylint: disable = no-member + ) -> _R: + async with lock: + if args not in cache: + cache[args] = await f(*args, **kwargs) + return cache[args] + + return wrapper + + +_CORES = asyncio.BoundedSemaphore(os.cpu_count() or 1) + + +async def _run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str | None: + command = [tool, *args] + async with _CORES: + if echo: + print(shlex.join(command)) + try: + process = await asyncio.create_subprocess_exec( + *command, stdout=subprocess.PIPE + ) + except FileNotFoundError: + return None + out, _ = await process.communicate() + if process.returncode: + raise RuntimeError(f"{tool} exited with return code {process.returncode}") + return out.decode() + + +@_async_cache +async def _check_tool_version(name: str, *, echo: bool = False) -> bool: + output = await _run(name, ["--version"], echo=echo) + return bool(output and _LLVM_VERSION_PATTERN.search(output)) + + +@_async_cache +async def _get_brew_llvm_prefix(*, echo: bool = False) -> str | None: + output = await _run("brew", ["--prefix", f"llvm@{_LLVM_VERSION}"], echo=echo) + return output and output.removesuffix("\n") + + +@_async_cache +async def _find_tool(tool: str, *, echo: bool = False) -> str | None: + # Unversioned executables: + path = tool + if await _check_tool_version(path, echo=echo): + return path + # Versioned executables: + path = f"{tool}-{_LLVM_VERSION}" + if await _check_tool_version(path, echo=echo): + return path + # Homebrew-installed executables: + prefix = await _get_brew_llvm_prefix(echo=echo) + if prefix is not None: + path = os.path.join(prefix, "bin", tool) + if await _check_tool_version(path, echo=echo): + return path + # Nothing found: + return None + + +async def maybe_run( + tool: str, args: typing.Iterable[str], echo: bool = False +) -> str | None: + """Run an LLVM tool if it can be found. Otherwise, return None.""" + path = await _find_tool(tool, echo=echo) + return path and await _run(path, args, echo=echo) + + +async def run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str: + """Run an LLVM tool if it can be found. Otherwise, raise RuntimeError.""" + output = await maybe_run(tool, args, echo=echo) + if output is None: + raise RuntimeError(f"Can't find {tool}-{_LLVM_VERSION}!") + return output diff --git a/Tools/jit/_schema.py b/Tools/jit/_schema.py new file mode 100644 index 000000000000000..8eeb78e6cd69eee --- /dev/null +++ b/Tools/jit/_schema.py @@ -0,0 +1,99 @@ +"""Schema for the JSON produced by llvm-readobj --elf-output-style=JSON.""" +import typing + +HoleKind: typing.TypeAlias = typing.Literal[ + "ARM64_RELOC_GOT_LOAD_PAGE21", + "ARM64_RELOC_GOT_LOAD_PAGEOFF12", + "ARM64_RELOC_UNSIGNED", + "IMAGE_REL_AMD64_ADDR64", + "IMAGE_REL_I386_DIR32", + "R_AARCH64_ABS64", + "R_AARCH64_CALL26", + "R_AARCH64_JUMP26", + "R_AARCH64_MOVW_UABS_G0_NC", + "R_AARCH64_MOVW_UABS_G1_NC", + "R_AARCH64_MOVW_UABS_G2_NC", + "R_AARCH64_MOVW_UABS_G3", + "R_X86_64_64", + "X86_64_RELOC_UNSIGNED", +] + + +class COFFRelocation(typing.TypedDict): + """A COFF object file relocation record.""" + + Type: dict[typing.Literal["Value"], HoleKind] + Symbol: str + Offset: int + + +class ELFRelocation(typing.TypedDict): + """An ELF object file relocation record.""" + + Addend: int + Offset: int + Symbol: dict[typing.Literal["Value"], str] + Type: dict[typing.Literal["Value"], HoleKind] + + +class MachORelocation(typing.TypedDict): + """A Mach-O object file relocation record.""" + + Offset: int + Section: typing.NotRequired[dict[typing.Literal["Value"], str]] + Symbol: typing.NotRequired[dict[typing.Literal["Value"], str]] + Type: dict[typing.Literal["Value"], HoleKind] + + +class _COFFSymbol(typing.TypedDict): + Name: str + Value: int + + +class _ELFSymbol(typing.TypedDict): + Name: dict[typing.Literal["Value"], str] + Value: int + + +class _MachOSymbol(typing.TypedDict): + Name: dict[typing.Literal["Value"], str] + Value: int + + +class COFFSection(typing.TypedDict): + """A COFF object file section.""" + + Characteristics: dict[ + typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]] + ] + Number: int + RawDataSize: int + Relocations: list[dict[typing.Literal["Relocation"], COFFRelocation]] + SectionData: typing.NotRequired[dict[typing.Literal["Bytes"], list[int]]] + Symbols: list[dict[typing.Literal["Symbol"], _COFFSymbol]] + + +class ELFSection(typing.TypedDict): + """An ELF object file section.""" + + Flags: dict[typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]]] + Index: int + Info: int + Relocations: list[dict[typing.Literal["Relocation"], ELFRelocation]] + SectionData: dict[typing.Literal["Bytes"], list[int]] + Symbols: list[dict[typing.Literal["Symbol"], _ELFSymbol]] + Type: dict[typing.Literal["Value"], str] + + +class MachOSection(typing.TypedDict): + """A Mach-O object file section.""" + + Address: int + Attributes: dict[typing.Literal["Flags"], list[dict[typing.Literal["Name"], str]]] + Index: int + Name: dict[typing.Literal["Value"], str] + Relocations: typing.NotRequired[ + list[dict[typing.Literal["Relocation"], MachORelocation]] + ] + SectionData: typing.NotRequired[dict[typing.Literal["Bytes"], list[int]]] + Symbols: typing.NotRequired[list[dict[typing.Literal["Symbol"], _MachOSymbol]]] diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py new file mode 100644 index 000000000000000..71c678e04fbfd5a --- /dev/null +++ b/Tools/jit/_stencils.py @@ -0,0 +1,220 @@ +"""Core data structures for compiled code templates.""" +import dataclasses +import enum +import sys + +import _schema + + +@enum.unique +class HoleValue(enum.Enum): + """ + Different "base" values that can be patched into holes (usually combined with the + address of a symbol and/or an addend). + """ + + # The base address of the machine code for the current uop (exposed as _JIT_ENTRY): + CODE = enum.auto() + # The base address of the machine code for the next uop (exposed as _JIT_CONTINUE): + CONTINUE = enum.auto() + # The base address of the read-only data for this uop: + DATA = enum.auto() + # The address of the current executor (exposed as _JIT_EXECUTOR): + EXECUTOR = enum.auto() + # The base address of the "global" offset table located in the read-only data. + # Shouldn't be present in the final stencils, since these are all replaced with + # equivalent DATA values: + GOT = enum.auto() + # The current uop's oparg (exposed as _JIT_OPARG): + OPARG = enum.auto() + # The current uop's operand (exposed as _JIT_OPERAND): + OPERAND = enum.auto() + # The current uop's target (exposed as _JIT_TARGET): + TARGET = enum.auto() + # The base address of the machine code for the first uop (exposed as _JIT_TOP): + TOP = enum.auto() + # A hardcoded value of zero (used for symbol lookups): + ZERO = enum.auto() + + +@dataclasses.dataclass +class Hole: + """ + A "hole" in the stencil to be patched with a computed runtime value. + + Analogous to relocation records in an object file. + """ + + offset: int + kind: _schema.HoleKind + # Patch with this base value: + value: HoleValue + # ...plus the address of this symbol: + symbol: str | None + # ...plus this addend: + addend: int + # Convenience method: + replace = dataclasses.replace + + def as_c(self) -> str: + """Dump this hole as an initialization of a C Hole struct.""" + parts = [ + f"{self.offset:#x}", + f"HoleKind_{self.kind}", + f"HoleValue_{self.value.name}", + f"&{self.symbol}" if self.symbol else "NULL", + _format_addend(self.addend), + ] + return f"{{{', '.join(parts)}}}" + + +@dataclasses.dataclass +class Stencil: + """ + A contiguous block of machine code or data to be copied-and-patched. + + Analogous to a section or segment in an object file. + """ + + body: bytearray = dataclasses.field(default_factory=bytearray, init=False) + holes: list[Hole] = dataclasses.field(default_factory=list, init=False) + disassembly: list[str] = dataclasses.field(default_factory=list, init=False) + + def pad(self, alignment: int) -> None: + """Pad the stencil to the given alignment.""" + offset = len(self.body) + padding = -offset % alignment + self.disassembly.append(f"{offset:x}: {' '.join(['00'] * padding)}") + self.body.extend([0] * padding) + + def emit_aarch64_trampoline(self, hole: Hole) -> None: + """Even with the large code model, AArch64 Linux insists on 28-bit jumps.""" + base = len(self.body) + where = slice(hole.offset, hole.offset + 4) + instruction = int.from_bytes(self.body[where], sys.byteorder) + instruction &= 0xFC000000 + instruction |= ((base - hole.offset) >> 2) & 0x03FFFFFF + self.body[where] = instruction.to_bytes(4, sys.byteorder) + self.disassembly += [ + f"{base + 4 * 0: x}: d2800008 mov x8, #0x0", + f"{base + 4 * 0:016x}: R_AARCH64_MOVW_UABS_G0_NC {hole.symbol}", + f"{base + 4 * 1:x}: f2a00008 movk x8, #0x0, lsl #16", + f"{base + 4 * 1:016x}: R_AARCH64_MOVW_UABS_G1_NC {hole.symbol}", + f"{base + 4 * 2:x}: f2c00008 movk x8, #0x0, lsl #32", + f"{base + 4 * 2:016x}: R_AARCH64_MOVW_UABS_G2_NC {hole.symbol}", + f"{base + 4 * 3:x}: f2e00008 movk x8, #0x0, lsl #48", + f"{base + 4 * 3:016x}: R_AARCH64_MOVW_UABS_G3 {hole.symbol}", + f"{base + 4 * 4:x}: d61f0100 br x8", + ] + for code in [ + 0xD2800008.to_bytes(4, sys.byteorder), + 0xF2A00008.to_bytes(4, sys.byteorder), + 0xF2C00008.to_bytes(4, sys.byteorder), + 0xF2E00008.to_bytes(4, sys.byteorder), + 0xD61F0100.to_bytes(4, sys.byteorder), + ]: + self.body.extend(code) + for i, kind in enumerate( + [ + "R_AARCH64_MOVW_UABS_G0_NC", + "R_AARCH64_MOVW_UABS_G1_NC", + "R_AARCH64_MOVW_UABS_G2_NC", + "R_AARCH64_MOVW_UABS_G3", + ] + ): + self.holes.append(hole.replace(offset=base + 4 * i, kind=kind)) + + +@dataclasses.dataclass +class StencilGroup: + """ + Code and data corresponding to a given micro-opcode. + + Analogous to an entire object file. + """ + + code: Stencil = dataclasses.field(default_factory=Stencil, init=False) + data: Stencil = dataclasses.field(default_factory=Stencil, init=False) + symbols: dict[int | str, tuple[HoleValue, int]] = dataclasses.field( + default_factory=dict, init=False + ) + _got: dict[str, int] = dataclasses.field(default_factory=dict, init=False) + + def process_relocations(self, *, alignment: int = 1) -> None: + """Fix up all GOT and internal relocations for this stencil group.""" + self.code.pad(alignment) + self.data.pad(8) + for stencil in [self.code, self.data]: + holes = [] + for hole in stencil.holes: + if hole.value is HoleValue.GOT: + assert hole.symbol is not None + hole.value = HoleValue.DATA + hole.addend += self._global_offset_table_lookup(hole.symbol) + hole.symbol = None + elif hole.symbol in self.symbols: + hole.value, addend = self.symbols[hole.symbol] + hole.addend += addend + hole.symbol = None + elif ( + hole.kind in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26"} + and hole.value is HoleValue.ZERO + ): + self.code.emit_aarch64_trampoline(hole) + continue + holes.append(hole) + stencil.holes[:] = holes + self.code.pad(alignment) + self._emit_global_offset_table() + self.code.holes.sort(key=lambda hole: hole.offset) + self.data.holes.sort(key=lambda hole: hole.offset) + + def _global_offset_table_lookup(self, symbol: str) -> int: + return len(self.data.body) + self._got.setdefault(symbol, 8 * len(self._got)) + + def _emit_global_offset_table(self) -> None: + got = len(self.data.body) + for s, offset in self._got.items(): + if s in self.symbols: + value, addend = self.symbols[s] + symbol = None + else: + value, symbol = symbol_to_value(s) + addend = 0 + self.data.holes.append( + Hole(got + offset, "R_X86_64_64", value, symbol, addend) + ) + value_part = value.name if value is not HoleValue.ZERO else "" + if value_part and not symbol and not addend: + addend_part = "" + else: + addend_part = f"&{symbol}" if symbol else "" + addend_part += _format_addend(addend, signed=symbol is not None) + if value_part: + value_part += "+" + self.data.disassembly.append( + f"{len(self.data.body):x}: {value_part}{addend_part}" + ) + self.data.body.extend([0] * 8) + + +def symbol_to_value(symbol: str) -> tuple[HoleValue, str | None]: + """ + Convert a symbol name to a HoleValue and a symbol name. + + Some symbols (starting with "_JIT_") are special and are converted to their + own HoleValues. + """ + if symbol.startswith("_JIT_"): + try: + return HoleValue[symbol.removeprefix("_JIT_")], None + except KeyError: + pass + return HoleValue.ZERO, symbol + + +def _format_addend(addend: int, signed: bool = False) -> str: + addend %= 1 << 64 + if addend & (1 << 63): + addend -= 1 << 64 + return f"{addend:{'+#x' if signed else '#x'}}" diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py new file mode 100644 index 000000000000000..51b091eb2464131 --- /dev/null +++ b/Tools/jit/_targets.py @@ -0,0 +1,394 @@ +"""Target-specific code generation, parsing, and processing.""" +import asyncio +import dataclasses +import hashlib +import json +import os +import pathlib +import re +import sys +import tempfile +import typing + +import _llvm +import _schema +import _stencils +import _writer + +if sys.version_info < (3, 11): + raise RuntimeError("Building the JIT compiler requires Python 3.11 or newer!") + +TOOLS_JIT_BUILD = pathlib.Path(__file__).resolve() +TOOLS_JIT = TOOLS_JIT_BUILD.parent +TOOLS = TOOLS_JIT.parent +CPYTHON = TOOLS.parent +PYTHON_EXECUTOR_CASES_C_H = CPYTHON / "Python" / "executor_cases.c.h" +TOOLS_JIT_TEMPLATE_C = TOOLS_JIT / "template.c" + + +_S = typing.TypeVar("_S", _schema.COFFSection, _schema.ELFSection, _schema.MachOSection) +_R = typing.TypeVar( + "_R", _schema.COFFRelocation, _schema.ELFRelocation, _schema.MachORelocation +) + + +@dataclasses.dataclass +class _Target(typing.Generic[_S, _R]): + triple: str + _: dataclasses.KW_ONLY + alignment: int = 1 + prefix: str = "" + debug: bool = False + force: bool = False + verbose: bool = False + + def _compute_digest(self, out: pathlib.Path) -> str: + hasher = hashlib.sha256() + hasher.update(self.triple.encode()) + hasher.update(self.alignment.to_bytes()) + hasher.update(self.prefix.encode()) + # These dependencies are also reflected in _JITSources in regen.targets: + hasher.update(PYTHON_EXECUTOR_CASES_C_H.read_bytes()) + hasher.update((out / "pyconfig.h").read_bytes()) + for dirpath, _, filenames in sorted(os.walk(TOOLS_JIT)): + for filename in filenames: + hasher.update(pathlib.Path(dirpath, filename).read_bytes()) + return hasher.hexdigest() + + async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup: + group = _stencils.StencilGroup() + args = ["--disassemble", "--reloc", f"{path}"] + output = await _llvm.maybe_run("llvm-objdump", args, echo=self.verbose) + if output is not None: + group.code.disassembly.extend( + line.expandtabs().strip() + for line in output.splitlines() + if not line.isspace() + ) + args = [ + "--elf-output-style=JSON", + "--expand-relocs", + # "--pretty-print", + "--section-data", + "--section-relocations", + "--section-symbols", + "--sections", + f"{path}", + ] + output = await _llvm.run("llvm-readobj", args, echo=self.verbose) + # --elf-output-style=JSON is only *slightly* broken on Mach-O... + output = output.replace("PrivateExtern\n", "\n") + output = output.replace("Extern\n", "\n") + # ...and also COFF: + output = output[output.index("[", 1, None) :] + output = output[: output.rindex("]", None, -1) + 1] + sections: list[dict[typing.Literal["Section"], _S]] = json.loads(output) + for wrapped_section in sections: + self._handle_section(wrapped_section["Section"], group) + assert group.symbols["_JIT_ENTRY"] == (_stencils.HoleValue.CODE, 0) + if group.data.body: + line = f"0: {str(bytes(group.data.body)).removeprefix('b')}" + group.data.disassembly.append(line) + group.process_relocations() + return group + + def _handle_section(self, section: _S, group: _stencils.StencilGroup) -> None: + raise NotImplementedError(type(self)) + + def _handle_relocation( + self, base: int, relocation: _R, raw: bytes + ) -> _stencils.Hole: + raise NotImplementedError(type(self)) + + async def _compile( + self, opname: str, c: pathlib.Path, tempdir: pathlib.Path + ) -> _stencils.StencilGroup: + o = tempdir / f"{opname}.o" + args = [ + f"--target={self.triple}", + "-DPy_BUILD_CORE", + "-D_DEBUG" if self.debug else "-DNDEBUG", + f"-D_JIT_OPCODE={opname}", + "-D_PyJIT_ACTIVE", + "-D_Py_JIT", + "-I.", + f"-I{CPYTHON / 'Include'}", + f"-I{CPYTHON / 'Include' / 'internal'}", + f"-I{CPYTHON / 'Include' / 'internal' / 'mimalloc'}", + f"-I{CPYTHON / 'Python'}", + "-O3", + "-c", + "-fno-asynchronous-unwind-tables", + # SET_FUNCTION_ATTRIBUTE on 32-bit Windows debug builds: + "-fno-jump-tables", + # Position-independent code adds indirection to every load and jump: + "-fno-pic", + # Don't make calls to weird stack-smashing canaries: + "-fno-stack-protector", + # We have three options for code model: + # - "small": the default, assumes that code and data reside in the + # lowest 2GB of memory (128MB on aarch64) + # - "medium": assumes that code resides in the lowest 2GB of memory, + # and makes no assumptions about data (not available on aarch64) + # - "large": makes no assumptions about either code or data + "-mcmodel=large", + "-o", + f"{o}", + "-std=c11", + f"{c}", + ] + await _llvm.run("clang", args, echo=self.verbose) + return await self._parse(o) + + async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]: + generated_cases = PYTHON_EXECUTOR_CASES_C_H.read_text() + opnames = sorted(re.findall(r"\n {8}case (\w+): \{\n", generated_cases)) + tasks = [] + with tempfile.TemporaryDirectory() as tempdir: + work = pathlib.Path(tempdir).resolve() + async with asyncio.TaskGroup() as group: + for opname in opnames: + coro = self._compile(opname, TOOLS_JIT_TEMPLATE_C, work) + tasks.append(group.create_task(coro, name=opname)) + return {task.get_name(): task.result() for task in tasks} + + def build(self, out: pathlib.Path, *, comment: str = "") -> None: + """Build jit_stencils.h in the given directory.""" + digest = f"// {self._compute_digest(out)}\n" + jit_stencils = out / "jit_stencils.h" + if ( + not self.force + and jit_stencils.exists() + and jit_stencils.read_text().startswith(digest) + ): + return + stencil_groups = asyncio.run(self._build_stencils()) + with jit_stencils.open("w") as file: + file.write(digest) + if comment: + file.write(f"// {comment}\n") + file.write("") + for line in _writer.dump(stencil_groups): + file.write(f"{line}\n") + + +class _COFF( + _Target[_schema.COFFSection, _schema.COFFRelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.COFFSection, group: _stencils.StencilGroup + ) -> None: + flags = {flag["Name"] for flag in section["Characteristics"]["Flags"]} + if "SectionData" in section: + section_data_bytes = section["SectionData"]["Bytes"] + else: + # Zeroed BSS data, seen with printf debugging calls: + section_data_bytes = [0] * section["RawDataSize"] + if "IMAGE_SCN_MEM_EXECUTE" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + elif "IMAGE_SCN_MEM_READ" in flags: + value = _stencils.HoleValue.DATA + stencil = group.data + else: + return + base = len(stencil.body) + group.symbols[section["Number"]] = value, base + stencil.body.extend(section_data_bytes) + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = base + symbol["Value"] + name = symbol["Name"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + + def _handle_relocation( + self, base: int, relocation: _schema.COFFRelocation, raw: bytes + ) -> _stencils.Hole: + match relocation: + case { + "Offset": offset, + "Symbol": s, + "Type": {"Value": "IMAGE_REL_AMD64_ADDR64" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = int.from_bytes(raw[offset : offset + 8], "little") + case { + "Offset": offset, + "Symbol": s, + "Type": {"Value": "IMAGE_REL_I386_DIR32" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = int.from_bytes(raw[offset : offset + 4], "little") + case _: + raise NotImplementedError(relocation) + return _stencils.Hole(offset, kind, value, symbol, addend) + + +class _ELF( + _Target[_schema.ELFSection, _schema.ELFRelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.ELFSection, group: _stencils.StencilGroup + ) -> None: + section_type = section["Type"]["Value"] + flags = {flag["Name"] for flag in section["Flags"]["Flags"]} + if section_type == "SHT_RELA": + assert "SHF_INFO_LINK" in flags, flags + assert not section["Symbols"] + value, base = group.symbols[section["Info"]] + if value is _stencils.HoleValue.CODE: + stencil = group.code + else: + assert value is _stencils.HoleValue.DATA + stencil = group.data + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + elif section_type == "SHT_PROGBITS": + if "SHF_ALLOC" not in flags: + return + if "SHF_EXECINSTR" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + else: + value = _stencils.HoleValue.DATA + stencil = group.data + group.symbols[section["Index"]] = value, len(stencil.body) + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = len(stencil.body) + symbol["Value"] + name = symbol["Name"]["Value"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + stencil.body.extend(section["SectionData"]["Bytes"]) + assert not section["Relocations"] + else: + assert section_type in { + "SHT_GROUP", + "SHT_LLVM_ADDRSIG", + "SHT_NULL", + "SHT_STRTAB", + "SHT_SYMTAB", + }, section_type + + def _handle_relocation( + self, base: int, relocation: _schema.ELFRelocation, raw: bytes + ) -> _stencils.Hole: + match relocation: + case { + "Addend": addend, + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + case _: + raise NotImplementedError(relocation) + return _stencils.Hole(offset, kind, value, symbol, addend) + + +class _MachO( + _Target[_schema.MachOSection, _schema.MachORelocation] +): # pylint: disable = too-few-public-methods + def _handle_section( + self, section: _schema.MachOSection, group: _stencils.StencilGroup + ) -> None: + assert section["Address"] >= len(group.code.body) + assert "SectionData" in section + flags = {flag["Name"] for flag in section["Attributes"]["Flags"]} + name = section["Name"]["Value"] + name = name.removeprefix(self.prefix) + if "SomeInstructions" in flags: + value = _stencils.HoleValue.CODE + stencil = group.code + start_address = 0 + group.symbols[name] = value, section["Address"] - start_address + else: + value = _stencils.HoleValue.DATA + stencil = group.data + start_address = len(group.code.body) + group.symbols[name] = value, len(group.code.body) + base = section["Address"] - start_address + group.symbols[section["Index"]] = value, base + stencil.body.extend( + [0] * (section["Address"] - len(group.code.body) - len(group.data.body)) + ) + stencil.body.extend(section["SectionData"]["Bytes"]) + assert "Symbols" in section + for wrapped_symbol in section["Symbols"]: + symbol = wrapped_symbol["Symbol"] + offset = symbol["Value"] - start_address + name = symbol["Name"]["Value"] + name = name.removeprefix(self.prefix) + group.symbols[name] = value, offset + assert "Relocations" in section + for wrapped_relocation in section["Relocations"]: + relocation = wrapped_relocation["Relocation"] + hole = self._handle_relocation(base, relocation, stencil.body) + stencil.holes.append(hole) + + def _handle_relocation( + self, base: int, relocation: _schema.MachORelocation, raw: bytes + ) -> _stencils.Hole: + symbol: str | None + match relocation: + case { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "ARM64_RELOC_GOT_LOAD_PAGE21" + | "ARM64_RELOC_GOT_LOAD_PAGEOFF12" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s + addend = 0 + case { + "Offset": offset, + "Section": {"Value": s}, + "Type": {"Value": kind}, + } | { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = 0 + case _: + raise NotImplementedError(relocation) + # Turn Clang's weird __bzero calls into normal bzero calls: + if symbol == "__bzero": + symbol = "bzero" + return _stencils.Hole(offset, kind, value, symbol, addend) + + +def get_target(host: str) -> _COFF | _ELF | _MachO: + """Build a _Target for the given host "triple" and options.""" + if re.fullmatch(r"aarch64-apple-darwin.*", host): + return _MachO(host, alignment=8, prefix="_") + if re.fullmatch(r"aarch64-.*-linux-gnu", host): + return _ELF(host, alignment=8) + if re.fullmatch(r"i686-pc-windows-msvc", host): + return _COFF(host, prefix="_") + if re.fullmatch(r"x86_64-apple-darwin.*", host): + return _MachO(host, prefix="_") + if re.fullmatch(r"x86_64-pc-windows-msvc", host): + return _COFF(host) + if re.fullmatch(r"x86_64-.*-linux-gnu", host): + return _ELF(host) + raise ValueError(host) diff --git a/Tools/jit/_writer.py b/Tools/jit/_writer.py new file mode 100644 index 000000000000000..8a2a42e75cfb9b3 --- /dev/null +++ b/Tools/jit/_writer.py @@ -0,0 +1,95 @@ +"""Utilities for writing StencilGroups out to a C header file.""" +import typing + +import _schema +import _stencils + + +def _dump_header() -> typing.Iterator[str]: + yield "typedef enum {" + for kind in typing.get_args(_schema.HoleKind): + yield f" HoleKind_{kind}," + yield "} HoleKind;" + yield "" + yield "typedef enum {" + for value in _stencils.HoleValue: + yield f" HoleValue_{value.name}," + yield "} HoleValue;" + yield "" + yield "typedef struct {" + yield " const uint64_t offset;" + yield " const HoleKind kind;" + yield " const HoleValue value;" + yield " const void *symbol;" + yield " const uint64_t addend;" + yield "} Hole;" + yield "" + yield "typedef struct {" + yield " const size_t body_size;" + yield " const unsigned char * const body;" + yield " const size_t holes_size;" + yield " const Hole * const holes;" + yield "} Stencil;" + yield "" + yield "typedef struct {" + yield " const Stencil code;" + yield " const Stencil data;" + yield "} StencilGroup;" + yield "" + + +def _dump_footer(opnames: typing.Iterable[str]) -> typing.Iterator[str]: + yield "#define INIT_STENCIL(STENCIL) { \\" + yield " .body_size = Py_ARRAY_LENGTH(STENCIL##_body) - 1, \\" + yield " .body = STENCIL##_body, \\" + yield " .holes_size = Py_ARRAY_LENGTH(STENCIL##_holes) - 1, \\" + yield " .holes = STENCIL##_holes, \\" + yield "}" + yield "" + yield "#define INIT_STENCIL_GROUP(OP) { \\" + yield " .code = INIT_STENCIL(OP##_code), \\" + yield " .data = INIT_STENCIL(OP##_data), \\" + yield "}" + yield "" + yield "static const StencilGroup stencil_groups[512] = {" + for opname in opnames: + yield f" [{opname}] = INIT_STENCIL_GROUP({opname})," + yield "};" + yield "" + yield "#define GET_PATCHES() { \\" + for value in _stencils.HoleValue: + yield f" [HoleValue_{value.name}] = (uint64_t)0xBADBADBADBADBADB, \\" + yield "}" + + +def _dump_stencil(opname: str, group: _stencils.StencilGroup) -> typing.Iterator[str]: + yield f"// {opname}" + for part, stencil in [("code", group.code), ("data", group.data)]: + for line in stencil.disassembly: + yield f"// {line}" + if stencil.body: + size = len(stencil.body) + 1 + yield f"static const unsigned char {opname}_{part}_body[{size}] = {{" + for i in range(0, len(stencil.body), 8): + row = " ".join(f"{byte:#04x}," for byte in stencil.body[i : i + 8]) + yield f" {row}" + yield "};" + else: + yield f"static const unsigned char {opname}_{part}_body[1];" + if stencil.holes: + size = len(stencil.holes) + 1 + yield f"static const Hole {opname}_{part}_holes[{size}] = {{" + for hole in stencil.holes: + yield f" {hole.as_c()}," + yield "};" + else: + yield f"static const Hole {opname}_{part}_holes[1];" + yield "" + + +def dump(groups: dict[str, _stencils.StencilGroup]) -> typing.Iterator[str]: + """Yield a JIT compiler line-by-line as a C header file.""" + yield from _dump_header() + for opname, group in groups.items(): + yield from _dump_stencil(opname, group) + yield from _dump_footer(groups) diff --git a/Tools/jit/build.py b/Tools/jit/build.py new file mode 100644 index 000000000000000..4d4ace14ebf26c4 --- /dev/null +++ b/Tools/jit/build.py @@ -0,0 +1,28 @@ +"""Build an experimental just-in-time compiler for CPython.""" +import argparse +import pathlib +import shlex +import sys + +import _targets + +if __name__ == "__main__": + comment = f"$ {shlex.join([sys.executable] + sys.argv)}" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "target", type=_targets.get_target, help="a PEP 11 target triple to compile for" + ) + parser.add_argument( + "-d", "--debug", action="store_true", help="compile for a debug build of Python" + ) + parser.add_argument( + "-f", "--force", action="store_true", help="force the entire JIT to be rebuilt" + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="echo commands as they are run" + ) + args = parser.parse_args() + args.target.debug = args.debug + args.target.force = args.force + args.target.verbose = args.verbose + args.target.build(pathlib.Path.cwd(), comment=comment) diff --git a/Tools/jit/mypy.ini b/Tools/jit/mypy.ini new file mode 100644 index 000000000000000..768d0028516abd0 --- /dev/null +++ b/Tools/jit/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +files = Tools/jit +pretty = True +python_version = 3.11 +strict = True diff --git a/Tools/jit/template.c b/Tools/jit/template.c new file mode 100644 index 000000000000000..12303a550d8879e --- /dev/null +++ b/Tools/jit/template.c @@ -0,0 +1,98 @@ +#include "Python.h" + +#include "pycore_call.h" +#include "pycore_ceval.h" +#include "pycore_dict.h" +#include "pycore_emscripten_signal.h" +#include "pycore_intrinsics.h" +#include "pycore_jit.h" +#include "pycore_long.h" +#include "pycore_opcode_metadata.h" +#include "pycore_opcode_utils.h" +#include "pycore_range.h" +#include "pycore_setobject.h" +#include "pycore_sliceobject.h" + +#include "ceval_macros.h" + +#undef CURRENT_OPARG +#define CURRENT_OPARG() (_oparg) + +#undef CURRENT_OPERAND +#define CURRENT_OPERAND() (_operand) + +#undef DEOPT_IF +#define DEOPT_IF(COND, INSTNAME) \ + do { \ + if ((COND)) { \ + goto deoptimize; \ + } \ + } while (0) + +#undef ENABLE_SPECIALIZATION +#define ENABLE_SPECIALIZATION (0) + +#undef GOTO_ERROR +#define GOTO_ERROR(LABEL) \ + do { \ + goto LABEL ## _tier_two; \ + } while (0) + +#undef LOAD_IP +#define LOAD_IP(UNUSED) \ + do { \ + } while (0) + +#define PATCH_VALUE(TYPE, NAME, ALIAS) \ + extern void ALIAS; \ + TYPE NAME = (TYPE)(uint64_t)&ALIAS; + +#define PATCH_JUMP(ALIAS) \ + extern void ALIAS; \ + __attribute__((musttail)) \ + return ((jit_func)&ALIAS)(frame, stack_pointer, tstate); + +_Py_CODEUNIT * +_JIT_ENTRY(_PyInterpreterFrame *frame, PyObject **stack_pointer, PyThreadState *tstate) +{ + // Locals that the instruction implementations expect to exist: + PATCH_VALUE(_PyExecutorObject *, current_executor, _JIT_EXECUTOR) + int oparg; + int opcode = _JIT_OPCODE; + _PyUOpInstruction *next_uop; + // Other stuff we need handy: + PATCH_VALUE(uint16_t, _oparg, _JIT_OPARG) + PATCH_VALUE(uint64_t, _operand, _JIT_OPERAND) + PATCH_VALUE(uint32_t, _target, _JIT_TARGET) + // The actual instruction definitions (only one will be used): + if (opcode == _JUMP_TO_TOP) { + CHECK_EVAL_BREAKER(); + PATCH_JUMP(_JIT_TOP); + } + switch (opcode) { +#include "executor_cases.c.h" + default: + Py_UNREACHABLE(); + } + PATCH_JUMP(_JIT_CONTINUE); + // Labels that the instruction implementations expect to exist: +unbound_local_error_tier_two: + _PyEval_FormatExcCheckArg( + tstate, PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, + PyTuple_GetItem(_PyFrame_GetCode(frame)->co_localsplusnames, oparg)); + goto error_tier_two; +pop_4_error_tier_two: + STACK_SHRINK(1); +pop_3_error_tier_two: + STACK_SHRINK(1); +pop_2_error_tier_two: + STACK_SHRINK(1); +pop_1_error_tier_two: + STACK_SHRINK(1); +error_tier_two: + _PyFrame_SetStackPointer(frame, stack_pointer); + return NULL; +deoptimize: + _PyFrame_SetStackPointer(frame, stack_pointer); + return _PyCode_CODE(_PyFrame_GetCode(frame)) + _target; +} diff --git a/configure b/configure index b1153df4d7ec523..c563c3f5d3c7e6d 100755 --- a/configure +++ b/configure @@ -920,6 +920,7 @@ LLVM_AR PROFILE_TASK DEF_MAKE_RULE DEF_MAKE_ALL_RULE +REGEN_JIT_COMMAND ABIFLAGS LN MKDIR_P @@ -1074,6 +1075,7 @@ with_pydebug with_trace_refs enable_pystats with_assertions +enable_experimental_jit enable_optimizations with_lto enable_bolt @@ -1801,6 +1803,9 @@ Optional Features: --disable-gil enable experimental support for running without the GIL (default is no) --enable-pystats enable internal statistics gathering (default is no) + --enable-experimental-jit + build the experimental just-in-time compiler + (default is no) --enable-optimizations enable expensive, stable optimizations (PGO, etc.) (default is no) --enable-bolt enable usage of the llvm-bolt post-link optimizer @@ -7997,6 +8002,32 @@ else printf "%s\n" "no" >&6; } fi +# Check for --enable-experimental-jit: +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-experimental-jit" >&5 +printf %s "checking for --enable-experimental-jit... " >&6; } +# Check whether --enable-experimental-jit was given. +if test ${enable_experimental_jit+y} +then : + enableval=$enable_experimental_jit; +else $as_nop + enable_experimental_jit=no +fi + +if test "x$enable_experimental_jit" = xno +then : + +else $as_nop + as_fn_append CFLAGS_NODIST " -D_Py_JIT" + REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host" + if test "x$Py_DEBUG" = xtrue +then : + as_fn_append REGEN_JIT_COMMAND " --debug" +fi +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_experimental_jit" >&5 +printf "%s\n" "$enable_experimental_jit" >&6; } + # Enable optimization flags diff --git a/configure.ac b/configure.ac index 9587e6d63499aac..13c46b3e80151dc 100644 --- a/configure.ac +++ b/configure.ac @@ -1579,6 +1579,26 @@ else AC_MSG_RESULT([no]) fi +# Check for --enable-experimental-jit: +AC_MSG_CHECKING([for --enable-experimental-jit]) +AC_ARG_ENABLE([experimental-jit], + [AS_HELP_STRING([--enable-experimental-jit], + [build the experimental just-in-time compiler (default is no)])], + [], + [enable_experimental_jit=no]) +AS_VAR_IF([enable_experimental_jit], + [no], + [], + [AS_VAR_APPEND([CFLAGS_NODIST], [" -D_Py_JIT"]) + AS_VAR_SET([REGEN_JIT_COMMAND], + ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host"]) + AS_VAR_IF([Py_DEBUG], + [true], + [AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])], + [])]) +AC_SUBST([REGEN_JIT_COMMAND]) +AC_MSG_RESULT([$enable_experimental_jit]) + # Enable optimization flags AC_SUBST([DEF_MAKE_ALL_RULE]) AC_SUBST([DEF_MAKE_RULE]) From a16a9f978f42b8a09297c1efbf33877f6388c403 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Sun, 28 Jan 2024 18:52:58 -0800 Subject: [PATCH 074/263] GH-113464: A JIT backend for tier 2 (GH-113465) Add an option (--enable-experimental-jit for configure-based builds or --experimental-jit for PCbuild-based ones) to build an *experimental* just-in-time compiler, based on copy-and-patch (https://fredrikbk.com/publications/copy-and-patch.pdf). See Tools/jit/README.md for more information, including how to install the required build-time tooling. Merry JIT-mas! ;) From d7d0d13cd37651990586d31d8974c59bd25e1045 Mon Sep 17 00:00:00 2001 From: Stanley <46876382+slateny@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:19:22 -0800 Subject: [PATCH 075/263] gh-89159: Add some TarFile attribute types (GH-114520) Co-authored-by: Stanley <46876382+slateny@users.noreply.github.com> --- Doc/library/tarfile.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index 34a738a7f1c41f0..2134293a0bb0de1 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -673,6 +673,7 @@ be finalized; only the internally used file object will be closed. See the .. attribute:: TarFile.pax_headers + :type: dict A dictionary containing key-value pairs of pax global headers. @@ -838,26 +839,31 @@ A ``TarInfo`` object has the following public data attributes: attribute. .. attribute:: TarInfo.chksum + :type: int Header checksum. .. attribute:: TarInfo.devmajor + :type: int Device major number. .. attribute:: TarInfo.devminor + :type: int Device minor number. .. attribute:: TarInfo.offset + :type: int The tar header starts here. .. attribute:: TarInfo.offset_data + :type: int The file's data starts here. From 2124a3ddcc0e274521f74d239f0e94060e17dd7f Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:30:22 -0800 Subject: [PATCH 076/263] gh-109653: Improve import time of importlib.metadata / email.utils (#114664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My criterion for delayed imports is that they're only worth it if the majority of users of the module would benefit from it, otherwise you're just moving latency around unpredictably. mktime_tz is not used anywhere in the standard library and grep.app indicates it's not got much use in the ecosystem either. Distribution.files is not nearly as widely used as other importlib.metadata APIs, so we defer the csv import. Before: ``` λ hyperfine -w 8 './python -c "import importlib.metadata"' Benchmark 1: ./python -c "import importlib.metadata" Time (mean ± σ): 65.1 ms ± 0.5 ms [User: 55.3 ms, System: 9.8 ms] Range (min … max): 64.4 ms … 66.4 ms 44 runs ``` After: ``` λ hyperfine -w 8 './python -c "import importlib.metadata"' Benchmark 1: ./python -c "import importlib.metadata" Time (mean ± σ): 62.0 ms ± 0.3 ms [User: 52.5 ms, System: 9.6 ms] Range (min … max): 61.3 ms … 62.8 ms 46 runs ``` for about a 3ms saving with warm disk cache, maybe 7-11ms with cold disk cache. --- Lib/email/_parseaddr.py | 5 ++++- Lib/importlib/metadata/__init__.py | 5 ++++- .../Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py index febe411355d6be3..0f1bf8e4253ec4b 100644 --- a/Lib/email/_parseaddr.py +++ b/Lib/email/_parseaddr.py @@ -13,7 +13,7 @@ 'quote', ] -import time, calendar +import time SPACE = ' ' EMPTYSTRING = '' @@ -194,6 +194,9 @@ def mktime_tz(data): # No zone info, so localtime is better assumption than GMT return time.mktime(data[:8] + (-1,)) else: + # Delay the import, since mktime_tz is rarely used + import calendar + t = calendar.timegm(data) return t - data[9] diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 7b142e786e829ef..c612fbefee2e802 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -1,7 +1,6 @@ import os import re import abc -import csv import sys import json import email @@ -478,6 +477,10 @@ def make_file(name, hash=None, size_str=None): @pass_none def make_files(lines): + # Delay csv import, since Distribution.files is not as widely used + # as other parts of importlib.metadata + import csv + return starmap(make_file, csv.reader(lines)) @pass_none diff --git a/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst b/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst new file mode 100644 index 000000000000000..fb3382098853b30 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-00-48-12.gh-issue-109653.vF4exe.rst @@ -0,0 +1 @@ +Improve import time of :mod:`importlib.metadata` and :mod:`email.utils`. From 1ac1b2f9536a581f1656f0ac9330a7382420cda1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 29 Jan 2024 12:37:06 +0300 Subject: [PATCH 077/263] gh-114685: Fix incorrect use of PyBUF_READ in import.c (GH-114686) --- Python/import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index 2dd95d8364a0be0..2fd0c08a6bb5aec 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3544,7 +3544,7 @@ _imp_get_frozen_object_impl(PyObject *module, PyObject *name, struct frozen_info info = {0}; Py_buffer buf = {0}; if (PyObject_CheckBuffer(dataobj)) { - if (PyObject_GetBuffer(dataobj, &buf, PyBUF_READ) != 0) { + if (PyObject_GetBuffer(dataobj, &buf, PyBUF_SIMPLE) != 0) { return NULL; } info.data = (const char *)buf.buf; From 3b86891fd69093b60141300862f278614ba80613 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 29 Jan 2024 01:37:28 -0800 Subject: [PATCH 078/263] gh-110893: Improve the documentation for __future__ module (#114642) nedbat took issue with the phrasing "real module". I'm actually fine with that phrasing, but I do think the `__future__` page should be clear about the way in which the `__future__` module is special. (Yes, there was a footnote linking to the future statements part of the reference, but there should be upfront discussion). I'm sympathetic to nedbat's claim that no one really cares about `__future__._Feature`, so I've moved the interesting table up to the top. --- Doc/library/__future__.rst | 98 ++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/Doc/library/__future__.rst b/Doc/library/__future__.rst index d261e4a4f338a56..762f8b4695b3dd9 100644 --- a/Doc/library/__future__.rst +++ b/Doc/library/__future__.rst @@ -8,20 +8,68 @@ -------------- -:mod:`__future__` is a real module, and serves three purposes: +Imports of the form ``from __future__ import feature`` are called +:ref:`future statements `. These are special-cased by the Python compiler +to allow the use of new Python features in modules containing the future statement +before the release in which the feature becomes standard. + +While these future statements are given additional special meaning by the +Python compiler, they are still executed like any other import statement and +the :mod:`__future__` exists and is handled by the import system the same way +any other Python module would be. This design serves three purposes: * To avoid confusing existing tools that analyze import statements and expect to find the modules they're importing. -* To ensure that :ref:`future statements ` run under releases prior to - 2.1 at least yield runtime exceptions (the import of :mod:`__future__` will - fail, because there was no module of that name prior to 2.1). - * To document when incompatible changes were introduced, and when they will be --- or were --- made mandatory. This is a form of executable documentation, and can be inspected programmatically via importing :mod:`__future__` and examining its contents. +* To ensure that :ref:`future statements ` run under releases prior to + Python 2.1 at least yield runtime exceptions (the import of :mod:`__future__` + will fail, because there was no module of that name prior to 2.1). + +Module Contents +--------------- + +No feature description will ever be deleted from :mod:`__future__`. Since its +introduction in Python 2.1 the following features have found their way into the +language using this mechanism: + ++------------------+-------------+--------------+---------------------------------------------+ +| feature | optional in | mandatory in | effect | ++==================+=============+==============+=============================================+ +| nested_scopes | 2.1.0b1 | 2.2 | :pep:`227`: | +| | | | *Statically Nested Scopes* | ++------------------+-------------+--------------+---------------------------------------------+ +| generators | 2.2.0a1 | 2.3 | :pep:`255`: | +| | | | *Simple Generators* | ++------------------+-------------+--------------+---------------------------------------------+ +| division | 2.2.0a2 | 3.0 | :pep:`238`: | +| | | | *Changing the Division Operator* | ++------------------+-------------+--------------+---------------------------------------------+ +| absolute_import | 2.5.0a1 | 3.0 | :pep:`328`: | +| | | | *Imports: Multi-Line and Absolute/Relative* | ++------------------+-------------+--------------+---------------------------------------------+ +| with_statement | 2.5.0a1 | 2.6 | :pep:`343`: | +| | | | *The "with" Statement* | ++------------------+-------------+--------------+---------------------------------------------+ +| print_function | 2.6.0a2 | 3.0 | :pep:`3105`: | +| | | | *Make print a function* | ++------------------+-------------+--------------+---------------------------------------------+ +| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: | +| | | | *Bytes literals in Python 3000* | ++------------------+-------------+--------------+---------------------------------------------+ +| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: | +| | | | *StopIteration handling inside generators* | ++------------------+-------------+--------------+---------------------------------------------+ +| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: | +| | | | *Postponed evaluation of annotations* | ++------------------+-------------+--------------+---------------------------------------------+ + +.. XXX Adding a new entry? Remember to update simple_stmts.rst, too. + .. _future-classes: .. class:: _Feature @@ -65,43 +113,6 @@ dynamically compiled code. This flag is stored in the :attr:`_Feature.compiler_flag` attribute on :class:`_Feature` instances. -No feature description will ever be deleted from :mod:`__future__`. Since its -introduction in Python 2.1 the following features have found their way into the -language using this mechanism: - -+------------------+-------------+--------------+---------------------------------------------+ -| feature | optional in | mandatory in | effect | -+==================+=============+==============+=============================================+ -| nested_scopes | 2.1.0b1 | 2.2 | :pep:`227`: | -| | | | *Statically Nested Scopes* | -+------------------+-------------+--------------+---------------------------------------------+ -| generators | 2.2.0a1 | 2.3 | :pep:`255`: | -| | | | *Simple Generators* | -+------------------+-------------+--------------+---------------------------------------------+ -| division | 2.2.0a2 | 3.0 | :pep:`238`: | -| | | | *Changing the Division Operator* | -+------------------+-------------+--------------+---------------------------------------------+ -| absolute_import | 2.5.0a1 | 3.0 | :pep:`328`: | -| | | | *Imports: Multi-Line and Absolute/Relative* | -+------------------+-------------+--------------+---------------------------------------------+ -| with_statement | 2.5.0a1 | 2.6 | :pep:`343`: | -| | | | *The "with" Statement* | -+------------------+-------------+--------------+---------------------------------------------+ -| print_function | 2.6.0a2 | 3.0 | :pep:`3105`: | -| | | | *Make print a function* | -+------------------+-------------+--------------+---------------------------------------------+ -| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: | -| | | | *Bytes literals in Python 3000* | -+------------------+-------------+--------------+---------------------------------------------+ -| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: | -| | | | *StopIteration handling inside generators* | -+------------------+-------------+--------------+---------------------------------------------+ -| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: | -| | | | *Postponed evaluation of annotations* | -+------------------+-------------+--------------+---------------------------------------------+ - -.. XXX Adding a new entry? Remember to update simple_stmts.rst, too. - .. [1] ``from __future__ import annotations`` was previously scheduled to become mandatory in Python 3.10, but the Python Steering Council @@ -115,3 +126,6 @@ language using this mechanism: :ref:`future` How the compiler treats future imports. + + :pep:`236` - Back to the __future__ + The original proposal for the __future__ mechanism. From 97fb2480e4807a34b8197243ad57566ed7769e24 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 29 Jan 2024 12:56:11 +0300 Subject: [PATCH 079/263] gh-101100: Fix sphinx warnings in `Doc/c-api/memoryview.rst` (GH-114669) --- Doc/c-api/memoryview.rst | 13 +++++++++++++ Doc/tools/.nitignore | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/memoryview.rst b/Doc/c-api/memoryview.rst index 2aa43318e7a455c..f6038032805259f 100644 --- a/Doc/c-api/memoryview.rst +++ b/Doc/c-api/memoryview.rst @@ -20,6 +20,17 @@ any other object. read/write, otherwise it may be either read-only or read/write at the discretion of the exporter. + +.. c:macro:: PyBUF_READ + + Flag to request a readonly buffer. + + +.. c:macro:: PyBUF_WRITE + + Flag to request a writable buffer. + + .. c:function:: PyObject *PyMemoryView_FromMemory(char *mem, Py_ssize_t size, int flags) Create a memoryview object using *mem* as the underlying buffer. @@ -41,6 +52,8 @@ any other object. original memory. Otherwise, a copy is made and the memoryview points to a new bytes object. + *buffertype* can be one of :c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE`. + .. c:function:: int PyMemoryView_Check(PyObject *obj) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8b6847ef2a7d76a..b8b7c2299ca9f46 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -9,7 +9,6 @@ Doc/c-api/gcsupport.rst Doc/c-api/init.rst Doc/c-api/init_config.rst Doc/c-api/intro.rst -Doc/c-api/memoryview.rst Doc/c-api/module.rst Doc/c-api/stable.rst Doc/c-api/sys.rst From b7a12ab2146f946ae57e2d8019372cafe94d8375 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:12:19 +0200 Subject: [PATCH 080/263] gh-101100: Fix Sphinx warnings in `whatsnew/2.2.rst` (#112366) Co-authored-by: Hugo van Kemenade --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.2.rst | 140 +++++++++++++++++++++---------------------- 2 files changed, 70 insertions(+), 71 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index b8b7c2299ca9f46..763503205e16709 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -90,7 +90,6 @@ Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.0.rst Doc/whatsnew/2.1.rst -Doc/whatsnew/2.2.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/whatsnew/2.2.rst b/Doc/whatsnew/2.2.rst index 6efc23a82de9232..968bd7a126bdf0b 100644 --- a/Doc/whatsnew/2.2.rst +++ b/Doc/whatsnew/2.2.rst @@ -53,9 +53,9 @@ A long time ago I wrote a web page listing flaws in Python's design. One of the most significant flaws was that it's impossible to subclass Python types implemented in C. In particular, it's not possible to subclass built-in types, so you can't just subclass, say, lists in order to add a single useful method to -them. The :mod:`UserList` module provides a class that supports all of the +them. The :mod:`!UserList` module provides a class that supports all of the methods of lists and that can be subclassed further, but there's lots of C code -that expects a regular Python list and won't accept a :class:`UserList` +that expects a regular Python list and won't accept a :class:`!UserList` instance. Python 2.2 fixes this, and in the process adds some exciting new capabilities. @@ -69,7 +69,7 @@ A brief summary: * It's also possible to automatically call methods on accessing or setting an instance attribute by using a new mechanism called :dfn:`properties`. Many uses - of :meth:`__getattr__` can be rewritten to use properties instead, making the + of :meth:`!__getattr__` can be rewritten to use properties instead, making the resulting code simpler and faster. As a small side benefit, attributes can now have docstrings, too. @@ -120,7 +120,7 @@ added so if no built-in type is suitable, you can just subclass This means that :keyword:`class` statements that don't have any base classes are always classic classes in Python 2.2. (Actually you can also change this by -setting a module-level variable named :attr:`__metaclass__` --- see :pep:`253` +setting a module-level variable named :attr:`!__metaclass__` --- see :pep:`253` for the details --- but it's easier to just subclass :class:`object`.) The type objects for the built-in types are available as built-ins, named using @@ -134,8 +134,8 @@ type objects that behave as factories when called. :: 123 To make the set of types complete, new type objects such as :func:`dict` and -:func:`file` have been added. Here's a more interesting example, adding a -:meth:`lock` method to file objects:: +:func:`!file` have been added. Here's a more interesting example, adding a +:meth:`!lock` method to file objects:: class LockableFile(file): def lock (self, operation, length=0, start=0, whence=0): @@ -146,7 +146,7 @@ To make the set of types complete, new type objects such as :func:`dict` and The now-obsolete :mod:`!posixfile` module contained a class that emulated all of a file object's methods and also added a :meth:`!lock` method, but this class couldn't be passed to internal functions that expected a built-in file, -something which is possible with our new :class:`LockableFile`. +something which is possible with our new :class:`!LockableFile`. Descriptors @@ -154,11 +154,11 @@ Descriptors In previous versions of Python, there was no consistent way to discover what attributes and methods were supported by an object. There were some informal -conventions, such as defining :attr:`__members__` and :attr:`__methods__` +conventions, such as defining :attr:`!__members__` and :attr:`!__methods__` attributes that were lists of names, but often the author of an extension type or a class wouldn't bother to define them. You could fall back on inspecting the :attr:`~object.__dict__` of an object, but when class inheritance or an arbitrary -:meth:`__getattr__` hook were in use this could still be inaccurate. +:meth:`!__getattr__` hook were in use this could still be inaccurate. The one big idea underlying the new class model is that an API for describing the attributes of an object using :dfn:`descriptors` has been formalized. @@ -171,7 +171,7 @@ attributes of their own: * :attr:`~definition.__name__` is the attribute's name. -* :attr:`__doc__` is the attribute's docstring. +* :attr:`!__doc__` is the attribute's docstring. * ``__get__(object)`` is a method that retrieves the attribute value from *object*. @@ -186,7 +186,7 @@ are:: descriptor = obj.__class__.x descriptor.__get__(obj) -For methods, :meth:`descriptor.__get__` returns a temporary object that's +For methods, :meth:`!descriptor.__get__` returns a temporary object that's callable, and wraps up the instance and the method to be called on it. This is also why static methods and class methods are now possible; they have descriptors that wrap up just the method, or the method and the class. As a @@ -204,7 +204,7 @@ methods are defined like this:: ... g = classmethod(g) -The :func:`staticmethod` function takes the function :func:`f`, and returns it +The :func:`staticmethod` function takes the function :func:`!f`, and returns it wrapped up in a descriptor so it can be stored in the class object. You might expect there to be special syntax for creating such methods (``def static f``, ``defstatic f()``, or something like that) but no such syntax has been defined @@ -232,10 +232,10 @@ like this:: f = eiffelmethod(f, pre_f, post_f) -Note that a person using the new :func:`eiffelmethod` doesn't have to understand +Note that a person using the new :func:`!eiffelmethod` doesn't have to understand anything about descriptors. This is why I think the new features don't increase the basic complexity of the language. There will be a few wizards who need to -know about it in order to write :func:`eiffelmethod` or the ZODB or whatever, +know about it in order to write :func:`!eiffelmethod` or the ZODB or whatever, but most users will just write code on top of the resulting libraries and ignore the implementation details. @@ -263,10 +263,10 @@ from :pep:`253` by Guido van Rossum):: The lookup rule for classic classes is simple but not very smart; the base classes are searched depth-first, going from left to right. A reference to -:meth:`D.save` will search the classes :class:`D`, :class:`B`, and then -:class:`A`, where :meth:`save` would be found and returned. :meth:`C.save` -would never be found at all. This is bad, because if :class:`C`'s :meth:`save` -method is saving some internal state specific to :class:`C`, not calling it will +:meth:`!D.save` will search the classes :class:`!D`, :class:`!B`, and then +:class:`!A`, where :meth:`!save` would be found and returned. :meth:`!C.save` +would never be found at all. This is bad, because if :class:`!C`'s :meth:`!save` +method is saving some internal state specific to :class:`!C`, not calling it will result in that state never getting saved. New-style classes follow a different algorithm that's a bit more complicated to @@ -276,22 +276,22 @@ produces more useful results for really complicated inheritance graphs.) #. List all the base classes, following the classic lookup rule and include a class multiple times if it's visited repeatedly. In the above example, the list - of visited classes is [:class:`D`, :class:`B`, :class:`A`, :class:`C`, - :class:`A`]. + of visited classes is [:class:`!D`, :class:`!B`, :class:`!A`, :class:`!C`, + :class:`!A`]. #. Scan the list for duplicated classes. If any are found, remove all but one occurrence, leaving the *last* one in the list. In the above example, the list - becomes [:class:`D`, :class:`B`, :class:`C`, :class:`A`] after dropping + becomes [:class:`!D`, :class:`!B`, :class:`!C`, :class:`!A`] after dropping duplicates. -Following this rule, referring to :meth:`D.save` will return :meth:`C.save`, +Following this rule, referring to :meth:`!D.save` will return :meth:`!C.save`, which is the behaviour we're after. This lookup rule is the same as the one followed by Common Lisp. A new built-in function, :func:`super`, provides a way to get at a class's superclasses without having to reimplement Python's algorithm. The most commonly used form will be ``super(class, obj)``, which returns a bound superclass object (not the actual class object). This form will be used in methods to call a method in the superclass; for example, -:class:`D`'s :meth:`save` method would look like this:: +:class:`!D`'s :meth:`!save` method would look like this:: class D (B,C): def save (self): @@ -309,7 +309,7 @@ Attribute Access ---------------- A fair number of sophisticated Python classes define hooks for attribute access -using :meth:`__getattr__`; most commonly this is done for convenience, to make +using :meth:`~object.__getattr__`; most commonly this is done for convenience, to make code more readable by automatically mapping an attribute access such as ``obj.parent`` into a method call such as ``obj.get_parent``. Python 2.2 adds some new ways of controlling attribute access. @@ -321,22 +321,22 @@ instance's dictionary. New-style classes also support a new method, ``__getattribute__(attr_name)``. The difference between the two methods is -that :meth:`__getattribute__` is *always* called whenever any attribute is -accessed, while the old :meth:`__getattr__` is only called if ``foo`` isn't +that :meth:`~object.__getattribute__` is *always* called whenever any attribute is +accessed, while the old :meth:`~object.__getattr__` is only called if ``foo`` isn't found in the instance's dictionary. However, Python 2.2's support for :dfn:`properties` will often be a simpler way -to trap attribute references. Writing a :meth:`__getattr__` method is +to trap attribute references. Writing a :meth:`!__getattr__` method is complicated because to avoid recursion you can't use regular attribute accesses inside them, and instead have to mess around with the contents of -:attr:`~object.__dict__`. :meth:`__getattr__` methods also end up being called by Python -when it checks for other methods such as :meth:`__repr__` or :meth:`__coerce__`, +:attr:`~object.__dict__`. :meth:`~object.__getattr__` methods also end up being called by Python +when it checks for other methods such as :meth:`~object.__repr__` or :meth:`!__coerce__`, and so have to be written with this in mind. Finally, calling a function on every attribute access results in a sizable performance loss. :class:`property` is a new built-in type that packages up three functions that get, set, or delete an attribute, and a docstring. For example, if you want to -define a :attr:`size` attribute that's computed, but also settable, you could +define a :attr:`!size` attribute that's computed, but also settable, you could write:: class C(object): @@ -355,9 +355,9 @@ write:: "Storage size of this instance") That is certainly clearer and easier to write than a pair of -:meth:`__getattr__`/:meth:`__setattr__` methods that check for the :attr:`size` +:meth:`!__getattr__`/:meth:`!__setattr__` methods that check for the :attr:`!size` attribute and handle it specially while retrieving all other attributes from the -instance's :attr:`~object.__dict__`. Accesses to :attr:`size` are also the only ones +instance's :attr:`~object.__dict__`. Accesses to :attr:`!size` are also the only ones which have to perform the work of calling a function, so references to other attributes run at their usual speed. @@ -447,7 +447,7 @@ an iterator for the object *obj*, while ``iter(C, sentinel)`` returns an iterator that will invoke the callable object *C* until it returns *sentinel* to signal that the iterator is done. -Python classes can define an :meth:`__iter__` method, which should create and +Python classes can define an :meth:`!__iter__` method, which should create and return a new iterator for the object; if the object is its own iterator, this method can just return ``self``. In particular, iterators will usually be their own iterators. Extension types implemented in C can implement a :c:member:`~PyTypeObject.tp_iter` @@ -478,7 +478,7 @@ there are no more values to be returned, calling :meth:`next` should raise the In 2.2, Python's :keyword:`for` statement no longer expects a sequence; it expects something for which :func:`iter` will return an iterator. For backward compatibility and convenience, an iterator is automatically constructed for -sequences that don't implement :meth:`__iter__` or a :c:member:`~PyTypeObject.tp_iter` slot, so +sequences that don't implement :meth:`!__iter__` or a :c:member:`~PyTypeObject.tp_iter` slot, so ``for i in [1,2,3]`` will still work. Wherever the Python interpreter loops over a sequence, it's been changed to use the iterator protocol. This means you can do things like this:: @@ -510,8 +510,8 @@ Iterator support has been added to some of Python's basic types. Calling Oct 10 That's just the default behaviour. If you want to iterate over keys, values, or -key/value pairs, you can explicitly call the :meth:`iterkeys`, -:meth:`itervalues`, or :meth:`iteritems` methods to get an appropriate iterator. +key/value pairs, you can explicitly call the :meth:`!iterkeys`, +:meth:`!itervalues`, or :meth:`!iteritems` methods to get an appropriate iterator. In a minor related change, the :keyword:`in` operator now works on dictionaries, so ``key in dict`` is now equivalent to ``dict.has_key(key)``. @@ -580,7 +580,7 @@ allowed inside the :keyword:`!try` block of a :keyword:`try`...\ :keyword:`finally` statement; read :pep:`255` for a full explanation of the interaction between :keyword:`!yield` and exceptions.) -Here's a sample usage of the :func:`generate_ints` generator:: +Here's a sample usage of the :func:`!generate_ints` generator:: >>> gen = generate_ints(3) >>> gen @@ -641,7 +641,7 @@ like:: sentence := "Store it in the neighboring harbor" if (i := find("or", sentence)) > 5 then write(i) -In Icon the :func:`find` function returns the indexes at which the substring +In Icon the :func:`!find` function returns the indexes at which the substring "or" is found: 3, 23, 33. In the :keyword:`if` statement, ``i`` is first assigned a value of 3, but 3 is less than 5, so the comparison fails, and Icon retries it with the second value of 23. 23 is greater than 5, so the comparison @@ -671,7 +671,7 @@ PEP 237: Unifying Long Integers and Integers In recent versions, the distinction between regular integers, which are 32-bit values on most machines, and long integers, which can be of arbitrary size, was becoming an annoyance. For example, on platforms that support files larger than -``2**32`` bytes, the :meth:`tell` method of file objects has to return a long +``2**32`` bytes, the :meth:`!tell` method of file objects has to return a long integer. However, there were various bits of Python that expected plain integers and would raise an error if a long integer was provided instead. For example, in Python 1.5, only regular integers could be used as a slice index, and @@ -752,7 +752,7 @@ Here are the changes 2.2 introduces: 0.5. Without the ``__future__`` statement, ``/`` still means classic division. The default meaning of ``/`` will not change until Python 3.0. -* Classes can define methods called :meth:`__truediv__` and :meth:`__floordiv__` +* Classes can define methods called :meth:`~object.__truediv__` and :meth:`~object.__floordiv__` to overload the two division operators. At the C level, there are also slots in the :c:type:`PyNumberMethods` structure so extension types can define the two operators. @@ -785,17 +785,17 @@ support.) When built to use UCS-4 (a "wide Python"), the interpreter can natively handle Unicode characters from U+000000 to U+110000, so the range of legal values for -the :func:`unichr` function is expanded accordingly. Using an interpreter +the :func:`!unichr` function is expanded accordingly. Using an interpreter compiled to use UCS-2 (a "narrow Python"), values greater than 65535 will still -cause :func:`unichr` to raise a :exc:`ValueError` exception. This is all +cause :func:`!unichr` to raise a :exc:`ValueError` exception. This is all described in :pep:`261`, "Support for 'wide' Unicode characters"; consult it for further details. Another change is simpler to explain. Since their introduction, Unicode strings -have supported an :meth:`encode` method to convert the string to a selected +have supported an :meth:`!encode` method to convert the string to a selected encoding such as UTF-8 or Latin-1. A symmetric ``decode([*encoding*])`` method has been added to 8-bit strings (though not to Unicode strings) in 2.2. -:meth:`decode` assumes that the string is in the specified encoding and decodes +:meth:`!decode` assumes that the string is in the specified encoding and decodes it, returning whatever is returned by the codec. Using this new feature, codecs have been added for tasks not directly related to @@ -819,10 +819,10 @@ encoding, and compression with the :mod:`zlib` module:: >>> "sheesh".encode('rot-13') 'furrfu' -To convert a class instance to Unicode, a :meth:`__unicode__` method can be -defined by a class, analogous to :meth:`__str__`. +To convert a class instance to Unicode, a :meth:`!__unicode__` method can be +defined by a class, analogous to :meth:`!__str__`. -:meth:`encode`, :meth:`decode`, and :meth:`__unicode__` were implemented by +:meth:`!encode`, :meth:`!decode`, and :meth:`!__unicode__` were implemented by Marc-André Lemburg. The changes to support using UCS-4 internally were implemented by Fredrik Lundh and Martin von Löwis. @@ -859,7 +859,7 @@ doesn't work:: return g(value-1) + 1 ... -The function :func:`g` will always raise a :exc:`NameError` exception, because +The function :func:`!g` will always raise a :exc:`NameError` exception, because the binding of the name ``g`` isn't in either its local namespace or in the module-level namespace. This isn't much of a problem in practice (how often do you recursively define interior functions like this?), but this also made using @@ -915,7 +915,7 @@ To make the preceding explanation a bit clearer, here's an example:: Line 4 containing the ``exec`` statement is a syntax error, since ``exec`` would define a new local variable named ``x`` whose value should -be accessed by :func:`g`. +be accessed by :func:`!g`. This shouldn't be much of a limitation, since ``exec`` is rarely used in most Python code (and when it is used, it's often a sign of a poor design @@ -933,7 +933,7 @@ anyway). New and Improved Modules ======================== -* The :mod:`xmlrpclib` module was contributed to the standard library by Fredrik +* The :mod:`!xmlrpclib` module was contributed to the standard library by Fredrik Lundh, providing support for writing XML-RPC clients. XML-RPC is a simple remote procedure call protocol built on top of HTTP and XML. For example, the following snippet retrieves a list of RSS channels from the O'Reilly Network, @@ -956,7 +956,7 @@ New and Improved Modules # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ] - The :mod:`SimpleXMLRPCServer` module makes it easy to create straightforward + The :mod:`!SimpleXMLRPCServer` module makes it easy to create straightforward XML-RPC servers. See http://xmlrpc.scripting.com/ for more information about XML-RPC. * The new :mod:`hmac` module implements the HMAC algorithm described by @@ -964,9 +964,9 @@ New and Improved Modules * Several functions that originally returned lengthy tuples now return pseudo-sequences that still behave like tuples but also have mnemonic attributes such - as memberst_mtime or :attr:`tm_year`. The enhanced functions include - :func:`stat`, :func:`fstat`, :func:`statvfs`, and :func:`fstatvfs` in the - :mod:`os` module, and :func:`localtime`, :func:`gmtime`, and :func:`strptime` in + as :attr:`!memberst_mtime` or :attr:`!tm_year`. The enhanced functions include + :func:`~os.stat`, :func:`~os.fstat`, :func:`~os.statvfs`, and :func:`~os.fstatvfs` in the + :mod:`os` module, and :func:`~time.localtime`, :func:`~time.gmtime`, and :func:`~time.strptime` in the :mod:`time` module. For example, to obtain a file's size using the old tuples, you'd end up writing @@ -999,7 +999,7 @@ New and Improved Modules underlying the :mod:`re` module. For example, the :func:`re.sub` and :func:`re.split` functions have been rewritten in C. Another contributed patch speeds up certain Unicode character ranges by a factor of two, and a new - :meth:`finditer` method that returns an iterator over all the non-overlapping + :meth:`~re.finditer` method that returns an iterator over all the non-overlapping matches in a given string. (SRE is maintained by Fredrik Lundh. The BIGCHARSET patch was contributed by Martin von Löwis.) @@ -1012,33 +1012,33 @@ New and Improved Modules new extensions: the NAMESPACE extension defined in :rfc:`2342`, SORT, GETACL and SETACL. (Contributed by Anthony Baxter and Michel Pelletier.) -* The :mod:`rfc822` module's parsing of email addresses is now compliant with +* The :mod:`!rfc822` module's parsing of email addresses is now compliant with :rfc:`2822`, an update to :rfc:`822`. (The module's name is *not* going to be changed to ``rfc2822``.) A new package, :mod:`email`, has also been added for parsing and generating e-mail messages. (Contributed by Barry Warsaw, and arising out of his work on Mailman.) -* The :mod:`difflib` module now contains a new :class:`Differ` class for +* The :mod:`difflib` module now contains a new :class:`!Differ` class for producing human-readable lists of changes (a "delta") between two sequences of - lines of text. There are also two generator functions, :func:`ndiff` and - :func:`restore`, which respectively return a delta from two sequences, or one of + lines of text. There are also two generator functions, :func:`!ndiff` and + :func:`!restore`, which respectively return a delta from two sequences, or one of the original sequences from a delta. (Grunt work contributed by David Goodger, from ndiff.py code by Tim Peters who then did the generatorization.) -* New constants :const:`ascii_letters`, :const:`ascii_lowercase`, and - :const:`ascii_uppercase` were added to the :mod:`string` module. There were - several modules in the standard library that used :const:`string.letters` to +* New constants :const:`!ascii_letters`, :const:`!ascii_lowercase`, and + :const:`!ascii_uppercase` were added to the :mod:`string` module. There were + several modules in the standard library that used :const:`!string.letters` to mean the ranges A-Za-z, but that assumption is incorrect when locales are in - use, because :const:`string.letters` varies depending on the set of legal + use, because :const:`!string.letters` varies depending on the set of legal characters defined by the current locale. The buggy modules have all been fixed - to use :const:`ascii_letters` instead. (Reported by an unknown person; fixed by + to use :const:`!ascii_letters` instead. (Reported by an unknown person; fixed by Fred L. Drake, Jr.) * The :mod:`mimetypes` module now makes it easier to use alternative MIME-type - databases by the addition of a :class:`MimeTypes` class, which takes a list of + databases by the addition of a :class:`~mimetypes.MimeTypes` class, which takes a list of filenames to be parsed. (Contributed by Fred L. Drake, Jr.) -* A :class:`Timer` class was added to the :mod:`threading` module that allows +* A :class:`~threading.Timer` class was added to the :mod:`threading` module that allows scheduling an activity to happen at some future time. (Contributed by Itamar Shtull-Trauring.) @@ -1114,7 +1114,7 @@ code, none of the changes described here will affect you very much. * Two new wrapper functions, :c:func:`PyOS_snprintf` and :c:func:`PyOS_vsnprintf` were added to provide cross-platform implementations for the relatively new :c:func:`snprintf` and :c:func:`vsnprintf` C lib APIs. In contrast to the standard - :c:func:`sprintf` and :c:func:`vsprintf` functions, the Python versions check the + :c:func:`sprintf` and :c:func:`!vsprintf` functions, the Python versions check the bounds of the buffer used to protect against buffer overruns. (Contributed by M.-A. Lemburg.) @@ -1212,12 +1212,12 @@ Some of the more notable changes are: * The :file:`Tools/scripts/ftpmirror.py` script now parses a :file:`.netrc` file, if you have one. (Contributed by Mike Romberg.) -* Some features of the object returned by the :func:`xrange` function are now +* Some features of the object returned by the :func:`!xrange` function are now deprecated, and trigger warnings when they're accessed; they'll disappear in - Python 2.3. :class:`xrange` objects tried to pretend they were full sequence + Python 2.3. :class:`!xrange` objects tried to pretend they were full sequence types by supporting slicing, sequence multiplication, and the :keyword:`in` operator, but these features were rarely used and therefore buggy. The - :meth:`tolist` method and the :attr:`start`, :attr:`stop`, and :attr:`step` + :meth:`!tolist` method and the :attr:`!start`, :attr:`!stop`, and :attr:`!step` attributes are also being deprecated. At the C level, the fourth argument to the :c:func:`!PyRange_New` function, ``repeat``, has also been deprecated. From e8b8f5e9c2da6a436360ce648061c90bdfcba863 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Mon, 29 Jan 2024 08:43:44 -0600 Subject: [PATCH 081/263] gh-101100: Fix datetime reference warnings (GH-114661) Co-authored-by: Serhiy Storchaka --- Doc/conf.py | 5 ++ Doc/library/datetime.rst | 106 +++++++++++++++++++-------------------- Doc/tools/.nitignore | 1 - 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 458954370debe28..a96e7787d167a37 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -89,20 +89,25 @@ nitpick_ignore = [ # Standard C functions ('c:func', 'calloc'), + ('c:func', 'ctime'), ('c:func', 'dlopen'), ('c:func', 'exec'), ('c:func', 'fcntl'), ('c:func', 'fork'), ('c:func', 'free'), + ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), + ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), ('c:func', 'malloc'), + ('c:func', 'mktime'), ('c:func', 'printf'), ('c:func', 'realloc'), ('c:func', 'snprintf'), ('c:func', 'sprintf'), ('c:func', 'stat'), + ('c:func', 'strftime'), ('c:func', 'system'), ('c:func', 'time'), ('c:func', 'vsnprintf'), diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index b36f8c19cd6040b..47ecb0ba331bdc4 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -14,7 +14,7 @@ .. XXX what order should the types be discussed in? -The :mod:`datetime` module supplies classes for manipulating dates and times. +The :mod:`!datetime` module supplies classes for manipulating dates and times. While date and time arithmetic is supported, the focus of the implementation is on efficient attribute extraction for output formatting and manipulation. @@ -70,7 +70,7 @@ These :class:`tzinfo` objects capture information about the offset from UTC time, the time zone name, and whether daylight saving time is in effect. Only one concrete :class:`tzinfo` class, the :class:`timezone` class, is -supplied by the :mod:`datetime` module. The :class:`timezone` class can +supplied by the :mod:`!datetime` module. The :class:`timezone` class can represent simple timezones with fixed offsets from UTC, such as UTC itself or North American EST and EDT timezones. Supporting timezones at deeper levels of detail is up to the application. The rules for time adjustment across the @@ -80,7 +80,7 @@ standard suitable for every application aside from UTC. Constants --------- -The :mod:`datetime` module exports the following constants: +The :mod:`!datetime` module exports the following constants: .. data:: MINYEAR @@ -631,7 +631,7 @@ Notes: date2.toordinal()``. Date comparison raises :exc:`TypeError` if the other comparand isn't also a :class:`date` object. However, ``NotImplemented`` is returned instead if the other comparand has a - :meth:`timetuple` attribute. This hook gives other kinds of date objects a + :attr:`~date.timetuple` attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a :class:`date` object is compared to an object of a different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return @@ -1215,7 +1215,7 @@ Supported operations: object addresses, datetime comparison normally raises :exc:`TypeError` if the other comparand isn't also a :class:`.datetime` object. However, ``NotImplemented`` is returned instead if the other comparand has a - :meth:`timetuple` attribute. This hook gives other kinds of date objects a + :attr:`~.datetime.timetuple` attribute. This hook gives other kinds of date objects a chance at implementing mixed-type comparison. If not, when a :class:`.datetime` object is compared to an object of a different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return @@ -1347,22 +1347,22 @@ Instance methods: where ``yday = d.toordinal() - date(d.year, 1, 1).toordinal() + 1`` is the day number within the current year starting with ``1`` for January - 1st. The :attr:`tm_isdst` flag of the result is set according to the + 1st. The :attr:`~time.struct_time.tm_isdst` flag of the result is set according to the :meth:`dst` method: :attr:`.tzinfo` is ``None`` or :meth:`dst` returns - ``None``, :attr:`tm_isdst` is set to ``-1``; else if :meth:`dst` returns a - non-zero value, :attr:`tm_isdst` is set to ``1``; else :attr:`tm_isdst` is + ``None``, :attr:`!tm_isdst` is set to ``-1``; else if :meth:`dst` returns a + non-zero value, :attr:`!tm_isdst` is set to ``1``; else :attr:`!tm_isdst` is set to ``0``. .. method:: datetime.utctimetuple() If :class:`.datetime` instance *d* is naive, this is the same as - ``d.timetuple()`` except that :attr:`tm_isdst` is forced to 0 regardless of what + ``d.timetuple()`` except that :attr:`~.time.struct_time.tm_isdst` is forced to 0 regardless of what ``d.dst()`` returns. DST is never in effect for a UTC time. If *d* is aware, *d* is normalized to UTC time, by subtracting ``d.utcoffset()``, and a :class:`time.struct_time` for the - normalized time is returned. :attr:`tm_isdst` is forced to 0. Note + normalized time is returned. :attr:`!tm_isdst` is forced to 0. Note that an :exc:`OverflowError` may be raised if *d*.year was ``MINYEAR`` or ``MAXYEAR`` and UTC adjustment spills over a year boundary. @@ -1550,7 +1550,7 @@ Instance methods: Examples of Usage: :class:`.datetime` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Examples of working with :class:`~datetime.datetime` objects: +Examples of working with :class:`.datetime` objects: .. doctest:: @@ -1761,9 +1761,9 @@ is aware, :exc:`TypeError` is raised if an order comparison is attempted. For eq comparisons, naive instances are never equal to aware instances. If both comparands are aware, and have -the same :attr:`~time.tzinfo` attribute, the common :attr:`~time.tzinfo` attribute is +the same :attr:`~.time.tzinfo` attribute, the common :attr:`!tzinfo` attribute is ignored and the base times are compared. If both comparands are aware and -have different :attr:`~time.tzinfo` attributes, the comparands are first adjusted by +have different :attr:`!tzinfo` attributes, the comparands are first adjusted by subtracting their UTC offsets (obtained from ``self.utcoffset()``). In order to stop mixed-type comparisons from falling back to the default comparison by object address, when a :class:`.time` object is compared to an object of a @@ -1771,7 +1771,7 @@ different type, :exc:`TypeError` is raised unless the comparison is ``==`` or ``!=``. The latter cases return :const:`False` or :const:`True`, respectively. .. versionchanged:: 3.3 - Equality comparisons between aware and naive :class:`~datetime.time` instances + Equality comparisons between aware and naive :class:`.time` instances don't raise :exc:`TypeError`. In Boolean contexts, a :class:`.time` object is always considered to be true. @@ -1981,7 +1981,7 @@ Examples of working with a :class:`.time` object:: You need to derive a concrete subclass, and (at least) supply implementations of the standard :class:`tzinfo` methods needed by the - :class:`.datetime` methods you use. The :mod:`datetime` module provides + :class:`.datetime` methods you use. The :mod:`!datetime` module provides :class:`timezone`, a simple concrete subclass of :class:`tzinfo` which can represent timezones with fixed offset from UTC such as UTC itself or North American EST and EDT. @@ -1994,7 +1994,7 @@ Examples of working with a :class:`.time` object:: A concrete subclass of :class:`tzinfo` may need to implement the following methods. Exactly which methods are needed depends on the uses made of aware - :mod:`datetime` objects. If in doubt, simply implement all of them. + :mod:`!datetime` objects. If in doubt, simply implement all of them. .. method:: tzinfo.utcoffset(dt) @@ -2035,7 +2035,7 @@ Examples of working with a :class:`.time` object:: already been added to the UTC offset returned by :meth:`utcoffset`, so there's no need to consult :meth:`dst` unless you're interested in obtaining DST info separately. For example, :meth:`datetime.timetuple` calls its :attr:`~.datetime.tzinfo` - attribute's :meth:`dst` method to determine how the :attr:`tm_isdst` flag + attribute's :meth:`dst` method to determine how the :attr:`~time.struct_time.tm_isdst` flag should be set, and :meth:`tzinfo.fromutc` calls :meth:`dst` to account for DST changes when crossing time zones. @@ -2051,7 +2051,7 @@ Examples of working with a :class:`.time` object:: relies on this, but cannot detect violations; it's the programmer's responsibility to ensure it. If a :class:`tzinfo` subclass cannot guarantee this, it may be able to override the default implementation of - :meth:`tzinfo.fromutc` to work correctly with :meth:`astimezone` regardless. + :meth:`tzinfo.fromutc` to work correctly with :meth:`~.datetime.astimezone` regardless. Most implementations of :meth:`dst` will probably look like one of these two:: @@ -2080,7 +2080,7 @@ Examples of working with a :class:`.time` object:: .. method:: tzinfo.tzname(dt) Return the time zone name corresponding to the :class:`.datetime` object *dt*, as - a string. Nothing about string names is defined by the :mod:`datetime` module, + a string. Nothing about string names is defined by the :mod:`!datetime` module, and there's no requirement that it mean anything in particular. For example, "GMT", "UTC", "-500", "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies. Return ``None`` if a string name isn't known. Note that this is @@ -2128,7 +2128,7 @@ There is one more :class:`tzinfo` method that a subclass may wish to override: different years. An example of a time zone the default :meth:`fromutc` implementation may not handle correctly in all cases is one where the standard offset (from UTC) depends on the specific date and time passed, which can happen - for political reasons. The default implementations of :meth:`astimezone` and + for political reasons. The default implementations of :meth:`~.datetime.astimezone` and :meth:`fromutc` may not produce the result you want if the result is one of the hours straddling the moment the standard offset changes. @@ -2194,10 +2194,10 @@ hour that can't be spelled unambiguously in local wall time: the last hour of daylight time. In Eastern, that's times of the form 5:MM UTC on the day daylight time ends. The local wall clock leaps from 1:59 (daylight time) back to 1:00 (standard time) again. Local times of the form 1:MM are ambiguous. -:meth:`astimezone` mimics the local clock's behavior by mapping two adjacent UTC +:meth:`~.datetime.astimezone` mimics the local clock's behavior by mapping two adjacent UTC hours into the same local hour then. In the Eastern example, UTC times of the form 5:MM and 6:MM both map to 1:MM when converted to Eastern, but earlier times -have the :attr:`~datetime.fold` attribute set to 0 and the later times have it set to 1. +have the :attr:`~.datetime.fold` attribute set to 0 and the later times have it set to 1. For example, at the Fall back transition of 2016, we get:: >>> u0 = datetime(2016, 11, 6, 4, tzinfo=timezone.utc) @@ -2212,10 +2212,10 @@ For example, at the Fall back transition of 2016, we get:: 07:00:00 UTC = 02:00:00 EST 0 Note that the :class:`.datetime` instances that differ only by the value of the -:attr:`~datetime.fold` attribute are considered equal in comparisons. +:attr:`~.datetime.fold` attribute are considered equal in comparisons. Applications that can't bear wall-time ambiguities should explicitly check the -value of the :attr:`~datetime.fold` attribute or avoid using hybrid +value of the :attr:`~.datetime.fold` attribute or avoid using hybrid :class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`, or any other fixed-offset :class:`tzinfo` subclass (such as a class representing only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). @@ -2223,7 +2223,7 @@ only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). .. seealso:: :mod:`zoneinfo` - The :mod:`datetime` module has a basic :class:`timezone` class (for + The :mod:`!datetime` module has a basic :class:`timezone` class (for handling arbitrary fixed offsets from UTC) and its :attr:`timezone.utc` attribute (a UTC timezone instance). @@ -2241,7 +2241,7 @@ only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). .. _datetime-timezone: :class:`timezone` Objects --------------------------- +------------------------- The :class:`timezone` class is a subclass of :class:`tzinfo`, each instance of which represents a timezone defined by a fixed offset from @@ -2316,8 +2316,8 @@ Class attributes: .. _strftime-strptime-behavior: -:meth:`strftime` and :meth:`strptime` Behavior ----------------------------------------------- +:meth:`~.datetime.strftime` and :meth:`~.datetime.strptime` Behavior +-------------------------------------------------------------------- :class:`date`, :class:`.datetime`, and :class:`.time` objects all support a ``strftime(format)`` method, to create a string representing the time under the @@ -2327,8 +2327,8 @@ Conversely, the :meth:`datetime.strptime` class method creates a :class:`.datetime` object from a string representing a date and time and a corresponding format string. -The table below provides a high-level comparison of :meth:`strftime` -versus :meth:`strptime`: +The table below provides a high-level comparison of :meth:`~.datetime.strftime` +versus :meth:`~.datetime.strptime`: +----------------+--------------------------------------------------------+------------------------------------------------------------------------------+ | | ``strftime`` | ``strptime`` | @@ -2345,8 +2345,8 @@ versus :meth:`strptime`: .. _format-codes: -:meth:`strftime` and :meth:`strptime` Format Codes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:meth:`~.datetime.strftime` and :meth:`~.datetime.strptime` Format Codes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These methods accept format codes that can be used to parse and format dates:: @@ -2485,13 +2485,13 @@ convenience. These parameters all correspond to ISO 8601 date values. | | naive). | -03:07:12.345216 | | +-----------+--------------------------------+------------------------+-------+ -These may not be available on all platforms when used with the :meth:`strftime` +These may not be available on all platforms when used with the :meth:`~.datetime.strftime` method. The ISO 8601 year and ISO 8601 week directives are not interchangeable -with the year and week number directives above. Calling :meth:`strptime` with +with the year and week number directives above. Calling :meth:`~.datetime.strptime` with incomplete or ambiguous ISO 8601 directives will raise a :exc:`ValueError`. The full set of format codes supported varies across platforms, because Python -calls the platform C library's :func:`strftime` function, and platform +calls the platform C library's :c:func:`strftime` function, and platform variations are common. To see the full set of format codes supported on your platform, consult the :manpage:`strftime(3)` documentation. There are also differences between platforms in handling of unsupported format specifiers. @@ -2507,9 +2507,9 @@ Technical Detail Broadly speaking, ``d.strftime(fmt)`` acts like the :mod:`time` module's ``time.strftime(fmt, d.timetuple())`` although not all objects support a -:meth:`timetuple` method. +:meth:`~date.timetuple` method. -For the :meth:`datetime.strptime` class method, the default value is +For the :meth:`.datetime.strptime` class method, the default value is ``1900-01-01T00:00:00.000``: any components not specified in the format string will be pulled from the default value. [#]_ @@ -2544,27 +2544,27 @@ Notes: contain non-ASCII characters. (2) - The :meth:`strptime` method can parse years in the full [1, 9999] range, but + The :meth:`~.datetime.strptime` method can parse years in the full [1, 9999] range, but years < 1000 must be zero-filled to 4-digit width. .. versionchanged:: 3.2 - In previous versions, :meth:`strftime` method was restricted to + In previous versions, :meth:`~.datetime.strftime` method was restricted to years >= 1900. .. versionchanged:: 3.3 - In version 3.2, :meth:`strftime` method was restricted to + In version 3.2, :meth:`~.datetime.strftime` method was restricted to years >= 1000. (3) - When used with the :meth:`strptime` method, the ``%p`` directive only affects + When used with the :meth:`~.datetime.strptime` method, the ``%p`` directive only affects the output hour field if the ``%I`` directive is used to parse the hour. (4) - Unlike the :mod:`time` module, the :mod:`datetime` module does not support + Unlike the :mod:`time` module, the :mod:`!datetime` module does not support leap seconds. (5) - When used with the :meth:`strptime` method, the ``%f`` directive + When used with the :meth:`~.datetime.strptime` method, the ``%f`` directive accepts from one to six digits and zero pads on the right. ``%f`` is an extension to the set of format characters in the C standard (but implemented separately in datetime objects, and therefore always @@ -2577,7 +2577,7 @@ Notes: For an aware object: ``%z`` - :meth:`utcoffset` is transformed into a string of the form + :meth:`~.datetime.utcoffset` is transformed into a string of the form ``±HHMM[SS[.ffffff]]``, where ``HH`` is a 2-digit string giving the number of UTC offset hours, ``MM`` is a 2-digit string giving the number of UTC offset minutes, ``SS`` is a 2-digit string giving the number of UTC offset @@ -2585,14 +2585,14 @@ Notes: offset microseconds. The ``ffffff`` part is omitted when the offset is a whole number of seconds and both the ``ffffff`` and the ``SS`` part is omitted when the offset is a whole number of minutes. For example, if - :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is + :meth:`~.datetime.utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is replaced with the string ``'-0330'``. .. versionchanged:: 3.7 The UTC offset is not restricted to a whole number of minutes. .. versionchanged:: 3.7 - When the ``%z`` directive is provided to the :meth:`strptime` method, + When the ``%z`` directive is provided to the :meth:`~.datetime.strptime` method, the UTC offsets can have a colon as a separator between hours, minutes and seconds. For example, ``'+01:00:00'`` will be parsed as an offset of one hour. @@ -2603,11 +2603,11 @@ Notes: hours, minutes and seconds. ``%Z`` - In :meth:`strftime`, ``%Z`` is replaced by an empty string if - :meth:`tzname` returns ``None``; otherwise ``%Z`` is replaced by the + In :meth:`~.datetime.strftime`, ``%Z`` is replaced by an empty string if + :meth:`~.datetime.tzname` returns ``None``; otherwise ``%Z`` is replaced by the returned value, which must be a string. - :meth:`strptime` only accepts certain values for ``%Z``: + :meth:`~.datetime.strptime` only accepts certain values for ``%Z``: 1. any value in ``time.tzname`` for your machine's locale 2. the hard-coded values ``UTC`` and ``GMT`` @@ -2617,23 +2617,23 @@ Notes: invalid values. .. versionchanged:: 3.2 - When the ``%z`` directive is provided to the :meth:`strptime` method, an + When the ``%z`` directive is provided to the :meth:`~.datetime.strptime` method, an aware :class:`.datetime` object will be produced. The ``tzinfo`` of the result will be set to a :class:`timezone` instance. (7) - When used with the :meth:`strptime` method, ``%U`` and ``%W`` are only used + When used with the :meth:`~.datetime.strptime` method, ``%U`` and ``%W`` are only used in calculations when the day of the week and the calendar year (``%Y``) are specified. (8) Similar to ``%U`` and ``%W``, ``%V`` is only used in calculations when the day of the week and the ISO year (``%G``) are specified in a - :meth:`strptime` format string. Also note that ``%G`` and ``%Y`` are not + :meth:`~.datetime.strptime` format string. Also note that ``%G`` and ``%Y`` are not interchangeable. (9) - When used with the :meth:`strptime` method, the leading zero is optional + When used with the :meth:`~.datetime.strptime` method, the leading zero is optional for formats ``%d``, ``%m``, ``%H``, ``%I``, ``%M``, ``%S``, ``%j``, ``%U``, ``%W``, and ``%V``. Format ``%y`` does require a leading zero. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 763503205e16709..bba4fe0d5f2425e 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -26,7 +26,6 @@ Doc/library/asyncio-subprocess.rst Doc/library/bdb.rst Doc/library/collections.rst Doc/library/csv.rst -Doc/library/datetime.rst Doc/library/dbm.rst Doc/library/decimal.rst Doc/library/email.charset.rst From c87233fd3fa77067013c35328f8c4884f0567a59 Mon Sep 17 00:00:00 2001 From: mpage Date: Mon, 29 Jan 2024 07:08:23 -0800 Subject: [PATCH 082/263] gh-112050: Adapt collections.deque to Argument Clinic (#113963) --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + ...-01-11-22-58-45.gh-issue-112050.hDuvDW.rst | 1 + Modules/_collectionsmodule.c | 412 ++++++++++------- Modules/clinic/_collectionsmodule.c.h | 418 +++++++++++++++++- 7 files changed, 685 insertions(+), 152 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index e92707051c12b7f..57505b5388fd6c3 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1049,6 +1049,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(max_length)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxdigits)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxevents)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxlen)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxmem)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxsplit)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(maxvalue)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index eb60b80c964d423..0f4f3b619102414 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -538,6 +538,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(max_length) STRUCT_FOR_ID(maxdigits) STRUCT_FOR_ID(maxevents) + STRUCT_FOR_ID(maxlen) STRUCT_FOR_ID(maxmem) STRUCT_FOR_ID(maxsplit) STRUCT_FOR_ID(maxvalue) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 9b39de1d69c6c7f..63a2b54c839a4b5 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1047,6 +1047,7 @@ extern "C" { INIT_ID(max_length), \ INIT_ID(maxdigits), \ INIT_ID(maxevents), \ + INIT_ID(maxlen), \ INIT_ID(maxmem), \ INIT_ID(maxsplit), \ INIT_ID(maxvalue), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 898d386f4cfd05d..bf8cdd85e4be5c1 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1455,6 +1455,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(maxevents); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(maxlen); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(maxmem); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst new file mode 100644 index 000000000000000..e5f3d5ea0cea254 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-11-22-58-45.gh-issue-112050.hDuvDW.rst @@ -0,0 +1 @@ +Convert :class:`collections.deque` to use Argument Clinic. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index c8cd53de5e22622..ef77d34b10e47b9 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -44,8 +44,11 @@ find_module_state_by_def(PyTypeObject *type) /*[clinic input] module _collections class _tuplegetter "_tuplegetterobject *" "clinic_state()->tuplegetter_type" +class _collections.deque "dequeobject *" "clinic_state()->deque_type" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7356042a89862e0e]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a033cc2a8476b3f1]*/ + +typedef struct dequeobject dequeobject; /* We can safely assume type to be the defining class, * since tuplegetter is not a base type */ @@ -53,6 +56,12 @@ class _tuplegetter "_tuplegetterobject *" "clinic_state()->tuplegetter_type" #include "clinic/_collectionsmodule.c.h" #undef clinic_state +/*[python input] +class dequeobject_converter(self_converter): + type = "dequeobject *" +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=b6ae4a3ff852be2f]*/ + /* collections module implementation of a deque() datatype Written and maintained by Raymond D. Hettinger */ @@ -121,7 +130,7 @@ typedef struct BLOCK { struct BLOCK *rightlink; } block; -typedef struct { +struct dequeobject { PyObject_VAR_HEAD block *leftblock; block *rightblock; @@ -132,7 +141,7 @@ typedef struct { Py_ssize_t numfreeblocks; block *freeblocks[MAXFREEBLOCKS]; PyObject *weakreflist; -} dequeobject; +}; /* For debug builds, add error checking to track the endpoints * in the chain of links. The goal is to make sure that link @@ -219,8 +228,17 @@ deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)deque; } +/*[clinic input] +_collections.deque.pop as deque_pop + + deque: dequeobject + +Remove and return the rightmost element. +[clinic start generated code]*/ + static PyObject * -deque_pop(dequeobject *deque, PyObject *unused) +deque_pop_impl(dequeobject *deque) +/*[clinic end generated code: output=2e5f7890c4251f07 input=eb6e6d020f877dec]*/ { PyObject *item; block *prevblock; @@ -254,10 +272,17 @@ deque_pop(dequeobject *deque, PyObject *unused) return item; } -PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element."); +/*[clinic input] +_collections.deque.popleft as deque_popleft + + deque: dequeobject + +Remove and return the leftmost element. +[clinic start generated code]*/ static PyObject * -deque_popleft(dequeobject *deque, PyObject *unused) +deque_popleft_impl(dequeobject *deque) +/*[clinic end generated code: output=62b154897097ff68 input=acb41b9af50a9d9b]*/ { PyObject *item; block *prevblock; @@ -292,8 +317,6 @@ deque_popleft(dequeobject *deque, PyObject *unused) return item; } -PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element."); - /* The deque's size limit is d.maxlen. The limit can be zero or positive. * If there is no limit, then d.maxlen == -1. * @@ -326,7 +349,7 @@ deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) deque->rightindex++; deque->rightblock->data[deque->rightindex] = item; if (NEEDS_TRIM(deque, maxlen)) { - PyObject *olditem = deque_popleft(deque, NULL); + PyObject *olditem = deque_popleft_impl(deque); Py_DECREF(olditem); } else { deque->state++; @@ -334,16 +357,25 @@ deque_append_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) return 0; } +/*[clinic input] +_collections.deque.append as deque_append + + deque: dequeobject + item: object + / + +Add an element to the right side of the deque. +[clinic start generated code]*/ + static PyObject * deque_append(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=507b13efc4853ecc input=f112b83c380528e3]*/ { if (deque_append_internal(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(append_doc, "Add an element to the right side of the deque."); - static inline int deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) { @@ -362,7 +394,7 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) deque->leftindex--; deque->leftblock->data[deque->leftindex] = item; if (NEEDS_TRIM(deque, deque->maxlen)) { - PyObject *olditem = deque_pop(deque, NULL); + PyObject *olditem = deque_pop_impl(deque); Py_DECREF(olditem); } else { deque->state++; @@ -370,16 +402,25 @@ deque_appendleft_internal(dequeobject *deque, PyObject *item, Py_ssize_t maxlen) return 0; } +/*[clinic input] +_collections.deque.appendleft as deque_appendleft + + deque: dequeobject + item: object + / + +Add an element to the left side of the deque. +[clinic start generated code]*/ + static PyObject * deque_appendleft(dequeobject *deque, PyObject *item) +/*[clinic end generated code: output=de0335a64800ffd8 input=bbdaa60a3e956062]*/ { if (deque_appendleft_internal(deque, Py_NewRef(item), deque->maxlen) < 0) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(appendleft_doc, "Add an element to the left side of the deque."); - static PyObject* finalize_iterator(PyObject *it) { @@ -410,8 +451,19 @@ consume_iterator(PyObject *it) return finalize_iterator(it); } +/*[clinic input] +_collections.deque.extend as deque_extend + + deque: dequeobject + iterable: object + / + +Extend the right side of the deque with elements from the iterable. +[clinic start generated code]*/ + static PyObject * deque_extend(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=a3a6e74d17063f8d input=cfebfd34d5383339]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -454,11 +506,19 @@ deque_extend(dequeobject *deque, PyObject *iterable) return finalize_iterator(it); } -PyDoc_STRVAR(extend_doc, -"Extend the right side of the deque with elements from the iterable"); +/*[clinic input] +_collections.deque.extendleft as deque_extendleft + + deque: dequeobject + iterable: object + / + +Extend the left side of the deque with elements from the iterable. +[clinic start generated code]*/ static PyObject * deque_extendleft(dequeobject *deque, PyObject *iterable) +/*[clinic end generated code: output=2dba946c50498c67 input=f4820e695a6f9416]*/ { PyObject *it, *item; PyObject *(*iternext)(PyObject *); @@ -501,9 +561,6 @@ deque_extendleft(dequeobject *deque, PyObject *iterable) return finalize_iterator(it); } -PyDoc_STRVAR(extendleft_doc, -"Extend the left side of the deque with elements from the iterable"); - static PyObject * deque_inplace_concat(dequeobject *deque, PyObject *other) { @@ -517,8 +574,17 @@ deque_inplace_concat(dequeobject *deque, PyObject *other) return (PyObject *)deque; } +/*[clinic input] +_collections.deque.copy as deque_copy + + deque: dequeobject + +Return a shallow copy of a deque. +[clinic start generated code]*/ + static PyObject * -deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) +deque_copy_impl(dequeobject *deque) +/*[clinic end generated code: output=6409b3d1ad2898b5 input=0e22f138bc1fcbee]*/ { PyObject *result; dequeobject *old_deque = (dequeobject *)deque; @@ -537,7 +603,7 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) PyObject *item = old_deque->leftblock->data[old_deque->leftindex]; rv = deque_append(new_deque, item); } else { - rv = deque_extend(new_deque, deque); + rv = deque_extend(new_deque, (PyObject *)deque); } if (rv != NULL) { Py_DECREF(rv); @@ -547,7 +613,8 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) return NULL; } if (old_deque->maxlen < 0) - result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)), deque); + result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)), + (PyObject *)deque); else result = PyObject_CallFunction((PyObject *)(Py_TYPE(deque)), "Oi", deque, old_deque->maxlen, NULL); @@ -561,7 +628,18 @@ deque_copy(PyObject *deque, PyObject *Py_UNUSED(ignored)) return result; } -PyDoc_STRVAR(copy_doc, "Return a shallow copy of a deque."); +/*[clinic input] +_collections.deque.__copy__ as deque___copy__ = _collections.deque.copy + +Return a shallow copy of a deque. +[clinic start generated code]*/ + +static PyObject * +deque___copy___impl(dequeobject *deque) +/*[clinic end generated code: output=7c5821504342bf23 input=fce05df783e7912b]*/ +{ + return deque_copy_impl(deque); +} static PyObject * deque_concat(dequeobject *deque, PyObject *other) @@ -580,7 +658,7 @@ deque_concat(dequeobject *deque, PyObject *other) return NULL; } - new_deque = deque_copy((PyObject *)deque, NULL); + new_deque = deque_copy_impl(deque); if (new_deque == NULL) return NULL; result = deque_extend((dequeobject *)new_deque, other); @@ -669,22 +747,29 @@ deque_clear(dequeobject *deque) alternate_method: while (Py_SIZE(deque)) { - item = deque_pop(deque, NULL); + item = deque_pop_impl(deque); assert (item != NULL); Py_DECREF(item); } return 0; } +/*[clinic input] +_collections.deque.clear as deque_clearmethod + + deque: dequeobject + +Remove all elements from the deque. +[clinic start generated code]*/ + static PyObject * -deque_clearmethod(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque_clearmethod_impl(dequeobject *deque) +/*[clinic end generated code: output=79b2513e097615c1 input=20488eb932f89f9e]*/ { deque_clear(deque); Py_RETURN_NONE; } -PyDoc_STRVAR(clear_doc, "Remove all elements from the deque."); - static PyObject * deque_inplace_repeat(dequeobject *deque, Py_ssize_t n) { @@ -768,7 +853,7 @@ deque_repeat(dequeobject *deque, Py_ssize_t n) dequeobject *new_deque; PyObject *rv; - new_deque = (dequeobject *)deque_copy((PyObject *) deque, NULL); + new_deque = (dequeobject *)deque_copy_impl(deque); if (new_deque == NULL) return NULL; rv = deque_inplace_repeat(new_deque, n); @@ -925,36 +1010,36 @@ _deque_rotate(dequeobject *deque, Py_ssize_t n) return rv; } -static PyObject * -deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) -{ - Py_ssize_t n=1; +/*[clinic input] +_collections.deque.rotate as deque_rotate - if (!_PyArg_CheckPositional("deque.rotate", nargs, 0, 1)) { - return NULL; - } - if (nargs) { - PyObject *index = _PyNumber_Index(args[0]); - if (index == NULL) { - return NULL; - } - n = PyLong_AsSsize_t(index); - Py_DECREF(index); - if (n == -1 && PyErr_Occurred()) { - return NULL; - } - } + deque: dequeobject + n: Py_ssize_t = 1 + / +Rotate the deque n steps to the right. If n is negative, rotates left. +[clinic start generated code]*/ + +static PyObject * +deque_rotate_impl(dequeobject *deque, Py_ssize_t n) +/*[clinic end generated code: output=96c2402a371eb15d input=d22070f49cc06c76]*/ +{ if (!_deque_rotate(deque, n)) Py_RETURN_NONE; return NULL; } -PyDoc_STRVAR(rotate_doc, -"Rotate the deque n steps to the right (default n=1). If n is negative, rotates left."); +/*[clinic input] +_collections.deque.reverse as deque_reverse + + deque: dequeobject + +Reverse *IN PLACE*. +[clinic start generated code]*/ static PyObject * -deque_reverse(dequeobject *deque, PyObject *unused) +deque_reverse_impl(dequeobject *deque) +/*[clinic end generated code: output=bdeebc2cf8c1f064 input=f139787f406101c9]*/ { block *leftblock = deque->leftblock; block *rightblock = deque->rightblock; @@ -991,11 +1076,19 @@ deque_reverse(dequeobject *deque, PyObject *unused) Py_RETURN_NONE; } -PyDoc_STRVAR(reverse_doc, -"D.reverse() -- reverse *IN PLACE*"); +/*[clinic input] +_collections.deque.count as deque_count + + deque: dequeobject + value as v: object + / + +Return number of occurrences of value. +[clinic start generated code]*/ static PyObject * deque_count(dequeobject *deque, PyObject *v) +/*[clinic end generated code: output=7405d289d94d7b9b input=1892925260ff5d78]*/ { block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; @@ -1030,9 +1123,6 @@ deque_count(dequeobject *deque, PyObject *v) return PyLong_FromSsize_t(count); } -PyDoc_STRVAR(count_doc, -"D.count(value) -- return number of occurrences of value"); - static int deque_contains(dequeobject *deque, PyObject *v) { @@ -1071,22 +1161,33 @@ deque_len(dequeobject *deque) return Py_SIZE(deque); } +/*[clinic input] +@text_signature "($self, value, [start, [stop]])" +_collections.deque.index as deque_index + + deque: dequeobject + value as v: object + start: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', c_default='0') = NULL + stop: object(converter='_PyEval_SliceIndexNotNone', type='Py_ssize_t', c_default='Py_SIZE(deque)') = NULL + / + +Return first index of value. + +Raises ValueError if the value is not present. +[clinic start generated code]*/ + static PyObject * -deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, + Py_ssize_t stop) +/*[clinic end generated code: output=df45132753175ef9 input=140210c099830f64]*/ { - Py_ssize_t i, n, start=0, stop=Py_SIZE(deque); - PyObject *v, *item; + Py_ssize_t i, n; + PyObject *item; block *b = deque->leftblock; Py_ssize_t index = deque->leftindex; size_t start_state = deque->state; int cmp; - if (!_PyArg_ParseStack(args, nargs, "O|O&O&:index", &v, - _PyEval_SliceIndexNotNone, &start, - _PyEval_SliceIndexNotNone, &stop)) { - return NULL; - } - if (start < 0) { start += Py_SIZE(deque); if (start < 0) @@ -1138,10 +1239,6 @@ deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) return NULL; } -PyDoc_STRVAR(index_doc, -"D.index(value, [start, [stop]]) -- return first index of value.\n" -"Raises ValueError if the value is not present."); - /* insert(), remove(), and delitem() are implemented in terms of rotate() for simplicity and reasonable performance near the end points. If for some reason these methods become popular, it is not @@ -1150,18 +1247,24 @@ PyDoc_STRVAR(index_doc, boost (by moving each pointer only once instead of twice). */ +/*[clinic input] +_collections.deque.insert as deque_insert + + deque: dequeobject + index: Py_ssize_t + value: object + / + +Insert value before index. +[clinic start generated code]*/ + static PyObject * -deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value) +/*[clinic end generated code: output=ef4d2c15d5532b80 input=3e5c1c120d70c0e6]*/ { - Py_ssize_t index; Py_ssize_t n = Py_SIZE(deque); - PyObject *value; PyObject *rv; - if (!_PyArg_ParseStack(args, nargs, "nO:insert", &index, &value)) { - return NULL; - } - if (deque->maxlen == Py_SIZE(deque)) { PyErr_SetString(PyExc_IndexError, "deque already at its maximum size"); return NULL; @@ -1184,12 +1287,6 @@ deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) Py_RETURN_NONE; } -PyDoc_STRVAR(insert_doc, -"D.insert(index, object) -- insert object before index"); - -PyDoc_STRVAR(remove_doc, -"D.remove(value) -- remove first occurrence of value."); - static int valid_index(Py_ssize_t i, Py_ssize_t limit) { @@ -1246,15 +1343,26 @@ deque_del_item(dequeobject *deque, Py_ssize_t i) assert (i >= 0 && i < Py_SIZE(deque)); if (_deque_rotate(deque, -i)) return -1; - item = deque_popleft(deque, NULL); + item = deque_popleft_impl(deque); rv = _deque_rotate(deque, i); assert (item != NULL); Py_DECREF(item); return rv; } +/*[clinic input] +_collections.deque.remove as deque_remove + + deque: dequeobject + value: object + / + +Remove first occurrence of value. +[clinic start generated code]*/ + static PyObject * deque_remove(dequeobject *deque, PyObject *value) +/*[clinic end generated code: output=49e1666d612fe911 input=d972f32d15990880]*/ { PyObject *item; block *b = deque->leftblock; @@ -1375,8 +1483,17 @@ deque_traverse(dequeobject *deque, visitproc visit, void *arg) return 0; } +/*[clinic input] +_collections.deque.__reduce__ as deque___reduce__ + + deque: dequeobject + +Return state information for pickling. +[clinic start generated code]*/ + static PyObject * -deque_reduce(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque___reduce___impl(dequeobject *deque) +/*[clinic end generated code: output=cb85d9e0b7d2c5ad input=991a933a5bc7a526]*/ { PyObject *state, *it; @@ -1510,26 +1627,23 @@ deque_richcompare(PyObject *v, PyObject *w, int op) return NULL; } +/*[clinic input] +@text_signature "([iterable[, maxlen]])" +_collections.deque.__init__ as deque_init + + deque: dequeobject + iterable: object = NULL + maxlen as maxlenobj: object = NULL + +A list-like sequence optimized for data accesses near its endpoints. +[clinic start generated code]*/ + static int -deque_init(dequeobject *deque, PyObject *args, PyObject *kwdargs) +deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj) +/*[clinic end generated code: output=7084a39d71218dcd input=5ebdffc48a2d27ae]*/ + { - PyObject *iterable = NULL; - PyObject *maxlenobj = NULL; Py_ssize_t maxlen = -1; - char *kwlist[] = {"iterable", "maxlen", 0}; - - if (kwdargs == NULL && PyTuple_GET_SIZE(args) <= 2) { - if (PyTuple_GET_SIZE(args) > 0) { - iterable = PyTuple_GET_ITEM(args, 0); - } - if (PyTuple_GET_SIZE(args) > 1) { - maxlenobj = PyTuple_GET_ITEM(args, 1); - } - } else { - if (!PyArg_ParseTupleAndKeywords(args, kwdargs, "|OO:deque", kwlist, - &iterable, &maxlenobj)) - return -1; - } if (maxlenobj != NULL && maxlenobj != Py_None) { maxlen = PyLong_AsSsize_t(maxlenobj); if (maxlen == -1 && PyErr_Occurred()) @@ -1551,8 +1665,17 @@ deque_init(dequeobject *deque, PyObject *args, PyObject *kwdargs) return 0; } +/*[clinic input] +_collections.deque.__sizeof__ as deque___sizeof__ + + deque: dequeobject + +Return the size of the deque in memory, in bytes. +[clinic start generated code]*/ + static PyObject * -deque_sizeof(dequeobject *deque, void *unused) +deque___sizeof___impl(dequeobject *deque) +/*[clinic end generated code: output=4d36e9fb4f30bbaf input=4e7c9a00c03c3290]*/ { size_t res = _PyObject_SIZE(Py_TYPE(deque)); size_t blocks; @@ -1563,9 +1686,6 @@ deque_sizeof(dequeobject *deque, void *unused) return PyLong_FromSize_t(res); } -PyDoc_STRVAR(sizeof_doc, -"D.__sizeof__() -- size of D in memory, in bytes"); - static PyObject * deque_get_maxlen(dequeobject *deque, void *Py_UNUSED(ignored)) { @@ -1574,6 +1694,22 @@ deque_get_maxlen(dequeobject *deque, void *Py_UNUSED(ignored)) return PyLong_FromSsize_t(deque->maxlen); } +static PyObject *deque_reviter(dequeobject *deque); + +/*[clinic input] +_collections.deque.__reversed__ as deque___reversed__ + + deque: dequeobject + +Return a reverse iterator over the deque. +[clinic start generated code]*/ + +static PyObject * +deque___reversed___impl(dequeobject *deque) +/*[clinic end generated code: output=3e7e7e715883cf2e input=3d494c25a6fe5c7e]*/ +{ + return deque_reviter(deque); +} /* deque object ********************************************************/ @@ -1584,47 +1720,26 @@ static PyGetSetDef deque_getset[] = { }; static PyObject *deque_iter(dequeobject *deque); -static PyObject *deque_reviter(dequeobject *deque, PyObject *Py_UNUSED(ignored)); -PyDoc_STRVAR(reversed_doc, - "D.__reversed__() -- return a reverse iterator over the deque"); static PyMethodDef deque_methods[] = { - {"append", (PyCFunction)deque_append, - METH_O, append_doc}, - {"appendleft", (PyCFunction)deque_appendleft, - METH_O, appendleft_doc}, - {"clear", (PyCFunction)deque_clearmethod, - METH_NOARGS, clear_doc}, - {"__copy__", deque_copy, - METH_NOARGS, copy_doc}, - {"copy", deque_copy, - METH_NOARGS, copy_doc}, - {"count", (PyCFunction)deque_count, - METH_O, count_doc}, - {"extend", (PyCFunction)deque_extend, - METH_O, extend_doc}, - {"extendleft", (PyCFunction)deque_extendleft, - METH_O, extendleft_doc}, - {"index", _PyCFunction_CAST(deque_index), - METH_FASTCALL, index_doc}, - {"insert", _PyCFunction_CAST(deque_insert), - METH_FASTCALL, insert_doc}, - {"pop", (PyCFunction)deque_pop, - METH_NOARGS, pop_doc}, - {"popleft", (PyCFunction)deque_popleft, - METH_NOARGS, popleft_doc}, - {"__reduce__", (PyCFunction)deque_reduce, - METH_NOARGS, reduce_doc}, - {"remove", (PyCFunction)deque_remove, - METH_O, remove_doc}, - {"__reversed__", (PyCFunction)deque_reviter, - METH_NOARGS, reversed_doc}, - {"reverse", (PyCFunction)deque_reverse, - METH_NOARGS, reverse_doc}, - {"rotate", _PyCFunction_CAST(deque_rotate), - METH_FASTCALL, rotate_doc}, - {"__sizeof__", (PyCFunction)deque_sizeof, - METH_NOARGS, sizeof_doc}, + DEQUE_APPEND_METHODDEF + DEQUE_APPENDLEFT_METHODDEF + DEQUE_CLEARMETHOD_METHODDEF + DEQUE___COPY___METHODDEF + DEQUE_COPY_METHODDEF + DEQUE_COUNT_METHODDEF + DEQUE_EXTEND_METHODDEF + DEQUE_EXTENDLEFT_METHODDEF + DEQUE_INDEX_METHODDEF + DEQUE_INSERT_METHODDEF + DEQUE_POP_METHODDEF + DEQUE_POPLEFT_METHODDEF + DEQUE___REDUCE___METHODDEF + DEQUE_REMOVE_METHODDEF + DEQUE___REVERSED___METHODDEF + DEQUE_REVERSE_METHODDEF + DEQUE_ROTATE_METHODDEF + DEQUE___SIZEOF___METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ @@ -1635,17 +1750,12 @@ static PyMemberDef deque_members[] = { {NULL}, }; -PyDoc_STRVAR(deque_doc, -"deque([iterable[, maxlen]]) --> deque object\n\ -\n\ -A list-like sequence optimized for data accesses near its endpoints."); - static PyType_Slot deque_slots[] = { {Py_tp_dealloc, deque_dealloc}, {Py_tp_repr, deque_repr}, {Py_tp_hash, PyObject_HashNotImplemented}, {Py_tp_getattro, PyObject_GenericGetAttr}, - {Py_tp_doc, (void *)deque_doc}, + {Py_tp_doc, (void *)deque_init__doc__}, {Py_tp_traverse, deque_traverse}, {Py_tp_clear, deque_clear}, {Py_tp_richcompare, deque_richcompare}, @@ -1834,7 +1944,7 @@ static PyType_Spec dequeiter_spec = { /*********************** Deque Reverse Iterator **************************/ static PyObject * -deque_reviter(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +deque_reviter(dequeobject *deque) { dequeiterobject *it; collections_state *state = find_module_state_by_def(Py_TYPE(deque)); @@ -1889,7 +1999,7 @@ dequereviter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; assert(type == state->dequereviter_type); - it = (dequeiterobject*)deque_reviter((dequeobject *)deque, NULL); + it = (dequeiterobject *)deque_reviter((dequeobject *)deque); if (!it) return NULL; /* consume items from the queue */ diff --git a/Modules/clinic/_collectionsmodule.c.h b/Modules/clinic/_collectionsmodule.c.h index 591ab50c76a8e8f..60fb12a22316195 100644 --- a/Modules/clinic/_collectionsmodule.c.h +++ b/Modules/clinic/_collectionsmodule.c.h @@ -2,9 +2,425 @@ preserve [clinic start generated code]*/ +#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) +# include "pycore_gc.h" // PyGC_Head +# include "pycore_runtime.h" // _Py_ID() +#endif #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_CheckPositional() +PyDoc_STRVAR(deque_pop__doc__, +"pop($self, /)\n" +"--\n" +"\n" +"Remove and return the rightmost element."); + +#define DEQUE_POP_METHODDEF \ + {"pop", (PyCFunction)deque_pop, METH_NOARGS, deque_pop__doc__}, + +static PyObject * +deque_pop_impl(dequeobject *deque); + +static PyObject * +deque_pop(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_pop_impl(deque); +} + +PyDoc_STRVAR(deque_popleft__doc__, +"popleft($self, /)\n" +"--\n" +"\n" +"Remove and return the leftmost element."); + +#define DEQUE_POPLEFT_METHODDEF \ + {"popleft", (PyCFunction)deque_popleft, METH_NOARGS, deque_popleft__doc__}, + +static PyObject * +deque_popleft_impl(dequeobject *deque); + +static PyObject * +deque_popleft(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_popleft_impl(deque); +} + +PyDoc_STRVAR(deque_append__doc__, +"append($self, item, /)\n" +"--\n" +"\n" +"Add an element to the right side of the deque."); + +#define DEQUE_APPEND_METHODDEF \ + {"append", (PyCFunction)deque_append, METH_O, deque_append__doc__}, + +PyDoc_STRVAR(deque_appendleft__doc__, +"appendleft($self, item, /)\n" +"--\n" +"\n" +"Add an element to the left side of the deque."); + +#define DEQUE_APPENDLEFT_METHODDEF \ + {"appendleft", (PyCFunction)deque_appendleft, METH_O, deque_appendleft__doc__}, + +PyDoc_STRVAR(deque_extend__doc__, +"extend($self, iterable, /)\n" +"--\n" +"\n" +"Extend the right side of the deque with elements from the iterable."); + +#define DEQUE_EXTEND_METHODDEF \ + {"extend", (PyCFunction)deque_extend, METH_O, deque_extend__doc__}, + +PyDoc_STRVAR(deque_extendleft__doc__, +"extendleft($self, iterable, /)\n" +"--\n" +"\n" +"Extend the left side of the deque with elements from the iterable."); + +#define DEQUE_EXTENDLEFT_METHODDEF \ + {"extendleft", (PyCFunction)deque_extendleft, METH_O, deque_extendleft__doc__}, + +PyDoc_STRVAR(deque_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a deque."); + +#define DEQUE_COPY_METHODDEF \ + {"copy", (PyCFunction)deque_copy, METH_NOARGS, deque_copy__doc__}, + +static PyObject * +deque_copy_impl(dequeobject *deque); + +static PyObject * +deque_copy(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_copy_impl(deque); +} + +PyDoc_STRVAR(deque___copy____doc__, +"__copy__($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a deque."); + +#define DEQUE___COPY___METHODDEF \ + {"__copy__", (PyCFunction)deque___copy__, METH_NOARGS, deque___copy____doc__}, + +static PyObject * +deque___copy___impl(dequeobject *deque); + +static PyObject * +deque___copy__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___copy___impl(deque); +} + +PyDoc_STRVAR(deque_clearmethod__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all elements from the deque."); + +#define DEQUE_CLEARMETHOD_METHODDEF \ + {"clear", (PyCFunction)deque_clearmethod, METH_NOARGS, deque_clearmethod__doc__}, + +static PyObject * +deque_clearmethod_impl(dequeobject *deque); + +static PyObject * +deque_clearmethod(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_clearmethod_impl(deque); +} + +PyDoc_STRVAR(deque_rotate__doc__, +"rotate($self, n=1, /)\n" +"--\n" +"\n" +"Rotate the deque n steps to the right. If n is negative, rotates left."); + +#define DEQUE_ROTATE_METHODDEF \ + {"rotate", _PyCFunction_CAST(deque_rotate), METH_FASTCALL, deque_rotate__doc__}, + +static PyObject * +deque_rotate_impl(dequeobject *deque, Py_ssize_t n); + +static PyObject * +deque_rotate(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t n = 1; + + if (!_PyArg_CheckPositional("rotate", nargs, 0, 1)) { + goto exit; + } + if (nargs < 1) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + n = ival; + } +skip_optional: + return_value = deque_rotate_impl(deque, n); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_reverse__doc__, +"reverse($self, /)\n" +"--\n" +"\n" +"Reverse *IN PLACE*."); + +#define DEQUE_REVERSE_METHODDEF \ + {"reverse", (PyCFunction)deque_reverse, METH_NOARGS, deque_reverse__doc__}, + +static PyObject * +deque_reverse_impl(dequeobject *deque); + +static PyObject * +deque_reverse(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque_reverse_impl(deque); +} + +PyDoc_STRVAR(deque_count__doc__, +"count($self, value, /)\n" +"--\n" +"\n" +"Return number of occurrences of value."); + +#define DEQUE_COUNT_METHODDEF \ + {"count", (PyCFunction)deque_count, METH_O, deque_count__doc__}, + +PyDoc_STRVAR(deque_index__doc__, +"index($self, value, [start, [stop]])\n" +"--\n" +"\n" +"Return first index of value.\n" +"\n" +"Raises ValueError if the value is not present."); + +#define DEQUE_INDEX_METHODDEF \ + {"index", _PyCFunction_CAST(deque_index), METH_FASTCALL, deque_index__doc__}, + +static PyObject * +deque_index_impl(dequeobject *deque, PyObject *v, Py_ssize_t start, + Py_ssize_t stop); + +static PyObject * +deque_index(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *v; + Py_ssize_t start = 0; + Py_ssize_t stop = Py_SIZE(deque); + + if (!_PyArg_CheckPositional("index", nargs, 1, 3)) { + goto exit; + } + v = args[0]; + if (nargs < 2) { + goto skip_optional; + } + if (!_PyEval_SliceIndexNotNone(args[1], &start)) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + if (!_PyEval_SliceIndexNotNone(args[2], &stop)) { + goto exit; + } +skip_optional: + return_value = deque_index_impl(deque, v, start, stop); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_insert__doc__, +"insert($self, index, value, /)\n" +"--\n" +"\n" +"Insert value before index."); + +#define DEQUE_INSERT_METHODDEF \ + {"insert", _PyCFunction_CAST(deque_insert), METH_FASTCALL, deque_insert__doc__}, + +static PyObject * +deque_insert_impl(dequeobject *deque, Py_ssize_t index, PyObject *value); + +static PyObject * +deque_insert(dequeobject *deque, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + Py_ssize_t index; + PyObject *value; + + if (!_PyArg_CheckPositional("insert", nargs, 2, 2)) { + goto exit; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[0]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + index = ival; + } + value = args[1]; + return_value = deque_insert_impl(deque, index, value); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque_remove__doc__, +"remove($self, value, /)\n" +"--\n" +"\n" +"Remove first occurrence of value."); + +#define DEQUE_REMOVE_METHODDEF \ + {"remove", (PyCFunction)deque_remove, METH_O, deque_remove__doc__}, + +PyDoc_STRVAR(deque___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n" +"Return state information for pickling."); + +#define DEQUE___REDUCE___METHODDEF \ + {"__reduce__", (PyCFunction)deque___reduce__, METH_NOARGS, deque___reduce____doc__}, + +static PyObject * +deque___reduce___impl(dequeobject *deque); + +static PyObject * +deque___reduce__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___reduce___impl(deque); +} + +PyDoc_STRVAR(deque_init__doc__, +"deque([iterable[, maxlen]])\n" +"--\n" +"\n" +"A list-like sequence optimized for data accesses near its endpoints."); + +static int +deque_init_impl(dequeobject *deque, PyObject *iterable, PyObject *maxlenobj); + +static int +deque_init(PyObject *deque, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(iterable), &_Py_ID(maxlen), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"iterable", "maxlen", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "deque", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; + PyObject *iterable = NULL; + PyObject *maxlenobj = NULL; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 2, 0, argsbuf); + if (!fastargs) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (fastargs[0]) { + iterable = fastargs[0]; + if (!--noptargs) { + goto skip_optional_pos; + } + } + maxlenobj = fastargs[1]; +skip_optional_pos: + return_value = deque_init_impl((dequeobject *)deque, iterable, maxlenobj); + +exit: + return return_value; +} + +PyDoc_STRVAR(deque___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"Return the size of the deque in memory, in bytes."); + +#define DEQUE___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)deque___sizeof__, METH_NOARGS, deque___sizeof____doc__}, + +static PyObject * +deque___sizeof___impl(dequeobject *deque); + +static PyObject * +deque___sizeof__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___sizeof___impl(deque); +} + +PyDoc_STRVAR(deque___reversed____doc__, +"__reversed__($self, /)\n" +"--\n" +"\n" +"Return a reverse iterator over the deque."); + +#define DEQUE___REVERSED___METHODDEF \ + {"__reversed__", (PyCFunction)deque___reversed__, METH_NOARGS, deque___reversed____doc__}, + +static PyObject * +deque___reversed___impl(dequeobject *deque); + +static PyObject * +deque___reversed__(dequeobject *deque, PyObject *Py_UNUSED(ignored)) +{ + return deque___reversed___impl(deque); +} + PyDoc_STRVAR(_collections__count_elements__doc__, "_count_elements($module, mapping, iterable, /)\n" "--\n" @@ -72,4 +488,4 @@ tuplegetter_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=c896a72f8c45930d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3633a5cbc23e8440 input=a9049054013a1b77]*/ From 15fe8cea174772060b24c96d335a498aba3b8ed4 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 29 Jan 2024 16:45:31 +0100 Subject: [PATCH 083/263] gh-91325: Skip Stable ABI checks with Py_TRACE_REFS special build (GH-92046) Skip Stable ABI checks with Py_TRACE_REFS special build This build is not compatible with Py_LIMITED_API nor with the stable ABI. --- Lib/test/test_stable_abi_ctypes.py | 36 +++++++++++++++++++--------- Misc/stable_abi.toml | 4 ++++ Modules/_testcapi_feature_macros.inc | 9 +++++++ Tools/build/stable_abi.py | 19 ++++++++------- 4 files changed, 48 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 4976ac3642bbe46..90d452728384208 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -9,6 +9,13 @@ from _testcapi import get_feature_macros feature_macros = get_feature_macros() + +# Stable ABI is incompatible with Py_TRACE_REFS builds due to PyObject +# layout differences. +# See https://github.com/python/cpython/issues/88299#issuecomment-1113366226 +if feature_macros['Py_TRACE_REFS']: + raise unittest.SkipTest("incompatible with Py_TRACE_REFS.") + ctypes_test = import_module('ctypes') class TestStableABIAvailability(unittest.TestCase): @@ -441,7 +448,9 @@ def test_windows_feature_macros(self): "PyModule_AddObjectRef", "PyModule_AddStringConstant", "PyModule_AddType", + "PyModule_Create2", "PyModule_ExecDef", + "PyModule_FromDefAndSpec2", "PyModule_GetDef", "PyModule_GetDict", "PyModule_GetFilename", @@ -911,6 +920,13 @@ def test_windows_feature_macros(self): "_Py_TrueStruct", "_Py_VaBuildValue_SizeT", ) +if feature_macros['HAVE_FORK']: + SYMBOL_NAMES += ( + 'PyOS_AfterFork', + 'PyOS_AfterFork_Child', + 'PyOS_AfterFork_Parent', + 'PyOS_BeforeFork', + ) if feature_macros['MS_WINDOWS']: SYMBOL_NAMES += ( 'PyErr_SetExcFromWindowsErr', @@ -926,17 +942,6 @@ def test_windows_feature_macros(self): 'PyUnicode_DecodeMBCSStateful', 'PyUnicode_EncodeCodePage', ) -if feature_macros['HAVE_FORK']: - SYMBOL_NAMES += ( - 'PyOS_AfterFork', - 'PyOS_AfterFork_Child', - 'PyOS_AfterFork_Parent', - 'PyOS_BeforeFork', - ) -if feature_macros['USE_STACKCHECK']: - SYMBOL_NAMES += ( - 'PyOS_CheckStack', - ) if feature_macros['PY_HAVE_THREAD_NATIVE_ID']: SYMBOL_NAMES += ( 'PyThread_get_thread_native_id', @@ -946,14 +951,23 @@ def test_windows_feature_macros(self): '_Py_NegativeRefcount', '_Py_RefTotal', ) +if feature_macros['Py_TRACE_REFS']: + SYMBOL_NAMES += ( + ) +if feature_macros['USE_STACKCHECK']: + SYMBOL_NAMES += ( + 'PyOS_CheckStack', + ) EXPECTED_FEATURE_MACROS = set(['HAVE_FORK', 'MS_WINDOWS', 'PY_HAVE_THREAD_NATIVE_ID', 'Py_REF_DEBUG', + 'Py_TRACE_REFS', 'USE_STACKCHECK']) WINDOWS_FEATURE_MACROS = {'HAVE_FORK': False, 'MS_WINDOWS': True, 'PY_HAVE_THREAD_NATIVE_ID': True, 'Py_REF_DEBUG': 'maybe', + 'Py_TRACE_REFS': 'maybe', 'USE_STACKCHECK': 'maybe'} diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 22b25dd0ec141fd..2e6b0fff9cd7704 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -78,6 +78,10 @@ [feature_macro.Py_REF_DEBUG] doc = 'when Python is compiled in debug mode (with Py_REF_DEBUG)' windows = 'maybe' +[feature_macro.Py_TRACE_REFS] + # nb. This mode is not compatible with Stable ABI/Limited API. + doc = 'when Python is compiled with Py_TRACE_REFS' + windows = 'maybe' # Mentioned in PEP 384: diff --git a/Modules/_testcapi_feature_macros.inc b/Modules/_testcapi_feature_macros.inc index a076e7149800743..f5f3524f2c0177f 100644 --- a/Modules/_testcapi_feature_macros.inc +++ b/Modules/_testcapi_feature_macros.inc @@ -38,6 +38,15 @@ if (res) { Py_DECREF(result); return NULL; } +#ifdef Py_TRACE_REFS + res = PyDict_SetItemString(result, "Py_TRACE_REFS", Py_True); +#else + res = PyDict_SetItemString(result, "Py_TRACE_REFS", Py_False); +#endif +if (res) { + Py_DECREF(result); return NULL; +} + #ifdef USE_STACKCHECK res = PyDict_SetItemString(result, "USE_STACKCHECK", Py_True); #else diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 85c437d521a15ad..83146622c74f941 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -278,6 +278,13 @@ def gen_ctypes_test(manifest, args, outfile): from _testcapi import get_feature_macros feature_macros = get_feature_macros() + + # Stable ABI is incompatible with Py_TRACE_REFS builds due to PyObject + # layout differences. + # See https://github.com/python/cpython/issues/88299#issuecomment-1113366226 + if feature_macros['Py_TRACE_REFS']: + raise unittest.SkipTest("incompatible with Py_TRACE_REFS.") + ctypes_test = import_module('ctypes') class TestStableABIAvailability(unittest.TestCase): @@ -308,16 +315,11 @@ def test_windows_feature_macros(self): {'function', 'data'}, include_abi_only=True, ) - optional_items = {} + feature_macros = list(manifest.select({'feature_macro'})) + optional_items = {m.name: [] for m in feature_macros} for item in items: - if item.name in ( - # Some symbols aren't exported on all platforms. - # This is a bug: https://bugs.python.org/issue44133 - 'PyModule_Create2', 'PyModule_FromDefAndSpec2', - ): - continue if item.ifdef: - optional_items.setdefault(item.ifdef, []).append(item.name) + optional_items[item.ifdef].append(item.name) else: write(f' "{item.name}",') write(")") @@ -328,7 +330,6 @@ def test_windows_feature_macros(self): write(f" {name!r},") write(" )") write("") - feature_macros = list(manifest.select({'feature_macro'})) feature_names = sorted(m.name for m in feature_macros) write(f"EXPECTED_FEATURE_MACROS = set({pprint.pformat(feature_names)})") From 0f54ee4c6cdba74492183eb2dd142393c7dba403 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Mon, 29 Jan 2024 11:00:15 -0500 Subject: [PATCH 084/263] Remove limit in calendar CLI help message for year arg (GH-114719) The limit was removed in 66c88ce30ca2b23daa37038e1a3c0de98f241f50 (GH-4109). --- Lib/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/calendar.py b/Lib/calendar.py index 03469d8ac96bcd9..3c79540f986b63c 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -737,7 +737,7 @@ def main(args=None): parser.add_argument( "year", nargs='?', type=int, - help="year number (1-9999)" + help="year number" ) parser.add_argument( "month", From e351ca3c205860e94cad5da25c74bd76933f5f11 Mon Sep 17 00:00:00 2001 From: Soumendra Ganguly <67527439+8vasu@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:10:28 +0100 Subject: [PATCH 085/263] gh-85984: Add POSIX pseudo-terminal functions. (GH-102413) Signed-off-by: Soumendra Ganguly Co-authored-by: Gregory P. Smith Co-authored-by: Petr Viktorin --- Doc/conf.py | 5 + Doc/library/os.rst | 59 ++++++ Lib/test/test_os.py | 45 ++++- ...3-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst | 2 + Modules/clinic/posixmodule.c.h | 168 +++++++++++++++++- Modules/posixmodule.c | 147 +++++++++++++++ configure | 30 ++++ configure.ac | 8 +- pyconfig.h.in | 15 ++ 9 files changed, 468 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst diff --git a/Doc/conf.py b/Doc/conf.py index a96e7787d167a37..e12128ad356e1be 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -97,12 +97,16 @@ ('c:func', 'free'), ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), + ('c:func', 'grantpt'), ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), ('c:func', 'malloc'), ('c:func', 'mktime'), + ('c:func', 'posix_openpt'), ('c:func', 'printf'), + ('c:func', 'ptsname'), + ('c:func', 'ptsname_r'), ('c:func', 'realloc'), ('c:func', 'snprintf'), ('c:func', 'sprintf'), @@ -110,6 +114,7 @@ ('c:func', 'strftime'), ('c:func', 'system'), ('c:func', 'time'), + ('c:func', 'unlockpt'), ('c:func', 'vsnprintf'), # Standard C types ('c:type', 'FILE'), diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 0008ec6a40c76ff..cc9f3e75a80c517 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1122,6 +1122,20 @@ as internal buffering of data. .. versionchanged:: 3.12 Added support for pipes on Windows. + +.. function:: grantpt(fd, /) + + Grant access to the slave pseudo-terminal device associated with the + master pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the C standard library function :c:func:`grantpt`. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: isatty(fd, /) Return ``True`` if the file descriptor *fd* is open and connected to a @@ -1429,6 +1443,23 @@ or `the MSDN `_ on Windo .. versionadded:: 3.3 +.. function:: posix_openpt(oflag, /) + + Open and return a file descriptor for a master pseudo-terminal device. + + Calls the C standard library function :c:func:`posix_openpt`. The *oflag* + argument is used to set file status flags and file access modes as + specified in the manual page of :c:func:`posix_openpt` of your system. + + The returned file descriptor is :ref:`non-inheritable `. + If the value :data:`O_CLOEXEC` is available on the system, it is added to + *oflag*. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: preadv(fd, buffers, offset, flags=0, /) Read from a file descriptor *fd* at a position of *offset* into mutable @@ -1486,6 +1517,21 @@ or `the MSDN `_ on Windo .. versionadded:: 3.7 +.. function:: ptsname(fd, /) + + Return the name of the slave pseudo-terminal device associated with the + master pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the reentrant C standard library function :c:func:`ptsname_r` if + it is available; otherwise, the C standard library function + :c:func:`ptsname`, which is not guaranteed to be thread-safe, is called. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: pwrite(fd, str, offset, /) Write the bytestring in *str* to file descriptor *fd* at position of @@ -1738,6 +1784,19 @@ or `the MSDN `_ on Windo .. availability:: Unix. +.. function:: unlockpt(fd, /) + + Unlock the slave pseudo-terminal device associated with the master + pseudo-terminal device to which the file descriptor *fd* refers. + The file descriptor *fd* is not closed upon failure. + + Calls the C standard library function :c:func:`unlockpt`. + + .. availability:: Unix, not Emscripten, not WASI. + + .. versionadded:: 3.13 + + .. function:: write(fd, str, /) Write the bytestring in *str* to file descriptor *fd*. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index e6f0dfde8cb4aea..ed79a2c24ef30bf 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -4536,13 +4536,46 @@ def test_dup2(self): self.assertEqual(os.dup2(fd, fd3, inheritable=False), fd3) self.assertFalse(os.get_inheritable(fd3)) - @unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") +@unittest.skipUnless(hasattr(os, 'openpty'), "need os.openpty()") +class PseudoterminalTests(unittest.TestCase): + def open_pty(self): + """Open a pty fd-pair, and schedule cleanup for it""" + main_fd, second_fd = os.openpty() + self.addCleanup(os.close, main_fd) + self.addCleanup(os.close, second_fd) + return main_fd, second_fd + def test_openpty(self): - master_fd, slave_fd = os.openpty() - self.addCleanup(os.close, master_fd) - self.addCleanup(os.close, slave_fd) - self.assertEqual(os.get_inheritable(master_fd), False) - self.assertEqual(os.get_inheritable(slave_fd), False) + main_fd, second_fd = self.open_pty() + self.assertEqual(os.get_inheritable(main_fd), False) + self.assertEqual(os.get_inheritable(second_fd), False) + + @unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()") + @unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR") + @unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY") + def test_open_via_ptsname(self): + main_fd, second_fd = self.open_pty() + second_path = os.ptsname(main_fd) + reopened_second_fd = os.open(second_path, os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, reopened_second_fd) + os.write(reopened_second_fd, b'foo') + self.assertEqual(os.read(main_fd, 3), b'foo') + + @unittest.skipUnless(hasattr(os, 'posix_openpt'), "need os.posix_openpt()") + @unittest.skipUnless(hasattr(os, 'grantpt'), "need os.grantpt()") + @unittest.skipUnless(hasattr(os, 'unlockpt'), "need os.unlockpt()") + @unittest.skipUnless(hasattr(os, 'ptsname'), "need os.ptsname()") + @unittest.skipUnless(hasattr(os, 'O_RDWR'), "need os.O_RDWR") + @unittest.skipUnless(hasattr(os, 'O_NOCTTY'), "need os.O_NOCTTY") + def test_posix_pty_functions(self): + mother_fd = os.posix_openpt(os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, mother_fd) + os.grantpt(mother_fd) + os.unlockpt(mother_fd) + son_path = os.ptsname(mother_fd) + son_fd = os.open(son_path, os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, son_fd) + self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd)) @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.openpty()") def test_pipe_spawnl(self): diff --git a/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst b/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst new file mode 100644 index 000000000000000..0e54a1fe3c8a1cf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-15-03-21-18.gh-issue-85984.Xaq6ZN.rst @@ -0,0 +1,2 @@ +Add POSIX pseudo-terminal functions :func:`os.posix_openpt`, +:func:`os.grantpt`, :func:`os.unlockpt`, and :func:`os.ptsname`. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index ba3e1cfa8dbc21b..1373bdef03ba5e5 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4465,6 +4465,156 @@ os_sched_getaffinity(PyObject *module, PyObject *arg) #endif /* defined(HAVE_SCHED_H) && defined(HAVE_SCHED_SETAFFINITY) */ +#if defined(HAVE_POSIX_OPENPT) + +PyDoc_STRVAR(os_posix_openpt__doc__, +"posix_openpt($module, oflag, /)\n" +"--\n" +"\n" +"Open and return a file descriptor for a master pseudo-terminal device.\n" +"\n" +"Performs a posix_openpt() C function call. The oflag argument is used to\n" +"set file status flags and file access modes as specified in the manual page\n" +"of posix_openpt() of your system."); + +#define OS_POSIX_OPENPT_METHODDEF \ + {"posix_openpt", (PyCFunction)os_posix_openpt, METH_O, os_posix_openpt__doc__}, + +static int +os_posix_openpt_impl(PyObject *module, int oflag); + +static PyObject * +os_posix_openpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int oflag; + int _return_value; + + oflag = PyLong_AsInt(arg); + if (oflag == -1 && PyErr_Occurred()) { + goto exit; + } + _return_value = os_posix_openpt_impl(module, oflag); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_POSIX_OPENPT) */ + +#if defined(HAVE_GRANTPT) + +PyDoc_STRVAR(os_grantpt__doc__, +"grantpt($module, fd, /)\n" +"--\n" +"\n" +"Grant access to the slave pseudo-terminal device.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"Performs a grantpt() C function call."); + +#define OS_GRANTPT_METHODDEF \ + {"grantpt", (PyCFunction)os_grantpt, METH_O, os_grantpt__doc__}, + +static PyObject * +os_grantpt_impl(PyObject *module, int fd); + +static PyObject * +os_grantpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_grantpt_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_GRANTPT) */ + +#if defined(HAVE_UNLOCKPT) + +PyDoc_STRVAR(os_unlockpt__doc__, +"unlockpt($module, fd, /)\n" +"--\n" +"\n" +"Unlock a pseudo-terminal master/slave pair.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"Performs an unlockpt() C function call."); + +#define OS_UNLOCKPT_METHODDEF \ + {"unlockpt", (PyCFunction)os_unlockpt, METH_O, os_unlockpt__doc__}, + +static PyObject * +os_unlockpt_impl(PyObject *module, int fd); + +static PyObject * +os_unlockpt(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_unlockpt_impl(module, fd); + +exit: + return return_value; +} + +#endif /* defined(HAVE_UNLOCKPT) */ + +#if (defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R)) + +PyDoc_STRVAR(os_ptsname__doc__, +"ptsname($module, fd, /)\n" +"--\n" +"\n" +"Return the name of the slave pseudo-terminal device.\n" +"\n" +" fd\n" +" File descriptor of a master pseudo-terminal device.\n" +"\n" +"If the ptsname_r() C function is available, it is called;\n" +"otherwise, performs a ptsname() C function call."); + +#define OS_PTSNAME_METHODDEF \ + {"ptsname", (PyCFunction)os_ptsname, METH_O, os_ptsname__doc__}, + +static PyObject * +os_ptsname_impl(PyObject *module, int fd); + +static PyObject * +os_ptsname(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + + if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + goto exit; + } + return_value = os_ptsname_impl(module, fd); + +exit: + return return_value; +} + +#endif /* (defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R)) */ + #if (defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(HAVE_DEV_PTMX)) PyDoc_STRVAR(os_openpty__doc__, @@ -11991,6 +12141,22 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #define OS_SCHED_GETAFFINITY_METHODDEF #endif /* !defined(OS_SCHED_GETAFFINITY_METHODDEF) */ +#ifndef OS_POSIX_OPENPT_METHODDEF + #define OS_POSIX_OPENPT_METHODDEF +#endif /* !defined(OS_POSIX_OPENPT_METHODDEF) */ + +#ifndef OS_GRANTPT_METHODDEF + #define OS_GRANTPT_METHODDEF +#endif /* !defined(OS_GRANTPT_METHODDEF) */ + +#ifndef OS_UNLOCKPT_METHODDEF + #define OS_UNLOCKPT_METHODDEF +#endif /* !defined(OS_UNLOCKPT_METHODDEF) */ + +#ifndef OS_PTSNAME_METHODDEF + #define OS_PTSNAME_METHODDEF +#endif /* !defined(OS_PTSNAME_METHODDEF) */ + #ifndef OS_OPENPTY_METHODDEF #define OS_OPENPTY_METHODDEF #endif /* !defined(OS_OPENPTY_METHODDEF) */ @@ -12422,4 +12588,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=18c128534c355d84 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=43e4e557c771358a input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 007fc1cb116f84c..40ff131b119d66f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8358,6 +8358,149 @@ os_sched_getaffinity_impl(PyObject *module, pid_t pid) #endif /* HAVE_SCHED_H */ +#ifdef HAVE_POSIX_OPENPT +/*[clinic input] +os.posix_openpt -> int + + oflag: int + / + +Open and return a file descriptor for a master pseudo-terminal device. + +Performs a posix_openpt() C function call. The oflag argument is used to +set file status flags and file access modes as specified in the manual page +of posix_openpt() of your system. +[clinic start generated code]*/ + +static int +os_posix_openpt_impl(PyObject *module, int oflag) +/*[clinic end generated code: output=ee0bc2624305fc79 input=0de33d0e29693caa]*/ +{ + int fd; + +#if defined(O_CLOEXEC) + oflag |= O_CLOEXEC; +#endif + + fd = posix_openpt(oflag); + if (fd == -1) { + posix_error(); + return -1; + } + + // Just in case, likely a no-op given O_CLOEXEC above. + if (_Py_set_inheritable(fd, 0, NULL) < 0) { + close(fd); + return -1; + } + + return fd; +} +#endif /* HAVE_POSIX_OPENPT */ + +#ifdef HAVE_GRANTPT +/*[clinic input] +os.grantpt + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Grant access to the slave pseudo-terminal device. + +Performs a grantpt() C function call. +[clinic start generated code]*/ + +static PyObject * +os_grantpt_impl(PyObject *module, int fd) +/*[clinic end generated code: output=dfd580015cf548ab input=0668e3b96760e849]*/ +{ + int ret; + int saved_errno; + PyOS_sighandler_t sig_saved; + + sig_saved = PyOS_setsig(SIGCHLD, SIG_DFL); + + ret = grantpt(fd); + if (ret == -1) + saved_errno = errno; + + PyOS_setsig(SIGCHLD, sig_saved); + + if (ret == -1) { + errno = saved_errno; + return posix_error(); + } + + Py_RETURN_NONE; +} +#endif /* HAVE_GRANTPT */ + +#ifdef HAVE_UNLOCKPT +/*[clinic input] +os.unlockpt + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Unlock a pseudo-terminal master/slave pair. + +Performs an unlockpt() C function call. +[clinic start generated code]*/ + +static PyObject * +os_unlockpt_impl(PyObject *module, int fd) +/*[clinic end generated code: output=e08d354dec12d30c input=de7ab1f59f69a2b4]*/ +{ + if (unlockpt(fd) == -1) + return posix_error(); + + Py_RETURN_NONE; +} +#endif /* HAVE_UNLOCKPT */ + +#if defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R) +/*[clinic input] +os.ptsname + + fd: fildes + File descriptor of a master pseudo-terminal device. + / + +Return the name of the slave pseudo-terminal device. + +If the ptsname_r() C function is available, it is called; +otherwise, performs a ptsname() C function call. +[clinic start generated code]*/ + +static PyObject * +os_ptsname_impl(PyObject *module, int fd) +/*[clinic end generated code: output=ef300fadc5675872 input=1369ccc0546f3130]*/ +{ +#ifdef HAVE_PTSNAME_R + int ret; + char name[MAXPATHLEN+1]; + + ret = ptsname_r(fd, name, sizeof(name)); + if (ret != 0) { + errno = ret; + return posix_error(); + } +#else + char *name; + + name = ptsname(fd); + /* POSIX manpage: Upon failure, ptsname() shall return a null pointer and may set errno. + *MAY* set errno? Hmm... */ + if (name == NULL) + return posix_error(); +#endif /* HAVE_PTSNAME_R */ + + return PyUnicode_DecodeFSDefault(name); +} +#endif /* defined(HAVE_PTSNAME) || defined(HAVE_PTSNAME_R) */ + /* AIX uses /dev/ptc but is otherwise the same as /dev/ptmx */ #if defined(HAVE_DEV_PTC) && !defined(HAVE_DEV_PTMX) # define DEV_PTY_FILE "/dev/ptc" @@ -16275,6 +16418,10 @@ static PyMethodDef posix_methods[] = { OS_SCHED_YIELD_METHODDEF OS_SCHED_SETAFFINITY_METHODDEF OS_SCHED_GETAFFINITY_METHODDEF + OS_POSIX_OPENPT_METHODDEF + OS_GRANTPT_METHODDEF + OS_UNLOCKPT_METHODDEF + OS_PTSNAME_METHODDEF OS_OPENPTY_METHODDEF OS_LOGIN_TTY_METHODDEF OS_FORKPTY_METHODDEF diff --git a/configure b/configure index c563c3f5d3c7e6d..adc5a8f014c795a 100755 --- a/configure +++ b/configure @@ -17637,6 +17637,12 @@ if test "x$ac_cv_func_getwd" = xyes then : printf "%s\n" "#define HAVE_GETWD 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "grantpt" "ac_cv_func_grantpt" +if test "x$ac_cv_func_grantpt" = xyes +then : + printf "%s\n" "#define HAVE_GRANTPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "if_nameindex" "ac_cv_func_if_nameindex" if test "x$ac_cv_func_if_nameindex" = xyes @@ -17823,6 +17829,12 @@ if test "x$ac_cv_func_posix_fallocate" = xyes then : printf "%s\n" "#define HAVE_POSIX_FALLOCATE 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "posix_openpt" "ac_cv_func_posix_openpt" +if test "x$ac_cv_func_posix_openpt" = xyes +then : + printf "%s\n" "#define HAVE_POSIX_OPENPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "posix_spawn" "ac_cv_func_posix_spawn" if test "x$ac_cv_func_posix_spawn" = xyes @@ -17877,6 +17889,18 @@ if test "x$ac_cv_func_pthread_kill" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_KILL 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "ptsname" "ac_cv_func_ptsname" +if test "x$ac_cv_func_ptsname" = xyes +then : + printf "%s\n" "#define HAVE_PTSNAME 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "ptsname_r" "ac_cv_func_ptsname_r" +if test "x$ac_cv_func_ptsname_r" = xyes +then : + printf "%s\n" "#define HAVE_PTSNAME_R 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pwrite" "ac_cv_func_pwrite" if test "x$ac_cv_func_pwrite" = xyes @@ -18285,6 +18309,12 @@ if test "x$ac_cv_func_unlinkat" = xyes then : printf "%s\n" "#define HAVE_UNLINKAT 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "unlockpt" "ac_cv_func_unlockpt" +if test "x$ac_cv_func_unlockpt" = xyes +then : + printf "%s\n" "#define HAVE_UNLOCKPT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "utimensat" "ac_cv_func_utimensat" if test "x$ac_cv_func_utimensat" = xyes diff --git a/configure.ac b/configure.ac index 13c46b3e80151dc..f5fa17fd8b7d0dc 100644 --- a/configure.ac +++ b/configure.ac @@ -4791,12 +4791,12 @@ AC_CHECK_FUNCS([ \ getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ - getspnam getuid getwd if_nameindex initgroups kill killpg lchown linkat \ + getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ - pipe2 plock poll posix_fadvise posix_fallocate posix_spawn posix_spawnp \ + pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ + pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill ptsname ptsname_r \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ @@ -4806,7 +4806,7 @@ AC_CHECK_FUNCS([ \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ - tmpnam tmpnam_r truncate ttyname umask uname unlinkat utimensat utimes vfork \ + tmpnam tmpnam_r truncate ttyname umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ ]) diff --git a/pyconfig.h.in b/pyconfig.h.in index d8a9f68951afbd1..02e33c7007196d3 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -601,6 +601,9 @@ bcopy. */ #undef HAVE_GLIBC_MEMMOVE_BUG +/* Define to 1 if you have the `grantpt' function. */ +#undef HAVE_GRANTPT + /* Define to 1 if you have the header file. */ #undef HAVE_GRP_H @@ -899,6 +902,9 @@ /* Define to 1 if you have the `posix_fallocate' function. */ #undef HAVE_POSIX_FALLOCATE +/* Define to 1 if you have the `posix_openpt' function. */ +#undef HAVE_POSIX_OPENPT + /* Define to 1 if you have the `posix_spawn' function. */ #undef HAVE_POSIX_SPAWN @@ -951,6 +957,12 @@ /* Define if platform requires stubbed pthreads support */ #undef HAVE_PTHREAD_STUBS +/* Define to 1 if you have the `ptsname' function. */ +#undef HAVE_PTSNAME + +/* Define to 1 if you have the `ptsname_r' function. */ +#undef HAVE_PTSNAME_R + /* Define to 1 if you have the header file. */ #undef HAVE_PTY_H @@ -1459,6 +1471,9 @@ /* Define to 1 if you have the `unlinkat' function. */ #undef HAVE_UNLINKAT +/* Define to 1 if you have the `unlockpt' function. */ +#undef HAVE_UNLOCKPT + /* Define to 1 if you have the `unshare' function. */ #undef HAVE_UNSHARE From 39c766b579cabc71a4a50773d299d4350221a70b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 29 Jan 2024 18:20:13 +0200 Subject: [PATCH 086/263] Fix more references to datetime and time classes (GH-114717) They could be confused with references to datetime and time modules. --- Doc/library/datetime.rst | 8 ++++---- Doc/library/mailbox.rst | 8 ++++---- Doc/whatsnew/3.8.rst | 4 ++-- Misc/NEWS.d/3.13.0a1.rst | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 47ecb0ba331bdc4..4ff049c8709289c 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1255,7 +1255,7 @@ Instance methods: ``tzinfo=None`` can be specified to create a naive datetime from an aware datetime with no conversion of date and time data. - :class:`datetime` objects are also supported by generic function + :class:`.datetime` objects are also supported by generic function :func:`copy.replace`. .. versionchanged:: 3.6 @@ -1678,7 +1678,7 @@ Usage of ``KabulTz`` from above:: :class:`.time` Objects ---------------------- -A :class:`time` object represents a (local) time of day, independent of any particular +A :class:`.time` object represents a (local) time of day, independent of any particular day, and subject to adjustment via a :class:`tzinfo` object. .. class:: time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0) @@ -1836,7 +1836,7 @@ Instance methods: ``tzinfo=None`` can be specified to create a naive :class:`.time` from an aware :class:`.time`, without conversion of the time data. - :class:`time` objects are also supported by generic function + :class:`.time` objects are also supported by generic function :func:`copy.replace`. .. versionchanged:: 3.6 @@ -2522,7 +2522,7 @@ information, which are supported in ``datetime.strptime`` but are discarded by ``time.strptime``. For :class:`.time` objects, the format codes for year, month, and day should not -be used, as :class:`time` objects have no such values. If they're used anyway, +be used, as :class:`!time` objects have no such values. If they're used anyway, ``1900`` is substituted for the year, and ``1`` for the month and day. For :class:`date` objects, the format codes for hours, minutes, seconds, and diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index fa5b273093f583f..a613548c9e518e0 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -1136,8 +1136,8 @@ When a :class:`!MaildirMessage` instance is created based upon a leading "From " or trailing newline. For convenience, *time_* may be specified and will be formatted appropriately and appended to *from_*. If *time_* is specified, it should be a :class:`time.struct_time` instance, a - tuple suitable for passing to :meth:`time.strftime`, or ``True`` (to use - :meth:`time.gmtime`). + tuple suitable for passing to :func:`time.strftime`, or ``True`` (to use + :func:`time.gmtime`). .. method:: get_flags() @@ -1508,8 +1508,8 @@ When a :class:`!BabylMessage` instance is created based upon an leading "From " or trailing newline. For convenience, *time_* may be specified and will be formatted appropriately and appended to *from_*. If *time_* is specified, it should be a :class:`time.struct_time` instance, a - tuple suitable for passing to :meth:`time.strftime`, or ``True`` (to use - :meth:`time.gmtime`). + tuple suitable for passing to :func:`time.strftime`, or ``True`` (to use + :func:`time.gmtime`). .. method:: get_flags() diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 304d1b4ef4efe80..b041e592d61ed11 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -754,8 +754,8 @@ datetime -------- Added new alternate constructors :meth:`datetime.date.fromisocalendar` and -:meth:`datetime.datetime.fromisocalendar`, which construct :class:`date` and -:class:`datetime` objects respectively from ISO year, week number, and weekday; +:meth:`datetime.datetime.fromisocalendar`, which construct :class:`~datetime.date` and +:class:`~datetime.datetime` objects respectively from ISO year, week number, and weekday; these are the inverse of each class's ``isocalendar`` method. (Contributed by Paul Ganssle in :issue:`36004`.) diff --git a/Misc/NEWS.d/3.13.0a1.rst b/Misc/NEWS.d/3.13.0a1.rst index 102bddcee5c5c2a..d385b6a4504f97a 100644 --- a/Misc/NEWS.d/3.13.0a1.rst +++ b/Misc/NEWS.d/3.13.0a1.rst @@ -2276,7 +2276,7 @@ creation. .. nonce: m2H5Bk .. section: Library -Remove unnecessary extra ``__slots__`` in :py:class:`datetime`\'s pure +Remove unnecessary extra ``__slots__`` in :class:`~datetime.datetime`\'s pure python implementation to reduce memory size, as they are defined in the superclass. Patch by James Hilton-Balfe From 2c089b09ac0872e08d146c55ed60d754154761c3 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Mon, 29 Jan 2024 11:58:21 -0500 Subject: [PATCH 087/263] gh-112240: Add option to calendar module CLI to specify the weekday to start each week (GH-112241) --- Doc/library/calendar.rst | 8 +++++++- Lib/calendar.py | 7 +++++++ .../2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 6586f539a8da4fc..c4dcf5641d60663 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -586,10 +586,16 @@ The following options are accepted: or as an HTML document. +.. option:: --first-weekday WEEKDAY, -f WEEKDAY + + The weekday to start each week. + Must be a number between 0 (Monday) and 6 (Sunday). + Defaults to 0. + + .. option:: year The year to print the calendar for. - Must be a number between 1 and 9999. Defaults to the current year. diff --git a/Lib/calendar.py b/Lib/calendar.py index 3c79540f986b63c..833ce331b14a0cc 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -734,6 +734,11 @@ def main(args=None): choices=("text", "html"), help="output type (text or html)" ) + parser.add_argument( + "-f", "--first-weekday", + type=int, default=0, + help="weekday (0 is Monday, 6 is Sunday) to start each week (default 0)" + ) parser.add_argument( "year", nargs='?', type=int, @@ -761,6 +766,7 @@ def main(args=None): cal = LocaleHTMLCalendar(locale=locale) else: cal = HTMLCalendar() + cal.setfirstweekday(options.first_weekday) encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() @@ -775,6 +781,7 @@ def main(args=None): cal = LocaleTextCalendar(locale=locale) else: cal = TextCalendar() + cal.setfirstweekday(options.first_weekday) optdict = dict(w=options.width, l=options.lines) if options.month is None: optdict["c"] = options.spacing diff --git a/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst b/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst new file mode 100644 index 000000000000000..686f0311e80dcbf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-18-16-30-21.gh-issue-112240.YXS0tj.rst @@ -0,0 +1,2 @@ +Add option to calendar module CLI to specify the weekday to start each week. +Patch by Steven Ward. From 3d716655d22dc14e79ac0d30f33eef0a49efdac0 Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Mon, 29 Jan 2024 09:38:03 -0800 Subject: [PATCH 088/263] gh-112075: Use PyMem_* for allocating dict keys objects (#114543) Use PyMem_* for keys allocation --- Objects/dictobject.c | 66 +++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e608b91679b5688..c5477ab15f8dc9a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -262,7 +262,7 @@ _PyDict_ClearFreeList(PyInterpreterState *interp) PyObject_GC_Del(op); } while (state->keys_numfree) { - PyObject_Free(state->keys_free_list[--state->keys_numfree]); + PyMem_Free(state->keys_free_list[--state->keys_numfree]); } #endif } @@ -332,6 +332,22 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) _Py_DecRefTotal(_PyInterpreterState_GET()); #endif if (--dk->dk_refcnt == 0) { + if (DK_IS_UNICODE(dk)) { + PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dk); + Py_ssize_t i, n; + for (i = 0, n = dk->dk_nentries; i < n; i++) { + Py_XDECREF(entries[i].me_key); + Py_XDECREF(entries[i].me_value); + } + } + else { + PyDictKeyEntry *entries = DK_ENTRIES(dk); + Py_ssize_t i, n; + for (i = 0, n = dk->dk_nentries; i < n; i++) { + Py_XDECREF(entries[i].me_key); + Py_XDECREF(entries[i].me_value); + } + } free_keys_object(interp, dk); } } @@ -640,9 +656,9 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) else #endif { - dk = PyObject_Malloc(sizeof(PyDictKeysObject) - + ((size_t)1 << log2_bytes) - + entry_size * usable); + dk = PyMem_Malloc(sizeof(PyDictKeysObject) + + ((size_t)1 << log2_bytes) + + entry_size * usable); if (dk == NULL) { PyErr_NoMemory(); return NULL; @@ -666,23 +682,6 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) { - assert(keys != Py_EMPTY_KEYS); - if (DK_IS_UNICODE(keys)) { - PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(keys); - Py_ssize_t i, n; - for (i = 0, n = keys->dk_nentries; i < n; i++) { - Py_XDECREF(entries[i].me_key); - Py_XDECREF(entries[i].me_value); - } - } - else { - PyDictKeyEntry *entries = DK_ENTRIES(keys); - Py_ssize_t i, n; - for (i = 0, n = keys->dk_nentries; i < n; i++) { - Py_XDECREF(entries[i].me_key); - Py_XDECREF(entries[i].me_value); - } - } #if PyDict_MAXFREELIST > 0 struct _Py_dict_state *state = get_dict_state(interp); #ifdef Py_DEBUG @@ -697,7 +696,7 @@ free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) return; } #endif - PyObject_Free(keys); + PyMem_Free(keys); } static inline PyDictValues* @@ -798,7 +797,7 @@ clone_combined_dict_keys(PyDictObject *orig) assert(orig->ma_keys->dk_refcnt == 1); size_t keys_size = _PyDict_KeysSize(orig->ma_keys); - PyDictKeysObject *keys = PyObject_Malloc(keys_size); + PyDictKeysObject *keys = PyMem_Malloc(keys_size); if (keys == NULL) { PyErr_NoMemory(); return NULL; @@ -1544,32 +1543,13 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, } } - // We can not use free_keys_object here because key's reference - // are moved already. if (oldkeys != Py_EMPTY_KEYS) { #ifdef Py_REF_DEBUG _Py_DecRefTotal(_PyInterpreterState_GET()); #endif assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); assert(oldkeys->dk_refcnt == 1); -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // dictresize() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif - if (DK_LOG_SIZE(oldkeys) == PyDict_LOG_MINSIZE && - DK_IS_UNICODE(oldkeys) && - state->keys_numfree < PyDict_MAXFREELIST) - { - state->keys_free_list[state->keys_numfree++] = oldkeys; - OBJECT_STAT_INC(to_freelist); - } - else -#endif - { - PyObject_Free(oldkeys); - } + free_keys_object(interp, oldkeys); } } From 0cd9bacb8ad41fe86f95b326e9199caa749539eb Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Mon, 29 Jan 2024 09:47:54 -0800 Subject: [PATCH 089/263] gh-112075: Dictionary global version counter should use atomic increments (#114568) Dictionary global version counter should use atomic increments --- Include/internal/pycore_dict.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index d96870e9197bbf0..b4e1f8cf1e320b3 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -209,8 +209,14 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS) #define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1) +#ifdef Py_GIL_DISABLED +#define DICT_NEXT_VERSION(INTERP) \ + (_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT) + +#else #define DICT_NEXT_VERSION(INTERP) \ ((INTERP)->dict_state.global_version += DICT_VERSION_INCREMENT) +#endif void _PyDict_SendEvent(int watcher_bits, From aa3402ad451777d8dd3ec560e14cb16dc8540c0e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 29 Jan 2024 19:58:31 +0200 Subject: [PATCH 090/263] gh-114678: Fix incorrect deprecation warning for 'N' specifier in Decimal format (GH-114683) Co-authored-by: Stefan Krah --- Lib/test/test_decimal.py | 10 +++++++++- .../2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst | 3 +++ Modules/_decimal/_decimal.c | 14 ++++++++------ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 7a5fe62b4673720..1423bc61c7f6906 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -41,6 +41,7 @@ darwin_malloc_err_warning, is_emscripten) from test.support.import_helper import import_fresh_module from test.support import threading_helper +from test.support import warnings_helper import random import inspect import threading @@ -1237,7 +1238,14 @@ def test_deprecated_N_format(self): else: self.assertRaises(ValueError, format, h, 'N') self.assertRaises(ValueError, format, h, '010.3N') - + with warnings_helper.check_no_warnings(self): + self.assertEqual(format(h, 'N>10.3'), 'NN6.63E-34') + self.assertEqual(format(h, 'N>10.3n'), 'NN6.63e-34') + self.assertEqual(format(h, 'N>10.3e'), 'N6.626e-34') + self.assertEqual(format(h, 'N>10.3f'), 'NNNNN0.000') + self.assertRaises(ValueError, format, h, '>Nf') + self.assertRaises(ValueError, format, h, '10Nf') + self.assertRaises(ValueError, format, h, 'Nx') @run_with_locale('LC_ALL', 'ps_AF') def test_wide_char_separator_decimal_point(self): diff --git a/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst b/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst new file mode 100644 index 000000000000000..2306af4a39dcf62 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-19-40-40.gh-issue-114678.kYKcJw.rst @@ -0,0 +1,3 @@ +Ensure that deprecation warning for 'N' specifier in :class:`~decimal.Decimal` +format is not raised for cases where 'N' appears in other places +in the format specifier. Based on patch by Stefan Krah. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 8b93f8e2cbcf0bb..127f5f2887d4cd1 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3446,6 +3446,14 @@ dec_format(PyObject *dec, PyObject *args) if (fmt == NULL) { return NULL; } + + if (size > 0 && fmt[size-1] == 'N') { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Format specifier 'N' is deprecated", 1) < 0) { + return NULL; + } + } + /* NOTE: If https://github.com/python/cpython/pull/29438 lands, the * format string manipulation below can be eliminated by enhancing * the forked mpd_parse_fmt_str(). */ @@ -3593,12 +3601,6 @@ dec_format(PyObject *dec, PyObject *args) if (replace_fillchar) { dec_replace_fillchar(decstring); } - if (strchr(fmt, 'N') != NULL) { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Format specifier 'N' is deprecated", 1) < 0) { - goto finish; - } - } result = PyUnicode_DecodeUTF8(decstring, size, NULL); From 29952c86f3f8a972203a1ccd8381448efe145ada Mon Sep 17 00:00:00 2001 From: Matan Perelman Date: Mon, 29 Jan 2024 21:12:33 +0200 Subject: [PATCH 091/263] TaskGroup: Use explicit None check for cancellation error (#114708) --- Lib/asyncio/taskgroups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index e1c56d140bef7de..f322b1f6653f6a2 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -132,7 +132,7 @@ async def __aexit__(self, et, exc, tb): # Propagate CancelledError if there is one, except if there # are other errors -- those have priority. - if propagate_cancellation_error and not self._errors: + if propagate_cancellation_error is not None and not self._errors: raise propagate_cancellation_error if et is not None and not issubclass(et, exceptions.CancelledError): From 53d921ed96e1c57b2e42f984d3a5ca8347fedb81 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Mon, 29 Jan 2024 21:48:49 +0100 Subject: [PATCH 092/263] gh-114569: Use PyMem_* APIs for non-PyObjects in unicodeobject.c (#114690) --- Objects/unicodeobject.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 4b03cc3f4da5fab..b236ddba9cdc69f 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -996,7 +996,7 @@ resize_compact(PyObject *unicode, Py_ssize_t length) new_size = (struct_size + (length + 1) * char_size); if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); _PyUnicode_UTF8(unicode) = NULL; _PyUnicode_UTF8_LENGTH(unicode) = 0; } @@ -1049,7 +1049,7 @@ resize_inplace(PyObject *unicode, Py_ssize_t length) if (!share_utf8 && _PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); _PyUnicode_UTF8(unicode) = NULL; _PyUnicode_UTF8_LENGTH(unicode) = 0; } @@ -1590,10 +1590,10 @@ unicode_dealloc(PyObject *unicode) return; } if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) { - PyObject_Free(_PyUnicode_UTF8(unicode)); + PyMem_Free(_PyUnicode_UTF8(unicode)); } if (!PyUnicode_IS_COMPACT(unicode) && _PyUnicode_DATA_ANY(unicode)) { - PyObject_Free(_PyUnicode_DATA_ANY(unicode)); + PyMem_Free(_PyUnicode_DATA_ANY(unicode)); } Py_TYPE(unicode)->tp_free(unicode); @@ -5203,7 +5203,7 @@ unicode_fill_utf8(PyObject *unicode) PyBytes_AS_STRING(writer.buffer); Py_ssize_t len = end - start; - char *cache = PyObject_Malloc(len + 1); + char *cache = PyMem_Malloc(len + 1); if (cache == NULL) { _PyBytesWriter_Dealloc(&writer); PyErr_NoMemory(); @@ -14674,7 +14674,7 @@ unicode_subtype_new(PyTypeObject *type, PyObject *unicode) PyErr_NoMemory(); goto onError; } - data = PyObject_Malloc((length + 1) * char_size); + data = PyMem_Malloc((length + 1) * char_size); if (data == NULL) { PyErr_NoMemory(); goto onError; From 3996cbdd33a479b7e59757b81489cbb3370f85e5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:24:21 +0200 Subject: [PATCH 093/263] Set `hosted_on` for Read the Docs builds (#114697) --- Doc/conf.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index e12128ad356e1be..c2d57696aeeaa37 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -6,7 +6,9 @@ # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). -import sys, os, time +import os +import sys +import time sys.path.append(os.path.abspath('tools/extensions')) sys.path.append(os.path.abspath('includes')) @@ -55,7 +57,7 @@ # General substitutions. project = 'Python' -copyright = '2001-%s, Python Software Foundation' % time.strftime('%Y') +copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation" # We look for the Include/patchlevel.h file in the current Python source tree # and replace the values accordingly. @@ -302,6 +304,9 @@ 'root_include_title': False # We use the version switcher instead. } +if os.getenv("READTHEDOCS"): + html_theme_options["hosted_on"] = 'Read the Docs' + # Override stylesheet fingerprinting for Windows CHM htmlhelp to fix GH-91207 # https://github.com/python/cpython/issues/91207 if any('htmlhelp' in arg for arg in sys.argv): @@ -310,7 +315,7 @@ print("It may be removed in the future\n") # Short title used e.g. for HTML tags. -html_short_title = '%s Documentation' % release +html_short_title = f'{release} Documentation' # Deployment preview information # (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html) @@ -359,12 +364,9 @@ latex_engine = 'xelatex' -# Get LaTeX to handle Unicode correctly latex_elements = { -} - -# Additional stuff for the LaTeX preamble. -latex_elements['preamble'] = r''' + # For the LaTeX preamble. + 'preamble': r''' \authoraddress{ \sphinxstrong{Python Software Foundation}\\ Email: \sphinxemail{docs@python.org} @@ -372,13 +374,12 @@ \let\Verbatim=\OriginalVerbatim \let\endVerbatim=\endOriginalVerbatim \setcounter{tocdepth}{2} -''' - -# The paper size ('letter' or 'a4'). -latex_elements['papersize'] = 'a4' - -# The font size ('10pt', '11pt' or '12pt'). -latex_elements['pointsize'] = '10pt' +''', + # The paper size ('letter' or 'a4'). + 'papersize': 'a4', + # The font size ('10pt', '11pt' or '12pt'). + 'pointsize': '10pt', +} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). @@ -441,9 +442,9 @@ # Regexes to find C items in the source files. coverage_c_regexes = { - 'cfunction': (r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'), - 'data': (r'^PyAPI_DATA\(.*\)\s+([^_][\w_]+)'), - 'macro': (r'^#define ([^_][\w_]+)\(.*\)[\s|\\]'), + 'cfunction': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)', + 'data': r'^PyAPI_DATA\(.*\)\s+([^_][\w_]+)', + 'macro': r'^#define ([^_][\w_]+)\(.*\)[\s|\\]', } # The coverage checker will ignore all C items whose names match these regexes From 8612230c1cacab6d48bfbeb9e17d04ef5a9acf21 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 30 Jan 2024 00:04:34 +0100 Subject: [PATCH 094/263] gh-114569: Use PyMem_* APIs for non-PyObjects in compiler (#114587) --- Python/compile.c | 25 ++++++++++++------------- Python/flowgraph.c | 6 +++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Python/compile.c b/Python/compile.c index 7cf05dd06831195..4c1d3bb2d2b475a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -160,7 +160,7 @@ _PyCompile_EnsureArrayLargeEnough(int idx, void **array, int *alloc, if (idx >= new_alloc) { new_alloc = idx + default_alloc; } - arr = PyObject_Calloc(new_alloc, item_size); + arr = PyMem_Calloc(new_alloc, item_size); if (arr == NULL) { PyErr_NoMemory(); return ERROR; @@ -181,7 +181,7 @@ _PyCompile_EnsureArrayLargeEnough(int idx, void **array, int *alloc, } assert(newsize > 0); - void *tmp = PyObject_Realloc(arr, newsize); + void *tmp = PyMem_Realloc(arr, newsize); if (tmp == NULL) { PyErr_NoMemory(); return ERROR; @@ -282,10 +282,10 @@ instr_sequence_insert_instruction(instr_sequence *seq, int pos, static void instr_sequence_fini(instr_sequence *seq) { - PyObject_Free(seq->s_labelmap); + PyMem_Free(seq->s_labelmap); seq->s_labelmap = NULL; - PyObject_Free(seq->s_instrs); + PyMem_Free(seq->s_instrs); seq->s_instrs = NULL; } @@ -690,7 +690,7 @@ compiler_unit_free(struct compiler_unit *u) Py_CLEAR(u->u_metadata.u_cellvars); Py_CLEAR(u->u_metadata.u_fasthidden); Py_CLEAR(u->u_private); - PyObject_Free(u); + PyMem_Free(u); } static int @@ -1262,8 +1262,7 @@ compiler_enter_scope(struct compiler *c, identifier name, struct compiler_unit *u; - u = (struct compiler_unit *)PyObject_Calloc(1, sizeof( - struct compiler_unit)); + u = (struct compiler_unit *)PyMem_Calloc(1, sizeof(struct compiler_unit)); if (!u) { PyErr_NoMemory(); return ERROR; @@ -6657,7 +6656,7 @@ ensure_fail_pop(struct compiler *c, pattern_context *pc, Py_ssize_t n) return SUCCESS; } Py_ssize_t needed = sizeof(jump_target_label) * size; - jump_target_label *resized = PyObject_Realloc(pc->fail_pop, needed); + jump_target_label *resized = PyMem_Realloc(pc->fail_pop, needed); if (resized == NULL) { PyErr_NoMemory(); return ERROR; @@ -6696,13 +6695,13 @@ emit_and_reset_fail_pop(struct compiler *c, location loc, USE_LABEL(c, pc->fail_pop[pc->fail_pop_size]); if (codegen_addop_noarg(INSTR_SEQUENCE(c), POP_TOP, loc) < 0) { pc->fail_pop_size = 0; - PyObject_Free(pc->fail_pop); + PyMem_Free(pc->fail_pop); pc->fail_pop = NULL; return ERROR; } } USE_LABEL(c, pc->fail_pop[0]); - PyObject_Free(pc->fail_pop); + PyMem_Free(pc->fail_pop); pc->fail_pop = NULL; return SUCCESS; } @@ -7206,7 +7205,7 @@ compiler_pattern_or(struct compiler *c, pattern_ty p, pattern_context *pc) Py_DECREF(pc->stores); *pc = old_pc; Py_INCREF(pc->stores); - // Need to NULL this for the PyObject_Free call in the error block. + // Need to NULL this for the PyMem_Free call in the error block. old_pc.fail_pop = NULL; // No match. Pop the remaining copy of the subject and fail: if (codegen_addop_noarg(INSTR_SEQUENCE(c), POP_TOP, LOC(p)) < 0 || @@ -7252,7 +7251,7 @@ compiler_pattern_or(struct compiler *c, pattern_ty p, pattern_context *pc) diff: compiler_error(c, LOC(p), "alternative patterns bind different names"); error: - PyObject_Free(old_pc.fail_pop); + PyMem_Free(old_pc.fail_pop); Py_DECREF(old_pc.stores); Py_XDECREF(control); return ERROR; @@ -7453,7 +7452,7 @@ compiler_match(struct compiler *c, stmt_ty s) pattern_context pc; pc.fail_pop = NULL; int result = compiler_match_inner(c, s, &pc); - PyObject_Free(pc.fail_pop); + PyMem_Free(pc.fail_pop); return result; } diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 96610b3cb11a43d..bfc23a298ff492d 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -162,7 +162,7 @@ basicblock_last_instr(const basicblock *b) { static basicblock * cfg_builder_new_block(cfg_builder *g) { - basicblock *b = (basicblock *)PyObject_Calloc(1, sizeof(basicblock)); + basicblock *b = (basicblock *)PyMem_Calloc(1, sizeof(basicblock)); if (b == NULL) { PyErr_NoMemory(); return NULL; @@ -437,10 +437,10 @@ _PyCfgBuilder_Free(cfg_builder *g) basicblock *b = g->g_block_list; while (b != NULL) { if (b->b_instr) { - PyObject_Free((void *)b->b_instr); + PyMem_Free((void *)b->b_instr); } basicblock *next = b->b_list; - PyObject_Free((void *)b); + PyMem_Free((void *)b); b = next; } PyMem_Free(g); From 742ba6081c92744ba30f16a0bb17ef9d9e809611 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Mon, 29 Jan 2024 16:29:54 -0800 Subject: [PATCH 095/263] GH-113464: Make Brandt a codeowner for JIT stuff (GH-114739) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ae915423ece9551..f4d0411504a8325 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,7 @@ configure* @erlend-aasland @corona10 **/*context* @1st1 **/*genobject* @markshannon **/*hamt* @1st1 +**/*jit* @brandtbucher Objects/set* @rhettinger Objects/dict* @methane @markshannon Objects/typevarobject.c @JelleZijlstra @@ -37,7 +38,6 @@ Python/ast_opt.c @isidentical Python/bytecodes.c @markshannon @gvanrossum Python/optimizer*.c @markshannon @gvanrossum Lib/test/test_patma.py @brandtbucher -Lib/test/test_peepholer.py @brandtbucher Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum Tools/c-analyzer/ @ericsnowcurrently From 963904335e579bfe39101adf3fd6a0cf705975ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= <wk@sydorenko.org.ua> Date: Tue, 30 Jan 2024 02:25:31 +0100 Subject: [PATCH 096/263] GH-80789: Get rid of the ``ensurepip`` infra for many wheels (#109245) Co-authored-by: vstinner@python.org Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com> Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- Lib/ensurepip/__init__.py | 129 ++++++++++--------------- Lib/test/test_ensurepip.py | 46 ++++----- Tools/build/verify_ensurepip_wheels.py | 6 +- 3 files changed, 73 insertions(+), 108 deletions(-) diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index a09bf3201e1fb7d..80ee125cfd4ed32 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,78 +1,64 @@ -import collections import os -import os.path import subprocess import sys import sysconfig import tempfile +from contextlib import nullcontext from importlib import resources +from pathlib import Path +from shutil import copy2 __all__ = ["version", "bootstrap"] -_PACKAGE_NAMES = ('pip',) _PIP_VERSION = "23.3.2" -_PROJECTS = [ - ("pip", _PIP_VERSION, "py3"), -] - -# Packages bundled in ensurepip._bundled have wheel_name set. -# Packages from WHEEL_PKG_DIR have wheel_path set. -_Package = collections.namedtuple('Package', - ('version', 'wheel_name', 'wheel_path')) # Directory of system wheel packages. Some Linux distribution packaging # policies recommend against bundling dependencies. For example, Fedora # installs wheel packages in the /usr/share/python-wheels/ directory and don't # install the ensurepip._bundled package. -_WHEEL_PKG_DIR = sysconfig.get_config_var('WHEEL_PKG_DIR') +if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None: + _WHEEL_PKG_DIR = Path(_pkg_dir).resolve() +else: + _WHEEL_PKG_DIR = None + +def _find_wheel_pkg_dir_pip(): + if _WHEEL_PKG_DIR is None: + # NOTE: The compile-time `WHEEL_PKG_DIR` is unset so there is no place + # NOTE: for looking up the wheels. + return None -def _find_packages(path): - packages = {} + dist_matching_wheels = _WHEEL_PKG_DIR.glob('pip-*.whl') try: - filenames = os.listdir(path) - except OSError: - # Ignore: path doesn't exist or permission error - filenames = () - # Make the code deterministic if a directory contains multiple wheel files - # of the same package, but don't attempt to implement correct version - # comparison since this case should not happen. - filenames = sorted(filenames) - for filename in filenames: - # filename is like 'pip-21.2.4-py3-none-any.whl' - if not filename.endswith(".whl"): - continue - for name in _PACKAGE_NAMES: - prefix = name + '-' - if filename.startswith(prefix): - break - else: - continue - - # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' - version = filename.removeprefix(prefix).partition('-')[0] - wheel_path = os.path.join(path, filename) - packages[name] = _Package(version, None, wheel_path) - return packages - - -def _get_packages(): - global _PACKAGES, _WHEEL_PKG_DIR - if _PACKAGES is not None: - return _PACKAGES - - packages = {} - for name, version, py_tag in _PROJECTS: - wheel_name = f"{name}-{version}-{py_tag}-none-any.whl" - packages[name] = _Package(version, wheel_name, None) - if _WHEEL_PKG_DIR: - dir_packages = _find_packages(_WHEEL_PKG_DIR) - # only used the wheel package directory if all packages are found there - if all(name in dir_packages for name in _PACKAGE_NAMES): - packages = dir_packages - _PACKAGES = packages - return packages -_PACKAGES = None + last_matching_dist_wheel = sorted(dist_matching_wheels)[-1] + except IndexError: + # NOTE: `WHEEL_PKG_DIR` does not contain any wheel files for `pip`. + return None + + return nullcontext(last_matching_dist_wheel) + + +def _get_pip_whl_path_ctx(): + # Prefer pip from the wheel package directory, if present. + if (alternative_pip_wheel_path := _find_wheel_pkg_dir_pip()) is not None: + return alternative_pip_wheel_path + + return resources.as_file( + resources.files('ensurepip') + / '_bundled' + / f'pip-{_PIP_VERSION}-py3-none-any.whl' + ) + + +def _get_pip_version(): + with _get_pip_whl_path_ctx() as bundled_wheel_path: + wheel_name = bundled_wheel_path.name + return ( + # Extract '21.2.4' from 'pip-21.2.4-py3-none-any.whl' + wheel_name. + removeprefix('pip-'). + partition('-')[0] + ) def _run_pip(args, additional_paths=None): @@ -105,7 +91,7 @@ def version(): """ Returns a string specifying the bundled version of pip. """ - return _get_packages()['pip'].version + return _get_pip_version() def _disable_pip_configuration_settings(): @@ -167,24 +153,10 @@ def _bootstrap(*, root=None, upgrade=False, user=False, with tempfile.TemporaryDirectory() as tmpdir: # Put our bundled wheels into a temporary directory and construct the # additional paths that need added to sys.path - additional_paths = [] - for name, package in _get_packages().items(): - if package.wheel_name: - # Use bundled wheel package - wheel_name = package.wheel_name - wheel_path = resources.files("ensurepip") / "_bundled" / wheel_name - whl = wheel_path.read_bytes() - else: - # Use the wheel package directory - with open(package.wheel_path, "rb") as fp: - whl = fp.read() - wheel_name = os.path.basename(package.wheel_path) - - filename = os.path.join(tmpdir, wheel_name) - with open(filename, "wb") as fp: - fp.write(whl) - - additional_paths.append(filename) + tmpdir_path = Path(tmpdir) + with _get_pip_whl_path_ctx() as bundled_wheel_path: + tmp_wheel_path = tmpdir_path / bundled_wheel_path.name + copy2(bundled_wheel_path, tmp_wheel_path) # Construct the arguments to be passed to the pip command args = ["install", "--no-cache-dir", "--no-index", "--find-links", tmpdir] @@ -197,7 +169,8 @@ def _bootstrap(*, root=None, upgrade=False, user=False, if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *_PACKAGE_NAMES], additional_paths) + return _run_pip([*args, "pip"], [os.fsdecode(tmp_wheel_path)]) + def _uninstall_helper(*, verbosity=0): """Helper to support a clean default uninstall process on Windows @@ -227,7 +200,7 @@ def _uninstall_helper(*, verbosity=0): if verbosity: args += ["-" + "v" * verbosity] - return _run_pip([*args, *reversed(_PACKAGE_NAMES)]) + return _run_pip([*args, "pip"]) def _main(argv=None): diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py index 69ab2a4feaa9389..a4b36a90d8815ea 100644 --- a/Lib/test/test_ensurepip.py +++ b/Lib/test/test_ensurepip.py @@ -6,6 +6,8 @@ import test.support import unittest import unittest.mock +from importlib.resources.abc import Traversable +from pathlib import Path import ensurepip import ensurepip._uninstall @@ -20,41 +22,35 @@ def test_version(self): # Test version() with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, "pip-1.2.3b1-py2.py3-none-any.whl") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): self.assertEqual(ensurepip.version(), '1.2.3b1') - def test_get_packages_no_dir(self): - # Test _get_packages() without a wheel package directory - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None)): - packages = ensurepip._get_packages() - - # when bundled wheel packages are used, we get _PIP_VERSION + def test_version_no_dir(self): + # Test version() without a wheel package directory + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + # when the bundled pip wheel is used, we get _PIP_VERSION self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version()) - # use bundled wheel packages - self.assertIsNotNone(packages['pip'].wheel_name) + def test_selected_wheel_path_no_dir(self): + pip_filename = f'pip-{ensurepip._PIP_VERSION}-py3-none-any.whl' + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) - def test_get_packages_with_dir(self): - # Test _get_packages() with a wheel package directory + def test_selected_wheel_path_with_dir(self): + # Test _get_pip_whl_path_ctx() with a wheel package directory pip_filename = "pip-20.2.2-py2.py3-none-any.whl" with tempfile.TemporaryDirectory() as tmpdir: self.touch(tmpdir, pip_filename) - # not used, make sure that it's ignored + # not used, make sure that they're ignored + self.touch(tmpdir, "pip-1.2.3-py2.py3-none-any.whl") self.touch(tmpdir, "wheel-0.34.2-py2.py3-none-any.whl") + self.touch(tmpdir, "pip-script.py") - with (unittest.mock.patch.object(ensurepip, '_PACKAGES', None), - unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', tmpdir)): - packages = ensurepip._get_packages() - - self.assertEqual(packages['pip'].version, '20.2.2') - self.assertEqual(packages['pip'].wheel_path, - os.path.join(tmpdir, pip_filename)) - - # wheel package is ignored - self.assertEqual(sorted(packages), ['pip']) + with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', Path(tmpdir)): + with ensurepip._get_pip_whl_path_ctx() as bundled_wheel_path: + self.assertEqual(pip_filename, bundled_wheel_path.name) class EnsurepipMixin: @@ -69,7 +65,7 @@ def setUp(self): real_devnull = os.devnull os_patch = unittest.mock.patch("ensurepip.os") patched_os = os_patch.start() - # But expose os.listdir() used by _find_packages() + # But expose os.listdir() used by _find_wheel_pkg_dir_pip() patched_os.listdir = os.listdir self.addCleanup(os_patch.stop) patched_os.devnull = real_devnull diff --git a/Tools/build/verify_ensurepip_wheels.py b/Tools/build/verify_ensurepip_wheels.py index 29897425da6c036..a37da2f70757e58 100755 --- a/Tools/build/verify_ensurepip_wheels.py +++ b/Tools/build/verify_ensurepip_wheels.py @@ -14,7 +14,6 @@ from pathlib import Path from urllib.request import urlopen -PACKAGE_NAMES = ("pip",) ENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip" WHEEL_DIR = ENSURE_PIP_ROOT / "_bundled" ENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8") @@ -97,8 +96,5 @@ def verify_wheel(package_name: str) -> bool: if __name__ == "__main__": - exit_status = 0 - for package_name in PACKAGE_NAMES: - if not verify_wheel(package_name): - exit_status = 1 + exit_status = int(not verify_wheel("pip")) raise SystemExit(exit_status) From 58f883b91bd8dd4cac38b58a026397363104a129 Mon Sep 17 00:00:00 2001 From: Victor Stinner <vstinner@python.org> Date: Tue, 30 Jan 2024 11:47:58 +0100 Subject: [PATCH 097/263] gh-103323: Remove current_fast_get() unused parameter (#114593) The current_fast_get() static inline function doesn't use its 'runtime' parameter, so just remove it. --- Python/pystate.c | 50 +++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 8e097c848cf4a18..430121a6a35d7f9 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -67,7 +67,7 @@ _Py_thread_local PyThreadState *_Py_tss_tstate = NULL; #endif static inline PyThreadState * -current_fast_get(_PyRuntimeState *Py_UNUSED(runtime)) +current_fast_get(void) { #ifdef HAVE_THREAD_LOCAL return _Py_tss_tstate; @@ -101,14 +101,14 @@ current_fast_clear(_PyRuntimeState *Py_UNUSED(runtime)) } #define tstate_verify_not_active(tstate) \ - if (tstate == current_fast_get((tstate)->interp->runtime)) { \ + if (tstate == current_fast_get()) { \ _Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate); \ } PyThreadState * _PyThreadState_GetCurrent(void) { - return current_fast_get(&_PyRuntime); + return current_fast_get(); } @@ -360,10 +360,9 @@ holds_gil(PyThreadState *tstate) // XXX Fall back to tstate->interp->runtime->ceval.gil.last_holder // (and tstate->interp->runtime->ceval.gil.locked). assert(tstate != NULL); - _PyRuntimeState *runtime = tstate->interp->runtime; /* Must be the tstate for this thread */ - assert(tstate == gilstate_tss_get(runtime)); - return tstate == current_fast_get(runtime); + assert(tstate == gilstate_tss_get(tstate->interp->runtime)); + return tstate == current_fast_get(); } @@ -723,7 +722,7 @@ PyInterpreterState * PyInterpreterState_New(void) { // tstate can be NULL - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); PyInterpreterState *interp; PyStatus status = _PyInterpreterState_New(tstate, &interp); @@ -882,7 +881,7 @@ PyInterpreterState_Clear(PyInterpreterState *interp) // Use the current Python thread state to call audit hooks and to collect // garbage. It can be different than the current Python thread state // of 'interp'. - PyThreadState *current_tstate = current_fast_get(interp->runtime); + PyThreadState *current_tstate = current_fast_get(); _PyImport_ClearCore(interp); interpreter_clear(interp, current_tstate); } @@ -908,7 +907,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) // XXX Clearing the "current" thread state should happen before // we start finalizing the interpreter (or the current thread state). - PyThreadState *tcur = current_fast_get(runtime); + PyThreadState *tcur = current_fast_get(); if (tcur != NULL && interp == tcur->interp) { /* Unset current thread. After this, many C API calls become crashy. */ _PyThreadState_Detach(tcur); @@ -1010,7 +1009,7 @@ _PyInterpreterState_SetRunningMain(PyInterpreterState *interp) if (_PyInterpreterState_FailIfRunningMain(interp) < 0) { return -1; } - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); if (tstate->interp != interp) { PyErr_SetString(PyExc_RuntimeError, @@ -1025,7 +1024,7 @@ void _PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp) { PyThreadState *tstate = interp->threads.main; - assert(tstate == current_fast_get(&_PyRuntime)); + assert(tstate == current_fast_get()); if (tstate->on_delete != NULL) { // The threading module was imported for the first time in this @@ -1178,7 +1177,7 @@ PyInterpreterState_GetDict(PyInterpreterState *interp) PyInterpreterState* PyInterpreterState_Get(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); PyInterpreterState *interp = tstate->interp; if (interp == NULL) { @@ -1474,7 +1473,7 @@ void PyThreadState_Clear(PyThreadState *tstate) { assert(tstate->_status.initialized && !tstate->_status.cleared); - assert(current_fast_get(&_PyRuntime)->interp == tstate->interp); + assert(current_fast_get()->interp == tstate->interp); // XXX assert(!tstate->_status.bound || tstate->_status.unbound); tstate->_status.finalizing = 1; // just in case @@ -1656,7 +1655,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate) void PyThreadState_DeleteCurrent(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _PyThreadState_DeleteCurrent(tstate); } @@ -1732,7 +1731,7 @@ _PyThreadState_GetDict(PyThreadState *tstate) PyObject * PyThreadState_GetDict(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); if (tstate == NULL) { return NULL; } @@ -1853,7 +1852,7 @@ _PyThreadState_Attach(PyThreadState *tstate) #endif _Py_EnsureTstateNotNULL(tstate); - if (current_fast_get(&_PyRuntime) != NULL) { + if (current_fast_get() != NULL) { Py_FatalError("non-NULL old thread state"); } @@ -1883,7 +1882,7 @@ detach_thread(PyThreadState *tstate, int detached_state) { // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(tstate->state == _Py_THREAD_ATTACHED); - assert(tstate == current_fast_get(&_PyRuntime)); + assert(tstate == current_fast_get()); if (tstate->critical_section != 0) { _PyCriticalSection_SuspendAll(tstate); } @@ -2168,14 +2167,14 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc) PyThreadState * PyThreadState_GetUnchecked(void) { - return current_fast_get(&_PyRuntime); + return current_fast_get(); } PyThreadState * PyThreadState_Get(void) { - PyThreadState *tstate = current_fast_get(&_PyRuntime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); return tstate; } @@ -2183,7 +2182,7 @@ PyThreadState_Get(void) PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { - PyThreadState *oldts = current_fast_get(runtime); + PyThreadState *oldts = current_fast_get(); if (oldts != NULL) { _PyThreadState_Detach(oldts); } @@ -2278,7 +2277,7 @@ PyObject * _PyThread_CurrentFrames(void) { _PyRuntimeState *runtime = &_PyRuntime; - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); if (_PySys_Audit(tstate, "sys._current_frames", NULL) < 0) { return NULL; } @@ -2339,7 +2338,7 @@ PyObject * _PyThread_CurrentExceptions(void) { _PyRuntimeState *runtime = &_PyRuntime; - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); @@ -2481,7 +2480,7 @@ PyGILState_Check(void) return 1; } - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); if (tstate == NULL) { return 0; } @@ -2579,7 +2578,7 @@ PyGILState_Release(PyGILState_STATE oldstate) * races; see bugs 225673 and 1061968 (that nasty bug has a * habit of coming back). */ - assert(current_fast_get(runtime) == tstate); + assert(current_fast_get() == tstate); _PyThreadState_DeleteCurrent(tstate); } /* Release the lock if necessary */ @@ -2645,9 +2644,8 @@ _PyInterpreterState_GetConfigCopy(PyConfig *config) const PyConfig* _Py_GetConfig(void) { - _PyRuntimeState *runtime = &_PyRuntime; assert(PyGILState_Check()); - PyThreadState *tstate = current_fast_get(runtime); + PyThreadState *tstate = current_fast_get(); _Py_EnsureTstateNotNULL(tstate); return _PyInterpreterState_GetConfig(tstate->interp); } From ea30a28c3e89b69a214c536e61402660242c0f2a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 30 Jan 2024 14:21:12 +0200 Subject: [PATCH 098/263] gh-113732: Fix support of QUOTE_NOTNULL and QUOTE_STRINGS in csv.reader (GH-113738) --- Doc/whatsnew/3.12.rst | 2 +- Lib/test/test_csv.py | 25 ++++++++++ ...-01-05-16-27-34.gh-issue-113732.fgDRXA.rst | 2 + Modules/_csv.c | 46 ++++++++++++------- 4 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 77b12f9284ba0d9..100312a5940b796 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -690,7 +690,7 @@ csv * Add :const:`csv.QUOTE_NOTNULL` and :const:`csv.QUOTE_STRINGS` flags to provide finer grained control of ``None`` and empty strings by - :class:`csv.writer` objects. + :class:`~csv.reader` and :class:`~csv.writer` objects. dis --- diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 69fef5945ae66f5..21a4cb586ff6658 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -392,10 +392,26 @@ def test_read_quoting(self): # will this fail where locale uses comma for decimals? self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]], quoting=csv.QUOTE_NONNUMERIC) + self._read_test([',3,"5",7.3, 9'], [[None, '3', '5', '7.3', ' 9']], + quoting=csv.QUOTE_NOTNULL) + self._read_test([',3,"5",7.3, 9'], [[None, 3, '5', 7.3, 9]], + quoting=csv.QUOTE_STRINGS) + + self._read_test([',,"",'], [['', '', '', '']]) + self._read_test([',,"",'], [['', '', '', '']], + quoting=csv.QUOTE_NONNUMERIC) + self._read_test([',,"",'], [[None, None, '', None]], + quoting=csv.QUOTE_NOTNULL) + self._read_test([',,"",'], [[None, None, '', None]], + quoting=csv.QUOTE_STRINGS) + self._read_test(['"a\nb", 7'], [['a\nb', ' 7']]) self.assertRaises(ValueError, self._read_test, ['abc,3'], [[]], quoting=csv.QUOTE_NONNUMERIC) + self.assertRaises(ValueError, self._read_test, + ['abc,3'], [[]], + quoting=csv.QUOTE_STRINGS) self._read_test(['1,@,3,@,5'], [['1', ',3,', '5']], quotechar='@') self._read_test(['1,\0,3,\0,5'], [['1', ',3,', '5']], quotechar='\0') @@ -403,6 +419,15 @@ def test_read_skipinitialspace(self): self._read_test(['no space, space, spaces,\ttab'], [['no space', 'space', 'spaces', '\ttab']], skipinitialspace=True) + self._read_test([' , , '], + [['', '', '']], + skipinitialspace=True) + self._read_test([' , , '], + [[None, None, None]], + skipinitialspace=True, quoting=csv.QUOTE_NOTNULL) + self._read_test([' , , '], + [[None, None, None]], + skipinitialspace=True, quoting=csv.QUOTE_STRINGS) def test_read_bigfield(self): # This exercises the buffer realloc functionality and field size diff --git a/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst new file mode 100644 index 000000000000000..7582603dcf95f5f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-05-16-27-34.gh-issue-113732.fgDRXA.rst @@ -0,0 +1,2 @@ +Fix support of :data:`~csv.QUOTE_NOTNULL` and :data:`~csv.QUOTE_STRINGS` in +:func:`csv.reader`. diff --git a/Modules/_csv.c b/Modules/_csv.c index 929c21584ac2ef0..3aa648b8e9cec44 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -131,7 +131,7 @@ typedef struct { Py_UCS4 *field; /* temporary buffer */ Py_ssize_t field_size; /* size of allocated buffer */ Py_ssize_t field_len; /* length of current field */ - int numeric_field; /* treat field as numeric */ + bool unquoted_field; /* true if no quotes around the current field */ unsigned long line_num; /* Source-file line number */ } ReaderObj; @@ -644,22 +644,33 @@ _call_dialect(_csvstate *module_state, PyObject *dialect_inst, PyObject *kwargs) static int parse_save_field(ReaderObj *self) { + int quoting = self->dialect->quoting; PyObject *field; - field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, - (void *) self->field, self->field_len); - if (field == NULL) - return -1; - self->field_len = 0; - if (self->numeric_field) { - PyObject *tmp; - - self->numeric_field = 0; - tmp = PyNumber_Float(field); - Py_DECREF(field); - if (tmp == NULL) + if (self->unquoted_field && + self->field_len == 0 && + (quoting == QUOTE_NOTNULL || quoting == QUOTE_STRINGS)) + { + field = Py_NewRef(Py_None); + } + else { + field = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, + (void *) self->field, self->field_len); + if (field == NULL) { return -1; - field = tmp; + } + if (self->unquoted_field && + self->field_len != 0 && + (quoting == QUOTE_NONNUMERIC || quoting == QUOTE_STRINGS)) + { + PyObject *tmp = PyNumber_Float(field); + Py_DECREF(field); + if (tmp == NULL) { + return -1; + } + field = tmp; + } + self->field_len = 0; } if (PyList_Append(self->fields, field) < 0) { Py_DECREF(field); @@ -721,6 +732,7 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) /* fallthru */ case START_FIELD: /* expecting field */ + self->unquoted_field = true; if (c == '\n' || c == '\r' || c == EOL) { /* save empty field - return [fields] */ if (parse_save_field(self) < 0) @@ -730,10 +742,12 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) else if (c == dialect->quotechar && dialect->quoting != QUOTE_NONE) { /* start quoted field */ + self->unquoted_field = false; self->state = IN_QUOTED_FIELD; } else if (c == dialect->escapechar) { /* possible escaped character */ + self->unquoted_field = false; self->state = ESCAPED_CHAR; } else if (c == ' ' && dialect->skipinitialspace) @@ -746,8 +760,6 @@ parse_process_char(ReaderObj *self, _csvstate *module_state, Py_UCS4 c) } else { /* begin new unquoted field */ - if (dialect->quoting == QUOTE_NONNUMERIC) - self->numeric_field = 1; if (parse_add_char(self, module_state, c) < 0) return -1; self->state = IN_FIELD; @@ -892,7 +904,7 @@ parse_reset(ReaderObj *self) return -1; self->field_len = 0; self->state = START_RECORD; - self->numeric_field = 0; + self->unquoted_field = false; return 0; } From e21754d7f8336d4647e28f355d8a3dbd5a2c7545 Mon Sep 17 00:00:00 2001 From: Vinay Sajip <vinay_sajip@yahoo.co.uk> Date: Tue, 30 Jan 2024 12:34:18 +0000 Subject: [PATCH 099/263] gh-114706: Allow QueueListener.stop() to be called more than once. (GH-114748) --- Lib/logging/handlers.py | 7 ++++--- Lib/test/test_logging.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index 9840b7b0aeba884..e7f1322e4ba3d9d 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -1586,6 +1586,7 @@ def stop(self): Note that if you don't call this before your application exits, there may be some records still left on the queue, which won't be processed. """ - self.enqueue_sentinel() - self._thread.join() - self._thread = None + if self._thread: # see gh-114706 - allow calling this more than once + self.enqueue_sentinel() + self._thread.join() + self._thread = None diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 908e242b85f5e7a..888523227c2ac43 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4089,6 +4089,7 @@ def test_queue_listener(self): self.que_logger.critical(self.next_message()) finally: listener.stop() + listener.stop() # gh-114706 - ensure no crash if called again self.assertTrue(handler.matches(levelno=logging.WARNING, message='1')) self.assertTrue(handler.matches(levelno=logging.ERROR, message='2')) self.assertTrue(handler.matches(levelno=logging.CRITICAL, message='3')) From 809eed48058ea7391df57ead09dff53bcc5d81e9 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 30 Jan 2024 14:25:16 +0000 Subject: [PATCH 100/263] GH-114610: Fix `pathlib._abc.PurePathBase.with_suffix('.ext')` handling of stems (#114613) Raise `ValueError` if `with_suffix('.ext')` is called on a path without a stem. Paths may only have a non-empty suffix if they also have a non-empty stem. ABC-only bugfix; no effect on public classes. --- Lib/pathlib/_abc.py | 7 +++++-- Lib/test/test_pathlib/test_pathlib.py | 7 ------- Lib/test/test_pathlib/test_pathlib_abc.py | 5 ++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index ad5684829ebc800..580d631cbf3b530 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -299,10 +299,13 @@ def with_suffix(self, suffix): has no suffix, add given suffix. If the given suffix is an empty string, remove the suffix from the path. """ + stem = self.stem if not suffix: - return self.with_name(self.stem) + return self.with_name(stem) + elif not stem: + raise ValueError(f"{self!r} has an empty name") elif suffix.startswith('.') and len(suffix) > 1: - return self.with_name(self.stem + suffix) + return self.with_name(stem + suffix) else: raise ValueError(f"Invalid suffix {suffix!r}") diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 5ce3b605c58e637..a8cc30ef0ab63f6 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -327,13 +327,6 @@ def test_with_stem_empty(self): self.assertRaises(ValueError, P('a/b').with_stem, '') self.assertRaises(ValueError, P('a/b').with_stem, '.') - def test_with_suffix_empty(self): - # Path doesn't have a "filename" component. - P = self.cls - self.assertRaises(ValueError, P('').with_suffix, '.gz') - self.assertRaises(ValueError, P('.').with_suffix, '.gz') - self.assertRaises(ValueError, P('/').with_suffix, '.gz') - def test_relative_to_several_args(self): P = self.cls p = P('a/b') diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index ab989cb5503f99d..18a612f6b81bace 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -977,9 +977,8 @@ def test_with_suffix_windows(self): def test_with_suffix_empty(self): P = self.cls # Path doesn't have a "filename" component. - self.assertEqual(P('').with_suffix('.gz'), P('.gz')) - self.assertEqual(P('.').with_suffix('.gz'), P('..gz')) - self.assertEqual(P('/').with_suffix('.gz'), P('/.gz')) + self.assertRaises(ValueError, P('').with_suffix, '.gz') + self.assertRaises(ValueError, P('/').with_suffix, '.gz') def test_with_suffix_seps(self): P = self.cls From 4287e8608bcabcc5bde851d838d4709db5d69089 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:12:11 +0200 Subject: [PATCH 101/263] gh-109975: Copyedit "What's New in Python 3.13" (#114401) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> --- Doc/whatsnew/3.13.rst | 218 +++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 110 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 985e34b453f63a5..fec1e55e0daf0e6 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -146,14 +146,6 @@ New Modules Improved Modules ================ -ast ---- - -* :func:`ast.parse` now accepts an optional argument ``optimize`` - which is passed on to the :func:`compile` built-in. This makes it - possible to obtain an optimized ``AST``. - (Contributed by Irit Katriel in :gh:`108113`). - array ----- @@ -161,6 +153,14 @@ array It can be used instead of ``'u'`` type code, which is deprecated. (Contributed by Inada Naoki in :gh:`80480`.) +ast +--- + +* :func:`ast.parse` now accepts an optional argument ``optimize`` + which is passed on to the :func:`compile` built-in. This makes it + possible to obtain an optimized ``AST``. + (Contributed by Irit Katriel in :gh:`108113`.) + asyncio ------- @@ -180,6 +180,13 @@ copy any user classes which define the :meth:`!__replace__` method. (Contributed by Serhiy Storchaka in :gh:`108751`.) +dbm +--- + +* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items + from the database. + (Contributed by Donghee Na in :gh:`107122`.) + dis --- @@ -189,13 +196,6 @@ dis the ``show_offsets`` parameter. (Contributed by Irit Katriel in :gh:`112137`.) -dbm ---- - -* Add :meth:`dbm.gnu.gdbm.clear` and :meth:`dbm.ndbm.ndbm.clear` methods that remove all items - from the database. - (Contributed by Donghee Na in :gh:`107122`.) - doctest ------- @@ -213,7 +213,7 @@ email parameter to these two functions: use ``strict=False`` to get the old behavior, accept malformed inputs. ``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to - check if the *strict* paramater is available. + check if the *strict* parameter is available. (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve the CVE-2023-27043 fix.) @@ -223,7 +223,7 @@ fractions * Formatting for objects of type :class:`fractions.Fraction` now supports the standard format specification mini-language rules for fill, alignment, sign handling, minimum width and grouping. (Contributed by Mark Dickinson - in :gh:`111320`) + in :gh:`111320`.) glob ---- @@ -297,17 +297,17 @@ os the new environment variable :envvar:`PYTHON_CPU_COUNT` or the new command-line option :option:`-X cpu_count <-X>`. This option is useful for users who need to limit CPU resources of a container system without having to modify the container (application code). - (Contributed by Donghee Na in :gh:`109595`) + (Contributed by Donghee Na in :gh:`109595`.) * Add support of :func:`os.lchmod` and the *follow_symlinks* argument in :func:`os.chmod` on Windows. Note that the default value of *follow_symlinks* in :func:`!os.lchmod` is ``False`` on Windows. - (Contributed by Serhiy Storchaka in :gh:`59616`) + (Contributed by Serhiy Storchaka in :gh:`59616`.) * Add support of :func:`os.fchmod` and a file descriptor in :func:`os.chmod` on Windows. - (Contributed by Serhiy Storchaka in :gh:`113191`) + (Contributed by Serhiy Storchaka in :gh:`113191`.) * :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned process use the current process environment. @@ -357,7 +357,7 @@ pdb the new ``exceptions [exc_number]`` command for Pdb. (Contributed by Matthias Bussonnier in :gh:`106676`.) -* Expressions/Statements whose prefix is a pdb command are now correctly +* Expressions/statements whose prefix is a pdb command are now correctly identified and executed. (Contributed by Tian Gao in :gh:`108464`.) @@ -487,34 +487,69 @@ Deprecated Replace ``ctypes.ARRAY(item_type, size)`` with ``item_type * size``. (Contributed by Victor Stinner in :gh:`105733`.) +* :mod:`decimal`: Deprecate non-standard format specifier "N" for + :class:`decimal.Decimal`. + It was not documented and only supported in the C implementation. + (Contributed by Serhiy Storchaka in :gh:`89902`.) + +* :mod:`dis`: The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check + membership in :data:`~dis.hasarg` instead. + (Contributed by Irit Katriel in :gh:`109319`.) + * :mod:`getopt` and :mod:`optparse` modules: They are now - :term:`soft deprecated`: the :mod:`argparse` should be used for new projects. + :term:`soft deprecated`: the :mod:`argparse` module should be used for new projects. Previously, the :mod:`optparse` module was already deprecated, its removal was not scheduled, and no warnings was emitted: so there is no change in practice. (Contributed by Victor Stinner in :gh:`106535`.) +* :mod:`gettext`: Emit deprecation warning for non-integer numbers in + :mod:`gettext` functions and methods that consider plural forms even if the + translation was not found. + (Contributed by Serhiy Storchaka in :gh:`88434`.) + * :mod:`http.server`: :class:`http.server.CGIHTTPRequestHandler` now emits a - :exc:`DeprecationWarning` as it will be removed in 3.15. Process based CGI - http servers have been out of favor for a very long time. This code was + :exc:`DeprecationWarning` as it will be removed in 3.15. Process-based CGI + HTTP servers have been out of favor for a very long time. This code was outdated, unmaintained, and rarely used. It has a high potential for both security and functionality bugs. This includes removal of the ``--cgi`` flag to the ``python -m http.server`` command line in 3.15. * :mod:`pathlib`: + :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. + +* :mod:`pydoc`: Deprecate undocumented :func:`!pydoc.ispackage` function. + (Contributed by Zackery Spytz in :gh:`64020`.) + +* :mod:`sqlite3`: Passing more than one positional argument to + :func:`sqlite3.connect` and the :class:`sqlite3.Connection` constructor is + deprecated. The remaining parameters will become keyword-only in Python 3.15. + + Deprecate passing name, number of arguments, and the callable as keyword + arguments for the following :class:`sqlite3.Connection` APIs: + + * :meth:`~sqlite3.Connection.create_function` + * :meth:`~sqlite3.Connection.create_aggregate` + + Deprecate passing the callback callable by keyword for the following + :class:`sqlite3.Connection` APIs: + + * :meth:`~sqlite3.Connection.set_authorizer` + * :meth:`~sqlite3.Connection.set_progress_handler` + * :meth:`~sqlite3.Connection.set_trace_callback` + + The affected parameters will become positional-only in Python 3.15. - * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for - removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved - paths on Windows. + (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.) * :mod:`sys`: :func:`sys._enablelegacywindowsfsencoding` function. - Replace it with :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. + Replace it with the :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. (Contributed by Inada Naoki in :gh:`73427`.) -* :mod:`traceback`: - - * The field *exc_type* of :class:`traceback.TracebackException` is - deprecated. Use *exc_type_str* instead. +* :mod:`traceback`: The field *exc_type* of :class:`traceback.TracebackException` + is deprecated. Use *exc_type_str* instead. * :mod:`typing`: @@ -550,39 +585,6 @@ Deprecated They will be removed in Python 3.15. (Contributed by Victor Stinner in :gh:`105096`.) -* Passing more than one positional argument to :func:`sqlite3.connect` and the - :class:`sqlite3.Connection` constructor is deprecated. The remaining - parameters will become keyword-only in Python 3.15. - - Deprecate passing name, number of arguments, and the callable as keyword - arguments, for the following :class:`sqlite3.Connection` APIs: - - * :meth:`~sqlite3.Connection.create_function` - * :meth:`~sqlite3.Connection.create_aggregate` - - Deprecate passing the callback callable by keyword for the following - :class:`sqlite3.Connection` APIs: - - * :meth:`~sqlite3.Connection.set_authorizer` - * :meth:`~sqlite3.Connection.set_progress_handler` - * :meth:`~sqlite3.Connection.set_trace_callback` - - The affected parameters will become positional-only in Python 3.15. - - (Contributed by Erlend E. Aasland in :gh:`107948` and :gh:`108278`.) - -* The ``dis.HAVE_ARGUMENT`` separator is deprecated. Check membership - in :data:`~dis.hasarg` instead. - (Contributed by Irit Katriel in :gh:`109319`.) - -* Deprecate non-standard format specifier "N" for :class:`decimal.Decimal`. - It was not documented and only supported in the C implementation. - (Contributed by Serhiy Storchaka in :gh:`89902`.) - -* Emit deprecation warning for non-integer numbers in :mod:`gettext` functions - and methods that consider plural forms even if the translation was not found. - (Contributed by Serhiy Storchaka in :gh:`88434`.) - * Calling :meth:`frame.clear` on a suspended frame raises :exc:`RuntimeError` (as has always been the case for an executing frame). (Contributed by Irit Katriel in :gh:`79932`.) @@ -593,9 +595,6 @@ Deprecated coroutine. (Contributed by Irit Katriel in :gh:`81137`.) -* Deprecate undocumented :func:`!pydoc.ispackage` function. - (Contributed by Zackery Spytz in :gh:`64020`.) - Pending Removal in Python 3.14 ------------------------------ @@ -657,11 +656,11 @@ Pending Removal in Python 3.14 :func:`~multiprocessing.set_start_method` APIs to explicitly specify when your code *requires* ``'fork'``. See :ref:`multiprocessing-start-methods`. -* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to`, +* :mod:`pathlib`: :meth:`~pathlib.PurePath.is_relative_to` and :meth:`~pathlib.PurePath.relative_to`: passing additional arguments is deprecated. -* :func:`pkgutil.find_loader` and :func:`pkgutil.get_loader` +* :mod:`pkgutil`: :func:`~pkgutil.find_loader` and :func:`~pkgutil.get_loader` now raise :exc:`DeprecationWarning`; use :func:`importlib.util.find_spec` instead. (Contributed by Nikita Sobolev in :gh:`97850`.) @@ -719,10 +718,16 @@ Pending Removal in Python 3.15 (Contributed by Hugo van Kemenade in :gh:`111187`.) * :mod:`pathlib`: + :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for + removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved + paths on Windows. - * :meth:`pathlib.PurePath.is_reserved` is deprecated and scheduled for - removal in Python 3.15. Use :func:`os.path.isreserved` to detect reserved - paths on Windows. +* :mod:`threading`: + Passing any arguments to :func:`threading.RLock` is now deprecated. + C version allows any numbers of args and kwargs, + but they are just ignored. Python version does not allow any arguments. + All arguments will be removed from :func:`threading.RLock` in Python 3.15. + (Contributed by Nikita Sobolev in :gh:`102029`.) * :class:`typing.NamedTuple`: @@ -749,12 +754,6 @@ Pending Removal in Python 3.15 They will be removed in Python 3.15. (Contributed by Victor Stinner in :gh:`105096`.) -* Passing any arguments to :func:`threading.RLock` is now deprecated. - C version allows any numbers of args and kwargs, - but they are just ignored. Python version does not allow any arguments. - All arguments will be removed from :func:`threading.RLock` in Python 3.15. - (Contributed by Nikita Sobolev in :gh:`102029`.) - Pending Removal in Python 3.16 ------------------------------ @@ -801,6 +800,9 @@ although there is currently no date scheduled for their removal. :data:`calendar.FEBRUARY`. (Contributed by Prince Roshan in :gh:`103636`.) +* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method + instead. + * :mod:`datetime`: * :meth:`~datetime.datetime.utcnow`: @@ -836,11 +838,13 @@ although there is currently no date scheduled for their removal. underscore. (Contributed by Serhiy Storchaka in :gh:`91760`.) +* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. + * :mod:`ssl` options and protocols: * :class:`ssl.SSLContext` without protocol argument is deprecated. * :class:`ssl.SSLContext`: :meth:`~ssl.SSLContext.set_npn_protocols` and - :meth:`!~ssl.SSLContext.selected_npn_protocol` are deprecated: use ALPN + :meth:`!selected_npn_protocol` are deprecated: use ALPN instead. * ``ssl.OP_NO_SSL*`` options * ``ssl.OP_NO_TLS*`` options @@ -853,13 +857,6 @@ although there is currently no date scheduled for their removal. * ``ssl.TLSVersion.TLSv1`` * ``ssl.TLSVersion.TLSv1_1`` -* :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. - -* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method - instead. - -* :class:`typing.Text` (:gh:`92332`). - * :func:`sysconfig.is_python_build` *check_home* parameter is deprecated and ignored. @@ -874,14 +871,10 @@ although there is currently no date scheduled for their removal. * :meth:`!threading.currentThread`: use :meth:`threading.current_thread`. * :meth:`!threading.activeCount`: use :meth:`threading.active_count`. -* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value - that is not None from a test case. - -* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and - :class:`~urllib.request.FancyURLopener` style of invoking requests is - deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods. +* :class:`typing.Text` (:gh:`92332`). -* :func:`!urllib.parse.to_bytes`. +* :class:`unittest.IsolatedAsyncioTestCase`: it is deprecated to return a value + that is not ``None`` from a test case. * :mod:`urllib.parse` deprecated functions: :func:`~urllib.parse.urlparse` instead @@ -895,6 +888,11 @@ although there is currently no date scheduled for their removal. * ``splittype()`` * ``splituser()`` * ``splitvalue()`` + * ``to_bytes()`` + +* :mod:`urllib.request`: :class:`~urllib.request.URLopener` and + :class:`~urllib.request.FancyURLopener` style of invoking requests is + deprecated. Use newer :func:`~urllib.request.urlopen` functions and methods. * :mod:`wsgiref`: ``SimpleHandler.stdout.write()`` should not do partial writes. @@ -1190,10 +1188,10 @@ Changes in the Python API * Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`, :c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`, :c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and - :c:func:`PySys_GetObject`, which clear all errors occurred during calling - the function, report now them using :func:`sys.unraisablehook`. - You can consider to replace these functions with other functions as - recomended in the documentation. + :c:func:`PySys_GetObject`, which clear all errors which occurred when calling + them, now report them using :func:`sys.unraisablehook`. + You may replace them with other functions as + recommended in the documentation. (Contributed by Serhiy Storchaka in :gh:`106672`.) * An :exc:`OSError` is now raised by :func:`getpass.getuser` for any failure to @@ -1202,7 +1200,7 @@ Changes in the Python API * The :mod:`threading` module now expects the :mod:`!_thread` module to have an ``_is_main_interpreter`` attribute. It is a function with no - arguments that returns ``True`` if the current interpreter is the + arguments that return ``True`` if the current interpreter is the main interpreter. Any library or application that provides a custom ``_thread`` module @@ -1225,7 +1223,7 @@ Build Changes (Contributed by Erlend Aasland in :gh:`105875`.) * Python built with :file:`configure` :option:`--with-trace-refs` (tracing - references) is now ABI compatible with Python release build and + references) is now ABI compatible with the Python release build and :ref:`debug build <debug-build>`. (Contributed by Victor Stinner in :gh:`108634`.) @@ -1252,7 +1250,7 @@ New Features (Contributed by Inada Naoki in :gh:`104922`.) * The *keywords* parameter of :c:func:`PyArg_ParseTupleAndKeywords` and - :c:func:`PyArg_VaParseTupleAndKeywords` has now type :c:expr:`char * const *` + :c:func:`PyArg_VaParseTupleAndKeywords` now has type :c:expr:`char * const *` in C and :c:expr:`const char * const *` in C++, instead of :c:expr:`char **`. It makes these functions compatible with arguments of type :c:expr:`const char * const *`, :c:expr:`const char **` or @@ -1309,14 +1307,14 @@ New Features always steals a reference to the value. (Contributed by Serhiy Storchaka in :gh:`86493`.) -* Added :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef` +* Add :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef` functions: similar to :c:func:`PyDict_GetItemWithError` but returning a :term:`strong reference` instead of a :term:`borrowed reference`. Moreover, these functions return -1 on error and so checking ``PyErr_Occurred()`` is not needed. (Contributed by Victor Stinner in :gh:`106004`.) -* Added :c:func:`PyDict_ContainsString` function: same as +* Add :c:func:`PyDict_ContainsString` function: same as :c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. (Contributed by Victor Stinner in :gh:`108314`.) @@ -1374,7 +1372,7 @@ New Features (Contributed by Victor Stinner in :gh:`85283`.) * Add :c:func:`PyErr_FormatUnraisable` function: similar to - :c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage. + :c:func:`PyErr_WriteUnraisable`, but allow customizing the warning message. (Contributed by Serhiy Storchaka in :gh:`108082`.) * Add :c:func:`PyList_Extend` and :c:func:`PyList_Clear` functions: similar to @@ -1384,7 +1382,7 @@ New Features * Add :c:func:`PyDict_Pop` and :c:func:`PyDict_PopString` functions: remove a key from a dictionary and optionally return the removed value. This is similar to :meth:`dict.pop`, but without the default value and not raising - :exc:`KeyError` if the key missing. + :exc:`KeyError` if the key is missing. (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.) * Add :c:func:`Py_HashPointer` function to hash a pointer. @@ -1497,7 +1495,7 @@ Removed ------- * Removed chained :class:`classmethod` descriptors (introduced in - :issue:`19072`). This can no longer be used to wrap other descriptors + :gh:`63272`). This can no longer be used to wrap other descriptors such as :class:`property`. The core design of this feature was flawed and caused a number of downstream problems. To "pass-through" a :class:`classmethod`, consider using the :attr:`!__wrapped__` @@ -1511,14 +1509,14 @@ Removed add ``cc @vstinner`` to the issue to notify Victor Stinner. (Contributed by Victor Stinner in :gh:`106320`.) -* Remove functions deprecated in Python 3.9. +* Remove functions deprecated in Python 3.9: * ``PyEval_CallObject()``, ``PyEval_CallObjectWithKeywords()``: use :c:func:`PyObject_CallNoArgs` or :c:func:`PyObject_Call` instead. Warning: :c:func:`PyObject_Call` positional arguments must be a - :class:`tuple` and must not be *NULL*, keyword arguments must be a - :class:`dict` or *NULL*, whereas removed functions checked arguments type - and accepted *NULL* positional and keyword arguments. + :class:`tuple` and must not be ``NULL``, keyword arguments must be a + :class:`dict` or ``NULL``, whereas removed functions checked arguments type + and accepted ``NULL`` positional and keyword arguments. To replace ``PyEval_CallObjectWithKeywords(func, NULL, kwargs)`` with :c:func:`PyObject_Call`, pass an empty tuple as positional arguments using :c:func:`PyTuple_New(0) <PyTuple_New>`. From 1f515e8a109204f7399d85b7fd806135166422d9 Mon Sep 17 00:00:00 2001 From: Eugene Toder <eltoder@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:19:46 -0500 Subject: [PATCH 102/263] gh-112919: Speed-up datetime, date and time.replace() (GH-112921) Use argument clinic and call new_* functions directly. This speeds up these functions 6x to 7.5x when calling with keyword arguments. --- .../pycore_global_objects_fini_generated.h | 8 + Include/internal/pycore_global_strings.h | 8 + .../internal/pycore_runtime_init_generated.h | 8 + .../internal/pycore_unicodeobject_generated.h | 24 ++ ...-12-09-23-31-17.gh-issue-112919.S5k9QN.rst | 2 + Modules/_datetimemodule.c | 170 ++++----- Modules/clinic/_datetimemodule.c.h | 352 +++++++++++++++++- 7 files changed, 476 insertions(+), 96 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 57505b5388fd6c3..dd09ff40f39fe6f 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -876,6 +876,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(database)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(day)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decode)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(decoder)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(default)); @@ -945,6 +946,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fix_imports)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flags)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(flush)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fold)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(follow_symlinks)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(from_param)); @@ -975,6 +977,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(headers)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); @@ -1059,11 +1062,14 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metaclass)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(metadata)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(method)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(microsecond)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(minute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mod)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mode)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module_globals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(modules)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(month)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mro)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mycmp)); @@ -1168,6 +1174,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(salt)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(sched_priority)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(scheduler)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(second)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seek)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(seekable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(selectors)); @@ -1244,6 +1251,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(type_params)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tz)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzinfo)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tzname)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(uid)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(unlink)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 0f4f3b619102414..79d6509abcdfd91 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -365,6 +365,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(d) STRUCT_FOR_ID(data) STRUCT_FOR_ID(database) + STRUCT_FOR_ID(day) STRUCT_FOR_ID(decode) STRUCT_FOR_ID(decoder) STRUCT_FOR_ID(default) @@ -434,6 +435,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fix_imports) STRUCT_FOR_ID(flags) STRUCT_FOR_ID(flush) + STRUCT_FOR_ID(fold) STRUCT_FOR_ID(follow_symlinks) STRUCT_FOR_ID(format) STRUCT_FOR_ID(from_param) @@ -464,6 +466,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(headers) STRUCT_FOR_ID(hi) STRUCT_FOR_ID(hook) + STRUCT_FOR_ID(hour) STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) STRUCT_FOR_ID(identity_hint) @@ -548,11 +551,14 @@ struct _Py_global_strings { STRUCT_FOR_ID(metaclass) STRUCT_FOR_ID(metadata) STRUCT_FOR_ID(method) + STRUCT_FOR_ID(microsecond) + STRUCT_FOR_ID(minute) STRUCT_FOR_ID(mod) STRUCT_FOR_ID(mode) STRUCT_FOR_ID(module) STRUCT_FOR_ID(module_globals) STRUCT_FOR_ID(modules) + STRUCT_FOR_ID(month) STRUCT_FOR_ID(mro) STRUCT_FOR_ID(msg) STRUCT_FOR_ID(mycmp) @@ -657,6 +663,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(salt) STRUCT_FOR_ID(sched_priority) STRUCT_FOR_ID(scheduler) + STRUCT_FOR_ID(second) STRUCT_FOR_ID(seek) STRUCT_FOR_ID(seekable) STRUCT_FOR_ID(selectors) @@ -733,6 +740,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(type) STRUCT_FOR_ID(type_params) STRUCT_FOR_ID(tz) + STRUCT_FOR_ID(tzinfo) STRUCT_FOR_ID(tzname) STRUCT_FOR_ID(uid) STRUCT_FOR_ID(unlink) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 63a2b54c839a4b5..f3c55acfb3c282f 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -874,6 +874,7 @@ extern "C" { INIT_ID(d), \ INIT_ID(data), \ INIT_ID(database), \ + INIT_ID(day), \ INIT_ID(decode), \ INIT_ID(decoder), \ INIT_ID(default), \ @@ -943,6 +944,7 @@ extern "C" { INIT_ID(fix_imports), \ INIT_ID(flags), \ INIT_ID(flush), \ + INIT_ID(fold), \ INIT_ID(follow_symlinks), \ INIT_ID(format), \ INIT_ID(from_param), \ @@ -973,6 +975,7 @@ extern "C" { INIT_ID(headers), \ INIT_ID(hi), \ INIT_ID(hook), \ + INIT_ID(hour), \ INIT_ID(id), \ INIT_ID(ident), \ INIT_ID(identity_hint), \ @@ -1057,11 +1060,14 @@ extern "C" { INIT_ID(metaclass), \ INIT_ID(metadata), \ INIT_ID(method), \ + INIT_ID(microsecond), \ + INIT_ID(minute), \ INIT_ID(mod), \ INIT_ID(mode), \ INIT_ID(module), \ INIT_ID(module_globals), \ INIT_ID(modules), \ + INIT_ID(month), \ INIT_ID(mro), \ INIT_ID(msg), \ INIT_ID(mycmp), \ @@ -1166,6 +1172,7 @@ extern "C" { INIT_ID(salt), \ INIT_ID(sched_priority), \ INIT_ID(scheduler), \ + INIT_ID(second), \ INIT_ID(seek), \ INIT_ID(seekable), \ INIT_ID(selectors), \ @@ -1242,6 +1249,7 @@ extern "C" { INIT_ID(type), \ INIT_ID(type_params), \ INIT_ID(tz), \ + INIT_ID(tzinfo), \ INIT_ID(tzname), \ INIT_ID(uid), \ INIT_ID(unlink), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index bf8cdd85e4be5c1..2e9572382fe0332 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -936,6 +936,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(database); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(day); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(decode); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1143,6 +1146,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(flush); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(fold); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(follow_symlinks); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1233,6 +1239,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(hook); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(hour); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(id); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1485,6 +1494,12 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(method); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(microsecond); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(minute); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mod); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1500,6 +1515,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(modules); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(month); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(mro); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1812,6 +1830,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(scheduler); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(second); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(seek); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -2040,6 +2061,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(tz); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(tzinfo); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(tzname); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst new file mode 100644 index 000000000000000..3e99d480139cbea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-09-23-31-17.gh-issue-112919.S5k9QN.rst @@ -0,0 +1,2 @@ +Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` and +:func:`datetime.time.replace`. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index cb5403e8461ff0e..9b8e0a719d9048c 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -61,16 +61,6 @@ static datetime_state _datetime_global_state; #define STATIC_STATE() (&_datetime_global_state) -/*[clinic input] -module datetime -class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" -class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType" -class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType" -[clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81bec0fa19837f63]*/ - -#include "clinic/_datetimemodule.c.h" - /* We require that C int be at least 32 bits, and use int virtually * everywhere. In just a few cases we use a temp long, where a Python * API returns a C long. In such cases, we have to ensure that the @@ -161,6 +151,17 @@ static PyTypeObject PyDateTime_TimeZoneType; static int check_tzinfo_subclass(PyObject *p); +/*[clinic input] +module datetime +class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType" +class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType" +class datetime.time "PyDateTime_Time *" "&PyDateTime_TimeType" +class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6f65a48dd22fa40f]*/ + +#include "clinic/_datetimemodule.c.h" + /* --------------------------------------------------------------------------- * Math utilities. @@ -3466,24 +3467,22 @@ date_timetuple(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored)) 0, 0, 0, -1); } +/*[clinic input] +datetime.date.replace + + year: int(c_default="GET_YEAR(self)") = unchanged + month: int(c_default="GET_MONTH(self)") = unchanged + day: int(c_default="GET_DAY(self)") = unchanged + +Return date with new specified fields. +[clinic start generated code]*/ + static PyObject * -date_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw) +datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, + int day) +/*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/ { - PyObject *clone; - PyObject *tuple; - int year = GET_YEAR(self); - int month = GET_MONTH(self); - int day = GET_DAY(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws, - &year, &month, &day)) - return NULL; - tuple = Py_BuildValue("iii", year, month, day); - if (tuple == NULL) - return NULL; - clone = date_new(Py_TYPE(self), tuple, NULL); - Py_DECREF(tuple); - return clone; + return new_date_ex(year, month, day, Py_TYPE(self)); } static Py_hash_t @@ -3596,10 +3595,9 @@ static PyMethodDef date_methods[] = { PyDoc_STR("Return the day of the week represented by the date.\n" "Monday == 0 ... Sunday == 6")}, - {"replace", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return date with new specified fields.")}, + DATETIME_DATE_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL | METH_KEYWORDS}, {"__reduce__", (PyCFunction)date_reduce, METH_NOARGS, PyDoc_STR("__reduce__() -> (cls, state)")}, @@ -4573,36 +4571,28 @@ time_hash(PyDateTime_Time *self) return self->hashcode; } +/*[clinic input] +datetime.time.replace + + hour: int(c_default="TIME_GET_HOUR(self)") = unchanged + minute: int(c_default="TIME_GET_MINUTE(self)") = unchanged + second: int(c_default="TIME_GET_SECOND(self)") = unchanged + microsecond: int(c_default="TIME_GET_MICROSECOND(self)") = unchanged + tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged + * + fold: int(c_default="TIME_GET_FOLD(self)") = unchanged + +Return time with new specified fields. +[clinic start generated code]*/ + static PyObject * -time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) +datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold) +/*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/ { - PyObject *clone; - PyObject *tuple; - int hh = TIME_GET_HOUR(self); - int mm = TIME_GET_MINUTE(self); - int ss = TIME_GET_SECOND(self); - int us = TIME_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - int fold = TIME_GET_FOLD(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i:replace", - time_kws, - &hh, &mm, &ss, &us, &tzinfo, &fold)) - return NULL; - if (fold != 0 && fold != 1) { - PyErr_SetString(PyExc_ValueError, - "fold must be either 0 or 1"); - return NULL; - } - tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = time_new(Py_TYPE(self), tuple, NULL); - if (clone != NULL) { - TIME_SET_FOLD(clone, fold); - } - Py_DECREF(tuple); - return clone; + return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold, + Py_TYPE(self)); } static PyObject * @@ -4732,10 +4722,9 @@ static PyMethodDef time_methods[] = { {"dst", (PyCFunction)time_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, - {"replace", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return time with new specified fields.")}, + DATETIME_TIME_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL | METH_KEYWORDS}, {"fromisoformat", (PyCFunction)time_fromisoformat, METH_O | METH_CLASS, PyDoc_STR("string -> time from a string in ISO 8601 format")}, @@ -6042,40 +6031,32 @@ datetime_hash(PyDateTime_DateTime *self) return self->hashcode; } +/*[clinic input] +datetime.datetime.replace + + year: int(c_default="GET_YEAR(self)") = unchanged + month: int(c_default="GET_MONTH(self)") = unchanged + day: int(c_default="GET_DAY(self)") = unchanged + hour: int(c_default="DATE_GET_HOUR(self)") = unchanged + minute: int(c_default="DATE_GET_MINUTE(self)") = unchanged + second: int(c_default="DATE_GET_SECOND(self)") = unchanged + microsecond: int(c_default="DATE_GET_MICROSECOND(self)") = unchanged + tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged + * + fold: int(c_default="DATE_GET_FOLD(self)") = unchanged + +Return datetime with new specified fields. +[clinic start generated code]*/ + static PyObject * -datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) +datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, + int month, int day, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold) +/*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/ { - PyObject *clone; - PyObject *tuple; - int y = GET_YEAR(self); - int m = GET_MONTH(self); - int d = GET_DAY(self); - int hh = DATE_GET_HOUR(self); - int mm = DATE_GET_MINUTE(self); - int ss = DATE_GET_SECOND(self); - int us = DATE_GET_MICROSECOND(self); - PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; - int fold = DATE_GET_FOLD(self); - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace", - datetime_kws, - &y, &m, &d, &hh, &mm, &ss, &us, - &tzinfo, &fold)) - return NULL; - if (fold != 0 && fold != 1) { - PyErr_SetString(PyExc_ValueError, - "fold must be either 0 or 1"); - return NULL; - } - tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = datetime_new(Py_TYPE(self), tuple, NULL); - if (clone != NULL) { - DATE_SET_FOLD(clone, fold); - } - Py_DECREF(tuple); - return clone; + return new_datetime_ex2(year, month, day, hour, minute, second, + microsecond, tzinfo, fold, Py_TYPE(self)); } static PyObject * @@ -6597,10 +6578,9 @@ static PyMethodDef datetime_methods[] = { {"dst", (PyCFunction)datetime_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, - {"replace", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS, - PyDoc_STR("Return datetime with new specified fields.")}, + DATETIME_DATETIME_REPLACE_METHODDEF - {"__replace__", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS}, + {"__replace__", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL | METH_KEYWORDS}, {"astimezone", _PyCFunction_CAST(datetime_astimezone), METH_VARARGS | METH_KEYWORDS, PyDoc_STR("tz -> convert to local time in new timezone tz\n")}, diff --git a/Modules/clinic/_datetimemodule.c.h b/Modules/clinic/_datetimemodule.c.h index 1ee50fc2a137621..48499e0aaf7783d 100644 --- a/Modules/clinic/_datetimemodule.c.h +++ b/Modules/clinic/_datetimemodule.c.h @@ -82,6 +82,207 @@ iso_calendar_date_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) return return_value; } +PyDoc_STRVAR(datetime_date_replace__doc__, +"replace($self, /, year=unchanged, month=unchanged, day=unchanged)\n" +"--\n" +"\n" +"Return date with new specified fields."); + +#define DATETIME_DATE_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL|METH_KEYWORDS, datetime_date_replace__doc__}, + +static PyObject * +datetime_date_replace_impl(PyDateTime_Date *self, int year, int month, + int day); + +static PyObject * +datetime_date_replace(PyDateTime_Date *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"year", "month", "day", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 3, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + year = PyLong_AsInt(args[0]); + if (year == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + month = PyLong_AsInt(args[1]); + if (month == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + day = PyLong_AsInt(args[2]); + if (day == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = datetime_date_replace_impl(self, year, month, day); + +exit: + return return_value; +} + +PyDoc_STRVAR(datetime_time_replace__doc__, +"replace($self, /, hour=unchanged, minute=unchanged, second=unchanged,\n" +" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n" +"--\n" +"\n" +"Return time with new specified fields."); + +#define DATETIME_TIME_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL|METH_KEYWORDS, datetime_time_replace__doc__}, + +static PyObject * +datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold); + +static PyObject * +datetime_time_replace(PyDateTime_Time *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 6 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[6]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int hour = TIME_GET_HOUR(self); + int minute = TIME_GET_MINUTE(self); + int second = TIME_GET_SECOND(self); + int microsecond = TIME_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = TIME_GET_FOLD(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 5, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + hour = PyLong_AsInt(args[0]); + if (hour == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + minute = PyLong_AsInt(args[1]); + if (minute == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[2]) { + second = PyLong_AsInt(args[2]); + if (second == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[3]) { + microsecond = PyLong_AsInt(args[3]); + if (microsecond == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[4]) { + tzinfo = args[4]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + fold = PyLong_AsInt(args[5]); + if (fold == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = datetime_time_replace_impl(self, hour, minute, second, microsecond, tzinfo, fold); + +exit: + return return_value; +} + PyDoc_STRVAR(datetime_datetime_now__doc__, "now($type, /, tz=None)\n" "--\n" @@ -146,4 +347,153 @@ datetime_datetime_now(PyTypeObject *type, PyObject *const *args, Py_ssize_t narg exit: return return_value; } -/*[clinic end generated code: output=562813dd3e164794 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(datetime_datetime_replace__doc__, +"replace($self, /, year=unchanged, month=unchanged, day=unchanged,\n" +" hour=unchanged, minute=unchanged, second=unchanged,\n" +" microsecond=unchanged, tzinfo=unchanged, *, fold=unchanged)\n" +"--\n" +"\n" +"Return datetime with new specified fields."); + +#define DATETIME_DATETIME_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL|METH_KEYWORDS, datetime_datetime_replace__doc__}, + +static PyObject * +datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year, + int month, int day, int hour, int minute, + int second, int microsecond, PyObject *tzinfo, + int fold); + +static PyObject * +datetime_datetime_replace(PyDateTime_DateTime *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 9 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(year), &_Py_ID(month), &_Py_ID(day), &_Py_ID(hour), &_Py_ID(minute), &_Py_ID(second), &_Py_ID(microsecond), &_Py_ID(tzinfo), &_Py_ID(fold), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"year", "month", "day", "hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "replace", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[9]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int year = GET_YEAR(self); + int month = GET_MONTH(self); + int day = GET_DAY(self); + int hour = DATE_GET_HOUR(self); + int minute = DATE_GET_MINUTE(self); + int second = DATE_GET_SECOND(self); + int microsecond = DATE_GET_MICROSECOND(self); + PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None; + int fold = DATE_GET_FOLD(self); + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 8, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + year = PyLong_AsInt(args[0]); + if (year == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[1]) { + month = PyLong_AsInt(args[1]); + if (month == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[2]) { + day = PyLong_AsInt(args[2]); + if (day == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[3]) { + hour = PyLong_AsInt(args[3]); + if (hour == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[4]) { + minute = PyLong_AsInt(args[4]); + if (minute == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[5]) { + second = PyLong_AsInt(args[5]); + if (second == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[6]) { + microsecond = PyLong_AsInt(args[6]); + if (microsecond == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } + } + if (args[7]) { + tzinfo = args[7]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + fold = PyLong_AsInt(args[8]); + if (fold == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = datetime_datetime_replace_impl(self, year, month, day, hour, minute, second, microsecond, tzinfo, fold); + +exit: + return return_value; +} +/*[clinic end generated code: output=c7a04b865b1e0890 input=a9049054013a1b77]*/ From 39d102c2ee8eec8ab0bacbcd62d62a72742ecc7c Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado <Pablogsal@gmail.com> Date: Tue, 30 Jan 2024 16:21:30 +0000 Subject: [PATCH 103/263] gh-113744: Add a new IncompleteInputError exception to improve incomplete input detection in the codeop module (#113745) Signed-off-by: Pablo Galindo <pablogsal@gmail.com> --- Doc/data/stable_abi.dat | 1 + Include/pyerrors.h | 1 + Lib/codeop.py | 5 +++-- Lib/test/exception_hierarchy.txt | 1 + Lib/test/test_pickle.py | 3 ++- Lib/test/test_stable_abi_ctypes.py | 1 + Misc/stable_abi.toml | 2 ++ Objects/exceptions.c | 6 ++++++ PC/python3dll.c | 1 + Parser/pegen.c | 2 +- Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 11 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 811b1bd84d24174..da28a2be60bc1bc 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -220,6 +220,7 @@ var,PyExc_GeneratorExit,3.2,, var,PyExc_IOError,3.2,, var,PyExc_ImportError,3.2,, var,PyExc_ImportWarning,3.2,, +var,PyExc_IncompleteInputError,3.13,, var,PyExc_IndentationError,3.2,, var,PyExc_IndexError,3.2,, var,PyExc_InterruptedError,3.7,, diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 5d0028c116e2d86..68d7985dac8876b 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -108,6 +108,7 @@ PyAPI_DATA(PyObject *) PyExc_NotImplementedError; PyAPI_DATA(PyObject *) PyExc_SyntaxError; PyAPI_DATA(PyObject *) PyExc_IndentationError; PyAPI_DATA(PyObject *) PyExc_TabError; +PyAPI_DATA(PyObject *) PyExc_IncompleteInputError; PyAPI_DATA(PyObject *) PyExc_ReferenceError; PyAPI_DATA(PyObject *) PyExc_SystemError; PyAPI_DATA(PyObject *) PyExc_SystemExit; diff --git a/Lib/codeop.py b/Lib/codeop.py index 91146be2c438e2c..6ad60e7f85098d8 100644 --- a/Lib/codeop.py +++ b/Lib/codeop.py @@ -65,9 +65,10 @@ def _maybe_compile(compiler, source, filename, symbol): try: compiler(source + "\n", filename, symbol) return None + except IncompleteInputError as e: + return None except SyntaxError as e: - if "incomplete input" in str(e): - return None + pass # fallthrough return compiler(source, filename, symbol, incomplete_input=False) diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 1eca123be0fecbf..217ee15d4c8af54 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -44,6 +44,7 @@ BaseException ├── StopAsyncIteration ├── StopIteration ├── SyntaxError + │ └── IncompleteInputError │ └── IndentationError │ └── TabError ├── SystemError diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index b2245ddf72f7085..5e187e5189d1179 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -567,7 +567,8 @@ def test_exceptions(self): RecursionError, EncodingWarning, BaseExceptionGroup, - ExceptionGroup): + ExceptionGroup, + IncompleteInputError): continue if exc is not OSError and issubclass(exc, OSError): self.assertEqual(reverse_mapping('builtins', name), diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 90d452728384208..054e7f0feb1a19d 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -261,6 +261,7 @@ def test_windows_feature_macros(self): "PyExc_IOError", "PyExc_ImportError", "PyExc_ImportWarning", + "PyExc_IncompleteInputError", "PyExc_IndentationError", "PyExc_IndexError", "PyExc_InterruptedError", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 2e6b0fff9cd7704..ae19d25809ec867 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2485,3 +2485,5 @@ [function._Py_SetRefcnt] added = '3.13' abi_only = true +[data.PyExc_IncompleteInputError] + added = '3.13' diff --git a/Objects/exceptions.c b/Objects/exceptions.c index a685ed803cd02db..cff55d05163b6ba 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2566,6 +2566,11 @@ MiddlingExtendsException(PyExc_SyntaxError, IndentationError, SyntaxError, MiddlingExtendsException(PyExc_IndentationError, TabError, SyntaxError, "Improper mixture of spaces and tabs."); +/* + * IncompleteInputError extends SyntaxError + */ +MiddlingExtendsException(PyExc_SyntaxError, IncompleteInputError, SyntaxError, + "incomplete input."); /* * LookupError extends Exception @@ -3635,6 +3640,7 @@ static struct static_exception static_exceptions[] = { // Level 4: Other subclasses ITEM(IndentationError), // base: SyntaxError(Exception) + ITEM(IncompleteInputError), // base: SyntaxError(Exception) ITEM(IndexError), // base: LookupError(Exception) ITEM(KeyError), // base: LookupError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception) diff --git a/PC/python3dll.c b/PC/python3dll.c index 07aa84c91f9fc7f..09ecf98fe56ea62 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -830,6 +830,7 @@ EXPORT_DATA(PyExc_FutureWarning) EXPORT_DATA(PyExc_GeneratorExit) EXPORT_DATA(PyExc_ImportError) EXPORT_DATA(PyExc_ImportWarning) +EXPORT_DATA(PyExc_IncompleteInputError) EXPORT_DATA(PyExc_IndentationError) EXPORT_DATA(PyExc_IndexError) EXPORT_DATA(PyExc_InterruptedError) diff --git a/Parser/pegen.c b/Parser/pegen.c index 7766253a76066f0..3d3e64559403b16 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -844,7 +844,7 @@ _PyPegen_run_parser(Parser *p) if (res == NULL) { if ((p->flags & PyPARSE_ALLOW_INCOMPLETE_INPUT) && _is_end_of_source(p)) { PyErr_Clear(); - return RAISE_SYNTAX_ERROR("incomplete input"); + return _PyPegen_raise_error(p, PyExc_IncompleteInputError, 0, "incomplete input"); } if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_SyntaxError)) { return NULL; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index e3a1b5d532bda2a..0b02ad01d399836 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -197,6 +197,7 @@ Objects/exceptions.c - _PyExc_AttributeError - Objects/exceptions.c - _PyExc_SyntaxError - Objects/exceptions.c - _PyExc_IndentationError - Objects/exceptions.c - _PyExc_TabError - +Objects/exceptions.c - _PyExc_IncompleteInputError - Objects/exceptions.c - _PyExc_LookupError - Objects/exceptions.c - _PyExc_IndexError - Objects/exceptions.c - _PyExc_KeyError - @@ -261,6 +262,7 @@ Objects/exceptions.c - PyExc_AttributeError - Objects/exceptions.c - PyExc_SyntaxError - Objects/exceptions.c - PyExc_IndentationError - Objects/exceptions.c - PyExc_TabError - +Objects/exceptions.c - PyExc_IncompleteInputError - Objects/exceptions.c - PyExc_LookupError - Objects/exceptions.c - PyExc_IndexError - Objects/exceptions.c - PyExc_KeyError - From 0990d55725cb649e74739c983b67cf08c58e8439 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 30 Jan 2024 09:33:36 -0800 Subject: [PATCH 104/263] gh-112075: refactor dictionary lookup functions for better re-usability (#114629) Refactor dict lookup functions to use force inline helpers --- Objects/dictobject.c | 192 +++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c5477ab15f8dc9a..23d7e9b5e38a356 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -874,11 +874,11 @@ lookdict_index(PyDictKeysObject *k, Py_hash_t hash, Py_ssize_t index) Py_UNREACHABLE(); } -// Search non-Unicode key from Unicode table -static Py_ssize_t -unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +static inline Py_ALWAYS_INLINE Py_ssize_t +do_lookup(PyDictObject *mp, PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, + Py_ssize_t (*check_lookup)(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t ix, PyObject *key, Py_hash_t)) { - PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk); + void *ep0 = _DK_ENTRIES(dk); size_t mask = DK_MASK(dk); size_t perturb = hash; size_t i = (size_t)hash & mask; @@ -886,73 +886,26 @@ unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key for (;;) { ix = dictkeys_get_index(dk, i); if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key) { + Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash); + if (cmp < 0) { + return cmp; + } else if (cmp) { return ix; } - if (unicode_get_hash(ep->me_key) == hash) { - PyObject *startkey = ep->me_key; - Py_INCREF(startkey); - int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) { - return DKIX_ERROR; - } - if (dk == mp->ma_keys && ep->me_key == startkey) { - if (cmp > 0) { - return ix; - } - } - else { - /* The dict was mutated, restart */ - return DKIX_KEY_CHANGED; - } - } } else if (ix == DKIX_EMPTY) { return DKIX_EMPTY; } perturb >>= PERTURB_SHIFT; i = mask & (i*5 + perturb + 1); - } - Py_UNREACHABLE(); -} -// Search Unicode key from Unicode table. -static Py_ssize_t _Py_HOT_FUNCTION -unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) -{ - PyDictUnicodeEntry *ep0 = DK_UNICODE_ENTRIES(dk); - size_t mask = DK_MASK(dk); - size_t perturb = hash; - size_t i = (size_t)hash & mask; - Py_ssize_t ix; - for (;;) { - ix = dictkeys_get_index(dk, i); - if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key || - (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { - return ix; - } - } - else if (ix == DKIX_EMPTY) { - return DKIX_EMPTY; - } - perturb >>= PERTURB_SHIFT; - i = mask & (i*5 + perturb + 1); // Manual loop unrolling ix = dictkeys_get_index(dk, i); if (ix >= 0) { - PyDictUnicodeEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - assert(PyUnicode_CheckExact(ep->me_key)); - if (ep->me_key == key || - (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { + Py_ssize_t cmp = check_lookup(mp, dk, ep0, ix, key, hash); + if (cmp < 0) { + return cmp; + } else if (cmp) { return ix; } } @@ -965,49 +918,94 @@ unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) Py_UNREACHABLE(); } -// Search key from Generic table. +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_unicode_generic(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + assert(PyUnicode_CheckExact(ep->me_key)); + assert(!PyUnicode_CheckExact(key)); + // TODO: Thread safety + + if (unicode_get_hash(ep->me_key) == hash) { + PyObject *startkey = ep->me_key; + Py_INCREF(startkey); + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; + } + if (dk == mp->ma_keys && ep->me_key == startkey) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; + } + } + return 0; +} + +// Search non-Unicode key from Unicode table static Py_ssize_t -dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +unicodekeys_lookup_generic(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) { - PyDictKeyEntry *ep0 = DK_ENTRIES(dk); - size_t mask = DK_MASK(dk); - size_t perturb = hash; - size_t i = (size_t)hash & mask; - Py_ssize_t ix; - for (;;) { - ix = dictkeys_get_index(dk, i); - if (ix >= 0) { - PyDictKeyEntry *ep = &ep0[ix]; - assert(ep->me_key != NULL); - if (ep->me_key == key) { - return ix; - } - if (ep->me_hash == hash) { - PyObject *startkey = ep->me_key; - Py_INCREF(startkey); - int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) { - return DKIX_ERROR; - } - if (dk == mp->ma_keys && ep->me_key == startkey) { - if (cmp > 0) { - return ix; - } - } - else { - /* The dict was mutated, restart */ - return DKIX_KEY_CHANGED; - } - } + return do_lookup(mp, dk, key, hash, compare_unicode_generic); +} + +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + assert(PyUnicode_CheckExact(ep->me_key)); + if (ep->me_key == key || + (unicode_get_hash(ep->me_key) == hash && unicode_eq(ep->me_key, key))) { + return 1; + } + return 0; +} + +static Py_ssize_t _Py_HOT_FUNCTION +unicodekeys_lookup_unicode(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(NULL, dk, key, hash, compare_unicode_unicode); +} + +static inline Py_ALWAYS_INLINE Py_ssize_t +compare_generic(PyDictObject *mp, PyDictKeysObject *dk, + void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash) +{ + PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix]; + assert(ep->me_key != NULL); + if (ep->me_key == key) { + return 1; + } + if (ep->me_hash == hash) { + PyObject *startkey = ep->me_key; + Py_INCREF(startkey); + int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); + Py_DECREF(startkey); + if (cmp < 0) { + return DKIX_ERROR; } - else if (ix == DKIX_EMPTY) { - return DKIX_EMPTY; + if (dk == mp->ma_keys && ep->me_key == startkey) { + return cmp; + } + else { + /* The dict was mutated, restart */ + return DKIX_KEY_CHANGED; } - perturb >>= PERTURB_SHIFT; - i = mask & (i*5 + perturb + 1); } - Py_UNREACHABLE(); + return 0; +} + +static Py_ssize_t +dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, Py_hash_t hash) +{ + return do_lookup(mp, dk, key, hash, compare_generic); } /* Lookup a string in a (all unicode) dict keys. From a1332a99cf1eb9b879d4b1f28761b096b5749a0d Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Tue, 30 Jan 2024 13:40:54 -0500 Subject: [PATCH 105/263] Clarify one-item tuple (#114745) A 'single tuple' means 'one typle, of whatever length. Remove the unneeded and slight distracting parenthetical 'singleton' comment. --- Doc/reference/expressions.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 87ebdc1ca1c9c6a..50e0f97a6534af2 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1890,8 +1890,9 @@ the unpacking. .. index:: pair: trailing; comma -The trailing comma is required only to create a single tuple (a.k.a. a -*singleton*); it is optional in all other cases. A single expression without a +A trailing comma is required only to create a one-item tuple, +such as ``1,``; it is optional in all other cases. +A single expression without a trailing comma doesn't create a tuple, but rather yields the value of that expression. (To create an empty tuple, use an empty pair of parentheses: ``()``.) From 6de8aa31f39b3d8dbfba132e6649724eb07b8348 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Tue, 30 Jan 2024 21:44:09 +0300 Subject: [PATCH 106/263] ``importlib/_bootstrap.py``: Reduce size of ``_List`` instances (GH-114747) Reduce size of _List instances --- Lib/importlib/_bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index d942045f3de666f..6d6292f95592534 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -53,7 +53,7 @@ def _new_module(name): # For a list that can have a weakref to it. class _List(list): - pass + __slots__ = ("__weakref__",) # Copied from weakref.py with some simplifications and modifications unique to From fda7445ca50b892955fc31bd72a3615fef1d70c6 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 30 Jan 2024 19:52:53 +0000 Subject: [PATCH 107/263] GH-70303: Make `pathlib.Path.glob('**')` return both files and directories (#114684) Return files and directories from `pathlib.Path.glob()` if the pattern ends with `**`. This is more compatible with `PurePath.full_match()` and with other glob implementations such as bash and `glob.glob()`. Users can add a trailing slash to match only directories. In my previous patch I added a `FutureWarning` with the intention of fixing this in Python 3.15. Upon further reflection I think this was an unnecessarily cautious remedy to a clear bug. --- Doc/library/pathlib.rst | 5 ++--- Doc/whatsnew/3.13.rst | 10 +++++++++ Lib/pathlib/__init__.py | 8 ------- Lib/test/test_pathlib/test_pathlib.py | 12 ----------- Lib/test/test_pathlib/test_pathlib_abc.py | 21 +++++++++++++++++++ ...4-01-28-18-38-18.gh-issue-70303._Lt_pj.rst | 2 ++ 6 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index f1aba793fda03e7..f94b6fb38056847 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1038,9 +1038,8 @@ call fails (for example because the path doesn't exist). The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 - Emits :exc:`FutureWarning` if the pattern ends with "``**``". In a - future Python release, patterns with this ending will match both files - and directories. Add a trailing slash to match only directories. + Return files and directories if *pattern* ends with "``**``". In + previous versions, only directories were returned. .. versionchanged:: 3.13 The *pattern* parameter accepts a :term:`path-like object`. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index fec1e55e0daf0e6..b33203efbb05c0c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -350,6 +350,11 @@ pathlib (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and Kamil Turek in :gh:`107962`). +* Return files and directories from :meth:`pathlib.Path.glob` and + :meth:`~pathlib.Path.rglob` when given a pattern that ends with "``**``". In + earlier versions, only directories were returned. + (Contributed by Barney Gale in :gh:`70303`). + pdb --- @@ -1211,6 +1216,11 @@ Changes in the Python API * :class:`mailbox.Maildir` now ignores files with a leading dot. (Contributed by Zackery Spytz in :gh:`65559`.) +* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both + files and directories if a pattern that ends with "``**``" is given, rather + than directories only. Users may add a trailing slash to match only + directories. + Build Changes ============= diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index cc159edab5796f5..4447f98e3e86897 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -465,14 +465,6 @@ def _pattern_stack(self): elif pattern[-1] in (self.pathmod.sep, self.pathmod.altsep): # GH-65238: pathlib doesn't preserve trailing slash. Add it back. parts.append('') - elif parts[-1] == '**': - # GH-70303: '**' only matches directories. Add trailing slash. - warnings.warn( - "Pattern ending '**' will match files and directories in a " - "future Python release. Add a trailing slash to match only " - "directories and remove this warning.", - FutureWarning, 4) - parts.append('') parts.reverse() return parts diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index a8cc30ef0ab63f6..3bee9b8c762b805 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1258,18 +1258,6 @@ def test_glob_above_recursion_limit(self): with set_recursion_limit(recursion_limit): list(base.glob('**/')) - def test_glob_recursive_no_trailing_slash(self): - P = self.cls - p = P(self.base) - with self.assertWarns(FutureWarning): - p.glob('**') - with self.assertWarns(FutureWarning): - p.glob('*/**') - with self.assertWarns(FutureWarning): - p.rglob('**') - with self.assertWarns(FutureWarning): - p.rglob('*/**') - def test_glob_pathlike(self): P = self.cls p = P(self.base) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 18a612f6b81bace..0e12182c162c142 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1765,16 +1765,26 @@ def _check(path, glob, expected): _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."]) + _check(p, "dir*/**", [ + "dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", + "dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE"]) _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", "dirB/linkD/..", "dirA/linkC/linkD/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**", [ + "dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", + "dirB/linkD", "dirB/linkD/fileB", + "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..", "dirB/linkD/..", "dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) @needs_symlinks @@ -1791,12 +1801,20 @@ def _check(path, glob, expected): _check(p, "*/fileB", ["dirB/fileB"]) _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) _check(p, "dir*/*/..", ["dirC/dirD/.."]) + _check(p, "dir*/**", [ + "dirA", "dirA/linkC", + "dirB", "dirB/fileB", "dirB/linkD", + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE"]) _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_rglob_common(self): @@ -1833,10 +1851,13 @@ def _check(glob, expected): "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) _check(p.rglob("*/"), ["dirC/dirD/"]) _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) + _check(p.rglob("**"), [ + "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p.rglob("*.txt"), ["dirC/novel.txt"]) diff --git a/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst b/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst new file mode 100644 index 000000000000000..dedda24b481241e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-28-18-38-18.gh-issue-70303._Lt_pj.rst @@ -0,0 +1,2 @@ +Return both files and directories from :meth:`pathlib.Path.glob` if a +pattern ends with "``**``". Previously only directories were returned. From e5e186609fdd74bc53e8478da22b76440d996baa Mon Sep 17 00:00:00 2001 From: Matt Prodani <mattp@nyu.edu> Date: Tue, 30 Jan 2024 16:22:17 -0500 Subject: [PATCH 108/263] gh-112606: Use pthread_cond_timedwait_relative_np() in parking_lot.c when available (#112616) Add a configure define for HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP and replaces pthread_cond_timedwait() with pthread_cond_timedwait_relative_np() for relative time when supported in semaphore waiting logic. --- Python/parking_lot.c | 6 +++++- configure | 6 ++++++ configure.ac | 4 ++-- pyconfig.h.in | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Python/parking_lot.c b/Python/parking_lot.c index d44c1b4b93b4d2b..c83d7443e289c56 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -158,11 +158,15 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) if (sema->counter == 0) { if (timeout >= 0) { struct timespec ts; - +#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + _PyTime_AsTimespec_clamp(timeout, &ts); + err = pthread_cond_timedwait_relative_np(&sema->cond, &sema->mutex, &ts); +#else _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); _PyTime_AsTimespec_clamp(deadline, &ts); err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts); +#endif // HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP } else { err = pthread_cond_wait(&sema->cond, &sema->mutex); diff --git a/configure b/configure index adc5a8f014c795a..7b2119ff7f4f784 100755 --- a/configure +++ b/configure @@ -17871,6 +17871,12 @@ if test "x$ac_cv_func_preadv2" = xyes then : printf "%s\n" "#define HAVE_PREADV2 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "pthread_cond_timedwait_relative_np" "ac_cv_func_pthread_cond_timedwait_relative_np" +if test "x$ac_cv_func_pthread_cond_timedwait_relative_np" = xyes +then : + printf "%s\n" "#define HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_condattr_setclock" "ac_cv_func_pthread_condattr_setclock" if test "x$ac_cv_func_pthread_condattr_setclock" = xyes diff --git a/configure.ac b/configure.ac index f5fa17fd8b7d0dc..5bef2351c987e83 100644 --- a/configure.ac +++ b/configure.ac @@ -4796,8 +4796,8 @@ AC_CHECK_FUNCS([ \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill ptsname ptsname_r \ - pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ + pread preadv preadv2 pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ + pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ sem_timedwait sem_unlink sendfile setegid seteuid setgid sethostname \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 02e33c7007196d3..b22740710bcbee5 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -936,6 +936,10 @@ /* Define to 1 if you have the `pthread_condattr_setclock' function. */ #undef HAVE_PTHREAD_CONDATTR_SETCLOCK +/* Define to 1 if you have the `pthread_cond_timedwait_relative_np' function. + */ +#undef HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP + /* Defined for Solaris 2.6 bug in pthread header. */ #undef HAVE_PTHREAD_DESTRUCTOR From 3911b42cc0d404e0eac87fce30b740b08618ff06 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Tue, 30 Jan 2024 15:54:37 -0600 Subject: [PATCH 109/263] gh-101100: Fix references in csv docs (GH-114658) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/library/csv.rst | 14 +++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 07f38f5690bb540..66888c22b7cc28a 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -88,7 +88,7 @@ The :mod:`csv` module defines the following functions: Return a writer object responsible for converting the user's data into delimited strings on the given file-like object. *csvfile* can be any object with a - :func:`write` method. If *csvfile* is a file object, it should be opened with + :meth:`~io.TextIOBase.write` method. If *csvfile* is a file object, it should be opened with ``newline=''`` [1]_. An optional *dialect* parameter can be given which is used to define a set of parameters specific to a particular CSV dialect. It may be an instance of a subclass of the @@ -197,10 +197,10 @@ The :mod:`csv` module defines the following classes: Create an object which operates like a regular writer but maps dictionaries onto output rows. The *fieldnames* parameter is a :mod:`sequence <collections.abc>` of keys that identify the order in which values in the - dictionary passed to the :meth:`writerow` method are written to file + dictionary passed to the :meth:`~csvwriter.writerow` method are written to file *f*. The optional *restval* parameter specifies the value to be written if the dictionary is missing a key in *fieldnames*. If the - dictionary passed to the :meth:`writerow` method contains a key not found in + dictionary passed to the :meth:`~csvwriter.writerow` method contains a key not found in *fieldnames*, the optional *extrasaction* parameter indicates what action to take. If it is set to ``'raise'``, the default value, a :exc:`ValueError` @@ -374,8 +374,8 @@ Dialects and Formatting Parameters To make it easier to specify the format of input and output records, specific formatting parameters are grouped together into dialects. A dialect is a -subclass of the :class:`Dialect` class having a set of specific methods and a -single :meth:`validate` method. When creating :class:`reader` or +subclass of the :class:`Dialect` class containing various attributes +describing the format of the CSV file. When creating :class:`reader` or :class:`writer` objects, the programmer can specify a string or a subclass of the :class:`Dialect` class as the dialect parameter. In addition to, or instead of, the *dialect* parameter, the programmer can also specify individual @@ -492,9 +492,9 @@ DictReader objects have the following public attribute: Writer Objects -------------- -:class:`Writer` objects (:class:`DictWriter` instances and objects returned by +:class:`writer` objects (:class:`DictWriter` instances and objects returned by the :func:`writer` function) have the following public methods. A *row* must be -an iterable of strings or numbers for :class:`Writer` objects and a dictionary +an iterable of strings or numbers for :class:`writer` objects and a dictionary mapping fieldnames to strings or numbers (by passing them through :func:`str` first) for :class:`DictWriter` objects. Note that complex numbers are written out surrounded by parens. This may cause some problems for other programs which diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index bba4fe0d5f2425e..7eacb46d6299b3c 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -25,7 +25,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/bdb.rst Doc/library/collections.rst -Doc/library/csv.rst Doc/library/dbm.rst Doc/library/decimal.rst Doc/library/email.charset.rst From 348a72ce3fda91de5e9ac1bf6b8c4387ec3007a5 Mon Sep 17 00:00:00 2001 From: Brandt Bucher <brandtbucher@microsoft.com> Date: Tue, 30 Jan 2024 14:08:53 -0800 Subject: [PATCH 110/263] GH-113464: Add aarch64-apple-darwin/clang to JIT CI (GH-114759) --- .github/workflows/jit.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index e137fd21b0a0dd7..3da729191812551 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -16,6 +16,7 @@ jobs: - i686-pc-windows-msvc/msvc - x86_64-pc-windows-msvc/msvc - x86_64-apple-darwin/clang + - aarch64-apple-darwin/clang - x86_64-unknown-linux-gnu/gcc - x86_64-unknown-linux-gnu/clang - aarch64-unknown-linux-gnu/gcc @@ -36,9 +37,12 @@ jobs: compiler: msvc - target: x86_64-apple-darwin/clang architecture: x86_64 - runner: macos-latest + runner: macos-13 + compiler: clang + - target: aarch64-apple-darwin/clang + architecture: aarch64 + runner: macos-14 compiler: clang - exclude: test_embed - target: x86_64-unknown-linux-gnu/gcc architecture: x86_64 runner: ubuntu-latest @@ -80,7 +84,7 @@ jobs: brew install llvm@${{ matrix.llvm }} export SDKROOT="$(xcrun --show-sdk-path)" ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} - make all --jobs 3 + make all --jobs 4 ./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 - name: Native Linux @@ -91,6 +95,7 @@ jobs: ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} make all --jobs 4 ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 + - name: Emulated Linux if: runner.os == 'Linux' && matrix.architecture != 'x86_64' run: | From dc4cd2c9ba60e2ee7e534e2f6e93c4c135df23b9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 31 Jan 2024 00:15:33 +0200 Subject: [PATCH 111/263] gh-106392: Fix inconsistency in deprecation warnings in datetime module (GH-114761) --- Lib/_pydatetime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index 355145387e355bb..54c12d3b2f3f16a 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -1809,7 +1809,7 @@ def fromtimestamp(cls, timestamp, tz=None): def utcfromtimestamp(cls, t): """Construct a naive UTC datetime from a POSIX timestamp.""" import warnings - warnings.warn("datetime.utcfromtimestamp() is deprecated and scheduled " + warnings.warn("datetime.datetime.utcfromtimestamp() is deprecated and scheduled " "for removal in a future version. Use timezone-aware " "objects to represent datetimes in UTC: " "datetime.datetime.fromtimestamp(t, datetime.UTC).", @@ -1827,8 +1827,8 @@ def now(cls, tz=None): def utcnow(cls): "Construct a UTC datetime from time.time()." import warnings - warnings.warn("datetime.utcnow() is deprecated and scheduled for " - "removal in a future version. Instead, Use timezone-aware " + warnings.warn("datetime.datetime.utcnow() is deprecated and scheduled for " + "removal in a future version. Use timezone-aware " "objects to represent datetimes in UTC: " "datetime.datetime.now(datetime.UTC).", DeprecationWarning, From a06b606462740058b5d52fefdcdcd679d4f40260 Mon Sep 17 00:00:00 2001 From: Diego Russo <diego.russo@arm.com> Date: Tue, 30 Jan 2024 23:53:04 +0000 Subject: [PATCH 112/263] gh-110190: Fix ctypes structs with array on Windows ARM64 (GH-114753) --- .../next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst | 1 + Modules/_ctypes/stgdict.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst diff --git a/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst b/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst new file mode 100644 index 000000000000000..af77e409963e04d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-30-15-34-08.gh-issue-110190.Z5PQQX.rst @@ -0,0 +1 @@ +Fix ctypes structs with array on Windows ARM64 platform by setting ``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 2397015ba658895..deafa696fdd0d03 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -707,7 +707,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct /* * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. */ -#if defined(__aarch64__) || defined(__arm__) +#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) # define MAX_STRUCT_SIZE 32 #elif defined(__powerpc64__) # define MAX_STRUCT_SIZE 64 From 1667c2868633a1091b3519594103ca7662d64d75 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Wed, 31 Jan 2024 00:38:01 +0000 Subject: [PATCH 113/263] pathlib ABCs: raise `UnsupportedOperation` directly. (#114776) Raise `UnsupportedOperation` directly, rather than via an `_unsupported()` helper, to give human readers and IDEs/typecheckers/etc a bigger hint that these methods are abstract. --- Lib/pathlib/__init__.py | 5 ++-- Lib/pathlib/_abc.py | 59 ++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4447f98e3e86897..65ce836765c42bb 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -514,9 +514,8 @@ class Path(_abc.PathBase, PurePath): as_uri = PurePath.as_uri @classmethod - def _unsupported(cls, method_name): - msg = f"{cls.__name__}.{method_name}() is unsupported on this system" - raise UnsupportedOperation(msg) + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported on this system" def __init__(self, *args, **kwargs): if kwargs: diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 580d631cbf3b530..85884bc4b4cb47b 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -149,39 +149,39 @@ class PathModuleBase: """ @classmethod - def _unsupported(cls, attr): - raise UnsupportedOperation(f"{cls.__name__}.{attr} is unsupported") + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" @property def sep(self): """The character used to separate path components.""" - self._unsupported('sep') + raise UnsupportedOperation(self._unsupported_msg('sep')) def join(self, path, *paths): """Join path segments.""" - self._unsupported('join()') + raise UnsupportedOperation(self._unsupported_msg('join()')) def split(self, path): """Split the path into a pair (head, tail), where *head* is everything before the final path separator, and *tail* is everything after. Either part may be empty. """ - self._unsupported('split()') + raise UnsupportedOperation(self._unsupported_msg('split()')) def splitdrive(self, path): """Split the path into a 2-item tuple (drive, tail), where *drive* is a device name or mount point, and *tail* is everything after the drive. Either part may be empty.""" - self._unsupported('splitdrive()') + raise UnsupportedOperation(self._unsupported_msg('splitdrive()')) def normcase(self, path): """Normalize the case of the path.""" - self._unsupported('normcase()') + raise UnsupportedOperation(self._unsupported_msg('normcase()')) def isabs(self, path): """Returns whether the path is absolute, i.e. unaffected by the current directory or drive.""" - self._unsupported('isabs()') + raise UnsupportedOperation(self._unsupported_msg('isabs()')) class PurePathBase: @@ -505,16 +505,15 @@ class PathBase(PurePathBase): _max_symlinks = 40 @classmethod - def _unsupported(cls, method_name): - msg = f"{cls.__name__}.{method_name}() is unsupported" - raise UnsupportedOperation(msg) + def _unsupported_msg(cls, attribute): + return f"{cls.__name__}.{attribute} is unsupported" def stat(self, *, follow_symlinks=True): """ Return the result of the stat() system call on this path, like os.stat() does. """ - self._unsupported("stat") + raise UnsupportedOperation(self._unsupported_msg('stat()')) def lstat(self): """ @@ -703,7 +702,7 @@ def open(self, mode='r', buffering=-1, encoding=None, Open the file pointed by this path and return a file object, as the built-in open() function does. """ - self._unsupported("open") + raise UnsupportedOperation(self._unsupported_msg('open()')) def read_bytes(self): """ @@ -744,7 +743,7 @@ def iterdir(self): The children are yielded in arbitrary order, and the special entries '.' and '..' are not included. """ - self._unsupported("iterdir") + raise UnsupportedOperation(self._unsupported_msg('iterdir()')) def _scandir(self): # Emulate os.scandir(), which returns an object that can be used as a @@ -871,7 +870,7 @@ def absolute(self): Use resolve() to resolve symlinks and remove '..' segments. """ - self._unsupported("absolute") + raise UnsupportedOperation(self._unsupported_msg('absolute()')) @classmethod def cwd(cls): @@ -886,7 +885,7 @@ def expanduser(self): """ Return a new path with expanded ~ and ~user constructs (as returned by os.path.expanduser) """ - self._unsupported("expanduser") + raise UnsupportedOperation(self._unsupported_msg('expanduser()')) @classmethod def home(cls): @@ -898,7 +897,7 @@ def readlink(self): """ Return the path to which the symbolic link points. """ - self._unsupported("readlink") + raise UnsupportedOperation(self._unsupported_msg('readlink()')) readlink._supported = False def resolve(self, strict=False): @@ -973,7 +972,7 @@ def symlink_to(self, target, target_is_directory=False): Make this path a symlink pointing to the target path. Note the order of arguments (link, target) is the reverse of os.symlink. """ - self._unsupported("symlink_to") + raise UnsupportedOperation(self._unsupported_msg('symlink_to()')) def hardlink_to(self, target): """ @@ -981,19 +980,19 @@ def hardlink_to(self, target): Note the order of arguments (self, target) is the reverse of os.link's. """ - self._unsupported("hardlink_to") + raise UnsupportedOperation(self._unsupported_msg('hardlink_to()')) def touch(self, mode=0o666, exist_ok=True): """ Create this file with the given access mode, if it doesn't exist. """ - self._unsupported("touch") + raise UnsupportedOperation(self._unsupported_msg('touch()')) def mkdir(self, mode=0o777, parents=False, exist_ok=False): """ Create a new directory at this given path. """ - self._unsupported("mkdir") + raise UnsupportedOperation(self._unsupported_msg('mkdir()')) def rename(self, target): """ @@ -1005,7 +1004,7 @@ def rename(self, target): Returns the new Path instance pointing to the target path. """ - self._unsupported("rename") + raise UnsupportedOperation(self._unsupported_msg('rename()')) def replace(self, target): """ @@ -1017,13 +1016,13 @@ def replace(self, target): Returns the new Path instance pointing to the target path. """ - self._unsupported("replace") + raise UnsupportedOperation(self._unsupported_msg('replace()')) def chmod(self, mode, *, follow_symlinks=True): """ Change the permissions of the path, like os.chmod(). """ - self._unsupported("chmod") + raise UnsupportedOperation(self._unsupported_msg('chmod()')) def lchmod(self, mode): """ @@ -1037,31 +1036,31 @@ def unlink(self, missing_ok=False): Remove this file or link. If the path is a directory, use rmdir() instead. """ - self._unsupported("unlink") + raise UnsupportedOperation(self._unsupported_msg('unlink()')) def rmdir(self): """ Remove this directory. The directory must be empty. """ - self._unsupported("rmdir") + raise UnsupportedOperation(self._unsupported_msg('rmdir()')) def owner(self, *, follow_symlinks=True): """ Return the login name of the file owner. """ - self._unsupported("owner") + raise UnsupportedOperation(self._unsupported_msg('owner()')) def group(self, *, follow_symlinks=True): """ Return the group name of the file gid. """ - self._unsupported("group") + raise UnsupportedOperation(self._unsupported_msg('group()')) @classmethod def from_uri(cls, uri): """Return a new path from the given 'file' URI.""" - cls._unsupported("from_uri") + raise UnsupportedOperation(cls._unsupported_msg('from_uri()')) def as_uri(self): """Return the path as a URI.""" - self._unsupported("as_uri") + raise UnsupportedOperation(self._unsupported_msg('as_uri()')) From 574291963f6b0eb7da3fde1ae9763236e7ece306 Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Wed, 31 Jan 2024 00:59:33 +0000 Subject: [PATCH 114/263] pathlib ABCs: drop partial, broken, untested support for `bytes` paths. (#114777) Methods like `full_match()`, `glob()`, etc, are difficult to make work with byte paths, and it's not worth the effort. This patch makes `PurePathBase` raise `TypeError` when given non-`str` path segments. --- Lib/pathlib/_abc.py | 7 +++---- Lib/test/test_pathlib/test_pathlib.py | 18 +--------------- Lib/test/test_pathlib/test_pathlib_abc.py | 25 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 85884bc4b4cb47b..91f5cd6c01e9d08 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -207,6 +207,9 @@ class PurePathBase: def __init__(self, path, *paths): self._raw_path = self.pathmod.join(path, *paths) if paths else path + if not isinstance(self._raw_path, str): + raise TypeError( + f"path should be a str, not {type(self._raw_path).__name__!r}") self._resolving = False def with_segments(self, *pathsegments): @@ -321,8 +324,6 @@ def relative_to(self, other, *, walk_up=False): other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack - if isinstance(anchor0, str) != isinstance(anchor1, str): - raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") while parts0 and parts1 and parts0[-1] == parts1[-1]: @@ -346,8 +347,6 @@ def is_relative_to(self, other): other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack - if isinstance(anchor0, str) != isinstance(anchor1, str): - raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: return False while parts0 and parts1 and parts0[-1] == parts1[-1]: diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 3bee9b8c762b805..2b166451243775b 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -189,7 +189,7 @@ def test_fspath_common(self): self._check_str(p.__fspath__(), ('a/b',)) self._check_str(os.fspath(p), ('a/b',)) - def test_bytes(self): + def test_bytes_exc_message(self): P = self.cls message = (r"argument should be a str or an os\.PathLike object " r"where __fspath__ returns a str, not 'bytes'") @@ -199,22 +199,6 @@ def test_bytes(self): P(b'a', 'b') with self.assertRaisesRegex(TypeError, message): P('a', b'b') - with self.assertRaises(TypeError): - P('a').joinpath(b'b') - with self.assertRaises(TypeError): - P('a') / b'b' - with self.assertRaises(TypeError): - b'a' / P('b') - with self.assertRaises(TypeError): - P('a').match(b'b') - with self.assertRaises(TypeError): - P('a').relative_to(b'b') - with self.assertRaises(TypeError): - P('a').with_name(b'b') - with self.assertRaises(TypeError): - P('a').with_stem(b'b') - with self.assertRaises(TypeError): - P('a').with_suffix(b'b') def test_as_bytes_common(self): sep = os.fsencode(self.sep) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 0e12182c162c142..207579ccbf443b7 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -155,6 +155,31 @@ def test_constructor_common(self): P('a/b/c') P('/a/b/c') + def test_bytes(self): + P = self.cls + with self.assertRaises(TypeError): + P(b'a') + with self.assertRaises(TypeError): + P(b'a', 'b') + with self.assertRaises(TypeError): + P('a', b'b') + with self.assertRaises(TypeError): + P('a').joinpath(b'b') + with self.assertRaises(TypeError): + P('a') / b'b' + with self.assertRaises(TypeError): + b'a' / P('b') + with self.assertRaises(TypeError): + P('a').match(b'b') + with self.assertRaises(TypeError): + P('a').relative_to(b'b') + with self.assertRaises(TypeError): + P('a').with_name(b'b') + with self.assertRaises(TypeError): + P('a').with_stem(b'b') + with self.assertRaises(TypeError): + P('a').with_suffix(b'b') + def _check_str_subclass(self, *args): # Issue #21127: it should be possible to construct a PurePath object # from a str subclass instance, and it then gets converted to From 2ed8f924ee05ec17ce0639a424e5ca7661b09a6b Mon Sep 17 00:00:00 2001 From: Brett Cannon <brett@python.org> Date: Tue, 30 Jan 2024 17:49:27 -0800 Subject: [PATCH 115/263] GH-114743: Set a low recursion limit for `test_main_recursion_error()` in `test_runpy` (GH-114772) This can fail under a debug build of WASI when directly executing test.test_runpy. --- .gitignore | 2 +- Lib/test/test_runpy.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 18eb2a9f0632ce2..6ed7197e3ab6269 100644 --- a/.gitignore +++ b/.gitignore @@ -159,5 +159,5 @@ Python/frozen_modules/MANIFEST /python !/Python/ -# main branch only: ABI files are not checked/maintained +# main branch only: ABI files are not checked/maintained. Doc/data/python*.abi diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 57fe859e366b5b7..9d76764c75be3eb 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -12,7 +12,8 @@ import textwrap import unittest import warnings -from test.support import no_tracing, verbose, requires_subprocess, requires_resource +from test.support import (infinite_recursion, no_tracing, verbose, + requires_subprocess, requires_resource) from test.support.import_helper import forget, make_legacy_pyc, unload from test.support.os_helper import create_empty_file, temp_dir from test.support.script_helper import make_script, make_zip_script @@ -743,7 +744,8 @@ def test_main_recursion_error(self): "runpy.run_path(%r)\n") % dummy_dir script_name = self._make_test_script(script_dir, mod_name, source) zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) - self.assertRaises(RecursionError, run_path, zip_name) + with infinite_recursion(25): + self.assertRaises(RecursionError, run_path, zip_name) def test_encoding(self): with temp_dir() as script_dir: From c8cf5d7d148944f2850f25b02334400dd0238cb0 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Wed, 31 Jan 2024 07:59:34 +0100 Subject: [PATCH 116/263] Docs: mark up dbm.gnu.open() and dbm.ndbm.open() using param list (#114762) --- Doc/library/dbm.rst | 117 +++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 076e86143d06a6f..9bb5e5f89509568 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -52,6 +52,10 @@ the Oracle Berkeley DB. .. |flag_n| replace:: Always create a new, empty database, open for reading and writing. +.. |mode_param_doc| replace:: + The Unix file access mode of the file (default: octal ``0o666``), + used only when the database has to be created. + .. |incompat_note| replace:: The file formats created by :mod:`dbm.gnu` and :mod:`dbm.ndbm` are incompatible and can not be used interchangeably. @@ -69,14 +73,13 @@ the Oracle Berkeley DB. :type file: :term:`path-like object` :param str flag: - * ``'r'`` (default), |flag_r| - * ``'w'``, |flag_w| - * ``'c'``, |flag_c| - * ``'n'``, |flag_n| + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| :param int mode: - The Unix file access mode of the file (default: octal ``0o666``), - used only when the database has to be created. + |mode_param_doc| .. versionchanged:: 3.11 *file* accepts a :term:`path-like object`. @@ -171,47 +174,45 @@ and the :meth:`!items` and :meth:`!values` methods are not supported. .. function:: open(filename, flag="r", mode=0o666, /) - Open a GDBM database and return a :class:`!gdbm` object. The *filename* - argument is the name of the database file. - - The optional *flag* argument can be: + Open a GDBM database and return a :class:`!gdbm` object. - .. csv-table:: - :header: "Value", "Meaning" + :param filename: + The database file to open. + :type filename: :term:`path-like object` - ``'r'`` (default), |flag_r| - ``'w'``, |flag_w| - ``'c'``, |flag_c| - ``'n'``, |flag_n| + :param str flag: + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| - The following additional characters may be appended to the flag to control - how the database is opened: + The following additional characters may be appended + to control how the database is opened: - +---------+--------------------------------------------+ - | Value | Meaning | - +=========+============================================+ - | ``'f'`` | Open the database in fast mode. Writes | - | | to the database will not be synchronized. | - +---------+--------------------------------------------+ - | ``'s'`` | Synchronized mode. This will cause changes | - | | to the database to be immediately written | - | | to the file. | - +---------+--------------------------------------------+ - | ``'u'`` | Do not lock database. | - +---------+--------------------------------------------+ + * ``'f'``: Open the database in fast mode. + Writes to the database will not be synchronized. + * ``'s'``: Synchronized mode. + Changes to the database will be written immediately to the file. + * ``'u'``: Do not lock database. - Not all flags are valid for all versions of GDBM. The module constant - :const:`open_flags` is a string of supported flag characters. The exception - :exc:`error` is raised if an invalid flag is specified. + Not all flags are valid for all versions of GDBM. + See the :data:`open_flags` member for a list of supported flag characters. - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666``. + :param int mode: + |mode_param_doc| - In addition to the dictionary-like methods, :class:`gdbm` objects have the - following methods: + :raises error: + If an invalid *flag* argument is passed. .. versionchanged:: 3.11 - Accepts :term:`path-like object` for filename. + *filename* accepts a :term:`path-like object`. + + .. data:: open_flags + + A string of characters the *flag* parameter of :meth:`~dbm.gnu.open` supports. + + In addition to the dictionary-like methods, :class:`gdbm` objects have the + following methods and attributes: .. method:: gdbm.firstkey() @@ -298,22 +299,20 @@ This module can be used with the "classic" NDBM interface or the .. function:: open(filename, flag="r", mode=0o666, /) Open an NDBM database and return an :class:`!ndbm` object. - The *filename* argument is the name of the database file - (without the :file:`.dir` or :file:`.pag` extensions). - - The optional *flag* argument must be one of these values: - .. csv-table:: - :header: "Value", "Meaning" + :param filename: + The basename of the database file + (without the :file:`.dir` or :file:`.pag` extensions). + :type filename: :term:`path-like object` - ``'r'`` (default), |flag_r| - ``'w'``, |flag_w| - ``'c'``, |flag_c| - ``'n'``, |flag_n| + :param str flag: + * ``'r'`` (default): |flag_r| + * ``'w'``: |flag_w| + * ``'c'``: |flag_c| + * ``'n'``: |flag_n| - The optional *mode* argument is the Unix mode of the file, used only when the - database has to be created. It defaults to octal ``0o666`` (and will be - modified by the prevailing umask). + :param int mode: + |mode_param_doc| In addition to the dictionary-like methods, :class:`!ndbm` objects provide the following method: @@ -382,17 +381,13 @@ The :mod:`!dbm.dumb` module defines the following: :type database: :term:`path-like object` :param str flag: - .. csv-table:: - :header: "Value", "Meaning" - - ``'r'``, |flag_r| - ``'w'``, |flag_w| - ``'c'`` (default), |flag_c| - ``'n'``, |flag_n| + * ``'r'``: |flag_r| + * ``'w'``: |flag_w| + * ``'c'`` (default): |flag_c| + * ``'n'``: |flag_n| :param int mode: - The Unix file access mode of the file (default: ``0o666``), - used only when the database has to be created. + |mode_param_doc| .. warning:: It is possible to crash the Python interpreter when loading a database @@ -400,7 +395,7 @@ The :mod:`!dbm.dumb` module defines the following: Python's AST compiler. .. versionchanged:: 3.5 - :func:`open` always creates a new database when *flag* is ``'n'``. + :func:`~dbm.dumb.open` always creates a new database when *flag* is ``'n'``. .. versionchanged:: 3.8 A database opened read-only if *flag* is ``'r'``. From 5e390a0fc825f21952beb158e2bda3c5e007fac9 Mon Sep 17 00:00:00 2001 From: Daniel Hollas <danekhollas@gmail.com> Date: Wed, 31 Jan 2024 09:29:44 +0000 Subject: [PATCH 117/263] gh-109653: Speedup import of threading module (#114509) Avoiding an import of functools leads to 50% speedup of import time. Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Lib/threading.py | 4 +--- .../Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst diff --git a/Lib/threading.py b/Lib/threading.py index 00b95f8d92a1f06..75a08e5aac97d60 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -3,7 +3,6 @@ import os as _os import sys as _sys import _thread -import functools import warnings from time import monotonic as _time @@ -1630,8 +1629,7 @@ def _register_atexit(func, *arg, **kwargs): if _SHUTTING_DOWN: raise RuntimeError("can't register atexit after shutdown") - call = functools.partial(func, *arg, **kwargs) - _threading_atexits.append(call) + _threading_atexits.append(lambda: func(*arg, **kwargs)) from _thread import stack_size diff --git a/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst b/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst new file mode 100644 index 000000000000000..76074df9c76fa61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-23-13-47.gh-issue-109653.KLBHmT.rst @@ -0,0 +1 @@ +Reduce the import time of :mod:`threading` module by ~50%. Patch by Daniel Hollas. From 7a93db44257c0404dc407ff2ddc997f4bb8890ed Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 31 Jan 2024 03:33:10 -0600 Subject: [PATCH 118/263] gh-101100: Fix class reference in library/test.rst (GH-114769) The text clearly seems to be referencing `TestFuncAcceptsSequencesMixin`, for which no target is available. Name the class properly and suppress the dangling reference. --- Doc/library/test.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 9173db07fd00718..cad1023021a5129 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -143,7 +143,7 @@ guidelines to be followed: arg = (1, 2, 3) When using this pattern, remember that all classes that inherit from - :class:`unittest.TestCase` are run as tests. The :class:`Mixin` class in the example above + :class:`unittest.TestCase` are run as tests. The :class:`!TestFuncAcceptsSequencesMixin` class in the example above does not have any data and so can't be run by itself, thus it does not inherit from :class:`unittest.TestCase`. From b7688ef71eddcaf14f71b1c22ff2f164f34b2c74 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 31 Jan 2024 13:11:35 +0200 Subject: [PATCH 119/263] gh-114685: Check flags in PyObject_GetBuffer() (GH-114707) PyObject_GetBuffer() now raises a SystemError if called with PyBUF_READ or PyBUF_WRITE as flags. These flags should only be used with the PyMemoryView_* C API. --- Lib/test/test_buffer.py | 6 ++++++ .../C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst | 3 +++ Modules/_testcapi/buffer.c | 6 ++++-- Objects/abstract.c | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 72a06d6af450e32..535b795f508a242 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4585,6 +4585,12 @@ def test_c_buffer(self): buf.__release_buffer__(mv) self.assertEqual(buf.references, 0) + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_buffer_invalid_flags(self): + buf = _testcapi.testBuf() + self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) + self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) + def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): diff --git a/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst b/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst new file mode 100644 index 000000000000000..55b02d1d8e1e9fc --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-29-12-13-24.gh-issue-114685.B07RME.rst @@ -0,0 +1,3 @@ +:c:func:`PyObject_GetBuffer` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. diff --git a/Modules/_testcapi/buffer.c b/Modules/_testcapi/buffer.c index 942774156c6c47f..7e2f6e5e29482c2 100644 --- a/Modules/_testcapi/buffer.c +++ b/Modules/_testcapi/buffer.c @@ -54,8 +54,10 @@ static int testbuf_getbuf(testBufObject *self, Py_buffer *view, int flags) { int buf = PyObject_GetBuffer(self->obj, view, flags); - Py_SETREF(view->obj, Py_NewRef(self)); - self->references++; + if (buf == 0) { + Py_SETREF(view->obj, Py_NewRef(self)); + self->references++; + } return buf; } diff --git a/Objects/abstract.c b/Objects/abstract.c index 1ec5c5b8c3dc2f5..daf04eb4ab2cda3 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -425,6 +425,12 @@ PyObject_AsWriteBuffer(PyObject *obj, int PyObject_GetBuffer(PyObject *obj, Py_buffer *view, int flags) { + if (flags != PyBUF_SIMPLE) { /* fast path */ + if (flags == PyBUF_READ || flags == PyBUF_WRITE) { + PyErr_BadInternalCall(); + return -1; + } + } PyBufferProcs *pb = Py_TYPE(obj)->tp_as_buffer; if (pb == NULL || pb->bf_getbuffer == NULL) { From 66f95ea6a65deff547cab0d312b8c8c8a4cf8beb Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 31 Jan 2024 06:22:24 -0500 Subject: [PATCH 120/263] gh-114737: Revert change to ElementTree.iterparse "root" attribute (GH-114755) Prior to gh-114269, the iterator returned by ElementTree.iterparse was initialized with the root attribute as None. This restores the previous behavior. --- Lib/test/test_xml_etree.py | 2 ++ Lib/xml/etree/ElementTree.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index b9e7937b0bbc00a..221545b315fa44d 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -536,7 +536,9 @@ def test_iterparse(self): iterparse = ET.iterparse context = iterparse(SIMPLE_XMLFILE) + self.assertIsNone(context.root) action, elem = next(context) + self.assertIsNone(context.root) self.assertEqual((action, elem.tag), ('end', 'element')) self.assertEqual([(action, elem.tag) for action, elem in context], [ ('end', 'element'), diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index ae6575028be11cb..bb7362d1634a726 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1256,8 +1256,8 @@ def __del__(self): source.close() it = IterParseIterator() + it.root = None wr = weakref.ref(it) - del IterParseIterator return it From 25ce7f872df661de9392122df17111c75c77dee0 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Wed, 31 Jan 2024 11:28:23 +0000 Subject: [PATCH 121/263] Remove Alex Waygood as an Argument Clinic CODEOWNER (#114796) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f4d0411504a8325..7933d3195505767 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -230,8 +230,8 @@ Doc/c-api/stable.rst @encukou **/*zipfile/_path/* @jaraco # Argument Clinic -/Tools/clinic/** @erlend-aasland @AlexWaygood -/Lib/test/test_clinic.py @erlend-aasland @AlexWaygood +/Tools/clinic/** @erlend-aasland +/Lib/test/test_clinic.py @erlend-aasland Doc/howto/clinic.rst @erlend-aasland # Subinterpreters From 1c2ea8b33c6b1f995db0aca0b223a9cc22426708 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Wed, 31 Jan 2024 14:32:27 +0300 Subject: [PATCH 122/263] gh-114790: Do not execute `workflows/require-pr-label.yml` on forks (#114791) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/require-pr-label.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/require-pr-label.yml b/.github/workflows/require-pr-label.yml index 080204bcfd3b94f..ff5cbdf3eda749a 100644 --- a/.github/workflows/require-pr-label.yml +++ b/.github/workflows/require-pr-label.yml @@ -11,6 +11,7 @@ permissions: jobs: label: name: DO-NOT-MERGE / unresolved review + if: github.repository_owner == 'python' runs-on: ubuntu-latest timeout-minutes: 10 From 765b9ce9fb357bdb79a50ce51207c827fbd13dd8 Mon Sep 17 00:00:00 2001 From: Tian Gao <gaogaotiantian@hotmail.com> Date: Wed, 31 Jan 2024 05:03:05 -0800 Subject: [PATCH 123/263] gh-59013: Set breakpoint on the first executable line of function when using `break func` in pdb (#112470) --- Lib/pdb.py | 51 ++++++++++++------- Lib/test/test_pdb.py | 31 +++++++++-- ...3-11-27-19-54-43.gh-issue-59013.chpQ0e.rst | 1 + 3 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst diff --git a/Lib/pdb.py b/Lib/pdb.py index 6f7719eb9ba6c5b..0754e8b628cf570 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -97,17 +97,47 @@ class Restart(Exception): __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", "post_mortem", "help"] + +def find_first_executable_line(code): + """ Try to find the first executable line of the code object. + + Equivalently, find the line number of the instruction that's + after RESUME + + Return code.co_firstlineno if no executable line is found. + """ + prev = None + for instr in dis.get_instructions(code): + if prev is not None and prev.opname == 'RESUME': + if instr.positions.lineno is not None: + return instr.positions.lineno + return code.co_firstlineno + prev = instr + return code.co_firstlineno + def find_function(funcname, filename): cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname)) try: fp = tokenize.open(filename) except OSError: return None + funcdef = "" + funcstart = None # consumer of this info expects the first line to be 1 with fp: for lineno, line in enumerate(fp, start=1): if cre.match(line): - return funcname, filename, lineno + funcstart, funcdef = lineno, line + elif funcdef: + funcdef += line + + if funcdef: + try: + funccode = compile(funcdef, filename, 'exec').co_consts[0] + except SyntaxError: + continue + lineno_offset = find_first_executable_line(funccode) + return funcname, filename, funcstart + lineno_offset - 1 return None def lasti2lineno(code, lasti): @@ -975,7 +1005,7 @@ def do_break(self, arg, temporary = 0): #use co_name to identify the bkpt (function names #could be aliased, but co_name is invariant) funcname = code.co_name - lineno = self._find_first_executable_line(code) + lineno = find_first_executable_line(code) filename = code.co_filename except: # last thing to try @@ -1078,23 +1108,6 @@ def checkline(self, filename, lineno): return 0 return lineno - def _find_first_executable_line(self, code): - """ Try to find the first executable line of the code object. - - Equivalently, find the line number of the instruction that's - after RESUME - - Return code.co_firstlineno if no executable line is found. - """ - prev = None - for instr in dis.get_instructions(code): - if prev is not None and prev.opname == 'RESUME': - if instr.positions.lineno is not None: - return instr.positions.lineno - return code.co_firstlineno - prev = instr - return code.co_firstlineno - def do_enable(self, arg): """enable bpnumber [bpnumber ...] diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c64df62c761471d..b2283cff6cb4622 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2661,7 +2661,7 @@ def quux(): pass """.encode(), 'bœr', - ('bœr', 4), + ('bœr', 5), ) def test_find_function_found_with_encoding_cookie(self): @@ -2678,7 +2678,7 @@ def quux(): pass """.encode('iso-8859-15'), 'bœr', - ('bœr', 5), + ('bœr', 6), ) def test_find_function_found_with_bom(self): @@ -2688,9 +2688,34 @@ def bœr(): pass """.encode(), 'bœr', - ('bœr', 1), + ('bœr', 2), ) + def test_find_function_first_executable_line(self): + code = textwrap.dedent("""\ + def foo(): pass + + def bar(): + pass # line 4 + + def baz(): + # comment + pass # line 8 + + def mul(): + # code on multiple lines + code = compile( # line 12 + 'def f()', + '<string>', + 'exec', + ) + """).encode() + + self._assert_find_function(code, 'foo', ('foo', 1)) + self._assert_find_function(code, 'bar', ('bar', 4)) + self._assert_find_function(code, 'baz', ('baz', 8)) + self._assert_find_function(code, 'mul', ('mul', 12)) + def test_issue7964(self): # open the file as binary so we can force \r\n newline with open(os_helper.TESTFN, 'wb') as f: diff --git a/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst new file mode 100644 index 000000000000000..a2be2fb8eacf17c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-27-19-54-43.gh-issue-59013.chpQ0e.rst @@ -0,0 +1 @@ +Set breakpoint on the first executable line of the function, instead of the line of function definition when the user do ``break func`` using :mod:`pdb` From b905fad83819ec9102ecfb97e3d8ab0aaddd9784 Mon Sep 17 00:00:00 2001 From: Nachtalb <9467802+Nachtalb@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:33:46 +0100 Subject: [PATCH 124/263] gh-111741: Recognise image/webp as a standard format in the mimetypes module (GH-111742) Previously it was supported as a non-standard type. --- Lib/mimetypes.py | 2 +- Lib/test/test_mimetypes.py | 3 +-- .../Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 37228de4828de59..51b99701c9d727b 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -528,6 +528,7 @@ def _default_mime_types(): '.tiff' : 'image/tiff', '.tif' : 'image/tiff', '.ico' : 'image/vnd.microsoft.icon', + '.webp' : 'image/webp', '.ras' : 'image/x-cmu-raster', '.pnm' : 'image/x-portable-anymap', '.pbm' : 'image/x-portable-bitmap', @@ -587,7 +588,6 @@ def _default_mime_types(): '.pict': 'image/pict', '.pct' : 'image/pict', '.pic' : 'image/pict', - '.webp': 'image/webp', '.xul' : 'text/xul', } diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index d64aee71fc48b13..01bba0ac2eed5a4 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -96,14 +96,12 @@ def test_non_standard_types(self): # First try strict eq(self.db.guess_type('foo.xul', strict=True), (None, None)) eq(self.db.guess_extension('image/jpg', strict=True), None) - eq(self.db.guess_extension('image/webp', strict=True), None) # And then non-strict eq(self.db.guess_type('foo.xul', strict=False), ('text/xul', None)) eq(self.db.guess_type('foo.XUL', strict=False), ('text/xul', None)) eq(self.db.guess_type('foo.invalid', strict=False), (None, None)) eq(self.db.guess_extension('image/jpg', strict=False), '.jpg') eq(self.db.guess_extension('image/JPG', strict=False), '.jpg') - eq(self.db.guess_extension('image/webp', strict=False), '.webp') def test_filename_with_url_delimiters(self): # bpo-38449: URL delimiters cases should be handled also. @@ -183,6 +181,7 @@ def check_extensions(): self.assertEqual(mimetypes.guess_extension('application/xml'), '.xsl') self.assertEqual(mimetypes.guess_extension('audio/mpeg'), '.mp3') self.assertEqual(mimetypes.guess_extension('image/avif'), '.avif') + self.assertEqual(mimetypes.guess_extension('image/webp'), '.webp') self.assertEqual(mimetypes.guess_extension('image/jpeg'), '.jpg') self.assertEqual(mimetypes.guess_extension('image/tiff'), '.tiff') self.assertEqual(mimetypes.guess_extension('message/rfc822'), '.eml') diff --git a/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst b/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst new file mode 100644 index 000000000000000..e43f93a270ce9c8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-04-22-32-27.gh-issue-111741.f1ufr8.rst @@ -0,0 +1 @@ +Recognise ``image/webp`` as a standard format in the :mod:`mimetypes` module. From 78c254582b1757c15098ae65e97a2589ae663cd7 Mon Sep 17 00:00:00 2001 From: Albert Zeyer <albzey@gmail.com> Date: Wed, 31 Jan 2024 20:14:44 +0100 Subject: [PATCH 125/263] gh-113939: Frame clear, clear locals (#113940) --- Lib/test/test_frame.py | 22 +++++++++++++++++++ ...-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst | 4 ++++ Objects/frameobject.c | 1 + 3 files changed, 27 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 7f17666a8d9697b..244ce8af7cdf088 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -55,6 +55,28 @@ class C: # The reference was released by .clear() self.assertIs(None, wr()) + def test_clear_locals_after_f_locals_access(self): + # see gh-113939 + class C: + pass + + wr = None + def inner(): + nonlocal wr + c = C() + wr = weakref.ref(c) + 1/0 + + try: + inner() + except ZeroDivisionError as exc: + support.gc_collect() + self.assertIsNotNone(wr()) + print(exc.__traceback__.tb_next.tb_frame.f_locals) + exc.__traceback__.tb_next.tb_frame.clear() + support.gc_collect() + self.assertIsNone(wr()) + def test_clear_does_not_clear_specials(self): class C: pass diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst new file mode 100644 index 000000000000000..28b8e4bdda6be4e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-12-16-40-07.gh-issue-113939.Yi3L-e.rst @@ -0,0 +1,4 @@ +frame.clear(): +Clear frame.f_locals as well, and not only the fast locals. +This is relevant once frame.f_locals was accessed, +which would contain also references to all the locals. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index cafe4ef6141d9af..a914c61aac2fd51 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -926,6 +926,7 @@ frame_tp_clear(PyFrameObject *f) Py_CLEAR(locals[i]); } f->f_frame->stacktop = 0; + Py_CLEAR(f->f_frame->f_locals); return 0; } From b25b7462d520f38049d25888f220f20f759bc077 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Wed, 31 Jan 2024 22:51:18 +0300 Subject: [PATCH 126/263] gh-114788: Do not run JIT workflow on unrelated changes (#114789) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/jit.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 3da729191812551..22e0bdba53ffd6f 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -1,10 +1,19 @@ name: JIT on: pull_request: - paths: '**jit**' + paths: + - '**jit**' + - 'Python/bytecodes.c' push: - paths: '**jit**' + paths: + - '**jit**' + - 'Python/bytecodes.c' workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: jit: name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) From 1836f674c0d86ec3375189a550c8f4a52ff89ae8 Mon Sep 17 00:00:00 2001 From: Bradley Reynolds <bradley.reynolds@darbia.dev> Date: Wed, 31 Jan 2024 15:33:28 -0600 Subject: [PATCH 127/263] Add note to `sys.orig_argv` clarifying the difference from `sys.argv` (#114630) Co-authored-by: Ned Batchelder <ned@nedbatchelder.com> --- Doc/library/sys.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index abf2c393a44928d..a97a369b77b88a8 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1293,7 +1293,10 @@ always available. The list of the original command line arguments passed to the Python executable. - See also :data:`sys.argv`. + The elements of :data:`sys.orig_argv` are the arguments to the Python interpreter, + while the elements of :data:`sys.argv` are the arguments to the user's program. + Arguments consumed by the interpreter itself will be present in :data:`sys.orig_argv` + and missing from :data:`sys.argv`. .. versionadded:: 3.10 From 7b9d406729e7e7adc482b5b8c920de1874c234d0 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Thu, 1 Feb 2024 08:58:08 +0900 Subject: [PATCH 128/263] gh-112087: Make PyList_{Append,Size,GetSlice} to be thread-safe (gh-114651) --- Include/internal/pycore_list.h | 3 ++- Include/object.h | 4 ++++ Objects/listobject.c | 22 +++++++++++++++------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 6c29d882335512e..4536f90e4144939 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -24,12 +24,13 @@ extern void _PyList_Fini(_PyFreeListState *); extern int _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem); +// In free-threaded build: self should be locked by the caller, if it should be thread-safe. static inline int _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem) { assert(self != NULL && newitem != NULL); assert(PyList_Check(self)); - Py_ssize_t len = PyList_GET_SIZE(self); + Py_ssize_t len = Py_SIZE(self); Py_ssize_t allocated = self->allocated; assert((size_t)len + 1 < PY_SSIZE_T_MAX); if (allocated > len) { diff --git a/Include/object.h b/Include/object.h index ef3fb721c2b0122..568d315d7606c42 100644 --- a/Include/object.h +++ b/Include/object.h @@ -428,7 +428,11 @@ static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) { assert(ob->ob_base.ob_type != &PyLong_Type); assert(ob->ob_base.ob_type != &PyBool_Type); +#ifdef Py_GIL_DISABLED + _Py_atomic_store_ssize_relaxed(&ob->ob_size, size); +#else ob->ob_size = size; +#endif } #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_SET_SIZE(ob, size) Py_SET_SIZE(_PyVarObject_CAST(ob), (size)) diff --git a/Objects/listobject.c b/Objects/listobject.c index 56785e5f37a450f..80a1f1da55b8bc8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -30,7 +30,6 @@ get_list_state(void) } #endif - /* Ensure ob_item has room for at least newsize elements, and set * ob_size to newsize. If newsize > ob_size on entry, the content * of the new slots at exit is undefined heap trash; it's the caller's @@ -221,8 +220,9 @@ PyList_Size(PyObject *op) PyErr_BadInternalCall(); return -1; } - else - return Py_SIZE(op); + else { + return PyList_GET_SIZE(op); + } } static inline int @@ -328,7 +328,7 @@ PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) int _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem) { - Py_ssize_t len = PyList_GET_SIZE(self); + Py_ssize_t len = Py_SIZE(self); assert(self->allocated == -1 || self->allocated == len); if (list_resize(self, len + 1) < 0) { Py_DECREF(newitem); @@ -342,7 +342,11 @@ int PyList_Append(PyObject *op, PyObject *newitem) { if (PyList_Check(op) && (newitem != NULL)) { - return _PyList_AppendTakeRef((PyListObject *)op, Py_NewRef(newitem)); + int ret; + Py_BEGIN_CRITICAL_SECTION(op); + ret = _PyList_AppendTakeRef((PyListObject *)op, Py_NewRef(newitem)); + Py_END_CRITICAL_SECTION(); + return ret; } PyErr_BadInternalCall(); return -1; @@ -473,7 +477,7 @@ static PyObject * list_item(PyObject *aa, Py_ssize_t i) { PyListObject *a = (PyListObject *)aa; - if (!valid_index(i, Py_SIZE(a))) { + if (!valid_index(i, PyList_GET_SIZE(a))) { PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); return NULL; } @@ -511,6 +515,8 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) PyErr_BadInternalCall(); return NULL; } + PyObject *ret; + Py_BEGIN_CRITICAL_SECTION(a); if (ilow < 0) { ilow = 0; } @@ -523,7 +529,9 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) else if (ihigh > Py_SIZE(a)) { ihigh = Py_SIZE(a); } - return list_slice((PyListObject *)a, ilow, ihigh); + ret = list_slice((PyListObject *)a, ilow, ihigh); + Py_END_CRITICAL_SECTION(); + return ret; } static PyObject * From 80aa7b3688b8fdc85cd53d4113cb5f6ce5500027 Mon Sep 17 00:00:00 2001 From: Jamie Phan <jamie@ordinarylab.dev> Date: Thu, 1 Feb 2024 07:42:17 +0700 Subject: [PATCH 129/263] gh-109534: fix reference leak when SSL handshake fails (#114074) --- Lib/asyncio/selector_events.py | 4 ++++ Lib/asyncio/sslproto.py | 1 + .../Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index dcd5e0aa345029a..10fbdd76e93f79c 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -235,6 +235,10 @@ async def _accept_connection2( await waiter except BaseException: transport.close() + # gh-109534: When an exception is raised by the SSLProtocol object the + # exception set in this future can keep the protocol object alive and + # cause a reference cycle. + waiter = None raise # It's now up to the protocol to handle the connection. diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 599e91ba0003d10..fa99d4533aa0a6a 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -579,6 +579,7 @@ def _on_handshake_complete(self, handshake_exc): peercert = sslobj.getpeercert() except Exception as exc: + handshake_exc = None self._set_state(SSLProtocolState.UNWRAPPED) if isinstance(exc, ssl.CertificateError): msg = 'SSL handshake failed on verifying the certificate' diff --git a/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst new file mode 100644 index 000000000000000..fc9a765a230037d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst @@ -0,0 +1,3 @@ +Fix a reference leak in +:class:`asyncio.selector_events.BaseSelectorEventLoop` when SSL handshakes +fail. Patch contributed by Jamie Phan. From a79a27242f75fc33416d4d135a4a542898d140e5 Mon Sep 17 00:00:00 2001 From: Aidan Holm <alfh@google.com> Date: Thu, 1 Feb 2024 08:42:38 +0800 Subject: [PATCH 130/263] gh-111112: Avoid potential confusion in TCP server example. (#111113) Improve misleading TCP server docs and example. socket.recv(), as documented by the Python reference documentation, returns at most `bufsize` bytes, and the underlying TCP protocol means there is no guaranteed correspondence between what is sent by the client and what is received by the server. This conflation could mislead readers into thinking that TCP is datagram-based or has similar semantics, which will likely appear to work for simple cases, but introduce difficult to reproduce bugs. --- Doc/library/socketserver.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/socketserver.rst b/Doc/library/socketserver.rst index 5fd213fa613c8d4..864b1dadb785623 100644 --- a/Doc/library/socketserver.rst +++ b/Doc/library/socketserver.rst @@ -494,7 +494,7 @@ This is the server side:: def handle(self): # self.request is the TCP socket connected to the client self.data = self.request.recv(1024).strip() - print("{} wrote:".format(self.client_address[0])) + print("Received from {}:".format(self.client_address[0])) print(self.data) # just send back the same data, but upper-cased self.request.sendall(self.data.upper()) @@ -525,8 +525,9 @@ objects that simplify communication by providing the standard file interface):: The difference is that the ``readline()`` call in the second handler will call ``recv()`` multiple times until it encounters a newline character, while the -single ``recv()`` call in the first handler will just return what has been sent -from the client in one ``sendall()`` call. +single ``recv()`` call in the first handler will just return what has been +received so far from the client's ``sendall()`` call (typically all of it, but +this is not guaranteed by the TCP protocol). This is the client side:: From 854e2bc42340b22cdeea5d16ac8b1ef3762c6909 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 1 Feb 2024 03:35:48 +0200 Subject: [PATCH 131/263] CI: Test on macOS M1 (#114766) Test on macOS M1 --- .github/workflows/reusable-macos.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index c24b6e963ddfd67..28e9dc52fd50eed 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -12,20 +12,27 @@ on: jobs: build_macos: name: 'build and test' - runs-on: macos-latest timeout-minutes: 60 env: HOMEBREW_NO_ANALYTICS: 1 HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 PYTHONSTRICTEXTENSIONBUILD: 1 + strategy: + fail-fast: false + matrix: + os: [ + "macos-14", # M1 + "macos-13", # Intel + ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Restore config.cache uses: actions/cache@v3 with: path: config.cache - key: ${{ github.job }}-${{ runner.os }}-${{ inputs.config_hash }} + key: ${{ github.job }}-${{ matrix.os }}-${{ inputs.config_hash }} - name: Install Homebrew dependencies run: brew install pkg-config openssl@3.0 xz gdbm tcl-tk - name: Configure CPython From ff8939e5abaad7cd87f4d1f07ca7f6d176090f6c Mon Sep 17 00:00:00 2001 From: Pradyot Ranjan <99216956+prady0t@users.noreply.github.com> Date: Thu, 1 Feb 2024 07:18:39 +0530 Subject: [PATCH 132/263] gh-114811: Change '\*' to '*' in warnings.rst (#114819) Regression in 3.12. --- Doc/library/warnings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index a9c469707e82273..500398636e11ae8 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -396,7 +396,7 @@ Available Functions ------------------- -.. function:: warn(message, category=None, stacklevel=1, source=None, \*, skip_file_prefixes=None) +.. function:: warn(message, category=None, stacklevel=1, source=None, *, skip_file_prefixes=None) Issue a warning, or maybe ignore it or raise an exception. The *category* argument, if given, must be a :ref:`warning category class <warning-categories>`; it From 586057e9f80d57f16334c0eee8431931e4aa8cff Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 31 Jan 2024 21:11:16 -0600 Subject: [PATCH 133/263] gh-67230: Add versionadded notes for QUOTE_NOTNULL and QUOTE_STRINGS (#114816) As @GPHemsley pointed out, #29469 omitted `versionadded` notes for the 2 new items. --- Doc/library/csv.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 66888c22b7cc28a..fd62b225fcebb80 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -351,6 +351,8 @@ The :mod:`csv` module defines the following constants: Instructs :class:`reader` objects to interpret an empty (unquoted) field as None and to otherwise behave as :data:`QUOTE_ALL`. + .. versionadded:: 3.12 + .. data:: QUOTE_STRINGS Instructs :class:`writer` objects to always place quotes around fields @@ -360,6 +362,8 @@ The :mod:`csv` module defines the following constants: Instructs :class:`reader` objects to interpret an empty (unquoted) string as ``None`` and to otherwise behave as :data:`QUOTE_NONNUMERIC`. + .. versionadded:: 3.12 + The :mod:`csv` module defines the following exception: From 57c3e775df5a5ca0982adf15010ed80a158b1b80 Mon Sep 17 00:00:00 2001 From: srinivasan <shivnaren@gmail.com> Date: Thu, 1 Feb 2024 09:16:49 +0530 Subject: [PATCH 134/263] gh-114648: Add IndexError exception to tutorial datastructures list.pop entry (#114681) Remove redundant explanation of optional argument. --- Doc/tutorial/datastructures.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/tutorial/datastructures.rst b/Doc/tutorial/datastructures.rst index 87614d082a1d4e0..de2827461e2f241 100644 --- a/Doc/tutorial/datastructures.rst +++ b/Doc/tutorial/datastructures.rst @@ -48,10 +48,9 @@ objects: :noindex: Remove the item at the given position in the list, and return it. If no index - is specified, ``a.pop()`` removes and returns the last item in the list. (The - square brackets around the *i* in the method signature denote that the parameter - is optional, not that you should type square brackets at that position. You - will see this notation frequently in the Python Library Reference.) + is specified, ``a.pop()`` removes and returns the last item in the list. + It raises an :exc:`IndexError` if the list is empty or the index is + outside the list range. .. method:: list.clear() From 5ce193e65a7e6f239337a8c5305895cf8a4d2726 Mon Sep 17 00:00:00 2001 From: technillogue <wisepoison@gmail.com> Date: Thu, 1 Feb 2024 01:03:58 -0500 Subject: [PATCH 135/263] gh-114364: Fix awkward wording about mmap.mmap.seekable (#114374) --------- Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- Doc/whatsnew/3.13.rst | 4 ++-- Misc/NEWS.d/3.13.0a2.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b33203efbb05c0c..6b94a3771406faf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -259,8 +259,8 @@ mmap ---- * The :class:`mmap.mmap` class now has an :meth:`~mmap.mmap.seekable` method - that can be used where it requires a file-like object with seekable and - the :meth:`~mmap.mmap.seek` method return the new absolute position. + that can be used when a seekable file-like object is required. + The :meth:`~mmap.mmap.seek` method now returns the new absolute position. (Contributed by Donghee Na and Sylvie Liberman in :gh:`111835`.) * :class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``, the file descriptor specified by *fileno* will not be duplicated. diff --git a/Misc/NEWS.d/3.13.0a2.rst b/Misc/NEWS.d/3.13.0a2.rst index d4be4fb8a3d3ab6..e5841e14c02efbf 100644 --- a/Misc/NEWS.d/3.13.0a2.rst +++ b/Misc/NEWS.d/3.13.0a2.rst @@ -565,9 +565,9 @@ part of a :exc:`BaseExceptionGroup`, in addition to the recent support for .. section: Library The :class:`mmap.mmap` class now has an :meth:`~mmap.mmap.seekable` method -that can be used where it requires a file-like object with seekable and the -:meth:`~mmap.mmap.seek` method return the new absolute position. Patch by -Donghee Na. +that can be used when a seekable file-like object is required. +The :meth:`~mmap.mmap.seek` method now returns the new absolute position. +Patch by Donghee Na. .. From e6d6d5dcc00af50446761b0c4d20bd6e92380135 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Thu, 1 Feb 2024 04:26:23 -0500 Subject: [PATCH 136/263] gh-114746: Avoid quadratic behavior in free-threaded GC (GH-114817) The free-threaded build's GC implementation is non-generational, but was scheduled as if it were collecting a young generation leading to quadratic behavior. This increases the minimum threshold and scales it to the number of live objects as we do for the old generation in the default build. Note that the scheduling is still not thread-safe without the GIL. Those changes will come in later PRs. A few tests, like "test_sneaky_frame_object" rely on prompt scheduling of the GC. For now, to keep that test passing, we disable the scaled threshold after calls like `gc.set_threshold(1, 0, 0)`. --- Python/gc_free_threading.c | 102 +++++++++++-------------------------- 1 file changed, 29 insertions(+), 73 deletions(-) diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index f2cd84981461a4f..a6513a2c4aba2a6 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -46,6 +46,7 @@ struct collection_state { GCState *gcstate; Py_ssize_t collected; Py_ssize_t uncollectable; + Py_ssize_t long_lived_total; struct worklist unreachable; struct worklist legacy_finalizers; struct worklist wrcb_to_call; @@ -443,7 +444,7 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, else { // object is reachable, restore `ob_tid`; we're done with these objects gc_restore_tid(op); - state->gcstate->long_lived_total++; + state->long_lived_total++; } return true; @@ -605,6 +606,8 @@ get_gc_state(void) void _PyGC_InitState(GCState *gcstate) { + // TODO: move to pycore_runtime_init.h once the incremental GC lands. + gcstate->generations[0].threshold = 2000; } @@ -885,62 +888,6 @@ invoke_gc_callback(PyThreadState *tstate, const char *phase, assert(!_PyErr_Occurred(tstate)); } - -/* Find the oldest generation (highest numbered) where the count - * exceeds the threshold. Objects in the that generation and - * generations younger than it will be collected. */ -static int -gc_select_generation(GCState *gcstate) -{ - for (int i = NUM_GENERATIONS-1; i >= 0; i--) { - if (gcstate->generations[i].count > gcstate->generations[i].threshold) { - /* Avoid quadratic performance degradation in number - of tracked objects (see also issue #4074): - - To limit the cost of garbage collection, there are two strategies; - - make each collection faster, e.g. by scanning fewer objects - - do less collections - This heuristic is about the latter strategy. - - In addition to the various configurable thresholds, we only trigger a - full collection if the ratio - - long_lived_pending / long_lived_total - - is above a given value (hardwired to 25%). - - The reason is that, while "non-full" collections (i.e., collections of - the young and middle generations) will always examine roughly the same - number of objects -- determined by the aforementioned thresholds --, - the cost of a full collection is proportional to the total number of - long-lived objects, which is virtually unbounded. - - Indeed, it has been remarked that doing a full collection every - <constant number> of object creations entails a dramatic performance - degradation in workloads which consist in creating and storing lots of - long-lived objects (e.g. building a large list of GC-tracked objects would - show quadratic performance, instead of linear as expected: see issue #4074). - - Using the above ratio, instead, yields amortized linear performance in - the total number of objects (the effect of which can be summarized - thusly: "each full garbage collection is more and more costly as the - number of objects grows, but we do fewer and fewer of them"). - - This heuristic was suggested by Martin von Löwis on python-dev in - June 2008. His original analysis and proposal can be found at: - http://mail.python.org/pipermail/python-dev/2008-June/080579.html - */ - if (i == NUM_GENERATIONS - 1 - && gcstate->long_lived_pending < gcstate->long_lived_total / 4) - { - continue; - } - return i; - } - } - return -1; -} - static void cleanup_worklist(struct worklist *worklist) { @@ -952,6 +899,21 @@ cleanup_worklist(struct worklist *worklist) } } +static bool +gc_should_collect(GCState *gcstate) +{ + int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); + int threshold = gcstate->generations[0].threshold; + if (count <= threshold || threshold == 0 || !gcstate->enabled) { + return false; + } + // Avoid quadratic behavior by scaling threshold to the number of live + // objects. A few tests rely on immediate scheduling of the GC so we ignore + // the scaled threshold if generations[1].threshold is set to zero. + return (count > gcstate->long_lived_total / 4 || + gcstate->generations[1].threshold == 0); +} + static void gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) { @@ -1029,15 +991,10 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) return 0; } - if (generation == GENERATION_AUTO) { - // Select the oldest generation that needs collecting. We will collect - // objects from that generation and all generations younger than it. - generation = gc_select_generation(gcstate); - if (generation < 0) { - // No generation needs to be collected. - _Py_atomic_store_int(&gcstate->collecting, 0); - return 0; - } + if (reason == _Py_GC_REASON_HEAP && !gc_should_collect(gcstate)) { + // Don't collect if the threshold is not exceeded. + _Py_atomic_store_int(&gcstate->collecting, 0); + return 0; } assert(generation >= 0 && generation < NUM_GENERATIONS); @@ -1082,6 +1039,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) m = state.collected; n = state.uncollectable; + gcstate->long_lived_total = state.long_lived_total; if (gcstate->debug & _PyGC_DEBUG_STATS) { double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); @@ -1523,12 +1481,10 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->generations[0].count++; /* number of allocated GC objects */ - if (gcstate->generations[0].count > gcstate->generations[0].threshold && - gcstate->enabled && - gcstate->generations[0].threshold && - !_Py_atomic_load_int_relaxed(&gcstate->collecting) && - !_PyErr_Occurred(tstate)) + gcstate->generations[0].count++; + + if (gc_should_collect(gcstate) && + !_Py_atomic_load_int_relaxed(&gcstate->collecting)) { _Py_ScheduleGC(tstate->interp); } @@ -1537,7 +1493,7 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); + gc_collect_main(tstate, 0, _Py_GC_REASON_HEAP); } static PyObject * From de6f97cd3519c5d8528d8ca1bb00fce4e9969671 Mon Sep 17 00:00:00 2001 From: Christophe Nanteuil <35002064+christopheNan@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:34:04 +0100 Subject: [PATCH 137/263] Fix typos in ElementTree documentation (GH-108848) PI objects instead of comment objects. --- Doc/library/xml.etree.elementtree.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index fe92400fb08dfdc..bb6773c361a9b41 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -664,7 +664,7 @@ Functions given. Returns an element instance, representing a processing instruction. Note that :class:`XMLParser` skips over processing instructions - in the input instead of creating comment objects for them. An + in the input instead of creating PI objects for them. An :class:`ElementTree` will only contain processing instruction nodes if they have been inserted into to the tree using one of the :class:`Element` methods. @@ -1302,8 +1302,8 @@ TreeBuilder Objects .. method:: pi(target, text) - Creates a comment with the given *target* name and *text*. If - ``insert_pis`` is true, this will also add it to the tree. + Creates a process instruction with the given *target* name and *text*. + If ``insert_pis`` is true, this will also add it to the tree. .. versionadded:: 3.8 From 21c01a009f970ccf73d2d3e4176b61fc8986adfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:52:05 +0000 Subject: [PATCH 138/263] build(deps-dev): bump types-setuptools from 69.0.0.0 to 69.0.0.20240125 in /Tools (#114853) build(deps-dev): bump types-setuptools in /Tools Bumps [types-setuptools](https://github.com/python/typeshed) from 69.0.0.0 to 69.0.0.20240125. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-setuptools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index b89f86a35d61153..11cce505f60d160 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -4,4 +4,4 @@ mypy==1.8.0 # needed for peg_generator: types-psutil==5.9.5.17 -types-setuptools==69.0.0.0 +types-setuptools==69.0.0.20240125 From 93bfaa858c22742b7229897ae7c15e9b6e456fc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:42:35 +0000 Subject: [PATCH 139/263] build(deps): bump hypothesis from 6.92.2 to 6.97.4 in /Tools (#114851) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.92.2 to 6.97.4. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.92.2...hypothesis-python-6.97.4) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-hypothesis.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-hypothesis.txt b/Tools/requirements-hypothesis.txt index 0e6e16ae198162c..064731a236ee863 100644 --- a/Tools/requirements-hypothesis.txt +++ b/Tools/requirements-hypothesis.txt @@ -1,4 +1,4 @@ # Requirements file for hypothesis that # we use to run our property-based tests in CI. -hypothesis==6.92.2 +hypothesis==6.97.4 From d4c5ec24c2bbb1e1d02d17b75709028aca84398e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:49:07 +0200 Subject: [PATCH 140/263] build(deps): bump actions/cache from 3 to 4 (#114856) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/reusable-docs.yml | 2 +- .github/workflows/reusable-macos.yml | 2 +- .github/workflows/reusable-ubuntu.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc5ecc09fbc5923..949c4ae95da07f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,7 +132,7 @@ jobs: with: python-version: '3.x' - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }} @@ -259,7 +259,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -274,7 +274,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -319,7 +319,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -342,7 +342,7 @@ jobs: - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -375,7 +375,7 @@ jobs: ./python -m venv $VENV_LOC && $VENV_PYTHON -m pip install -r ${GITHUB_WORKSPACE}/Tools/requirements-hypothesis.txt - name: 'Restore Hypothesis database' id: cache-hypothesis-database - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./hypothesis key: hypothesis-database-${{ github.head_ref || github.run_id }} @@ -421,7 +421,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ runner.os }}-${{ needs.check_source.outputs.config_hash }} @@ -440,7 +440,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index e534751ee1011da..cea8f93d67b29c1 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -89,7 +89,7 @@ jobs: timeout-minutes: 60 steps: - uses: actions/checkout@v4 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ubuntu-doc-${{ hashFiles('Doc/requirements.txt') }} diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 28e9dc52fd50eed..cad619b78ce5f2c 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ matrix.os }}-${{ inputs.config_hash }} diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index c2194280c0a50f4..ef52d99c15191b9 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -29,7 +29,7 @@ jobs: echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> $GITHUB_ENV - name: 'Restore OpenSSL build' id: cache-openssl - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} @@ -53,7 +53,7 @@ jobs: - name: Bind mount sources read-only run: sudo mount --bind -o ro $GITHUB_WORKSPACE $CPYTHON_RO_SRCDIR - name: Restore config.cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache key: ${{ github.job }}-${{ runner.os }}-${{ inputs.config_hash }} From 59ae215387d69119f0e77b2776e8214ca4bb8a5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:50:08 +0000 Subject: [PATCH 141/263] build(deps-dev): bump types-psutil from 5.9.5.17 to 5.9.5.20240106 in /Tools (#114852) build(deps-dev): bump types-psutil in /Tools Bumps [types-psutil](https://github.com/python/typeshed) from 5.9.5.17 to 5.9.5.20240106. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-psutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Tools/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index 11cce505f60d160..c0a63b40ff4155f 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -3,5 +3,5 @@ mypy==1.8.0 # needed for peg_generator: -types-psutil==5.9.5.17 +types-psutil==5.9.5.20240106 types-setuptools==69.0.0.20240125 From 0bf42dae7e73febc76ea96fd58af6b765a12b8a7 Mon Sep 17 00:00:00 2001 From: Tomas R <tomas.roun8@gmail.com> Date: Thu, 1 Feb 2024 12:49:01 +0100 Subject: [PATCH 142/263] gh-107461 ctypes: Add a testcase for nested `_as_parameter_` lookup (GH-107462) --- Lib/test/test_ctypes/test_as_parameter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_ctypes/test_as_parameter.py b/Lib/test/test_ctypes/test_as_parameter.py index a1a8745e737fa25..ca75e748256083b 100644 --- a/Lib/test/test_ctypes/test_as_parameter.py +++ b/Lib/test/test_ctypes/test_as_parameter.py @@ -221,5 +221,16 @@ class AsParamPropertyWrapperTestCase(BasicWrapTestCase): wrap = AsParamPropertyWrapper +class AsParamNestedWrapperTestCase(BasicWrapTestCase): + """Test that _as_parameter_ is evaluated recursively. + + The _as_parameter_ attribute can be another object which + defines its own _as_parameter_ attribute. + """ + + def wrap(self, param): + return AsParamWrapper(AsParamWrapper(AsParamWrapper(param))) + + if __name__ == '__main__': unittest.main() From 4dbb198d279a06fed74ea4c38f93d658baf38170 Mon Sep 17 00:00:00 2001 From: Ayappan Perumal <ayappap2@in.ibm.com> Date: Thu, 1 Feb 2024 17:22:54 +0530 Subject: [PATCH 143/263] gh-105089: Fix test_create_directory_with_write test failure in AIX (GH-105228) --- Lib/test/test_zipfile/test_core.py | 2 +- .../next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 9bdb08aeabb7817..a177044d735bed7 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -2959,7 +2959,7 @@ def test_create_directory_with_write(self): directory = os.path.join(TESTFN2, "directory2") os.mkdir(directory) - mode = os.stat(directory).st_mode + mode = os.stat(directory).st_mode & 0xFFFF zf.write(directory, arcname="directory2/") zinfo = zf.filelist[1] self.assertEqual(zinfo.filename, "directory2/") diff --git a/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst b/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst new file mode 100644 index 000000000000000..d04ef435dd572d8 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-06-02-05-04-15.gh-issue-105089.KaZFtU.rst @@ -0,0 +1,4 @@ +Fix +``test.test_zipfile.test_core.TestWithDirectory.test_create_directory_with_write`` +test in AIX by doing a bitwise AND of 0xFFFF on mode , so that it will be in +sync with ``zinfo.external_attr`` From 84e0e32184f658b8174b400e6ca9c418bfe8e0fc Mon Sep 17 00:00:00 2001 From: Anders Kaseorg <andersk@mit.edu> Date: Thu, 1 Feb 2024 11:26:22 -0500 Subject: [PATCH 144/263] Remove unused Py_XDECREF from _PyFrame_ClearExceptCode (GH-106158) frame->frame_obj was set to NULL a few lines earlier. Signed-off-by: Anders Kaseorg <andersk@mit.edu> --- Python/frame.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/frame.c b/Python/frame.c index 2865b2eab603c27..ddf6ef6ba5465cf 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -139,7 +139,6 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) for (int i = 0; i < frame->stacktop; i++) { Py_XDECREF(frame->localsplus[i]); } - Py_XDECREF(frame->frame_obj); Py_XDECREF(frame->f_locals); Py_DECREF(frame->f_funcobj); } From 2dea1cf7fd9b1f6a914e363ecb17a853f4b99b6b Mon Sep 17 00:00:00 2001 From: Guido van Rossum <guido@python.org> Date: Thu, 1 Feb 2024 08:54:44 -0800 Subject: [PATCH 145/263] Write about Tier 2 and JIT in "what's new 3.13" (#114826) (This will soon be superseded by Ken Jin's much more detailed version.) --- Doc/whatsnew/3.13.rst | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6b94a3771406faf..887c3009f885041 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -81,6 +81,13 @@ Important deprecations, removals or restrictions: * Python 3.13 and later have two years of full support, followed by three years of security fixes. +Interpreter improvements: + +* A basic :ref:`JIT compiler <whatsnew313-jit-compiler>` was added. + It is currently disabled by default (though we may turn it on later). + Performance improvements are modest -- we expect to be improving this + over the next few releases. + New Features ============ @@ -477,6 +484,46 @@ Optimizations FreeBSD and Solaris. See the ``subprocess`` section above for details. (Contributed by Jakub Kulik in :gh:`113117`.) +.. _whatsnew313-jit-compiler: + +Experimental JIT Compiler +========================= + +When CPython is configured using the ``--enable-experimental-jit`` option, +a just-in-time compiler is added which can speed up some Python programs. + +The internal architecture is roughly as follows. + +* We start with specialized *Tier 1 bytecode*. + See :ref:`What's new in 3.11 <whatsnew311-pep659>` for details. + +* When the Tier 1 bytecode gets hot enough, it gets translated + to a new, purely internal *Tier 2 IR*, a.k.a. micro-ops ("uops"). + +* The Tier 2 IR uses the same stack-based VM as Tier 1, but the + instruction format is better suited to translation to machine code. + +* We have several optimization passes for Tier 2 IR, which are applied + before it is interpreted or translated to machine code. + +* There is a Tier 2 interpreter, but it is mostly intended for debugging + the earlier stages of the optimization pipeline. If the JIT is not + enabled, the Tier 2 interpreter can be invoked by passing Python the + ``-X uops`` option or by setting the ``PYTHON_UOPS`` environment + variable to ``1``. + +* When the ``--enable-experimental-jit`` option is used, the optimized + Tier 2 IR is translated to machine code, which is then executed. + This does not require additional runtime options. + +* The machine code translation process uses an architecture called + *copy-and-patch*. It has no runtime dependencies, but there is a new + build-time dependency on LLVM. + +(JIT by Brandt Bucher, inspired by a paper by Haoran Xu and Fredrik Kjolstad. +Tier 2 IR by Mark Shannon and Guido van Rossum. +Tier 2 optimizer by Ken Jin.) + Deprecated ========== From 6d7ad57385e6c18545f19714b8f520644d305715 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Thu, 1 Feb 2024 19:56:24 +0300 Subject: [PATCH 146/263] Update outdated info in ``Tools/cases_generator/README.md`` (#114844) --- Tools/cases_generator/README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Tools/cases_generator/README.md b/Tools/cases_generator/README.md index ed802e44f31ad5f..7fec8a882336cdf 100644 --- a/Tools/cases_generator/README.md +++ b/Tools/cases_generator/README.md @@ -5,16 +5,30 @@ Documentation for the instruction definitions in `Python/bytecodes.c` What's currently here: +- `analyzer.py`: code for converting `AST` generated by `Parser` + to more high-level structure for easier interaction - `lexer.py`: lexer for C, originally written by Mark Shannon - `plexer.py`: OO interface on top of lexer.py; main class: `PLexer` -- `parsing.py`: Parser for instruction definition DSL; main class `Parser` -- `generate_cases.py`: driver script to read `Python/bytecodes.c` and +- `parsing.py`: Parser for instruction definition DSL; main class: `Parser` +- `parser.py` helper for interactions with `parsing.py` +- `tierN_generator.py`: a couple of driver scripts to read `Python/bytecodes.c` and write `Python/generated_cases.c.h` (and several other files) -- `analysis.py`: `Analyzer` class used to read the input files -- `flags.py`: abstractions related to metadata flags for instructions -- `formatting.py`: `Formatter` class used to write the output files -- `instructions.py`: classes to analyze and write instructions -- `stacking.py`: code to handle generalized stack effects +- `stack.py`: code to handle generalized stack effects +- `cwriter.py`: code which understands tokens and how to format C code; + main class: `CWriter` +- `generators_common.py`: helpers for generators +- `opcode_id_generator.py`: generate a list of opcodes and write them to + `Include/opcode_ids.h` +- `opcode_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Include/internal/pycore_opcode_metadata.h` +- `py_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Lib/_opcode_metadata.py` +- `target_generator.py`: generate targets for computed goto dispatch and + write them to `Python/opcode_targets.h` +- `uop_id_generator.py`: generate a list of uop IDs and write them to + `Include/internal/pycore_uop_ids.h` +- `uop_metadata_generator.py`: reads the instruction definitions and + write the metadata to `Include/internal/pycore_uop_metadata.h` Note that there is some dummy C code at the top and bottom of `Python/bytecodes.c` From e9dab656380ec03d628979975646748330b76b9b Mon Sep 17 00:00:00 2001 From: Nicholas Hollander <31573882+nhhollander@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:24:15 -0500 Subject: [PATCH 147/263] gh-105031: Clarify datetime documentation for ISO8601 (GH-105049) --- Doc/library/datetime.rst | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 4ff049c8709289c..db9a92ae4111e35 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -536,7 +536,15 @@ Other constructors, all class methods: .. classmethod:: date.fromisoformat(date_string) Return a :class:`date` corresponding to a *date_string* given in any valid - ISO 8601 format, except ordinal dates (e.g. ``YYYY-DDD``):: + ISO 8601 format, with the following exceptions: + + 1. Reduced precision dates are not currently supported (``YYYY-MM``, + ``YYYY``). + 2. Extended date representations are not currently supported + (``±YYYYYY-MM-DD``). + 3. Ordinal dates are not currently supported (``YYYY-OOO``). + + Examples:: >>> from datetime import date >>> date.fromisoformat('2019-12-04') @@ -1017,8 +1025,12 @@ Other constructors, all class methods: 1. Time zone offsets may have fractional seconds. 2. The ``T`` separator may be replaced by any single unicode character. - 3. Ordinal dates are not currently supported. - 4. Fractional hours and minutes are not supported. + 3. Fractional hours and minutes are not supported. + 4. Reduced precision dates are not currently supported (``YYYY-MM``, + ``YYYY``). + 5. Extended date representations are not currently supported + (``±YYYYYY-MM-DD``). + 6. Ordinal dates are not currently supported (``YYYY-OOO``). Examples:: From c9c6e04380ffedd25ea2e582f9057ab9612960c9 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Thu, 1 Feb 2024 12:07:16 -0600 Subject: [PATCH 148/263] Correct description of inheriting from another class (#114660) "inherits <someclass>" grates to this reader. I think it should be "inherits from <someclass>". --- Doc/library/gzip.rst | 3 +-- Doc/library/io.rst | 24 ++++++++++++------------ Doc/library/pickle.rst | 6 +++--- Doc/library/symtable.rst | 4 ++-- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst index 50cde09fa10a9d9..79be215a7660454 100644 --- a/Doc/library/gzip.rst +++ b/Doc/library/gzip.rst @@ -61,7 +61,7 @@ The module defines the following items: .. exception:: BadGzipFile - An exception raised for invalid gzip files. It inherits :exc:`OSError`. + An exception raised for invalid gzip files. It inherits from :exc:`OSError`. :exc:`EOFError` and :exc:`zlib.error` can also be raised for invalid gzip files. @@ -287,4 +287,3 @@ Command line options .. option:: -h, --help Show the help message. - diff --git a/Doc/library/io.rst b/Doc/library/io.rst index 6736aa9ee2b0efc..8eb531aa4ea2487 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -466,7 +466,7 @@ I/O Base Classes .. class:: RawIOBase - Base class for raw binary streams. It inherits :class:`IOBase`. + Base class for raw binary streams. It inherits from :class:`IOBase`. Raw binary streams typically provide low-level access to an underlying OS device or API, and do not try to encapsulate it in high-level primitives @@ -519,7 +519,7 @@ I/O Base Classes .. class:: BufferedIOBase Base class for binary streams that support some kind of buffering. - It inherits :class:`IOBase`. + It inherits from :class:`IOBase`. The main difference with :class:`RawIOBase` is that methods :meth:`read`, :meth:`readinto` and :meth:`write` will try (respectively) to read as much @@ -633,7 +633,7 @@ Raw File I/O .. class:: FileIO(name, mode='r', closefd=True, opener=None) A raw binary stream representing an OS-level file containing bytes data. It - inherits :class:`RawIOBase`. + inherits from :class:`RawIOBase`. The *name* can be one of two things: @@ -696,7 +696,7 @@ than raw I/O does. .. class:: BytesIO(initial_bytes=b'') - A binary stream using an in-memory bytes buffer. It inherits + A binary stream using an in-memory bytes buffer. It inherits from :class:`BufferedIOBase`. The buffer is discarded when the :meth:`~IOBase.close` method is called. @@ -745,7 +745,7 @@ than raw I/O does. .. class:: BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a readable, non - seekable :class:`RawIOBase` raw binary stream. It inherits + seekable :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedIOBase`. When reading data from this object, a larger amount of data may be @@ -783,7 +783,7 @@ than raw I/O does. .. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a writeable, non - seekable :class:`RawIOBase` raw binary stream. It inherits + seekable :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedIOBase`. When writing to this object, data is normally placed into an internal @@ -818,7 +818,7 @@ than raw I/O does. .. class:: BufferedRandom(raw, buffer_size=DEFAULT_BUFFER_SIZE) A buffered binary stream providing higher-level access to a seekable - :class:`RawIOBase` raw binary stream. It inherits :class:`BufferedReader` + :class:`RawIOBase` raw binary stream. It inherits from :class:`BufferedReader` and :class:`BufferedWriter`. The constructor creates a reader and writer for a seekable raw stream, given @@ -834,7 +834,7 @@ than raw I/O does. A buffered binary stream providing higher-level access to two non seekable :class:`RawIOBase` raw binary streams---one readable, the other writeable. - It inherits :class:`BufferedIOBase`. + It inherits from :class:`BufferedIOBase`. *reader* and *writer* are :class:`RawIOBase` objects that are readable and writeable respectively. If the *buffer_size* is omitted it defaults to @@ -857,7 +857,7 @@ Text I/O .. class:: TextIOBase Base class for text streams. This class provides a character and line based - interface to stream I/O. It inherits :class:`IOBase`. + interface to stream I/O. It inherits from :class:`IOBase`. :class:`TextIOBase` provides or overrides these data attributes and methods in addition to those from :class:`IOBase`: @@ -946,7 +946,7 @@ Text I/O line_buffering=False, write_through=False) A buffered text stream providing higher-level access to a - :class:`BufferedIOBase` buffered binary stream. It inherits + :class:`BufferedIOBase` buffered binary stream. It inherits from :class:`TextIOBase`. *encoding* gives the name of the encoding that the stream will be decoded or @@ -1073,7 +1073,7 @@ Text I/O .. class:: StringIO(initial_value='', newline='\n') - A text stream using an in-memory text buffer. It inherits + A text stream using an in-memory text buffer. It inherits from :class:`TextIOBase`. The text buffer is discarded when the :meth:`~IOBase.close` method is @@ -1124,7 +1124,7 @@ Text I/O .. class:: IncrementalNewlineDecoder A helper codec that decodes newlines for :term:`universal newlines` mode. - It inherits :class:`codecs.IncrementalDecoder`. + It inherits from :class:`codecs.IncrementalDecoder`. Performance diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index cfb251fca5c7cd9..1b718abfa481a02 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -272,13 +272,13 @@ The :mod:`pickle` module defines three exceptions: .. exception:: PickleError - Common base class for the other pickling exceptions. It inherits + Common base class for the other pickling exceptions. It inherits from :exc:`Exception`. .. exception:: PicklingError Error raised when an unpicklable object is encountered by :class:`Pickler`. - It inherits :exc:`PickleError`. + It inherits from :exc:`PickleError`. Refer to :ref:`pickle-picklable` to learn what kinds of objects can be pickled. @@ -286,7 +286,7 @@ The :mod:`pickle` module defines three exceptions: .. exception:: UnpicklingError Error raised when there is a problem unpickling an object, such as a data - corruption or a security violation. It inherits :exc:`PickleError`. + corruption or a security violation. It inherits from :exc:`PickleError`. Note that other exceptions may also be raised during unpickling, including (but not necessarily limited to) AttributeError, EOFError, ImportError, and diff --git a/Doc/library/symtable.rst b/Doc/library/symtable.rst index 46159dcef940e7c..47568387f9a7ce0 100644 --- a/Doc/library/symtable.rst +++ b/Doc/library/symtable.rst @@ -97,7 +97,7 @@ Examining Symbol Tables .. class:: Function - A namespace for a function or method. This class inherits + A namespace for a function or method. This class inherits from :class:`SymbolTable`. .. method:: get_parameters() @@ -123,7 +123,7 @@ Examining Symbol Tables .. class:: Class - A namespace of a class. This class inherits :class:`SymbolTable`. + A namespace of a class. This class inherits from :class:`SymbolTable`. .. method:: get_methods() From dc01b919c721f43ad024ba444a5d19541370e581 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Thu, 1 Feb 2024 21:37:55 +0300 Subject: [PATCH 149/263] gh-101100: Fix sphinx warnings in `howto/logging.rst` (#114846) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/howto/logging.rst | 43 +++++++++++++++++++++-------------------- Doc/library/logging.rst | 16 +++++++++++++-- Doc/tools/.nitignore | 1 - 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index f164b461c93b9cd..347330e98dd00c9 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -520,7 +520,7 @@ custom handlers) are the following configuration methods: * The :meth:`~Handler.setLevel` method, just as in logger objects, specifies the lowest severity that will be dispatched to the appropriate destination. Why - are there two :func:`setLevel` methods? The level set in the logger + are there two :meth:`~Handler.setLevel` methods? The level set in the logger determines which severity of messages it will pass to its handlers. The level set in each handler determines which messages that handler will send on. @@ -774,29 +774,29 @@ What happens if no configuration is provided If no logging configuration is provided, it is possible to have a situation where a logging event needs to be output, but no handlers can be found to -output the event. The behaviour of the logging package in these -circumstances is dependent on the Python version. +output the event. -For versions of Python prior to 3.2, the behaviour is as follows: +The event is output using a 'handler of last resort', stored in +:data:`lastResort`. This internal handler is not associated with any +logger, and acts like a :class:`~logging.StreamHandler` which writes the +event description message to the current value of ``sys.stderr`` (therefore +respecting any redirections which may be in effect). No formatting is +done on the message - just the bare event description message is printed. +The handler's level is set to ``WARNING``, so all events at this and +greater severities will be output. -* If *logging.raiseExceptions* is ``False`` (production mode), the event is - silently dropped. +.. versionchanged:: 3.2 -* If *logging.raiseExceptions* is ``True`` (development mode), a message - 'No handlers could be found for logger X.Y.Z' is printed once. + For versions of Python prior to 3.2, the behaviour is as follows: -In Python 3.2 and later, the behaviour is as follows: + * If :data:`raiseExceptions` is ``False`` (production mode), the event is + silently dropped. -* The event is output using a 'handler of last resort', stored in - ``logging.lastResort``. This internal handler is not associated with any - logger, and acts like a :class:`~logging.StreamHandler` which writes the - event description message to the current value of ``sys.stderr`` (therefore - respecting any redirections which may be in effect). No formatting is - done on the message - just the bare event description message is printed. - The handler's level is set to ``WARNING``, so all events at this and - greater severities will be output. + * If :data:`raiseExceptions` is ``True`` (development mode), a message + 'No handlers could be found for logger X.Y.Z' is printed once. -To obtain the pre-3.2 behaviour, ``logging.lastResort`` can be set to ``None``. + To obtain the pre-3.2 behaviour, + :data:`lastResort` can be set to ``None``. .. _library-config: @@ -998,7 +998,7 @@ Logged messages are formatted for presentation through instances of the use with the % operator and a dictionary. For formatting multiple messages in a batch, instances of -:class:`~handlers.BufferingFormatter` can be used. In addition to the format +:class:`BufferingFormatter` can be used. In addition to the format string (which is applied to each message in the batch), there is provision for header and trailer format strings. @@ -1034,7 +1034,8 @@ checks to see if a module-level variable, :data:`raiseExceptions`, is set. If set, a traceback is printed to :data:`sys.stderr`. If not set, the exception is swallowed. -.. note:: The default value of :data:`raiseExceptions` is ``True``. This is +.. note:: + The default value of :data:`raiseExceptions` is ``True``. This is because during development, you typically want to be notified of any exceptions that occur. It's advised that you set :data:`raiseExceptions` to ``False`` for production usage. @@ -1072,7 +1073,7 @@ You can write code like this:: expensive_func2()) so that if the logger's threshold is set above ``DEBUG``, the calls to -:func:`expensive_func1` and :func:`expensive_func2` are never made. +``expensive_func1`` and ``expensive_func2`` are never made. .. note:: In some cases, :meth:`~Logger.isEnabledFor` can itself be more expensive than you'd like (e.g. for deeply nested loggers where an explicit diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 4b756d10b4c586c..39eb41ce1f16708 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -531,12 +531,12 @@ subclasses. However, the :meth:`!__init__` method in subclasses needs to call This method should be called from handlers when an exception is encountered during an :meth:`emit` call. If the module-level attribute - ``raiseExceptions`` is ``False``, exceptions get silently ignored. This is + :data:`raiseExceptions` is ``False``, exceptions get silently ignored. This is what is mostly wanted for a logging system - most users will not care about errors in the logging system, they are more interested in application errors. You could, however, replace this with a custom handler if you wish. The specified record is the one which was being processed when the exception - occurred. (The default value of ``raiseExceptions`` is ``True``, as that is + occurred. (The default value of :data:`raiseExceptions` is ``True``, as that is more useful during development). @@ -1494,6 +1494,18 @@ Module-Level Attributes .. versionadded:: 3.2 +.. attribute:: raiseExceptions + + Used to see if exceptions during handling should be propagated. + + Default: ``True``. + + If :data:`raiseExceptions` is ``False``, + exceptions get silently ignored. This is what is mostly wanted + for a logging system - most users will not care about errors in + the logging system, they are more interested in application errors. + + Integration with the warnings module ------------------------------------ diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7eacb46d6299b3c..7127f30f240ce7a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -18,7 +18,6 @@ Doc/extending/extending.rst Doc/glossary.rst Doc/howto/descriptor.rst Doc/howto/enum.rst -Doc/howto/logging.rst Doc/library/ast.rst Doc/library/asyncio-extending.rst Doc/library/asyncio-policy.rst From 97cc58f9777ee8b8e91f4ca8726cdb9f79cf906c Mon Sep 17 00:00:00 2001 From: He Weidong <67892702+zlhwdsz@users.noreply.github.com> Date: Fri, 2 Feb 2024 03:27:53 +0800 Subject: [PATCH 150/263] Fix comment in pycore_runtime.h (GH-110540) --- Include/internal/pycore_runtime.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 02ab22b967b38ff..7c705d1224f915b 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -268,7 +268,7 @@ typedef struct pyruntimestate { a pointer type. */ - /* PyInterpreterState.interpreters.main */ + /* _PyRuntimeState.interpreters.main */ PyInterpreterState _main_interpreter; #if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE) From e66d0399cc2e78fcdb6a0113cd757d2ce567ca7c Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Thu, 1 Feb 2024 19:39:32 +0000 Subject: [PATCH 151/263] GH-114806. Don't specialize calls to classes with metaclasses. (GH-114870) --- Lib/test/test_class.py | 16 ++++++++++++++++ ...024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst | 3 +++ Python/specialize.c | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 1531aad4f1f779d..d59271435e9eb05 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -771,6 +771,22 @@ def add_one_level(): with self.assertRaises(RecursionError): add_one_level() + def testMetaclassCallOptimization(self): + calls = 0 + + class TypeMetaclass(type): + def __call__(cls, *args, **kwargs): + nonlocal calls + calls += 1 + return type.__call__(cls, *args, **kwargs) + + class Type(metaclass=TypeMetaclass): + def __init__(self, obj): + self._obj = obj + + for i in range(100): + Type(i) + self.assertEqual(calls, 100) if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst new file mode 100644 index 000000000000000..795f2529df82074 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-18-16-52.gh-issue-114806.wrH2J6.rst @@ -0,0 +1,3 @@ +No longer specialize calls to classes, if those classes have metaclasses. +Fixes bug where the ``__call__`` method of the metaclass was not being +called. diff --git a/Python/specialize.c b/Python/specialize.c index a9efbe0453b94e2..e38e3556a6d6425 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -540,6 +540,7 @@ _PyCode_Quicken(PyCodeObject *code) #define SPEC_FAIL_CALL_METHOD_WRAPPER 28 #define SPEC_FAIL_CALL_OPERATOR_WRAPPER 29 #define SPEC_FAIL_CALL_INIT_NOT_SIMPLE 30 +#define SPEC_FAIL_CALL_METACLASS 31 /* COMPARE_OP */ #define SPEC_FAIL_COMPARE_OP_DIFFERENT_TYPES 12 @@ -1757,6 +1758,10 @@ specialize_class_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs) SPEC_FAIL_CALL_STR : SPEC_FAIL_CALL_CLASS_NO_VECTORCALL); return -1; } + if (Py_TYPE(tp) != &PyType_Type) { + SPECIALIZATION_FAIL(CALL, SPEC_FAIL_CALL_METACLASS); + return -1; + } if (tp->tp_new == PyBaseObject_Type.tp_new) { PyFunctionObject *init = get_init_for_simple_managed_python_class(tp); if (type_get_version(tp, CALL) == 0) { From 500ede01178a8063bb2a3c664172dffa1b40d7c9 Mon Sep 17 00:00:00 2001 From: Oleg Iarygin <oleg@arhadthedev.net> Date: Thu, 1 Feb 2024 22:57:36 +0300 Subject: [PATCH 152/263] gh-89891: Refer SharedMemory implementation as POSIX (GH-104678) It only uses POSIX API. --- Doc/library/multiprocessing.shared_memory.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/multiprocessing.shared_memory.rst b/Doc/library/multiprocessing.shared_memory.rst index 10d7f061fb759b7..933fd07d62418a8 100644 --- a/Doc/library/multiprocessing.shared_memory.rst +++ b/Doc/library/multiprocessing.shared_memory.rst @@ -23,7 +23,7 @@ processes, a :class:`~multiprocessing.managers.BaseManager` subclass, :class:`~multiprocessing.managers.SharedMemoryManager`, is also provided in the :mod:`multiprocessing.managers` module. -In this module, shared memory refers to "System V style" shared memory blocks +In this module, shared memory refers to "POSIX style" shared memory blocks (though is not necessarily implemented explicitly as such) and does not refer to "distributed shared memory". This style of shared memory permits distinct processes to potentially read and write to a common (or shared) region of From 587d4802034749e2aace9c00b00bd73eccdae1e7 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Thu, 1 Feb 2024 15:29:19 -0500 Subject: [PATCH 153/263] gh-112529: Remove PyGC_Head from object pre-header in free-threaded build (#114564) * gh-112529: Remove PyGC_Head from object pre-header in free-threaded build This avoids allocating space for PyGC_Head in the free-threaded build. The GC implementation for free-threaded CPython does not use the PyGC_Head structure. * The trashcan mechanism uses the `ob_tid` field instead of `_gc_prev` in the free-threaded build. * The GDB libpython.py file now determines the offset of the managed dict field based on whether the running process is a free-threaded build. Those are identified by the `ob_ref_local` field in PyObject. * Fixes `_PySys_GetSizeOf()` which incorrectly incorrectly included the size of `PyGC_Head` in the size of static `PyTypeObject`. --- Include/internal/pycore_object.h | 27 ++++++++++++------- Include/object.h | 5 ++-- Lib/test/test_sys.py | 5 ++-- ...-01-25-18-50-49.gh-issue-112529.IbbApA.rst | 4 +++ Modules/_testinternalcapi.c | 12 ++++++++- Objects/object.c | 13 +++++++-- Python/gc_free_threading.c | 21 +++++++++++---- Python/sysmodule.c | 10 ++++++- Tools/gdb/libpython.py | 15 ++++++++--- 9 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index e32ea2f528940ae..34a83ea228e8b10 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -315,16 +315,15 @@ static inline void _PyObject_GC_TRACK( _PyObject_ASSERT_FROM(op, !_PyObject_GC_IS_TRACKED(op), "object already tracked by the garbage collector", filename, lineno, __func__); - +#ifdef Py_GIL_DISABLED + op->ob_gc_bits |= _PyGC_BITS_TRACKED; +#else PyGC_Head *gc = _Py_AS_GC(op); _PyObject_ASSERT_FROM(op, (gc->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0, "object is in generation which is garbage collected", filename, lineno, __func__); -#ifdef Py_GIL_DISABLED - op->ob_gc_bits |= _PyGC_BITS_TRACKED; -#else PyInterpreterState *interp = _PyInterpreterState_GET(); PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); @@ -594,8 +593,12 @@ _PyObject_IS_GC(PyObject *obj) static inline size_t _PyType_PreHeaderSize(PyTypeObject *tp) { - return _PyType_IS_GC(tp) * sizeof(PyGC_Head) + - _PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *); + return ( +#ifndef Py_GIL_DISABLED + _PyType_IS_GC(tp) * sizeof(PyGC_Head) + +#endif + _PyType_HasFeature(tp, Py_TPFLAGS_PREHEADER) * 2 * sizeof(PyObject *) + ); } void _PyObject_GC_Link(PyObject *op); @@ -625,6 +628,14 @@ extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values, PyObject *name); +#ifdef Py_GIL_DISABLED +# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1) +# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-2) +#else +# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-3) +# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4) +#endif + typedef union { PyObject *dict; /* Use a char* to generate a warning if directly assigning a PyDictValues */ @@ -635,7 +646,7 @@ static inline PyDictOrValues * _PyObject_DictOrValuesPointer(PyObject *obj) { assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); - return ((PyDictOrValues *)obj)-3; + return (PyDictOrValues *)((char *)obj + MANAGED_DICT_OFFSET); } static inline int @@ -664,8 +675,6 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values) ptr->values = ((char *)values) - 1; } -#define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4) - extern PyObject ** _PyObject_ComputedDictPointer(PyObject *); extern void _PyObject_FreeInstanceAttributes(PyObject *obj); extern int _PyObject_IsInstanceDictEmpty(PyObject *); diff --git a/Include/object.h b/Include/object.h index 568d315d7606c42..05187fe5dc4f20d 100644 --- a/Include/object.h +++ b/Include/object.h @@ -212,8 +212,9 @@ struct _object { struct _PyMutex { uint8_t v; }; struct _object { - // ob_tid stores the thread id (or zero). It is also used by the GC to - // store linked lists and the computed "gc_refs" refcount. + // ob_tid stores the thread id (or zero). It is also used by the GC and the + // trashcan mechanism as a linked list pointer and by the GC to store the + // computed "gc_refs" refcount. uintptr_t ob_tid; uint16_t _padding; struct _PyMutex ob_mutex; // per-object lock diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6c87dfabad9f0f7..71671a5a984256d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1392,6 +1392,7 @@ def setUp(self): self.longdigit = sys.int_info.sizeof_digit import _testinternalcapi self.gc_headsize = _testinternalcapi.SIZEOF_PYGC_HEAD + self.managed_pre_header_size = _testinternalcapi.SIZEOF_MANAGED_PRE_HEADER check_sizeof = test.support.check_sizeof @@ -1427,7 +1428,7 @@ class OverflowSizeof(int): def __sizeof__(self): return int(self) self.assertEqual(sys.getsizeof(OverflowSizeof(sys.maxsize)), - sys.maxsize + self.gc_headsize*2) + sys.maxsize + self.gc_headsize + self.managed_pre_header_size) with self.assertRaises(OverflowError): sys.getsizeof(OverflowSizeof(sys.maxsize + 1)) with self.assertRaises(ValueError): @@ -1650,7 +1651,7 @@ def delx(self): del self.__x # type # static type: PyTypeObject fmt = 'P2nPI13Pl4Pn9Pn12PIPc' - s = vsize('2P' + fmt) + s = vsize(fmt) check(int, s) # class s = vsize(fmt + # PyTypeObject diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst new file mode 100644 index 000000000000000..2a6d74fb2227023 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-25-18-50-49.gh-issue-112529.IbbApA.rst @@ -0,0 +1,4 @@ +The free-threaded build no longer allocates space for the ``PyGC_Head`` +structure in objects that support cyclic garbage collection. A number of +other fields and data structures are used as replacements, including +``ob_gc_bits``, ``ob_tid``, and mimalloc internal data structures. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index c4a648a1816392f..0bb739b5398b113 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1752,8 +1752,18 @@ module_exec(PyObject *module) return 1; } + Py_ssize_t sizeof_gc_head = 0; +#ifndef Py_GIL_DISABLED + sizeof_gc_head = sizeof(PyGC_Head); +#endif + if (PyModule_Add(module, "SIZEOF_PYGC_HEAD", - PyLong_FromSsize_t(sizeof(PyGC_Head))) < 0) { + PyLong_FromSsize_t(sizeof_gc_head)) < 0) { + return 1; + } + + if (PyModule_Add(module, "SIZEOF_MANAGED_PRE_HEADER", + PyLong_FromSsize_t(2 * sizeof(PyObject*))) < 0) { return 1; } diff --git a/Objects/object.c b/Objects/object.c index 587c5528c01345b..bbf7f98ae3daf92 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2671,7 +2671,12 @@ _PyTrash_thread_deposit_object(struct _py_trashcan *trash, PyObject *op) _PyObject_ASSERT(op, _PyObject_IS_GC(op)); _PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op)); _PyObject_ASSERT(op, Py_REFCNT(op) == 0); +#ifdef Py_GIL_DISABLED + _PyObject_ASSERT(op, op->ob_tid == 0); + op->ob_tid = (uintptr_t)trash->delete_later; +#else _PyGCHead_SET_PREV(_Py_AS_GC(op), (PyGC_Head*)trash->delete_later); +#endif trash->delete_later = op; } @@ -2697,8 +2702,12 @@ _PyTrash_thread_destroy_chain(struct _py_trashcan *trash) PyObject *op = trash->delete_later; destructor dealloc = Py_TYPE(op)->tp_dealloc; - trash->delete_later = - (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op)); +#ifdef Py_GIL_DISABLED + trash->delete_later = (PyObject*) op->ob_tid; + op->ob_tid = 0; +#else + trash->delete_later = (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op)); +#endif /* Call the deallocator directly. This used to try to * fool Py_DECREF into calling it indirectly, but diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index a6513a2c4aba2a6..53f927bfa653104 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -25,7 +25,10 @@ typedef struct _gc_runtime_state GCState; // Automatically choose the generation that needs collecting. #define GENERATION_AUTO (-1) -// A linked-list of objects using the `ob_tid` field as the next pointer. +// A linked list of objects using the `ob_tid` field as the next pointer. +// The linked list pointers are distinct from any real thread ids, because the +// thread ids returned by _Py_ThreadId() are also pointers to distinct objects. +// No thread will confuse its own id with a linked list pointer. struct worklist { uintptr_t head; }; @@ -221,7 +224,7 @@ gc_visit_heaps_lock_held(PyInterpreterState *interp, mi_block_visit_fun *visitor struct visitor_args *arg) { // Offset of PyObject header from start of memory block. - Py_ssize_t offset_base = sizeof(PyGC_Head); + Py_ssize_t offset_base = 0; if (_PyMem_DebugEnabled()) { // The debug allocator adds two words at the beginning of each block. offset_base += 2 * sizeof(size_t); @@ -331,8 +334,14 @@ update_refs(const mi_heap_t *heap, const mi_heap_area_t *area, Py_ssize_t refcount = Py_REFCNT(op); _PyObject_ASSERT(op, refcount >= 0); - // Add the actual refcount to ob_tid. + // We repurpose ob_tid to compute "gc_refs", the number of external + // references to the object (i.e., from outside the GC heaps). This means + // that ob_tid is no longer a valid thread id until it is restored by + // scan_heap_visitor(). Until then, we cannot use the standard reference + // counting functions or allow other threads to run Python code. gc_maybe_init_refs(op); + + // Add the actual refcount to ob_tid. gc_add_refs(op, refcount); // Subtract internal references from ob_tid. Objects with ob_tid > 0 @@ -1508,8 +1517,10 @@ gc_alloc(PyTypeObject *tp, size_t basicsize, size_t presize) if (mem == NULL) { return _PyErr_NoMemory(tstate); } - ((PyObject **)mem)[0] = NULL; - ((PyObject **)mem)[1] = NULL; + if (presize) { + ((PyObject **)mem)[0] = NULL; + ((PyObject **)mem)[1] = NULL; + } PyObject *op = (PyObject *)(mem + presize); _PyObject_GC_Link(op); return op; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f558a00a6916ebc..437d7f8dfc49580 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1878,7 +1878,15 @@ _PySys_GetSizeOf(PyObject *o) return (size_t)-1; } - return (size_t)size + _PyType_PreHeaderSize(Py_TYPE(o)); + size_t presize = 0; + if (!Py_IS_TYPE(o, &PyType_Type) || + PyType_HasFeature((PyTypeObject *)o, Py_TPFLAGS_HEAPTYPE)) + { + /* Add the size of the pre-header if "o" is not a static type */ + presize = _PyType_PreHeaderSize(Py_TYPE(o)); + } + + return (size_t)size + presize; } static PyObject * diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 5ef55524c11be20..483f28b46dfec74 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -70,6 +70,14 @@ def _type_unsigned_int_ptr(): def _sizeof_void_p(): return gdb.lookup_type('void').pointer().sizeof +def _managed_dict_offset(): + # See pycore_object.h + pyobj = gdb.lookup_type("PyObject") + if any(field.name == "ob_ref_local" for field in pyobj.fields()): + return -1 * _sizeof_void_p() + else: + return -3 * _sizeof_void_p() + Py_TPFLAGS_MANAGED_DICT = (1 << 4) Py_TPFLAGS_HEAPTYPE = (1 << 9) @@ -457,7 +465,7 @@ def get_attr_dict(self): if dictoffset < 0: if int_from_int(typeobj.field('tp_flags')) & Py_TPFLAGS_MANAGED_DICT: assert dictoffset == -1 - dictoffset = -3 * _sizeof_void_p() + dictoffset = _managed_dict_offset() else: type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer() tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size']) @@ -485,9 +493,8 @@ def get_keys_values(self): has_values = int_from_int(typeobj.field('tp_flags')) & Py_TPFLAGS_MANAGED_DICT if not has_values: return None - charptrptr_t = _type_char_ptr().pointer() - ptr = self._gdbval.cast(charptrptr_t) - 3 - char_ptr = ptr.dereference() + ptr = self._gdbval.cast(_type_char_ptr()) + _managed_dict_offset() + char_ptr = ptr.cast(_type_char_ptr().pointer()).dereference() if (int(char_ptr) & 1) == 0: return None char_ptr += 1 From 13907968d73b3b602c81e240fb7892a2627974d6 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Fri, 2 Feb 2024 05:53:53 +0900 Subject: [PATCH 154/263] gh-111968: Use per-thread freelists for dict in free-threading (gh-114323) --- Include/internal/pycore_dict.h | 3 +- Include/internal/pycore_dict_state.h | 19 ------ Include/internal/pycore_freelist.h | 13 ++++ Include/internal/pycore_gc.h | 2 +- Include/internal/pycore_interp.h | 2 +- Objects/dictobject.c | 88 ++++++++++++---------------- Objects/floatobject.c | 4 ++ Objects/genobject.c | 4 ++ Objects/listobject.c | 4 ++ Python/context.c | 4 ++ Python/gc_free_threading.c | 2 - Python/gc_gil.c | 2 - Python/pystate.c | 3 + 13 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index b4e1f8cf1e320b3..60acd89cf6c34a6 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -9,6 +9,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_freelist.h" // _PyFreeListState #include "pycore_identifier.h" // _Py_Identifier #include "pycore_object.h" // PyDictOrValues @@ -69,7 +70,7 @@ extern PyObject* _PyDictView_Intersect(PyObject* self, PyObject *other); /* runtime lifecycle */ -extern void _PyDict_Fini(PyInterpreterState *interp); +extern void _PyDict_Fini(PyInterpreterState *state); /* other API */ diff --git a/Include/internal/pycore_dict_state.h b/Include/internal/pycore_dict_state.h index ece0f10ca251707..a6dd63d36e040e6 100644 --- a/Include/internal/pycore_dict_state.h +++ b/Include/internal/pycore_dict_state.h @@ -8,16 +8,6 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif - -#ifndef WITH_FREELISTS -// without freelists -# define PyDict_MAXFREELIST 0 -#endif - -#ifndef PyDict_MAXFREELIST -# define PyDict_MAXFREELIST 80 -#endif - #define DICT_MAX_WATCHERS 8 struct _Py_dict_state { @@ -26,15 +16,6 @@ struct _Py_dict_state { * time that a dictionary is modified. */ uint64_t global_version; uint32_t next_keys_version; - -#if PyDict_MAXFREELIST > 0 - /* Dictionary reuse scheme to save calls to malloc and free */ - PyDictObject *free_list[PyDict_MAXFREELIST]; - PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; - int numfree; - int keys_numfree; -#endif - PyDict_WatchCallback watchers[DICT_MAX_WATCHERS]; }; diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index b91d2bc066b783b..82a42300991eccd 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -17,6 +17,7 @@ extern "C" { # define PyTuple_NFREELISTS PyTuple_MAXSAVESIZE # define PyTuple_MAXFREELIST 2000 # define PyList_MAXFREELIST 80 +# define PyDict_MAXFREELIST 80 # define PyFloat_MAXFREELIST 100 # define PyContext_MAXFREELIST 255 # define _PyAsyncGen_MAXFREELIST 80 @@ -25,6 +26,7 @@ extern "C" { # define PyTuple_NFREELISTS 0 # define PyTuple_MAXFREELIST 0 # define PyList_MAXFREELIST 0 +# define PyDict_MAXFREELIST 0 # define PyFloat_MAXFREELIST 0 # define PyContext_MAXFREELIST 0 # define _PyAsyncGen_MAXFREELIST 0 @@ -65,6 +67,16 @@ struct _Py_float_state { #endif }; +struct _Py_dict_freelist { +#ifdef WITH_FREELISTS + /* Dictionary reuse scheme to save calls to malloc and free */ + PyDictObject *free_list[PyDict_MAXFREELIST]; + PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST]; + int numfree; + int keys_numfree; +#endif +}; + struct _Py_slice_state { #ifdef WITH_FREELISTS /* Using a cache is very effective since typically only a single slice is @@ -106,6 +118,7 @@ typedef struct _Py_freelist_state { struct _Py_float_state floats; struct _Py_tuple_state tuples; struct _Py_list_state lists; + struct _Py_dict_freelist dicts; struct _Py_slice_state slices; struct _Py_context_state contexts; struct _Py_async_gen_state async_gens; diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index b362a294a59042a..ca1d9fdf5253b8d 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -267,7 +267,7 @@ extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization) extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PySlice_ClearCache(_PyFreeListState *state); -extern void _PyDict_ClearFreeList(PyInterpreterState *interp); +extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _Py_ScheduleGC(PyInterpreterState *interp); diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 04e75940dcb5734..c4732b1534199b4 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -20,6 +20,7 @@ extern "C" { #include "pycore_dtoa.h" // struct _dtoa_state #include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_floatobject.h" // struct _Py_float_state +#include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_function.h" // FUNC_MAX_WATCHERS #include "pycore_gc.h" // struct _gc_runtime_state #include "pycore_genobject.h" // struct _Py_async_gen_state @@ -230,7 +231,6 @@ struct _is { struct _dtoa_state dtoa; struct _py_func_state func_state; - struct _Py_tuple_state tuple; struct _Py_dict_state dict_state; struct _Py_exc_state exc_state; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 23d7e9b5e38a356..e24887b7d781bb5 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -118,6 +118,7 @@ As a consequence of this, split keys have a maximum size of 16. #include "pycore_ceval.h" // _PyEval_GetBuiltin() #include "pycore_code.h" // stats #include "pycore_dict.h" // export _PyDict_SizeOf() +#include "pycore_freelist.h" // _PyFreeListState_GET() #include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() #include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() @@ -242,40 +243,44 @@ static PyObject* dict_iter(PyObject *dict); #include "clinic/dictobject.c.h" -#if PyDict_MAXFREELIST > 0 -static struct _Py_dict_state * -get_dict_state(PyInterpreterState *interp) +#ifdef WITH_FREELISTS +static struct _Py_dict_freelist * +get_dict_state(void) { - return &interp->dict_state; + _PyFreeListState *state = _PyFreeListState_GET(); + return &state->dicts; } #endif void -_PyDict_ClearFreeList(PyInterpreterState *interp) +_PyDict_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) { -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = &interp->dict_state; - while (state->numfree) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = &freelist_state->dicts; + while (state->numfree > 0) { PyDictObject *op = state->free_list[--state->numfree]; assert(PyDict_CheckExact(op)); PyObject_GC_Del(op); } - while (state->keys_numfree) { + while (state->keys_numfree > 0) { PyMem_Free(state->keys_free_list[--state->keys_numfree]); } + if (is_finalization) { + state->numfree = -1; + state->keys_numfree = -1; + } #endif } - void -_PyDict_Fini(PyInterpreterState *interp) +_PyDict_Fini(PyInterpreterState *Py_UNUSED(interp)) { - _PyDict_ClearFreeList(interp); -#if defined(Py_DEBUG) && PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = &interp->dict_state; - state->numfree = -1; - state->keys_numfree = -1; + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED + _PyFreeListState *state = _PyFreeListState_GET(); + _PyDict_ClearFreeList(state, 1); #endif } @@ -290,9 +295,8 @@ unicode_get_hash(PyObject *o) void _PyDict_DebugMallocStats(FILE *out) { -#if PyDict_MAXFREELIST > 0 - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _Py_dict_state *state = get_dict_state(interp); +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); _PyDebugAllocatorStats(out, "free PyDictObject", state->numfree, sizeof(PyDictObject)); #endif @@ -300,7 +304,7 @@ _PyDict_DebugMallocStats(FILE *out) #define DK_MASK(dk) (DK_SIZE(dk)-1) -static void free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys); +static void free_keys_object(PyDictKeysObject *keys); /* PyDictKeysObject has refcounts like PyObject does, so we have the following two functions to mirror what Py_INCREF() and Py_DECREF() do. @@ -348,7 +352,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) Py_XDECREF(entries[i].me_value); } } - free_keys_object(interp, dk); + free_keys_object(dk); } } @@ -643,12 +647,8 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) log2_bytes = log2_size + 2; } -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_keys_object() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); if (log2_size == PyDict_LOG_MINSIZE && unicode && state->keys_numfree > 0) { dk = state->keys_free_list[--state->keys_numfree]; OBJECT_STAT_INC(from_freelist); @@ -680,16 +680,13 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } static void -free_keys_object(PyInterpreterState *interp, PyDictKeysObject *keys) +free_keys_object(PyDictKeysObject *keys) { -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // free_keys_object() must not be called after _PyDict_Fini() - assert(state->keys_numfree != -1); -#endif +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); if (DK_LOG_SIZE(keys) == PyDict_LOG_MINSIZE && state->keys_numfree < PyDict_MAXFREELIST + && state->keys_numfree >= 0 && DK_IS_UNICODE(keys)) { state->keys_free_list[state->keys_numfree++] = keys; OBJECT_STAT_INC(to_freelist); @@ -730,13 +727,9 @@ new_dict(PyInterpreterState *interp, { PyDictObject *mp; assert(keys != NULL); -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_dict() must not be called after _PyDict_Fini() - assert(state->numfree != -1); -#endif - if (state->numfree) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); + if (state->numfree > 0) { mp = state->free_list[--state->numfree]; assert (mp != NULL); assert (Py_IS_TYPE(mp, &PyDict_Type)); @@ -1547,7 +1540,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, #endif assert(oldkeys->dk_kind != DICT_KEYS_SPLIT); assert(oldkeys->dk_refcnt == 1); - free_keys_object(interp, oldkeys); + free_keys_object(oldkeys); } } @@ -2458,13 +2451,10 @@ dict_dealloc(PyObject *self) assert(keys->dk_refcnt == 1 || keys == Py_EMPTY_KEYS); dictkeys_decref(interp, keys); } -#if PyDict_MAXFREELIST > 0 - struct _Py_dict_state *state = get_dict_state(interp); -#ifdef Py_DEBUG - // new_dict() must not be called after _PyDict_Fini() - assert(state->numfree != -1); -#endif - if (state->numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) { +#ifdef WITH_FREELISTS + struct _Py_dict_freelist *state = get_dict_state(); + if (state->numfree < PyDict_MAXFREELIST && state->numfree >=0 && + Py_IS_TYPE(mp, &PyDict_Type)) { state->free_list[state->numfree++] = mp; OBJECT_STAT_INC(to_freelist); } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index b7611d5f96ac3be..c440e0dab0e79fa 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2013,7 +2013,11 @@ _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyFloat_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyFloat_ClearFreeList(state, 1); +#endif } void diff --git a/Objects/genobject.c b/Objects/genobject.c index f47197330fdd801..ab523e46cceaa31 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1685,7 +1685,11 @@ _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization void _PyAsyncGen_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyAsyncGen_ClearFreeLists(state, 1); +#endif } diff --git a/Objects/listobject.c b/Objects/listobject.c index 80a1f1da55b8bc8..da2b9cc32697dda 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -138,7 +138,11 @@ _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyList_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyList_ClearFreeList(state, 1); +#endif } /* Print summary info about the state of the optimized allocator */ diff --git a/Python/context.c b/Python/context.c index 294485e5b407dfe..793dfa2b72c7e3f 100644 --- a/Python/context.c +++ b/Python/context.c @@ -1287,7 +1287,11 @@ _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) void _PyContext_Fini(_PyFreeListState *state) { + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. +#ifndef Py_GIL_DISABLED _PyContext_ClearFreeList(state, 1); +#endif } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 53f927bfa653104..8fbcdb15109b76c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1676,8 +1676,6 @@ PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _PyDict_ClearFreeList(interp); - HEAD_LOCK(&_PyRuntime); _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head; while (tstate != NULL) { diff --git a/Python/gc_gil.c b/Python/gc_gil.c index 04c1c184250c609..4e2aa8f7af746c2 100644 --- a/Python/gc_gil.c +++ b/Python/gc_gil.c @@ -11,8 +11,6 @@ void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _PyDict_ClearFreeList(interp); - _Py_ClearFreeLists(&interp->freelist_state, 0); } diff --git a/Python/pystate.c b/Python/pystate.c index 430121a6a35d7f9..27b6d0573ade3b3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1461,9 +1461,12 @@ clear_datastack(PyThreadState *tstate) void _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization) { + // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() + // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() _PyFloat_ClearFreeList(state, is_finalization); _PyTuple_ClearFreeList(state, is_finalization); _PyList_ClearFreeList(state, is_finalization); + _PyDict_ClearFreeList(state, is_finalization); _PyContext_ClearFreeList(state, is_finalization); _PyAsyncGen_ClearFreeLists(state, is_finalization); _PyObjectStackChunk_ClearFreeList(state, is_finalization); From 618d7256e78da8200f6e2c6235094a1ef885dca4 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Thu, 1 Feb 2024 17:54:02 -0600 Subject: [PATCH 155/263] gh-111239: Update Windows build to use zlib 1.3.1 (GH-114877) --- .../next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst | 1 + PCbuild/get_externals.bat | 2 +- PCbuild/python.props | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst b/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst new file mode 100644 index 000000000000000..ea82c3b941f8026 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-01-14-35-05.gh-issue-111239.SO7SUF.rst @@ -0,0 +1 @@ +Update Windows builds to use zlib v1.3.1. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 3919c0592ec00d0..de73d923d8f4df1 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -58,7 +58,7 @@ set libraries=%libraries% sqlite-3.44.2.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 set libraries=%libraries% xz-5.2.5 -set libraries=%libraries% zlib-1.2.13 +set libraries=%libraries% zlib-1.3.1 for %%e in (%libraries%) do ( if exist "%EXTERNALS_DIR%\%%e" ( diff --git a/PCbuild/python.props b/PCbuild/python.props index e8796081c4eaf37..2cb16693e546b1b 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -78,7 +78,7 @@ <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.11\$(ArchName)\</opensslOutDir> <opensslIncludeDir Condition="$(opensslIncludeDir) == ''">$(opensslOutDir)include</opensslIncludeDir> <nasmDir Condition="$(nasmDir) == ''">$(ExternalsDir)\nasm-2.11.06\</nasmDir> - <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.2.13\</zlibDir> + <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.3.1\</zlibDir> </PropertyGroup> <PropertyGroup> From 1aec0644447e69e981d582449849761b23702ec8 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 2 Feb 2024 04:44:01 +0300 Subject: [PATCH 156/263] GH-114849: Set a 60-minute timeout for JIT CI jobs (GH-114850) --- .github/workflows/jit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 22e0bdba53ffd6f..69648d87947ad66 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -18,6 +18,7 @@ jobs: jit: name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }}) runs-on: ${{ matrix.runner }} + timeout-minutes: 60 strategy: fail-fast: false matrix: From 53339a0ef72fcfc15221792b117c4670b07a0b20 Mon Sep 17 00:00:00 2001 From: Michal Kaptur <kaptur.michal@gmail.com> Date: Fri, 2 Feb 2024 11:00:18 +0100 Subject: [PATCH 157/263] Move "format" param doc of shutil.make_archive() on its own paragraph (GH-103829) --- Doc/library/shutil.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index d9ec2cbc47e6114..7a7dd23177e6721 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -586,7 +586,9 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules. Create an archive file (such as zip or tar) and return its name. *base_name* is the name of the file to create, including the path, minus - any format-specific extension. *format* is the archive format: one of + any format-specific extension. + + *format* is the archive format: one of "zip" (if the :mod:`zlib` module is available), "tar", "gztar" (if the :mod:`zlib` module is available), "bztar" (if the :mod:`bz2` module is available), or "xztar" (if the :mod:`lzma` module is available). From d25d4ee60cc789a8b9c222859bb720ade1ab2e30 Mon Sep 17 00:00:00 2001 From: Christopher Chavez <chrischavez@gmx.us> Date: Fri, 2 Feb 2024 04:38:43 -0600 Subject: [PATCH 158/263] gh-103820: IDLE: Do not interpret buttons 4/5 as scrolling on non-X11 (GH-103821) Also fix test_mousewheel: do not skip a check which was broken due to incorrect delta on Aqua and XQuartz, and probably not because of `.update_idletasks()`. --- Lib/idlelib/editor.py | 5 +++-- Lib/idlelib/idle_test/test_sidebar.py | 22 ++++++++++++------- Lib/idlelib/tree.py | 10 +++++---- ...-04-25-03-01-23.gh-issue-103820.LCSpza.rst | 2 ++ 4 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 6ad383f460c7ee2..8ee8eba64367a50 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -166,8 +166,9 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text.bind("<3>",self.right_menu_event) text.bind('<MouseWheel>', wheel_event) - text.bind('<Button-4>', wheel_event) - text.bind('<Button-5>', wheel_event) + if text._windowingsystem == 'x11': + text.bind('<Button-4>', wheel_event) + text.bind('<Button-5>', wheel_event) text.bind('<Configure>', self.handle_winconfig) text.bind("<<cut>>", self.cut) text.bind("<<copy>>", self.copy) diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py index fb52b3a0179553d..605e7a892570d7d 100644 --- a/Lib/idlelib/idle_test/test_sidebar.py +++ b/Lib/idlelib/idle_test/test_sidebar.py @@ -690,16 +690,22 @@ def test_mousewheel(self): last_lineno = get_end_linenumber(text) self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Scroll up using the <MouseWheel> event. - # The meaning of delta is platform-dependent. - delta = -1 if sys.platform == 'darwin' else 120 - sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta) + # Delta for <MouseWheel>, whose meaning is platform-dependent. + delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120 + + # Scroll up. + if sidebar.canvas._windowingsystem == 'x11': + sidebar.canvas.event_generate('<Button-4>', x=0, y=0) + else: + sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta) yield - if sys.platform != 'darwin': # .update_idletasks() does not work. - self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) + self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) - # Scroll back down using the <Button-5> event. - sidebar.canvas.event_generate('<Button-5>', x=0, y=0) + # Scroll back down. + if sidebar.canvas._windowingsystem == 'x11': + sidebar.canvas.event_generate('<Button-5>', x=0, y=0) + else: + sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=-delta) yield self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0'))) diff --git a/Lib/idlelib/tree.py b/Lib/idlelib/tree.py index 9c2eb47b24aec94..0726d7e23660f64 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -285,8 +285,9 @@ def drawtext(self): self.label.bind("<1>", self.select_or_edit) self.label.bind("<Double-1>", self.flip) self.label.bind("<MouseWheel>", lambda e: wheel_event(e, self.canvas)) - self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas)) - self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas)) + if self.label._windowingsystem == 'x11': + self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas)) + self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas)) self.text_id = id def select_or_edit(self, event=None): @@ -460,8 +461,9 @@ def __init__(self, master, **opts): self.canvas.bind("<Key-Up>", self.unit_up) self.canvas.bind("<Key-Down>", self.unit_down) self.canvas.bind("<MouseWheel>", wheel_event) - self.canvas.bind("<Button-4>", wheel_event) - self.canvas.bind("<Button-5>", wheel_event) + if self.canvas._windowingsystem == 'x11': + self.canvas.bind("<Button-4>", wheel_event) + self.canvas.bind("<Button-5>", wheel_event) #if isinstance(master, Toplevel) or isinstance(master, Tk): self.canvas.bind("<Alt-Key-2>", self.zoom_height) self.canvas.focus_set() diff --git a/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst b/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst new file mode 100644 index 000000000000000..b9d7faf047b28e7 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2023-04-25-03-01-23.gh-issue-103820.LCSpza.rst @@ -0,0 +1,2 @@ +Revise IDLE bindings so that events from mouse button 4/5 on non-X11 +windowing systems (i.e. Win32 and Aqua) are not mistaken for scrolling. From 41fde89e471003b3e70fdd76d6726fba9982a1eb Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Fri, 2 Feb 2024 10:41:28 +0000 Subject: [PATCH 159/263] GH-113655 Lower C recursion limit from 4000 to 3000 on Windows. (GH-114896) --- Include/cpython/pystate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 1dbf97660f382fd..9bc8758e72bd8f2 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -229,7 +229,7 @@ struct _ts { #elif defined(__s390x__) # define Py_C_RECURSION_LIMIT 800 #elif defined(_WIN32) -# define Py_C_RECURSION_LIMIT 4000 +# define Py_C_RECURSION_LIMIT 3000 #elif defined(_Py_ADDRESS_SANITIZER) # define Py_C_RECURSION_LIMIT 4000 #else From 2091fb2a85c1aa2d9b22c02736b07831bd875c2a Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:26:31 +0000 Subject: [PATCH 160/263] gh-107901: make compiler inline basic blocks with no line number and no fallthrough (#114750) --- Lib/test/test_compile.py | 62 ++++++++++++++++++++++++------ Lib/test/test_monitoring.py | 10 ++--- Python/flowgraph.c | 75 ++++++++++++++++++++++++++----------- 3 files changed, 108 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 3b1ceceaa6305f7..ebb479f2de7c633 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1104,6 +1104,17 @@ async def test(aseq): code_lines = self.get_code_lines(test.__code__) self.assertEqual(expected_lines, code_lines) + def check_line_numbers(self, code, opnames=None): + # Check that all instructions whose op matches opnames + # have a line number. opnames can be a single name, or + # a sequence of names. If it is None, match all ops. + + if isinstance(opnames, str): + opnames = (opnames, ) + for inst in dis.Bytecode(code): + if opnames and inst.opname in opnames: + self.assertIsNotNone(inst.positions.lineno) + def test_line_number_synthetic_jump_multiple_predecessors(self): def f(): for x in it: @@ -1113,25 +1124,52 @@ def f(): except OSError: pass - # Ensure that all JUMP_BACKWARDs have line number - code = f.__code__ - for inst in dis.Bytecode(code): - if inst.opname == 'JUMP_BACKWARD': - self.assertIsNotNone(inst.positions.lineno) + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') - def test_lineno_of_backward_jump(self): + def test_line_number_synthetic_jump_multiple_predecessors_nested(self): + def f(): + for x in it: + try: + X = 3 + except OSError: + try: + if C3: + X = 4 + except OSError: + pass + return 42 + + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') + + def test_line_number_synthetic_jump_multiple_predecessors_more_nested(self): + def f(): + for x in it: + try: + X = 3 + except OSError: + try: + if C3: + if C4: + X = 4 + except OSError: + try: + if C3: + if C4: + X = 5 + except OSError: + pass + return 42 + + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') + + def test_lineno_of_backward_jump_conditional_in_loop(self): # Issue gh-107901 def f(): for i in x: if y: pass - linenos = list(inst.positions.lineno - for inst in dis.get_instructions(f.__code__) - if inst.opname == 'JUMP_BACKWARD') - - self.assertTrue(len(linenos) > 0) - self.assertTrue(all(l is not None for l in linenos)) + self.check_line_numbers(f.__code__, 'JUMP_BACKWARD') def test_big_dict_literal(self): # The compiler has a flushing point in "compiler_dict" that calls compiles diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index a64d1ed79decd83..60b6326bfbad5e9 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1466,9 +1466,8 @@ def func(): ('branch', 'func', 4, 4), ('line', 'func', 5), ('line', 'meth', 1), - ('jump', 'func', 5, 5), - ('jump', 'func', 5, '[offset=114]'), - ('branch', 'func', '[offset=120]', '[offset=124]'), + ('jump', 'func', 5, '[offset=118]'), + ('branch', 'func', '[offset=122]', '[offset=126]'), ('line', 'get_events', 11)]) self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [ @@ -1482,9 +1481,8 @@ def func(): ('line', 'func', 5), ('line', 'meth', 1), ('return', 'meth', None), - ('jump', 'func', 5, 5), - ('jump', 'func', 5, '[offset=114]'), - ('branch', 'func', '[offset=120]', '[offset=124]'), + ('jump', 'func', 5, '[offset=118]'), + ('branch', 'func', '[offset=122]', '[offset=126]'), ('return', 'func', None), ('line', 'get_events', 11)]) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index bfc23a298ff492d..1a648edf0880c02 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -212,14 +212,14 @@ basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc) } static inline int -basicblock_append_instructions(basicblock *target, basicblock *source) +basicblock_append_instructions(basicblock *to, basicblock *from) { - for (int i = 0; i < source->b_iused; i++) { - int n = basicblock_next_instr(target); + for (int i = 0; i < from->b_iused; i++) { + int n = basicblock_next_instr(to); if (n < 0) { return ERROR; } - target->b_instr[n] = source->b_instr[i]; + to->b_instr[n] = from->b_instr[i]; } return SUCCESS; } @@ -292,9 +292,9 @@ static void dump_basicblock(const basicblock *b) { const char *b_return = basicblock_returns(b) ? "return " : ""; - fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, %s\n", + fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, preds: %d %s\n", b->b_label.id, b->b_except_handler, b->b_cold, b->b_warm, BB_NO_FALLTHROUGH(b), b, b->b_iused, - b->b_startdepth, b_return); + b->b_startdepth, b->b_predecessors, b_return); if (b->b_instr) { int i; for (i = 0; i < b->b_iused; i++) { @@ -1165,15 +1165,26 @@ remove_redundant_jumps(cfg_builder *g) { return changes; } +static inline bool +basicblock_has_no_lineno(basicblock *b) { + for (int i = 0; i < b->b_iused; i++) { + if (b->b_instr[i].i_loc.lineno >= 0) { + return false; + } + } + return true; +} + /* Maximum size of basic block that should be copied in optimizer */ #define MAX_COPY_SIZE 4 -/* If this block ends with an unconditional jump to a small exit block, then +/* If this block ends with an unconditional jump to a small exit block or + * a block that has no line numbers (and no fallthrough), then * remove the jump and extend this block with the target. * Returns 1 if extended, 0 if no change, and -1 on error. */ static int -inline_small_exit_blocks(basicblock *bb) { +basicblock_inline_small_or_no_lineno_blocks(basicblock *bb) { cfg_instr *last = basicblock_last_instr(bb); if (last == NULL) { return 0; @@ -1182,14 +1193,46 @@ inline_small_exit_blocks(basicblock *bb) { return 0; } basicblock *target = last->i_target; - if (basicblock_exits_scope(target) && target->b_iused <= MAX_COPY_SIZE) { + bool small_exit_block = (basicblock_exits_scope(target) && + target->b_iused <= MAX_COPY_SIZE); + bool no_lineno_no_fallthrough = (basicblock_has_no_lineno(target) && + !BB_HAS_FALLTHROUGH(target)); + if (small_exit_block || no_lineno_no_fallthrough) { + assert(is_jump(last)); + int removed_jump_opcode = last->i_opcode; INSTR_SET_OP0(last, NOP); RETURN_IF_ERROR(basicblock_append_instructions(bb, target)); + if (no_lineno_no_fallthrough) { + last = basicblock_last_instr(bb); + if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode) && + removed_jump_opcode == JUMP) + { + /* Make sure we don't lose eval breaker checks */ + last->i_opcode = JUMP; + } + } + target->b_predecessors--; return 1; } return 0; } +static int +inline_small_or_no_lineno_blocks(basicblock *entryblock) { + bool changes; + do { + changes = false; + for (basicblock *b = entryblock; b != NULL; b = b->b_next) { + int res = basicblock_inline_small_or_no_lineno_blocks(b); + RETURN_IF_ERROR(res); + if (res) { + changes = true; + } + } + } while(changes); /* every change removes a jump, ensuring convergence */ + return changes; +} + // Attempt to eliminate jumps to jumps by updating inst to jump to // target->i_target using the provided opcode. Return whether or not the // optimization was successful. @@ -1804,9 +1847,7 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstl { assert(PyDict_CheckExact(const_cache)); RETURN_IF_ERROR(check_cfg(g)); - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(inline_small_exit_blocks(b)); - } + RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock)); RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts)); @@ -1814,9 +1855,6 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstl RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts)); } RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock)); - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(inline_small_exit_blocks(b)); - } RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); int removed_nops, removed_jumps; @@ -2333,12 +2371,7 @@ convert_pseudo_ops(cfg_builder *g) static inline bool is_exit_or_eval_check_without_lineno(basicblock *b) { if (basicblock_exits_scope(b) || basicblock_has_eval_break(b)) { - for (int i = 0; i < b->b_iused; i++) { - if (b->b_instr[i].i_loc.lineno >= 0) { - return false; - } - } - return true; + return basicblock_has_no_lineno(b); } else { return false; From 0e71a295e9530c939a5efcb45db23cf31e0303b4 Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Fri, 2 Feb 2024 12:14:34 +0000 Subject: [PATCH 161/263] GH-113710: Add a "globals to constants" pass (GH-114592) Converts specializations of `LOAD_GLOBAL` into constants during tier 2 optimization. --- Include/cpython/dictobject.h | 3 + Include/cpython/optimizer.h | 8 +- Include/internal/pycore_dict.h | 6 +- Include/internal/pycore_dict_state.h | 1 + Include/internal/pycore_interp.h | 2 +- Include/internal/pycore_optimizer.h | 5 +- Include/internal/pycore_uop_ids.h | 8 +- Include/internal/pycore_uop_metadata.h | 8 + Lib/test/test_capi/test_watchers.py | 12 +- Modules/_testcapi/watchers.c | 4 +- Objects/dictobject.c | 3 +- Python/bytecodes.c | 24 +++ Python/executor_cases.c.h | 42 +++++ Python/optimizer.c | 52 +++--- Python/optimizer_analysis.c | 230 ++++++++++++++++++++++++- Python/pylifecycle.c | 22 ++- 16 files changed, 375 insertions(+), 55 deletions(-) diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h index 944965fb9e5351a..1720fe6f01ea37d 100644 --- a/Include/cpython/dictobject.h +++ b/Include/cpython/dictobject.h @@ -17,6 +17,9 @@ typedef struct { /* Dictionary version: globally unique, value change each time the dictionary is modified */ #ifdef Py_BUILD_CORE + /* Bits 0-7 are for dict watchers. + * Bits 8-11 are for the watched mutation counter (used by tier2 optimization) + * The remaining bits (12-63) are the actual version tag. */ uint64_t ma_version_tag; #else Py_DEPRECATED(3.12) uint64_t ma_version_tag; diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index ecf3cae4cbc3f10..5a9ccaea3b22098 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -47,7 +47,10 @@ typedef struct _PyExecutorObject { typedef struct _PyOptimizerObject _PyOptimizerObject; /* Should return > 0 if a new executor is created. O if no executor is produced and < 0 if an error occurred. */ -typedef int (*optimize_func)(_PyOptimizerObject* self, PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutorObject **, int curr_stackentries); +typedef int (*optimize_func)( + _PyOptimizerObject* self, struct _PyInterpreterFrame *frame, + _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, + int curr_stackentries); typedef struct _PyOptimizerObject { PyObject_HEAD @@ -94,6 +97,9 @@ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void); /* Minimum of 16 additional executions before retry */ #define MINIMUM_TIER2_BACKOFF 4 +#define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3 +#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6 + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 60acd89cf6c34a6..233da058f464d17 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -207,8 +207,8 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) { #define DK_IS_UNICODE(dk) ((dk)->dk_kind != DICT_KEYS_GENERAL) -#define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS) -#define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1) +#define DICT_VERSION_INCREMENT (1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) +#define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1) #ifdef Py_GIL_DISABLED #define DICT_NEXT_VERSION(INTERP) \ @@ -234,7 +234,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp, PyObject *value) { assert(Py_REFCNT((PyObject*)mp) > 0); - int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK; + int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK; if (watcher_bits) { _PyDict_SendEvent(watcher_bits, event, mp, key, value); return DICT_NEXT_VERSION(interp) | watcher_bits; diff --git a/Include/internal/pycore_dict_state.h b/Include/internal/pycore_dict_state.h index a6dd63d36e040e6..1a44755c7a01a3a 100644 --- a/Include/internal/pycore_dict_state.h +++ b/Include/internal/pycore_dict_state.h @@ -9,6 +9,7 @@ extern "C" { #endif #define DICT_MAX_WATCHERS 8 +#define DICT_WATCHED_MUTATION_BITS 4 struct _Py_dict_state { /*Global counter used to set ma_version_tag field of dictionary. diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index c4732b1534199b4..f7c332ed747cfac 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -72,7 +72,6 @@ typedef struct _rare_events { uint8_t set_eval_frame_func; /* Modifying the builtins, __builtins__.__dict__[var] = ... */ uint8_t builtin_dict; - int builtins_dict_watcher_id; /* Modifying a function, e.g. func.__defaults__ = ..., etc. */ uint8_t func_modification; } _rare_events; @@ -243,6 +242,7 @@ struct _is { uint16_t optimizer_backedge_threshold; uint32_t next_func_version; _rare_events rare_events; + PyDict_WatchCallback builtins_dict_watcher; _Py_GlobalMonitors monitors; bool sys_profile_initialized; diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 31f30c673f207a1..e21412fc815540d 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -8,8 +8,9 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -int _Py_uop_analyze_and_optimize(PyCodeObject *code, - _PyUOpInstruction *trace, int trace_len, int curr_stackentries); +int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame, + _PyUOpInstruction *trace, int trace_len, int curr_stackentries, + _PyBloomFilter *dependencies); extern PyTypeObject _PyCounterExecutor_Type; extern PyTypeObject _PyCounterOptimizer_Type; diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index a7056586ff04c01..b2476e1c6e5c4b5 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -232,8 +232,12 @@ extern "C" { #define _CHECK_VALIDITY 379 #define _LOAD_CONST_INLINE 380 #define _LOAD_CONST_INLINE_BORROW 381 -#define _INTERNAL_INCREMENT_OPT_COUNTER 382 -#define MAX_UOP_ID 382 +#define _LOAD_CONST_INLINE_WITH_NULL 382 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 383 +#define _CHECK_GLOBALS 384 +#define _CHECK_BUILTINS 385 +#define _INTERNAL_INCREMENT_OPT_COUNTER 386 +#define MAX_UOP_ID 386 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 14d3382e895cdf4..2b5b37e6b8d6a43 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -204,6 +204,10 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = 0, [_LOAD_CONST_INLINE_BORROW] = 0, + [_LOAD_CONST_INLINE_WITH_NULL] = 0, + [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = 0, + [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, + [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, }; @@ -250,10 +254,12 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", [_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE", [_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT", + [_CHECK_BUILTINS] = "_CHECK_BUILTINS", [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", [_CHECK_EG_MATCH] = "_CHECK_EG_MATCH", [_CHECK_EXC_MATCH] = "_CHECK_EXC_MATCH", [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", + [_CHECK_GLOBALS] = "_CHECK_GLOBALS", [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", [_CHECK_PEP_523] = "_CHECK_PEP_523", [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", @@ -332,6 +338,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_LOAD_CONST] = "_LOAD_CONST", [_LOAD_CONST_INLINE] = "_LOAD_CONST_INLINE", [_LOAD_CONST_INLINE_BORROW] = "_LOAD_CONST_INLINE_BORROW", + [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = "_LOAD_CONST_INLINE_BORROW_WITH_NULL", + [_LOAD_CONST_INLINE_WITH_NULL] = "_LOAD_CONST_INLINE_WITH_NULL", [_LOAD_DEREF] = "_LOAD_DEREF", [_LOAD_FAST] = "_LOAD_FAST", [_LOAD_FAST_AND_CLEAR] = "_LOAD_FAST_AND_CLEAR", diff --git a/Lib/test/test_capi/test_watchers.py b/Lib/test/test_capi/test_watchers.py index 5981712c80c3a96..ae062b1bda26b7c 100644 --- a/Lib/test/test_capi/test_watchers.py +++ b/Lib/test/test_capi/test_watchers.py @@ -151,8 +151,8 @@ def test_watch_out_of_range_watcher_id(self): def test_watch_unassigned_watcher_id(self): d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.watch(1, d) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.watch(3, d) def test_unwatch_non_dict(self): with self.watcher() as wid: @@ -168,8 +168,8 @@ def test_unwatch_out_of_range_watcher_id(self): def test_unwatch_unassigned_watcher_id(self): d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.unwatch(1, d) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.unwatch(3, d) def test_clear_out_of_range_watcher_id(self): with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): @@ -178,8 +178,8 @@ def test_clear_out_of_range_watcher_id(self): self.clear_watcher(8) # DICT_MAX_WATCHERS = 8 def test_clear_unassigned_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.clear_watcher(1) + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"): + self.clear_watcher(3) class TestTypeWatchers(unittest.TestCase): diff --git a/Modules/_testcapi/watchers.c b/Modules/_testcapi/watchers.c index a763ff46a3c2901..1eb0db2c2e65761 100644 --- a/Modules/_testcapi/watchers.c +++ b/Modules/_testcapi/watchers.c @@ -15,8 +15,8 @@ module _testcapi /*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/ // Test dict watching -static PyObject *g_dict_watch_events; -static int g_dict_watchers_installed; +static PyObject *g_dict_watch_events = NULL; +static int g_dict_watchers_installed = 0; static int dict_watch_callback(PyDict_WatchEvent event, diff --git a/Objects/dictobject.c b/Objects/dictobject.c index e24887b7d781bb5..4bb818b90a4a72d 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5943,7 +5943,8 @@ PyDict_AddWatcher(PyDict_WatchCallback callback) { PyInterpreterState *interp = _PyInterpreterState_GET(); - for (int i = 0; i < DICT_MAX_WATCHERS; i++) { + /* Start at 2, as 0 and 1 are reserved for CPython */ + for (int i = 2; i < DICT_MAX_WATCHERS; i++) { if (!interp->dict_state.watchers[i]) { interp->dict_state.watchers[i] = callback; return i; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index ebd5b06abb2d4e4..6fb4d719e43991c 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4071,11 +4071,35 @@ dummy_func( } op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + TIER_TWO_ONLY value = Py_NewRef(ptr); } op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + TIER_TWO_ONLY + value = ptr; + } + + op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { + TIER_TWO_ONLY + value = Py_NewRef(ptr); + null = NULL; + } + + op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { + TIER_TWO_ONLY value = ptr; + null = NULL; + } + + op(_CHECK_GLOBALS, (dict/4 -- )) { + TIER_TWO_ONLY + DEOPT_IF(GLOBALS() != dict); + } + + op(_CHECK_BUILTINS, (dict/4 -- )) { + TIER_TWO_ONLY + DEOPT_IF(BUILTINS() != dict); } /* Internal -- for testing executors */ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 241b9056207715d..2d914b82dbf88f4 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3393,6 +3393,7 @@ case _LOAD_CONST_INLINE: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY value = Py_NewRef(ptr); stack_pointer[0] = value; stack_pointer += 1; @@ -3402,12 +3403,53 @@ case _LOAD_CONST_INLINE_BORROW: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY value = ptr; stack_pointer[0] = value; stack_pointer += 1; break; } + case _LOAD_CONST_INLINE_WITH_NULL: { + PyObject *value; + PyObject *null; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + value = Py_NewRef(ptr); + null = NULL; + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _LOAD_CONST_INLINE_BORROW_WITH_NULL: { + PyObject *value; + PyObject *null; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + value = ptr; + null = NULL; + stack_pointer[0] = value; + stack_pointer[1] = null; + stack_pointer += 2; + break; + } + + case _CHECK_GLOBALS: { + PyObject *dict = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + if (GLOBALS() != dict) goto deoptimize; + break; + } + + case _CHECK_BUILTINS: { + PyObject *dict = (PyObject *)CURRENT_OPERAND(); + TIER_TWO_ONLY + if (BUILTINS() != dict) goto deoptimize; + break; + } + case _INTERNAL_INCREMENT_OPT_COUNTER: { PyObject *opt; opt = stack_pointer[-1]; diff --git a/Python/optimizer.c b/Python/optimizer.c index 0d04b09fef1e846..d71ca0aef0e11ac 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -108,16 +108,14 @@ PyUnstable_Replace_Executor(PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutor } static int -error_optimize( +never_optimize( _PyOptimizerObject* self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec, int Py_UNUSED(stack_entries)) { - assert(0); - PyErr_Format(PyExc_SystemError, "Should never call error_optimize"); - return -1; + return 0; } PyTypeObject _PyDefaultOptimizer_Type = { @@ -130,7 +128,7 @@ PyTypeObject _PyDefaultOptimizer_Type = { _PyOptimizerObject _PyOptimizer_Default = { PyObject_HEAD_INIT(&_PyDefaultOptimizer_Type) - .optimize = error_optimize, + .optimize = never_optimize, .resume_threshold = INT16_MAX, .backedge_threshold = INT16_MAX, }; @@ -174,7 +172,7 @@ _PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject } _PyOptimizerObject *opt = interp->optimizer; _PyExecutorObject *executor = NULL; - int err = opt->optimize(opt, code, start, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); + int err = opt->optimize(opt, frame, start, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); if (err <= 0) { assert(executor == NULL); return err; @@ -363,7 +361,8 @@ BRANCH_TO_GUARD[4][2] = { ADD_TO_TRACE(_EXIT_TRACE, 0, 0, 0); \ goto done; \ } \ - trace_stack[trace_stack_depth].code = code; \ + assert(func->func_code == (PyObject *)code); \ + trace_stack[trace_stack_depth].func = func; \ trace_stack[trace_stack_depth].instr = instr; \ trace_stack_depth++; #define TRACE_STACK_POP() \ @@ -371,7 +370,8 @@ BRANCH_TO_GUARD[4][2] = { Py_FatalError("Trace stack underflow\n"); \ } \ trace_stack_depth--; \ - code = trace_stack[trace_stack_depth].code; \ + func = trace_stack[trace_stack_depth].func; \ + code = (PyCodeObject *)trace_stack[trace_stack_depth].func->func_code; \ instr = trace_stack[trace_stack_depth].instr; /* Returns 1 on success, @@ -380,20 +380,23 @@ BRANCH_TO_GUARD[4][2] = { */ static int translate_bytecode_to_trace( - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyUOpInstruction *trace, int buffer_size, _PyBloomFilter *dependencies) { bool progress_needed = true; + PyCodeObject *code = (PyCodeObject *)frame->f_executable; + PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; + assert(PyFunction_Check(func)); PyCodeObject *initial_code = code; _Py_BloomFilter_Add(dependencies, initial_code); _Py_CODEUNIT *initial_instr = instr; int trace_length = 0; int max_length = buffer_size; struct { - PyCodeObject *code; + PyFunctionObject *func; _Py_CODEUNIT *instr; } trace_stack[TRACE_STACK_SIZE]; int trace_stack_depth = 0; @@ -593,9 +596,9 @@ translate_bytecode_to_trace( ADD_TO_TRACE(uop, oparg, operand, target); if (uop == _POP_FRAME) { TRACE_STACK_POP(); - /* Set the operand to the code object returned to, + /* Set the operand to the function object returned to, * to assist optimization passes */ - trace[trace_length-1].operand = (uintptr_t)code; + trace[trace_length-1].operand = (uintptr_t)func; DPRINTF(2, "Returning to %s (%s:%d) at byte offset %d\n", PyUnicode_AsUTF8(code->co_qualname), @@ -611,10 +614,10 @@ translate_bytecode_to_trace( // Add one to account for the actual opcode/oparg pair: + 1; uint32_t func_version = read_u32(&instr[func_version_offset].cache); - PyFunctionObject *func = _PyFunction_LookupByVersion(func_version); + PyFunctionObject *new_func = _PyFunction_LookupByVersion(func_version); DPRINTF(3, "Function object: %p\n", func); - if (func != NULL) { - PyCodeObject *new_code = (PyCodeObject *)PyFunction_GET_CODE(func); + if (new_func != NULL) { + PyCodeObject *new_code = (PyCodeObject *)PyFunction_GET_CODE(new_func); if (new_code == code) { // Recursive call, bail (we could be here forever). DPRINTF(2, "Bailing on recursive call to %s (%s:%d)\n", @@ -639,8 +642,9 @@ translate_bytecode_to_trace( _Py_BloomFilter_Add(dependencies, new_code); /* Set the operand to the callee's code object, * to assist optimization passes */ - trace[trace_length-1].operand = (uintptr_t)new_code; + trace[trace_length-1].operand = (uintptr_t)new_func; code = new_code; + func = new_func; instr = _PyCode_CODE(code); DPRINTF(2, "Continuing in %s (%s:%d) at byte offset %d\n", @@ -808,7 +812,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) static int uop_optimize( _PyOptimizerObject *self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, int curr_stackentries) @@ -816,7 +820,7 @@ uop_optimize( _PyBloomFilter dependencies; _Py_BloomFilter_Init(&dependencies); _PyUOpInstruction buffer[UOP_MAX_TRACE_LENGTH]; - int err = translate_bytecode_to_trace(code, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies); + int err = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies); if (err <= 0) { // Error or nothing translated return err; @@ -824,9 +828,10 @@ uop_optimize( OPT_STAT_INC(traces_created); char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); if (uop_optimize == NULL || *uop_optimize > '0') { - err = _Py_uop_analyze_and_optimize(code, buffer, UOP_MAX_TRACE_LENGTH, curr_stackentries); - if (err < 0) { - return -1; + err = _Py_uop_analyze_and_optimize(frame, buffer, + UOP_MAX_TRACE_LENGTH, curr_stackentries, &dependencies); + if (err <= 0) { + return err; } } _PyExecutorObject *executor = make_executor_from_uops(buffer, &dependencies); @@ -887,12 +892,13 @@ PyTypeObject _PyCounterExecutor_Type = { static int counter_optimize( _PyOptimizerObject* self, - PyCodeObject *code, + _PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr, int Py_UNUSED(curr_stackentries) ) { + PyCodeObject *code = (PyCodeObject *)frame->f_executable; int oparg = instr->op.arg; while (instr->op.code == EXTENDED_ARG) { instr++; diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index d1225997e10be2b..2cfbf4b349d0f52 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -1,10 +1,12 @@ #include "Python.h" #include "opcode.h" +#include "pycore_dict.h" #include "pycore_interp.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_uop_metadata.h" +#include "pycore_dict.h" #include "pycore_long.h" #include "cpython/optimizer.h" #include <stdbool.h> @@ -12,9 +14,210 @@ #include <stddef.h> #include "pycore_optimizer.h" +static int +get_mutations(PyObject* dict) { + assert(PyDict_CheckExact(dict)); + PyDictObject *d = (PyDictObject *)dict; + return (d->ma_version_tag >> DICT_MAX_WATCHERS) & ((1 << DICT_WATCHED_MUTATION_BITS)-1); +} + static void -peephole_opt(PyCodeObject *co, _PyUOpInstruction *buffer, int buffer_size) +increment_mutations(PyObject* dict) { + assert(PyDict_CheckExact(dict)); + PyDictObject *d = (PyDictObject *)dict; + d->ma_version_tag += (1 << DICT_MAX_WATCHERS); +} + +static int +globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, + PyObject* key, PyObject* new_value) +{ + if (event == PyDict_EVENT_CLONED) { + return 0; + } + uint64_t watched_mutations = get_mutations(dict); + if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); + increment_mutations(dict); + } + else { + PyDict_Unwatch(1, dict); + } + return 0; +} + + +static void +global_to_const(_PyUOpInstruction *inst, PyObject *obj) +{ + assert(inst->opcode == _LOAD_GLOBAL_MODULE || inst->opcode == _LOAD_GLOBAL_BUILTINS); + assert(PyDict_CheckExact(obj)); + PyDictObject *dict = (PyDictObject *)obj; + assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); + PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dict->ma_keys); + assert(inst->operand <= UINT16_MAX); + PyObject *res = entries[inst->operand].me_value; + if (res == NULL) { + return; + } + if (_Py_IsImmortal(res)) { + inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_BORROW_WITH_NULL : _LOAD_CONST_INLINE_BORROW; + } + else { + inst->opcode = (inst->oparg & 1) ? _LOAD_CONST_INLINE_WITH_NULL : _LOAD_CONST_INLINE; + } + inst->operand = (uint64_t)res; +} + +static int +incorrect_keys(_PyUOpInstruction *inst, PyObject *obj) { + if (!PyDict_CheckExact(obj)) { + return 1; + } + PyDictObject *dict = (PyDictObject *)obj; + if (dict->ma_keys->dk_version != inst->operand) { + return 1; + } + return 0; +} + +/* The first two dict watcher IDs are reserved for CPython, + * so we don't need to check that they haven't been used */ +#define BUILTINS_WATCHER_ID 0 +#define GLOBALS_WATCHER_ID 1 + +/* Returns 1 if successfully optimized + * 0 if the trace is not suitable for optimization (yet) + * -1 if there was an error. */ +static int +remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, + int buffer_size, _PyBloomFilter *dependencies) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyObject *builtins = frame->f_builtins; + if (builtins != interp->builtins) { + return 1; + } + PyObject *globals = frame->f_globals; + assert(PyFunction_Check(((PyFunctionObject *)frame->f_funcobj))); + assert(((PyFunctionObject *)frame->f_funcobj)->func_builtins == builtins); + assert(((PyFunctionObject *)frame->f_funcobj)->func_globals == globals); + /* In order to treat globals as constants, we need to + * know that the globals dict is the one we expected, and + * that it hasn't changed + * In order to treat builtins as constants, we need to + * know that the builtins dict is the one we expected, and + * that it hasn't changed and that the global dictionary's + * keys have not changed */ + + /* These values represent stacks of booleans (one bool per bit). + * Pushing a frame shifts left, popping a frame shifts right. */ + uint32_t builtins_checked = 0; + uint32_t builtins_watched = 0; + uint32_t globals_checked = 0; + uint32_t globals_watched = 0; + if (interp->dict_state.watchers[1] == NULL) { + interp->dict_state.watchers[1] = globals_watcher_callback; + } + for (int pc = 0; pc < buffer_size; pc++) { + _PyUOpInstruction *inst = &buffer[pc]; + int opcode = inst->opcode; + switch(opcode) { + case _GUARD_BUILTINS_VERSION: + if (incorrect_keys(inst, builtins)) { + return 0; + } + if (interp->rare_events.builtin_dict >= _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { + continue; + } + if ((builtins_watched & 1) == 0) { + PyDict_Watch(BUILTINS_WATCHER_ID, builtins); + builtins_watched |= 1; + } + if (builtins_checked & 1) { + buffer[pc].opcode = NOP; + } + else { + buffer[pc].opcode = _CHECK_BUILTINS; + buffer[pc].operand = (uintptr_t)builtins; + builtins_checked |= 1; + } + break; + case _GUARD_GLOBALS_VERSION: + if (incorrect_keys(inst, globals)) { + return 0; + } + uint64_t watched_mutations = get_mutations(globals); + if (watched_mutations >= _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { + continue; + } + if ((globals_watched & 1) == 0) { + PyDict_Watch(GLOBALS_WATCHER_ID, globals); + _Py_BloomFilter_Add(dependencies, globals); + globals_watched |= 1; + } + if (globals_checked & 1) { + buffer[pc].opcode = NOP; + } + else { + buffer[pc].opcode = _CHECK_GLOBALS; + buffer[pc].operand = (uintptr_t)globals; + globals_checked |= 1; + } + break; + case _LOAD_GLOBAL_BUILTINS: + if (globals_checked & builtins_checked & globals_watched & builtins_watched & 1) { + global_to_const(inst, builtins); + } + break; + case _LOAD_GLOBAL_MODULE: + if (globals_checked & globals_watched & 1) { + global_to_const(inst, globals); + } + break; + case _PUSH_FRAME: + { + globals_checked <<= 1; + globals_watched <<= 1; + builtins_checked <<= 1; + builtins_watched <<= 1; + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + if (func == NULL) { + return 1; + } + assert(PyFunction_Check(func)); + globals = func->func_globals; + builtins = func->func_builtins; + if (builtins != interp->builtins) { + return 1; + } + break; + } + case _POP_FRAME: + { + globals_checked >>= 1; + globals_watched >>= 1; + builtins_checked >>= 1; + builtins_watched >>= 1; + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + assert(PyFunction_Check(func)); + globals = func->func_globals; + builtins = func->func_builtins; + break; + } + case _JUMP_TO_TOP: + case _EXIT_TRACE: + return 1; + } + } + return 0; +} + +static void +peephole_opt(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_size) +{ + PyCodeObject *co = (PyCodeObject *)frame->f_executable; for (int pc = 0; pc < buffer_size; pc++) { int opcode = buffer[pc].opcode; switch(opcode) { @@ -36,8 +239,17 @@ peephole_opt(PyCodeObject *co, _PyUOpInstruction *buffer, int buffer_size) } case _PUSH_FRAME: case _POP_FRAME: - co = (PyCodeObject *)buffer[pc].operand; + { + PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; + if (func == NULL) { + co = NULL; + } + else { + assert(PyFunction_Check(func)); + co = (PyCodeObject *)func->func_code; + } break; + } case _JUMP_TO_TOP: case _EXIT_TRACE: return; @@ -83,16 +295,20 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) } } - int _Py_uop_analyze_and_optimize( - PyCodeObject *co, + _PyInterpreterFrame *frame, _PyUOpInstruction *buffer, int buffer_size, - int curr_stacklen + int curr_stacklen, + _PyBloomFilter *dependencies ) { - peephole_opt(co, buffer, buffer_size); + int err = remove_globals(frame, buffer, buffer_size, dependencies); + if (err <= 0) { + return err; + } + peephole_opt(frame, buffer, buffer_size); remove_unneeded_uops(buffer, buffer_size); - return 0; + return 1; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 372f60602375b65..0cac7109340129c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -32,6 +32,7 @@ #include "pycore_typevarobject.h" // _Py_clear_generic_types() #include "pycore_unicodeobject.h" // _PyUnicode_InitTypes() #include "pycore_weakref.h" // _PyWeakref_GET_REF() +#include "cpython/optimizer.h" // _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS #include "pycore_obmalloc.h" // _PyMem_init_obmalloc() #include "opcode.h" @@ -609,7 +610,11 @@ init_interp_create_gil(PyThreadState *tstate, int gil) static int builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value) { - RARE_EVENT_INC(builtin_dict); + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (event != PyDict_EVENT_CLONED && interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { + _Py_Executors_InvalidateAll(interp); + } + RARE_EVENT_INTERP_INC(interp, builtin_dict); return 0; } @@ -1287,11 +1292,9 @@ init_interp_main(PyThreadState *tstate) } } - if ((interp->rare_events.builtins_dict_watcher_id = PyDict_AddWatcher(&builtins_dict_watcher)) == -1) { - return _PyStatus_ERR("failed to add builtin dict watcher"); - } - if (PyDict_Watch(interp->rare_events.builtins_dict_watcher_id, interp->builtins) != 0) { + interp->dict_state.watchers[0] = &builtins_dict_watcher; + if (PyDict_Watch(0, interp->builtins) != 0) { return _PyStatus_ERR("failed to set builtin dict watcher"); } @@ -1622,8 +1625,13 @@ finalize_modules(PyThreadState *tstate) { PyInterpreterState *interp = tstate->interp; - // Stop collecting stats on __builtin__ modifications during teardown - PyDict_Unwatch(interp->rare_events.builtins_dict_watcher_id, interp->builtins); + // Invalidate all executors and turn off tier 2 optimizer + _Py_Executors_InvalidateAll(interp); + Py_XDECREF(interp->optimizer); + interp->optimizer = &_PyOptimizer_Default; + + // Stop watching __builtin__ modifications + PyDict_Unwatch(0, interp->builtins); PyObject *modules = _PyImport_GetModules(interp); if (modules == NULL) { From d0f1307580a69372611d27b04bbf2551dc85a1ef Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 2 Feb 2024 08:03:15 -0500 Subject: [PATCH 162/263] gh-114329: Add `PyList_GetItemRef` function (GH-114504) The new `PyList_GetItemRef` is similar to `PyList_GetItem`, but returns a strong reference instead of a borrowed reference. Additionally, if the passed "list" object is not a list, the function sets a `TypeError` instead of calling `PyErr_BadInternalCall()`. --- Doc/c-api/list.rst | 12 ++++++++-- Doc/data/refcounts.dat | 4 ++++ Doc/data/stable_abi.dat | 1 + Doc/whatsnew/3.13.rst | 4 ++++ Include/listobject.h | 1 + Lib/test/test_capi/test_list.py | 22 +++++++++++-------- Lib/test/test_stable_abi_ctypes.py | 1 + ...-01-23-21-45-02.gh-issue-114329.YRaBoe.rst | 3 +++ Misc/stable_abi.toml | 2 ++ Modules/_testcapi/list.c | 13 +++++++++++ Objects/listobject.c | 15 +++++++++++++ PC/python3dll.c | 1 + 12 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index c8b64bad702f508..53eb54d3e1021a3 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -56,13 +56,21 @@ List Objects Similar to :c:func:`PyList_Size`, but without error checking. -.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index) +.. c:function:: PyObject* PyList_GetItemRef(PyObject *list, Py_ssize_t index) Return the object at position *index* in the list pointed to by *list*. The position must be non-negative; indexing from the end of the list is not - supported. If *index* is out of bounds (<0 or >=len(list)), + supported. If *index* is out of bounds (:code:`<0 or >=len(list)`), return ``NULL`` and set an :exc:`IndexError` exception. + .. versionadded:: 3.13 + + +.. c:function:: PyObject* PyList_GetItem(PyObject *list, Py_ssize_t index) + + Like :c:func:`PyList_GetItemRef`, but returns a + :term:`borrowed reference` instead of a :term:`strong reference`. + .. c:function:: PyObject* PyList_GET_ITEM(PyObject *list, Py_ssize_t i) diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index f719ce153b239a9..62a96146d605ff7 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -1133,6 +1133,10 @@ PyList_GetItem:PyObject*::0: PyList_GetItem:PyObject*:list:0: PyList_GetItem:Py_ssize_t:index:: +PyList_GetItemRef:PyObject*::+1: +PyList_GetItemRef:PyObject*:list:0: +PyList_GetItemRef:Py_ssize_t:index:: + PyList_GetSlice:PyObject*::+1: PyList_GetSlice:PyObject*:list:0: PyList_GetSlice:Py_ssize_t:low:: diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index da28a2be60bc1bc..def1903204add7a 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -336,6 +336,7 @@ var,PyListRevIter_Type,3.2,, function,PyList_Append,3.2,, function,PyList_AsTuple,3.2,, function,PyList_GetItem,3.2,, +function,PyList_GetItemRef,3.13,, function,PyList_GetSlice,3.2,, function,PyList_Insert,3.2,, function,PyList_New,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 887c3009f885041..f17c6ec0775beff 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1376,6 +1376,10 @@ New Features UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. (Contributed by Victor Stinner in :gh:`108314`.) +* Added :c:func:`PyList_GetItemRef` function: similar to + :c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of + a :term:`borrowed reference`. + * Add :c:func:`Py_IsFinalizing` function: check if the main Python interpreter is :term:`shutting down <interpreter shutdown>`. (Contributed by Victor Stinner in :gh:`108014`.) diff --git a/Include/listobject.h b/Include/listobject.h index 6b7041ba0b05d59..4e4084b43483a2d 100644 --- a/Include/listobject.h +++ b/Include/listobject.h @@ -29,6 +29,7 @@ PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *); PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t); +PyAPI_FUNC(PyObject *) PyList_GetItemRef(PyObject *, Py_ssize_t); PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *); PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *); PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index eb03d51d3def378..dceb4fce3c077bf 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -82,10 +82,8 @@ def test_list_get_size(self): # CRASHES size(UserList()) # CRASHES size(NULL) - - def test_list_getitem(self): - # Test PyList_GetItem() - getitem = _testcapi.list_getitem + def check_list_get_item(self, getitem, exctype): + # Common test cases for PyList_GetItem() and PyList_GetItemRef() lst = [1, 2, 3] self.assertEqual(getitem(lst, 0), 1) self.assertEqual(getitem(lst, 2), 3) @@ -93,12 +91,19 @@ def test_list_getitem(self): self.assertRaises(IndexError, getitem, lst, -1) self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MIN) self.assertRaises(IndexError, getitem, lst, PY_SSIZE_T_MAX) - self.assertRaises(SystemError, getitem, 42, 1) - self.assertRaises(SystemError, getitem, (1, 2, 3), 1) - self.assertRaises(SystemError, getitem, {1: 2}, 1) - + self.assertRaises(exctype, getitem, 42, 1) + self.assertRaises(exctype, getitem, (1, 2, 3), 1) + self.assertRaises(exctype, getitem, {1: 2}, 1) # CRASHES getitem(NULL, 1) + def test_list_getitem(self): + # Test PyList_GetItem() + self.check_list_get_item(_testcapi.list_getitem, SystemError) + + def test_list_get_item_ref(self): + # Test PyList_GetItemRef() + self.check_list_get_item(_testcapi.list_get_item_ref, TypeError) + def test_list_get_item(self): # Test PyList_GET_ITEM() get_item = _testcapi.list_get_item @@ -112,7 +117,6 @@ def test_list_get_item(self): # CRASHES get_item(21, 2) # CRASHES get_item(NULL, 1) - def test_list_setitem(self): # Test PyList_SetItem() setitem = _testcapi.list_setitem diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 054e7f0feb1a19d..8bd373976426ef3 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -372,6 +372,7 @@ def test_windows_feature_macros(self): "PyList_Append", "PyList_AsTuple", "PyList_GetItem", + "PyList_GetItemRef", "PyList_GetSlice", "PyList_Insert", "PyList_New", diff --git a/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst b/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst new file mode 100644 index 000000000000000..62d4ce0cfb8de54 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-23-21-45-02.gh-issue-114329.YRaBoe.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyList_GetItemRef`, which is similar to +:c:func:`PyList_GetItem` but returns a :term:`strong reference` instead of a +:term:`borrowed reference`. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index ae19d25809ec867..a9875f6ffd1a56a 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2487,3 +2487,5 @@ abi_only = true [data.PyExc_IncompleteInputError] added = '3.13' +[function.PyList_GetItemRef] + added = '3.13' diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 10e18699f01bc1f..2cb6499e28336df 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -59,6 +59,18 @@ list_get_item(PyObject *Py_UNUSED(module), PyObject *args) return Py_XNewRef(PyList_GET_ITEM(obj, i)); } +static PyObject * +list_get_item_ref(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return PyList_GetItemRef(obj, i); +} + static PyObject * list_setitem(PyObject *Py_UNUSED(module), PyObject *args) { @@ -191,6 +203,7 @@ static PyMethodDef test_methods[] = { {"list_get_size", list_get_size, METH_O}, {"list_getitem", list_getitem, METH_VARARGS}, {"list_get_item", list_get_item, METH_VARARGS}, + {"list_get_item_ref", list_get_item_ref, METH_VARARGS}, {"list_setitem", list_setitem, METH_VARARGS}, {"list_set_item", list_set_item, METH_VARARGS}, {"list_insert", list_insert, METH_VARARGS}, diff --git a/Objects/listobject.c b/Objects/listobject.c index da2b9cc32697dda..82a4ba952de07dc 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -257,6 +257,21 @@ PyList_GetItem(PyObject *op, Py_ssize_t i) return ((PyListObject *)op) -> ob_item[i]; } +PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t i) +{ + if (!PyList_Check(op)) { + PyErr_SetString(PyExc_TypeError, "expected a list"); + return NULL; + } + if (!valid_index(i, Py_SIZE(op))) { + _Py_DECLARE_STR(list_err, "list index out of range"); + PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); + return NULL; + } + return Py_NewRef(PyList_GET_ITEM(op, i)); +} + int PyList_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) diff --git a/PC/python3dll.c b/PC/python3dll.c index 09ecf98fe56ea62..aa6bfe2c4022db0 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -324,6 +324,7 @@ EXPORT_FUNC(PyIter_Send) EXPORT_FUNC(PyList_Append) EXPORT_FUNC(PyList_AsTuple) EXPORT_FUNC(PyList_GetItem) +EXPORT_FUNC(PyList_GetItemRef) EXPORT_FUNC(PyList_GetSlice) EXPORT_FUNC(PyList_Insert) EXPORT_FUNC(PyList_New) From d29f57f6036353b4e705a42637177442bf7e07e5 Mon Sep 17 00:00:00 2001 From: Justin Williams <97240811+juswil@users.noreply.github.com> Date: Fri, 2 Feb 2024 08:32:46 -0500 Subject: [PATCH 163/263] gh-103360: Add link in stdtypes.rst to escape sequences in lexical_analysis.rst (GH-103638) --- Doc/library/stdtypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 9028ff5c134fa92..1a4c12590c1018d 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -1528,7 +1528,7 @@ between them will be implicitly converted to a single string literal. That is, ``("spam " "eggs") == "spam eggs"``. See :ref:`strings` for more about the various forms of string literal, -including supported escape sequences, and the ``r`` ("raw") prefix that +including supported :ref:`escape sequences <escape-sequences>`, and the ``r`` ("raw") prefix that disables most escape sequence processing. Strings may also be created from other objects using the :class:`str` From b3f0b698daf2438a6e59d5d19ccb34acdba0bffc Mon Sep 17 00:00:00 2001 From: Andrew Rogers <32688592+adr26@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:50:51 +0000 Subject: [PATCH 164/263] gh-104530: Enable native Win32 condition variables by default (GH-104531) --- Include/internal/pycore_condvar.h | 10 ++++---- ...-05-16-06-52-34.gh-issue-104530.mJnA0W.rst | 1 + Python/ceval_gil.c | 8 +++++++ Python/condvar.h | 23 +++++++++++-------- Python/pystate.c | 12 +++++++++- Python/thread_nt.h | 22 ++---------------- 6 files changed, 41 insertions(+), 35 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst diff --git a/Include/internal/pycore_condvar.h b/Include/internal/pycore_condvar.h index 34c21aaad43197b..ee9533484e80488 100644 --- a/Include/internal/pycore_condvar.h +++ b/Include/internal/pycore_condvar.h @@ -35,14 +35,14 @@ #include <windows.h> // CRITICAL_SECTION /* options */ -/* non-emulated condition variables are provided for those that want - * to target Windows Vista. Modify this macro to enable them. +/* emulated condition variables are provided for those that want + * to target Windows XP or earlier. Modify this macro to enable them. */ #ifndef _PY_EMULATED_WIN_CV -#define _PY_EMULATED_WIN_CV 1 /* use emulated condition variables */ +#define _PY_EMULATED_WIN_CV 0 /* use non-emulated condition variables */ #endif -/* fall back to emulation if not targeting Vista */ +/* fall back to emulation if targeting earlier than Vista */ #if !defined NTDDI_VISTA || NTDDI_VERSION < NTDDI_VISTA #undef _PY_EMULATED_WIN_CV #define _PY_EMULATED_WIN_CV 1 @@ -77,7 +77,7 @@ typedef struct _PyCOND_T #else /* !_PY_EMULATED_WIN_CV */ -/* Use native Win7 primitives if build target is Win7 or higher */ +/* Use native Windows primitives if build target is Vista or higher */ /* SRWLOCK is faster and better than CriticalSection */ typedef SRWLOCK PyMUTEX_T; diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst new file mode 100644 index 000000000000000..8643a25ae51b13f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-16-06-52-34.gh-issue-104530.mJnA0W.rst @@ -0,0 +1 @@ +Use native Win32 condition variables. diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index f3b169241535f37..ad90359318761a5 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -610,8 +610,16 @@ PyEval_SaveThread(void) void PyEval_RestoreThread(PyThreadState *tstate) { +#ifdef MS_WINDOWS + int err = GetLastError(); +#endif + _Py_EnsureTstateNotNULL(tstate); _PyThreadState_Attach(tstate); + +#ifdef MS_WINDOWS + SetLastError(err); +#endif } diff --git a/Python/condvar.h b/Python/condvar.h index d54db94f2c871d4..dcabed6d55928ce 100644 --- a/Python/condvar.h +++ b/Python/condvar.h @@ -260,13 +260,13 @@ PyMUTEX_UNLOCK(PyMUTEX_T *cs) return 0; } - Py_LOCAL_INLINE(int) PyCOND_INIT(PyCOND_T *cv) { InitializeConditionVariable(cv); return 0; } + Py_LOCAL_INLINE(int) PyCOND_FINI(PyCOND_T *cv) { @@ -279,27 +279,32 @@ PyCOND_WAIT(PyCOND_T *cv, PyMUTEX_T *cs) return SleepConditionVariableSRW(cv, cs, INFINITE, 0) ? 0 : -1; } -/* This implementation makes no distinction about timeouts. Signal - * 2 to indicate that we don't know. - */ +/* return 0 for success, 1 on timeout, -1 on error */ Py_LOCAL_INLINE(int) PyCOND_TIMEDWAIT(PyCOND_T *cv, PyMUTEX_T *cs, long long us) { - return SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0) ? 2 : -1; + BOOL success = SleepConditionVariableSRW(cv, cs, (DWORD)(us/1000), 0); + if (!success) { + if (GetLastError() == ERROR_TIMEOUT) { + return 1; + } + return -1; + } + return 0; } Py_LOCAL_INLINE(int) PyCOND_SIGNAL(PyCOND_T *cv) { - WakeConditionVariable(cv); - return 0; + WakeConditionVariable(cv); + return 0; } Py_LOCAL_INLINE(int) PyCOND_BROADCAST(PyCOND_T *cv) { - WakeAllConditionVariable(cv); - return 0; + WakeAllConditionVariable(cv); + return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 27b6d0573ade3b3..7836c172bbfb618 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2488,7 +2488,17 @@ PyGILState_Check(void) return 0; } - return (tstate == gilstate_tss_get(runtime)); +#ifdef MS_WINDOWS + int err = GetLastError(); +#endif + + PyThreadState *tcur = gilstate_tss_get(runtime); + +#ifdef MS_WINDOWS + SetLastError(err); +#endif + + return (tstate == tcur); } PyGILState_STATE diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 14b9cddc24c0ec3..044e9fa111e9797 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -444,16 +444,7 @@ PyThread_set_key_value(int key, void *value) void * PyThread_get_key_value(int key) { - /* because TLS is used in the Py_END_ALLOW_THREAD macro, - * it is necessary to preserve the windows error state, because - * it is assumed to be preserved across the call to the macro. - * Ideally, the macro should be fixed, but it is simpler to - * do it here. - */ - DWORD error = GetLastError(); - void *result = TlsGetValue(key); - SetLastError(error); - return result; + return TlsGetValue(key); } void @@ -525,14 +516,5 @@ void * PyThread_tss_get(Py_tss_t *key) { assert(key != NULL); - /* because TSS is used in the Py_END_ALLOW_THREAD macro, - * it is necessary to preserve the windows error state, because - * it is assumed to be preserved across the call to the macro. - * Ideally, the macro should be fixed, but it is simpler to - * do it here. - */ - DWORD error = GetLastError(); - void *result = TlsGetValue(key->_key); - SetLastError(error); - return result; + return TlsGetValue(key->_key); } From ee66c333493105e014678be118850e138e3c62a8 Mon Sep 17 00:00:00 2001 From: Steven Ward <planet36@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:13:00 -0500 Subject: [PATCH 165/263] gh-114909: Add --first-weekday option to usage message (#114910) --- Doc/library/calendar.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index c4dcf5641d60663..e699a7284ac8023 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -512,7 +512,7 @@ to interactively print a calendar. python -m calendar [-h] [-L LOCALE] [-e ENCODING] [-t {text,html}] [-w WIDTH] [-l LINES] [-s SPACING] [-m MONTHS] [-c CSS] - [year] [month] + [-f FIRST_WEEKDAY] [year] [month] For example, to print a calendar for the year 2000: @@ -586,12 +586,13 @@ The following options are accepted: or as an HTML document. -.. option:: --first-weekday WEEKDAY, -f WEEKDAY +.. option:: --first-weekday FIRST_WEEKDAY, -f FIRST_WEEKDAY The weekday to start each week. Must be a number between 0 (Monday) and 6 (Sunday). Defaults to 0. + .. versionadded:: 3.13 .. option:: year From 9872855a31720f514b84373848b49fca09d66ecd Mon Sep 17 00:00:00 2001 From: patenaud <33957588+patenaud@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:31:55 -0500 Subject: [PATCH 166/263] GH-69695: Update ``PyImport_ImportModule`` description (GH-103836) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/c-api/import.rst | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 51c20b202f091c5..7c74e9e88678dc0 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -13,20 +13,8 @@ Importing Modules single: __all__ (package variable) single: modules (in module sys) - This is a simplified interface to :c:func:`PyImport_ImportModuleEx` below, - leaving the *globals* and *locals* arguments set to ``NULL`` and *level* set - to 0. When the *name* - argument contains a dot (when it specifies a submodule of a package), the - *fromlist* argument is set to the list ``['*']`` so that the return value is the - named module rather than the top-level package containing it as would otherwise - be the case. (Unfortunately, this has an additional side effect when *name* in - fact specifies a subpackage instead of a submodule: the submodules specified in - the package's ``__all__`` variable are loaded.) Return a new reference to the - imported module, or ``NULL`` with an exception set on failure. A failing - import of a module doesn't leave the module in :data:`sys.modules`. - - This function always uses absolute imports. - + This is a wrapper around :c:func:`PyImport_Import()` which takes a + :c:expr:`const char *` as an argument instead of a :c:expr:`PyObject *`. .. c:function:: PyObject* PyImport_ImportModuleNoBlock(const char *name) From c12240ed28aac6494750e00143bc550c4d6d8ad1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 2 Feb 2024 20:53:24 +0200 Subject: [PATCH 167/263] gh-114728: Fix documentation for comparison of objects in datetime module (GH-114749) --- Doc/library/datetime.rst | 118 +++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index db9a92ae4111e35..096670634ee9569 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -400,30 +400,7 @@ objects (see below). the :func:`divmod` function. True division and multiplication of a :class:`timedelta` object by a :class:`float` object are now supported. - -Comparisons of :class:`timedelta` objects are supported, with some caveats. - -The comparisons ``==`` or ``!=`` *always* return a :class:`bool`, no matter -the type of the compared object:: - - >>> from datetime import timedelta - >>> delta1 = timedelta(seconds=57) - >>> delta2 = timedelta(hours=25, seconds=2) - >>> delta2 != delta1 - True - >>> delta2 == 5 - False - -For all other comparisons (such as ``<`` and ``>``), when a :class:`timedelta` -object is compared to an object of a different type, :exc:`TypeError` -is raised:: - - >>> delta2 > delta1 - True - >>> delta2 > 5 - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - TypeError: '>' not supported between instances of 'datetime.timedelta' and 'int' +:class:`timedelta` objects support equality and order comparisons. In Boolean contexts, a :class:`timedelta` object is considered to be true if and only if it isn't equal to ``timedelta(0)``. @@ -614,8 +591,13 @@ Supported operations: +-------------------------------+----------------------------------------------+ | ``timedelta = date1 - date2`` | \(3) | +-------------------------------+----------------------------------------------+ -| ``date1 < date2`` | *date1* is considered less than *date2* when | -| | *date1* precedes *date2* in time. (4) | +| | ``date1 == date2`` | Equality comparison. (4) | +| | ``date1 != date2`` | | ++-------------------------------+----------------------------------------------+ +| | ``date1 < date2`` | Order comparison. (5) | +| | ``date1 > date2`` | | +| | ``date1 <= date2`` | | +| | ``date1 >= date2`` | | +-------------------------------+----------------------------------------------+ Notes: @@ -635,15 +617,12 @@ Notes: timedelta.microseconds are 0, and date2 + timedelta == date1 after. (4) + :class:`date` objects are equal if they represent the same date. + +(5) + *date1* is considered less than *date2* when *date1* precedes *date2* in time. In other words, ``date1 < date2`` if and only if ``date1.toordinal() < - date2.toordinal()``. Date comparison raises :exc:`TypeError` if - the other comparand isn't also a :class:`date` object. However, - ``NotImplemented`` is returned instead if the other comparand has a - :attr:`~date.timetuple` attribute. This hook gives other kinds of date objects a - chance at implementing mixed-type comparison. If not, when a :class:`date` - object is compared to an object of a different type, :exc:`TypeError` is raised - unless the comparison is ``==`` or ``!=``. The latter cases return - :const:`False` or :const:`True`, respectively. + date2.toordinal()``. In Boolean contexts, all :class:`date` objects are considered to be true. @@ -1170,8 +1149,13 @@ Supported operations: +---------------------------------------+--------------------------------+ | ``timedelta = datetime1 - datetime2`` | \(3) | +---------------------------------------+--------------------------------+ -| ``datetime1 < datetime2`` | Compares :class:`.datetime` to | -| | :class:`.datetime`. (4) | +| | ``datetime1 == datetime2`` | Equality comparison. (4) | +| | ``datetime1 != datetime2`` | | ++---------------------------------------+--------------------------------+ +| | ``datetime1 < datetime2`` | Order comparison. (5) | +| | ``datetime1 > datetime2`` | | +| | ``datetime1 <= datetime2`` | | +| | ``datetime1 >= datetime2`` | | +---------------------------------------+--------------------------------+ (1) @@ -1199,40 +1183,41 @@ Supported operations: are done in this case. If both are aware and have different :attr:`~.datetime.tzinfo` attributes, ``a-b`` acts - as if *a* and *b* were first converted to naive UTC datetimes first. The + as if *a* and *b* were first converted to naive UTC datetimes. The result is ``(a.replace(tzinfo=None) - a.utcoffset()) - (b.replace(tzinfo=None) - b.utcoffset())`` except that the implementation never overflows. (4) + :class:`.datetime` objects are equal if they represent the same date + and time, taking into account the time zone. + + Naive and aware :class:`!datetime` objects are never equal. + :class:`!datetime` objects are never equal to :class:`date` objects + that are not also :class:`!datetime` instances, even if they represent + the same date. + + If both comparands are aware and have different :attr:`~.datetime.tzinfo` + attributes, the comparison acts as comparands were first converted to UTC + datetimes except that the implementation never overflows. + :class:`!datetime` instances in a repeated interval are never equal to + :class:`!datetime` instances in other time zone. + +(5) *datetime1* is considered less than *datetime2* when *datetime1* precedes - *datetime2* in time. + *datetime2* in time, taking into account the time zone. - If one comparand is naive and the other is aware, :exc:`TypeError` - is raised if an order comparison is attempted. For equality - comparisons, naive instances are never equal to aware instances. + Order comparison between naive and aware :class:`.datetime` objects, + as well as a :class:`!datetime` object and a :class:`!date` object + that is not also a :class:`!datetime` instance, raises :exc:`TypeError`. - If both comparands are aware, and have the same :attr:`~.datetime.tzinfo` attribute, the - common :attr:`~.datetime.tzinfo` attribute is ignored and the base datetimes are - compared. If both comparands are aware and have different :attr:`~.datetime.tzinfo` - attributes, the comparands are first adjusted by subtracting their UTC - offsets (obtained from ``self.utcoffset()``). + If both comparands are aware and have different :attr:`~.datetime.tzinfo` + attributes, the comparison acts as comparands were first converted to UTC + datetimes except that the implementation never overflows. .. versionchanged:: 3.3 Equality comparisons between aware and naive :class:`.datetime` instances don't raise :exc:`TypeError`. - .. note:: - - In order to stop comparison from falling back to the default scheme of comparing - object addresses, datetime comparison normally raises :exc:`TypeError` if the - other comparand isn't also a :class:`.datetime` object. However, - ``NotImplemented`` is returned instead if the other comparand has a - :attr:`~.datetime.timetuple` attribute. This hook gives other kinds of date objects a - chance at implementing mixed-type comparison. If not, when a :class:`.datetime` - object is compared to an object of a different type, :exc:`TypeError` is raised - unless the comparison is ``==`` or ``!=``. The latter cases return - :const:`False` or :const:`True`, respectively. - Instance methods: .. method:: datetime.date() @@ -1766,21 +1751,18 @@ Instance attributes (read-only): .. versionadded:: 3.6 -:class:`.time` objects support comparison of :class:`.time` to :class:`.time`, -where *a* is considered less -than *b* when *a* precedes *b* in time. If one comparand is naive and the other -is aware, :exc:`TypeError` is raised if an order comparison is attempted. For equality -comparisons, naive instances are never equal to aware instances. +:class:`.time` objects support equality and order comparisons, +where *a* is considered less than *b* when *a* precedes *b* in time. + +Naive and aware :class:`!time` objects are never equal. +Order comparison between naive and aware :class:`!time` objects raises +:exc:`TypeError`. If both comparands are aware, and have the same :attr:`~.time.tzinfo` attribute, the common :attr:`!tzinfo` attribute is ignored and the base times are compared. If both comparands are aware and have different :attr:`!tzinfo` attributes, the comparands are first adjusted by -subtracting their UTC offsets (obtained from ``self.utcoffset()``). In order -to stop mixed-type comparisons from falling back to the default comparison by -object address, when a :class:`.time` object is compared to an object of a -different type, :exc:`TypeError` is raised unless the comparison is ``==`` or -``!=``. The latter cases return :const:`False` or :const:`True`, respectively. +subtracting their UTC offsets (obtained from ``self.utcoffset()``). .. versionchanged:: 3.3 Equality comparisons between aware and naive :class:`.time` instances From 7e2703bbff09c3c72c225790e5c71f423351b2d1 Mon Sep 17 00:00:00 2001 From: GILGAMESH <~@gilgamesh.cc> Date: Fri, 2 Feb 2024 10:59:53 -0800 Subject: [PATCH 168/263] Update venv activate.bat to escape custom PROMPT variables on Windows (GH-114885) --- Lib/venv/scripts/nt/activate.bat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index 2c98122362a060f..dd5ea8eb67b90a7 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -15,8 +15,8 @@ if not defined PROMPT set PROMPT=$P$G if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT% if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% -set _OLD_VIRTUAL_PROMPT=%PROMPT% -set PROMPT=(__VENV_PROMPT__) %PROMPT% +set "_OLD_VIRTUAL_PROMPT=%PROMPT%" +set "PROMPT=(__VENV_PROMPT__) %PROMPT%" if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% set PYTHONHOME= From 920b89f62751e64a35fa1bebc03701af6d6f31f2 Mon Sep 17 00:00:00 2001 From: Alex Waygood <Alex.Waygood@Gmail.com> Date: Fri, 2 Feb 2024 21:04:15 +0000 Subject: [PATCH 169/263] Bump ruff to 0.2.0 (#114932) --- .pre-commit-config.yaml | 2 +- Lib/test/.ruff.toml | 8 +++++--- Tools/clinic/.ruff.toml | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19033ce243d9d35..69d85238985150c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.2.0 hooks: - id: ruff name: Run Ruff on Lib/test/ diff --git a/Lib/test/.ruff.toml b/Lib/test/.ruff.toml index d6c1d8745036ece..1c9bac507209b12 100644 --- a/Lib/test/.ruff.toml +++ b/Lib/test/.ruff.toml @@ -1,7 +1,4 @@ fix = true -select = [ - "F811", # Redefinition of unused variable (useful for finding test methods with the same name) -] extend-exclude = [ # Excluded (run with the other AC files in its own separate ruff job in pre-commit) "test_clinic.py", @@ -21,3 +18,8 @@ extend-exclude = [ "test_pkg.py", "test_yield_from.py", ] + +[lint] +select = [ + "F811", # Redefinition of unused variable (useful for finding test methods with the same name) +] diff --git a/Tools/clinic/.ruff.toml b/Tools/clinic/.ruff.toml index cbb3a9a8f3a8c2c..c019572d0cb1864 100644 --- a/Tools/clinic/.ruff.toml +++ b/Tools/clinic/.ruff.toml @@ -1,5 +1,7 @@ target-version = "py310" fix = true + +[lint] select = [ "F", # Enable all pyflakes rules "UP", # Enable all pyupgrade rules by default From b27812d6320e35d62d91f1b3714be1e38021101a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 2 Feb 2024 23:09:16 +0200 Subject: [PATCH 170/263] Fix indentation of "versionchanged" in datetime.rst (GH-114933) --- Doc/library/datetime.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 096670634ee9569..735c3b24f81509e 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1214,9 +1214,9 @@ Supported operations: attributes, the comparison acts as comparands were first converted to UTC datetimes except that the implementation never overflows. - .. versionchanged:: 3.3 - Equality comparisons between aware and naive :class:`.datetime` - instances don't raise :exc:`TypeError`. +.. versionchanged:: 3.3 + Equality comparisons between aware and naive :class:`.datetime` + instances don't raise :exc:`TypeError`. Instance methods: From 73d20cafb54193c94577ca60df1ba0410b3ced74 Mon Sep 17 00:00:00 2001 From: John Belmonte <john@neggie.net> Date: Sat, 3 Feb 2024 06:42:17 +0900 Subject: [PATCH 171/263] Correct timedelta description (GH-101417) It only represents the difference between two datetime or date objects, not between two time objects. --- Doc/library/datetime.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 735c3b24f81509e..930af6cbbe9e8d1 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -130,8 +130,8 @@ Available Types .. class:: timedelta :noindex: - A duration expressing the difference between two :class:`date`, :class:`.time`, - or :class:`.datetime` instances to microsecond resolution. + A duration expressing the difference between two :class:`.datetime` + or :class:`date` instances to microsecond resolution. .. class:: tzinfo @@ -203,7 +203,7 @@ objects. -------------------------- A :class:`timedelta` object represents a duration, the difference between two -dates or times. +:class:`.datetime` or :class:`date` instances. .. class:: timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0) From c4a2e8a2c5188c3288d57b80852e92c83f46f6f3 Mon Sep 17 00:00:00 2001 From: Jokimax <77680901+Jokimax@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:13:00 +0200 Subject: [PATCH 172/263] gh-101599: argparse: simplify the option help string (GH-103372) If the option with argument has short and long names, output argument only once, after the long name: -o, --option ARG description instead of -o ARG, --option ARG description --- Lib/argparse.py | 10 +++------- Lib/test/test_argparse.py | 6 +++--- .../2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst | 1 + 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index a32884db80d1ea1..9e19f39fadd87bc 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -564,22 +564,18 @@ def _format_action_invocation(self, action): return metavar else: - parts = [] # if the Optional doesn't take a value, format is: # -s, --long if action.nargs == 0: - parts.extend(action.option_strings) + return ', '.join(action.option_strings) # if the Optional takes a value, format is: - # -s ARGS, --long ARGS + # -s, --long ARGS else: default = self._get_default_metavar_for_optional(action) args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) + return ', '.join(action.option_strings) + ' ' + args_string def _metavar_formatter(self, action, default_metavar): if action.metavar is not None: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 7c1f5d36999a3d5..940d7e95f96e20a 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -3922,7 +3922,7 @@ class TestHelpUsageWithParentheses(HelpTestCase): options: -h, --help show this help message and exit - -p {1 (option A), 2 (option B)}, --optional {1 (option A), 2 (option B)} + -p, --optional {1 (option A), 2 (option B)} ''' version = '' @@ -4405,8 +4405,8 @@ class TestHelpAlternatePrefixChars(HelpTestCase): help = usage + '''\ options: - ^^foo foo help - ;b BAR, ;;bar BAR bar help + ^^foo foo help + ;b, ;;bar BAR bar help ''' version = '' diff --git a/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst b/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst new file mode 100644 index 000000000000000..a1608a1ae0d2fae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-08-11-41-07.gh-issue-101599.PaWNFh.rst @@ -0,0 +1 @@ +Changed argparse flag options formatting to remove redundancy. From 1183f1e6bfba06ae6c8ea362f96e977bc288e627 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Fri, 2 Feb 2024 18:14:32 -0500 Subject: [PATCH 173/263] gh-114913: Add newline to subprocess doc (#114941) *creationflags* is a separate topic from *startupinfo*. Start sentence with 'If given', like previous sentence. --- Doc/library/subprocess.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index c437ce770b37d0d..f63ca73b3ec067e 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -664,7 +664,8 @@ functions. If given, *startupinfo* will be a :class:`STARTUPINFO` object, which is passed to the underlying ``CreateProcess`` function. - *creationflags*, if given, can be one or more of the following flags: + + If given, *creationflags*, can be one or more of the following flags: * :data:`CREATE_NEW_CONSOLE` * :data:`CREATE_NEW_PROCESS_GROUP` From f35c7c070ca6b49c5d6a97be34e6c02f828f5873 Mon Sep 17 00:00:00 2001 From: Malcolm Smith <smith@chaquo.com> Date: Fri, 2 Feb 2024 23:30:52 +0000 Subject: [PATCH 174/263] gh-114875: Require getgrent for building the grp extension module (#114876) Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- .../Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst | 1 + configure | 9 ++++++++- configure.ac | 6 ++++-- pyconfig.h.in | 3 +++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst diff --git a/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst b/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst new file mode 100644 index 000000000000000..20e9d6376b973cb --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-01-20-08-11.gh-issue-114875.x_2iZ9.rst @@ -0,0 +1 @@ +Add :c:func:`!getgrent` as a prerequisite for building the :mod:`grp` module. diff --git a/configure b/configure index 7b2119ff7f4f784..1d41d7ba66470d7 100755 --- a/configure +++ b/configure @@ -17475,6 +17475,12 @@ if test "x$ac_cv_func_getgid" = xyes then : printf "%s\n" "#define HAVE_GETGID 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "getgrent" "ac_cv_func_getgrent" +if test "x$ac_cv_func_getgrent" = xyes +then : + printf "%s\n" "#define HAVE_GETGRENT 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "getgrgid" "ac_cv_func_getgrgid" if test "x$ac_cv_func_getgrgid" = xyes @@ -28968,7 +28974,8 @@ then : if true then : - if test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes + if test "$ac_cv_func_getgrent" = "yes" && + { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; } then : py_cv_module_grp=yes else $as_nop diff --git a/configure.ac b/configure.ac index 5bef2351c987e83..b29cd028f8f2004 100644 --- a/configure.ac +++ b/configure.ac @@ -4787,7 +4787,7 @@ AC_CHECK_FUNCS([ \ copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ + gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \ getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ @@ -7313,7 +7313,9 @@ PY_STDLIB_MOD([_socket], -a "$ac_cv_header_netinet_in_h" = "yes"])) dnl platform specific extensions -PY_STDLIB_MOD([grp], [], [test "$ac_cv_func_getgrgid" = yes -o "$ac_cv_func_getgrgid_r" = yes]) +PY_STDLIB_MOD([grp], [], + [test "$ac_cv_func_getgrent" = "yes" && + { test "$ac_cv_func_getgrgid" = "yes" || test "$ac_cv_func_getgrgid_r" = "yes"; }]) PY_STDLIB_MOD([pwd], [], [test "$ac_cv_func_getpwuid" = yes -o "$ac_cv_func_getpwuid_r" = yes]) PY_STDLIB_MOD([resource], [], [test "$ac_cv_header_sys_resource_h" = yes]) PY_STDLIB_MOD([_scproxy], diff --git a/pyconfig.h.in b/pyconfig.h.in index b22740710bcbee5..2b4bb1a2b528662 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -474,6 +474,9 @@ /* Define to 1 if you have the `getgid' function. */ #undef HAVE_GETGID +/* Define to 1 if you have the `getgrent' function. */ +#undef HAVE_GETGRENT + /* Define to 1 if you have the `getgrgid' function. */ #undef HAVE_GETGRGID From f3cdd64de8a9d5bad122cc0b285b5c44cd9b202b Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 3 Feb 2024 04:52:58 +0300 Subject: [PATCH 175/263] ``Tools/cases_generator``: Fix typos and incorrect comments. (#114892) --- Tools/cases_generator/opcode_id_generator.py | 2 +- Tools/cases_generator/opcode_metadata_generator.py | 4 ++-- Tools/cases_generator/py_metadata_generator.py | 4 ++-- Tools/cases_generator/uop_metadata_generator.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py index dbea3d0b622c87f..5a3009a5c04c27b 100644 --- a/Tools/cases_generator/opcode_id_generator.py +++ b/Tools/cases_generator/opcode_id_generator.py @@ -1,6 +1,6 @@ """Generate the list of opcode IDs. Reads the instruction definitions from bytecodes.c. -Writes the IDs to opcode._ids.h by default. +Writes the IDs to opcode_ids.h by default. """ import argparse diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 1826a0b645c3b86..3e9fa3e26daa539 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -1,6 +1,6 @@ -"""Generate uop metedata. +"""Generate opcode metadata. Reads the instruction definitions from bytecodes.c. -Writes the metadata to pycore_uop_metadata.h by default. +Writes the metadata to pycore_opcode_metadata.h by default. """ import argparse diff --git a/Tools/cases_generator/py_metadata_generator.py b/Tools/cases_generator/py_metadata_generator.py index 43811fdacc8a9e1..0dbcd599f9d4d98 100644 --- a/Tools/cases_generator/py_metadata_generator.py +++ b/Tools/cases_generator/py_metadata_generator.py @@ -1,6 +1,6 @@ -"""Generate uop metedata. +"""Generate opcode metadata for Python. Reads the instruction definitions from bytecodes.c. -Writes the metadata to pycore_uop_metadata.h by default. +Writes the metadata to _opcode_metadata.py by default. """ import argparse diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py index d4f3a096d2acc18..9083ecc48bdf5b8 100644 --- a/Tools/cases_generator/uop_metadata_generator.py +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -1,4 +1,4 @@ -"""Generate uop metedata. +"""Generate uop metadata. Reads the instruction definitions from bytecodes.c. Writes the metadata to pycore_uop_metadata.h by default. """ From 00d7109075dfaadf438362c084e8a1890c53d4c8 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Fri, 2 Feb 2024 19:56:00 -0600 Subject: [PATCH 176/263] Normalize heading underline in multiprocessing.rst (#114923) This gets rid of the mildly confusing `>>>>>>>' underlines which look vaguely like `diff` punctuation. --- Doc/library/multiprocessing.rst | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 789a84b02d59d23..b104a6483b70e6f 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -56,7 +56,7 @@ will print to standard output :: The :class:`Process` class -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ In :mod:`multiprocessing`, processes are spawned by creating a :class:`Process` object and then calling its :meth:`~Process.start` method. :class:`Process` @@ -102,7 +102,7 @@ necessary, see :ref:`multiprocessing-programming`. .. _multiprocessing-start-methods: Contexts and start methods -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ Depending on the platform, :mod:`multiprocessing` supports three ways to start a process. These *start methods* are @@ -231,7 +231,7 @@ library user. Exchanging objects between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :mod:`multiprocessing` supports two types of communication channel between processes: @@ -283,7 +283,7 @@ processes: Synchronization between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :mod:`multiprocessing` contains equivalents of all the synchronization primitives from :mod:`threading`. For instance one can use a lock to ensure @@ -309,7 +309,7 @@ mixed up. Sharing state between processes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As mentioned above, when doing concurrent programming it is usually best to avoid using shared state as far as possible. This is particularly true when @@ -399,7 +399,7 @@ However, if you really do need to use some shared data then Using a pool of workers -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ The :class:`~multiprocessing.pool.Pool` class represents a pool of worker processes. It has methods which allows tasks to be offloaded to the worker @@ -490,7 +490,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the :class:`Process` and exceptions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Process(group=None, target=None, name=None, args=(), kwargs={}, \ *, daemon=None) @@ -724,7 +724,7 @@ The :mod:`multiprocessing` package mostly replicates the API of the Raised by methods with a timeout when the timeout expires. Pipes and Queues -~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ When using multiple processes, one generally uses message passing for communication between processes and avoids having to use any synchronization @@ -981,7 +981,7 @@ For an example of the usage of queues for interprocess communication see Miscellaneous -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ .. function:: active_children() @@ -1150,7 +1150,7 @@ Miscellaneous Connection Objects -~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^ .. currentmodule:: multiprocessing.connection @@ -1292,7 +1292,7 @@ For example: Synchronization primitives -~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^ .. currentmodule:: multiprocessing @@ -1481,7 +1481,7 @@ object -- see :ref:`multiprocessing-managers`. Shared :mod:`ctypes` Objects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is possible to create shared objects using shared memory which can be inherited by child processes. @@ -1543,7 +1543,7 @@ inherited by child processes. The :mod:`multiprocessing.sharedctypes` module ->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +"""""""""""""""""""""""""""""""""""""""""""""" .. module:: multiprocessing.sharedctypes :synopsis: Allocate ctypes objects from shared memory. @@ -1709,7 +1709,7 @@ The results printed are :: .. _multiprocessing-managers: Managers -~~~~~~~~ +^^^^^^^^ Managers provide a way to create data which can be shared between different processes, including sharing over a network between processes running on @@ -1954,7 +1954,7 @@ their parent process exits. The manager classes are defined in the Customized managers ->>>>>>>>>>>>>>>>>>> +""""""""""""""""""" To create one's own manager, one creates a subclass of :class:`BaseManager` and uses the :meth:`~BaseManager.register` classmethod to register new types or @@ -1981,7 +1981,7 @@ callables with the manager class. For example:: Using a remote manager ->>>>>>>>>>>>>>>>>>>>>> +"""""""""""""""""""""" It is possible to run a manager server on one machine and have clients use it from other machines (assuming that the firewalls involved allow it). @@ -2044,7 +2044,7 @@ client to access it remotely:: .. _multiprocessing-proxy_objects: Proxy Objects -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ A proxy is an object which *refers* to a shared object which lives (presumably) in a different process. The shared object is said to be the *referent* of the @@ -2196,7 +2196,7 @@ demonstrates a level of control over the synchronization. Cleanup ->>>>>>> +""""""" A proxy object uses a weakref callback so that when it gets garbage collected it deregisters itself from the manager which owns its referent. @@ -2206,7 +2206,7 @@ any proxies referring to it. Process Pools -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ .. module:: multiprocessing.pool :synopsis: Create pools of processes. @@ -2442,7 +2442,7 @@ The following example demonstrates the use of a pool:: .. _multiprocessing-listeners-clients: Listeners and Clients -~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^ .. module:: multiprocessing.connection :synopsis: API for dealing with sockets. @@ -2665,7 +2665,7 @@ wait for messages from multiple processes at once:: .. _multiprocessing-address-formats: Address Formats ->>>>>>>>>>>>>>> +""""""""""""""" * An ``'AF_INET'`` address is a tuple of the form ``(hostname, port)`` where *hostname* is a string and *port* is an integer. @@ -2685,7 +2685,7 @@ an ``'AF_PIPE'`` address rather than an ``'AF_UNIX'`` address. .. _multiprocessing-auth-keys: Authentication keys -~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^ When one uses :meth:`Connection.recv <Connection.recv>`, the data received is automatically @@ -2711,7 +2711,7 @@ Suitable authentication keys can also be generated by using :func:`os.urandom`. Logging -~~~~~~~ +^^^^^^^ Some support for logging is available. Note, however, that the :mod:`logging` package does not use process shared locks so it is possible (depending on the @@ -2759,7 +2759,7 @@ For a full table of logging levels, see the :mod:`logging` module. The :mod:`multiprocessing.dummy` module -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. module:: multiprocessing.dummy :synopsis: Dumb wrapper around threading. @@ -2818,7 +2818,7 @@ There are certain guidelines and idioms which should be adhered to when using All start methods -~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^ The following applies to all start methods. @@ -2977,7 +2977,7 @@ Beware of replacing :data:`sys.stdin` with a "file like object" For more information, see :issue:`5155`, :issue:`5313` and :issue:`5331` The *spawn* and *forkserver* start methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are a few extra restriction which don't apply to the *fork* start method. From 28bb2961ba2f650452c949fcfc75ccfe0b5517e9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak <felisiak.mariusz@gmail.com> Date: Sat, 3 Feb 2024 08:37:21 +0100 Subject: [PATCH 177/263] Update LOGGING example taken from Django docs. (#114903) For example, Django no longer provides a custom NullHandler https://github.com/django/django/commit/6c66a41c3dc697dc3bda4e31e8b05084d2ede915 * Remove require_debug_true. --- Doc/howto/logging-cookbook.rst | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index ea494f2fdbbce42..80147e31fcbae11 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -1933,30 +1933,28 @@ This dictionary is passed to :func:`~config.dictConfig` to put the configuration LOGGING = { 'version': 1, - 'disable_existing_loggers': True, + 'disable_existing_loggers': False, 'formatters': { 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', + 'style': '{', }, 'simple': { - 'format': '%(levelname)s %(message)s' + 'format': '{levelname} {message}', + 'style': '{', }, }, 'filters': { 'special': { '()': 'project.logging.SpecialFilter', 'foo': 'bar', - } + }, }, 'handlers': { - 'null': { - 'level':'DEBUG', - 'class':'django.utils.log.NullHandler', - }, - 'console':{ - 'level':'DEBUG', - 'class':'logging.StreamHandler', - 'formatter': 'simple' + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', }, 'mail_admins': { 'level': 'ERROR', @@ -1966,9 +1964,8 @@ This dictionary is passed to :func:`~config.dictConfig` to put the configuration }, 'loggers': { 'django': { - 'handlers':['null'], + 'handlers': ['console'], 'propagate': True, - 'level':'INFO', }, 'django.request': { 'handlers': ['mail_admins'], From efc489021c2a5dba46979bd304563aee0c479a31 Mon Sep 17 00:00:00 2001 From: Jason Zhang <yurenzhang2017@gmail.com> Date: Sat, 3 Feb 2024 15:11:10 +0000 Subject: [PATCH 178/263] gh-111417: Remove unused code block in math.trunc() and round() (GH-111454) _PyObject_LookupSpecial() now ensures that the type is ready. --- Modules/mathmodule.c | 5 ----- Python/bltinmodule.c | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 0be46b1574c1feb..a877bfcd6afb687 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2074,11 +2074,6 @@ math_trunc(PyObject *module, PyObject *x) return PyFloat_Type.tp_as_number->nb_int(x); } - if (!_PyType_IsReady(Py_TYPE(x))) { - if (PyType_Ready(Py_TYPE(x)) < 0) - return NULL; - } - math_module_state *state = get_math_module_state(module); trunc = _PyObject_LookupSpecial(x, state->str___trunc__); if (trunc == NULL) { diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index e54d5cbacdc96f9..31c1bf07e8fb913 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2382,11 +2382,6 @@ builtin_round_impl(PyObject *module, PyObject *number, PyObject *ndigits) { PyObject *round, *result; - if (!_PyType_IsReady(Py_TYPE(number))) { - if (PyType_Ready(Py_TYPE(number)) < 0) - return NULL; - } - round = _PyObject_LookupSpecial(number, &_Py_ID(__round__)); if (round == NULL) { if (!PyErr_Occurred()) From b4240fd68ecd2c22ec82ac549eabfe5fd35fab2a Mon Sep 17 00:00:00 2001 From: AN Long <aisk@users.noreply.github.com> Date: Sat, 3 Feb 2024 23:33:58 +0800 Subject: [PATCH 179/263] gh-114955: Add clear to MutableSequence's mixin methods in document (gh-114956) --- Doc/library/collections.abc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index 582bb18f752bd59..7bcaba60c6ddbd9 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -136,8 +136,8 @@ ABC Inherits from Abstract Methods Mi :class:`Collection` ``__len__`` ``index``, and ``count`` :class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and - ``__setitem__``, ``append``, ``reverse``, ``extend``, ``pop``, - ``__delitem__``, ``remove``, and ``__iadd__`` + ``__setitem__``, ``append``, ``clear``, ``reverse``, ``extend``, + ``__delitem__``, ``pop``, ``remove``, and ``__iadd__`` ``__len__``, ``insert`` From 96bce033c4a4da7112792ba335ef3eb9a3eb0da0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 3 Feb 2024 18:18:46 +0200 Subject: [PATCH 180/263] gh-114959: tarfile: do not ignore errors when extract a directory on top of a file (GH-114960) Also, add tests common to tarfile and zipfile. --- Lib/tarfile.py | 3 +- Lib/test/archiver_tests.py | 155 ++++++++++++++++++ Lib/test/test_tarfile.py | 33 ++++ Lib/test/test_zipfile/test_core.py | 28 ++++ ...-02-03-16-59-25.gh-issue-114959.dCfAG2.rst | 2 + 5 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Lib/test/archiver_tests.py create mode 100644 Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 20e0394507f5db1..9775040cbe372cd 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2456,7 +2456,8 @@ def makedir(self, tarinfo, targetpath): # later in _extract_member(). os.mkdir(targetpath, 0o700) except FileExistsError: - pass + if not os.path.isdir(targetpath): + raise def makefile(self, tarinfo, targetpath): """Make a file called targetpath. diff --git a/Lib/test/archiver_tests.py b/Lib/test/archiver_tests.py new file mode 100644 index 000000000000000..1a4bbb9e5706c53 --- /dev/null +++ b/Lib/test/archiver_tests.py @@ -0,0 +1,155 @@ +"""Tests common to tarfile and zipfile.""" + +import os +import sys + +from test.support import os_helper + +class OverwriteTests: + + def setUp(self): + os.makedirs(self.testdir) + self.addCleanup(os_helper.rmtree, self.testdir) + + def create_file(self, path, content=b''): + with open(path, 'wb') as f: + f.write(content) + + def open(self, path): + raise NotImplementedError + + def extractall(self, ar): + raise NotImplementedError + + + def test_overwrite_file_as_file(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_dir_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + self.assertTrue(os.path.isfile(os.path.join(target, 'file'))) + with open(os.path.join(target, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + def test_overwrite_dir_as_file(self): + target = os.path.join(self.testdir, 'test') + os.mkdir(target) + with self.open(self.ar_with_file) as ar: + with self.assertRaises(PermissionError if sys.platform == 'win32' + else IsADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isdir(target)) + + def test_overwrite_file_as_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + def test_overwrite_file_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + self.create_file(target, b'content') + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileNotFoundError if sys.platform == 'win32' + else NotADirectoryError): + self.extractall(ar) + self.assertTrue(os.path.isfile(target)) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'content') + + @os_helper.skip_unless_symlink + def test_overwrite_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + self.create_file(target2, b'content') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_file_symlink_as_file(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target) + with self.open(self.ar_with_file) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isfile(target2)) + with open(target2, 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_dir_symlink_as_implicit_dir(self): + # XXX: It is potential security vulnerability. + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.mkdir(target2) + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertTrue(os.path.isdir(target2)) + self.assertTrue(os.path.isfile(os.path.join(target2, 'file'))) + with open(os.path.join(target2, 'file'), 'rb') as f: + self.assertEqual(f.read(), b'newcontent') + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) + + @os_helper.skip_unless_symlink + def test_overwrite_broken_dir_symlink_as_implicit_dir(self): + target = os.path.join(self.testdir, 'test') + target2 = os.path.join(self.testdir, 'test2') + os.symlink('test2', target, target_is_directory=True) + with self.open(self.ar_with_implicit_dir) as ar: + with self.assertRaises(FileExistsError): + self.extractall(ar) + self.assertTrue(os.path.islink(target)) + self.assertFalse(os.path.exists(target2)) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index da5009126b3815b..51f070e96047a6e 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -15,6 +15,7 @@ import unittest.mock import tarfile +from test import archiver_tests from test import support from test.support import os_helper from test.support import script_helper @@ -4135,6 +4136,38 @@ def valueerror_filter(tarinfo, path): self.expect_exception(TypeError) # errorlevel is not int +class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = os.path.join(TEMPDIR, "testoverwrite") + + @classmethod + def setUpClass(cls): + p = cls.ar_with_file = os.path.join(TEMPDIR, 'tar-with-file.tar') + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + t = tarfile.TarInfo('test') + t.size = 10 + tar.addfile(t, io.BytesIO(b'newcontent')) + + p = cls.ar_with_dir = os.path.join(TEMPDIR, 'tar-with-dir.tar') + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + tar.addfile(tar.gettarinfo(os.curdir, 'test')) + + p = os.path.join(TEMPDIR, 'tar-with-implicit-dir.tar') + cls.ar_with_implicit_dir = p + cls.addClassCleanup(os_helper.unlink, p) + with tarfile.open(p, 'w') as tar: + t = tarfile.TarInfo('test/file') + t.size = 10 + tar.addfile(t, io.BytesIO(b'newcontent')) + + def open(self, path): + return tarfile.open(path, 'r') + + def extractall(self, ar): + ar.extractall(self.testdir, filter='fully_trusted') + + def setUpModule(): os_helper.unlink(TEMPDIR) os.makedirs(TEMPDIR) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index a177044d735bed7..087fa8d65cc3368 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -18,6 +18,7 @@ from tempfile import TemporaryFile from random import randint, random, randbytes +from test import archiver_tests from test.support import script_helper from test.support import ( findfile, requires_zlib, requires_bz2, requires_lzma, @@ -1687,6 +1688,33 @@ def _test_extract_hackers_arcnames(self, hacknames): unlink(TESTFN2) +class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = TESTFN + + @classmethod + def setUpClass(cls): + p = cls.ar_with_file = TESTFN + '-with-file.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test', b'newcontent') + + p = cls.ar_with_dir = TESTFN + '-with-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.mkdir('test') + + p = cls.ar_with_implicit_dir = TESTFN + '-with-implicit-dir.zip' + cls.addClassCleanup(unlink, p) + with zipfile.ZipFile(p, 'w') as zipfp: + zipfp.writestr('test/file', b'newcontent') + + def open(self, path): + return zipfile.ZipFile(path, 'r') + + def extractall(self, ar): + ar.extractall(self.testdir) + + class OtherTests(unittest.TestCase): def test_open_via_zip_info(self): # Create the ZIP archive diff --git a/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst b/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst new file mode 100644 index 000000000000000..5c6eaa7525e3b0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-03-16-59-25.gh-issue-114959.dCfAG2.rst @@ -0,0 +1,2 @@ +:mod:`tarfile` no longer ignores errors when trying to extract a directory on +top of a file. From 6b53d5fe04eadad76fb3706f0a4cc42d8f19f948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Valur=20J=C3=B3nsson?= <sweskman@gmail.com> Date: Sat, 3 Feb 2024 16:19:37 +0000 Subject: [PATCH 181/263] gh-112202: Ensure that condition.notify() succeeds even when racing with Task.cancel() (#112201) Also did a general cleanup of asyncio locks.py comments and docstrings. --- Doc/library/asyncio-sync.rst | 12 +- Lib/asyncio/locks.py | 112 ++++++++++-------- Lib/test/test_asyncio/test_locks.py | 92 ++++++++++++++ ...-01-12-09-35-07.gh-issue-112202.t_0V1m.rst | 1 + 4 files changed, 165 insertions(+), 52 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst diff --git a/Doc/library/asyncio-sync.rst b/Doc/library/asyncio-sync.rst index 05bdf5488af143c..3cf8e2737e85dcd 100644 --- a/Doc/library/asyncio-sync.rst +++ b/Doc/library/asyncio-sync.rst @@ -216,8 +216,8 @@ Condition .. method:: notify(n=1) - Wake up at most *n* tasks (1 by default) waiting on this - condition. The method is no-op if no tasks are waiting. + Wake up *n* tasks (1 by default) waiting on this + condition. If fewer than *n* tasks are waiting they are all awakened. The lock must be acquired before this method is called and released shortly after. If called with an *unlocked* lock @@ -257,12 +257,18 @@ Condition Once awakened, the Condition re-acquires its lock and this method returns ``True``. + Note that a task *may* return from this call spuriously, + which is why the caller should always re-check the state + and be prepared to :meth:`wait` again. For this reason, you may + prefer to use :meth:`wait_for` instead. + .. coroutinemethod:: wait_for(predicate) Wait until a predicate becomes *true*. The predicate must be a callable which result will be - interpreted as a boolean value. The final value is the + interpreted as a boolean value. The method will repeatedly + :meth:`wait` until the predicate evaluates to *true*. The final value is the return value. diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index 04158e667a895fc..aaee8ff07029233 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -24,25 +24,23 @@ class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """Primitive lock objects. A primitive lock is a synchronization primitive that is not owned - by a particular coroutine when locked. A primitive lock is in one + by a particular task when locked. A primitive lock is in one of two states, 'locked' or 'unlocked'. It is created in the unlocked state. It has two basic methods, acquire() and release(). When the state is unlocked, acquire() changes the state to locked and returns immediately. When the state is locked, acquire() blocks until a call to release() in - another coroutine changes it to unlocked, then the acquire() call + another task changes it to unlocked, then the acquire() call resets it to locked and returns. The release() method should only be called in the locked state; it changes the state to unlocked and returns immediately. If an attempt is made to release an unlocked lock, a RuntimeError will be raised. - When more than one coroutine is blocked in acquire() waiting for - the state to turn to unlocked, only one coroutine proceeds when a - release() call resets the state to unlocked; first coroutine which - is blocked in acquire() is being processed. - - acquire() is a coroutine and should be called with 'await'. + When more than one task is blocked in acquire() waiting for + the state to turn to unlocked, only one task proceeds when a + release() call resets the state to unlocked; successive release() + calls will unblock tasks in FIFO order. Locks also support the asynchronous context management protocol. 'async with lock' statement should be used. @@ -130,7 +128,7 @@ def release(self): """Release a lock. When the lock is locked, reset it to unlocked, and return. - If any other coroutines are blocked waiting for the lock to become + If any other tasks are blocked waiting for the lock to become unlocked, allow exactly one of them to proceed. When invoked on an unlocked lock, a RuntimeError is raised. @@ -182,8 +180,8 @@ def is_set(self): return self._value def set(self): - """Set the internal flag to true. All coroutines waiting for it to - become true are awakened. Coroutine that call wait() once the flag is + """Set the internal flag to true. All tasks waiting for it to + become true are awakened. Tasks that call wait() once the flag is true will not block at all. """ if not self._value: @@ -194,7 +192,7 @@ def set(self): fut.set_result(True) def clear(self): - """Reset the internal flag to false. Subsequently, coroutines calling + """Reset the internal flag to false. Subsequently, tasks calling wait() will block until set() is called to set the internal flag to true again.""" self._value = False @@ -203,7 +201,7 @@ async def wait(self): """Block until the internal flag is true. If the internal flag is true on entry, return True - immediately. Otherwise, block until another coroutine calls + immediately. Otherwise, block until another task calls set() to set the flag to true, then return True. """ if self._value: @@ -222,8 +220,8 @@ class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Condition. This class implements condition variable objects. A condition variable - allows one or more coroutines to wait until they are notified by another - coroutine. + allows one or more tasks to wait until they are notified by another + task. A new Lock object is created and used as the underlying lock. """ @@ -250,50 +248,64 @@ def __repr__(self): async def wait(self): """Wait until notified. - If the calling coroutine has not acquired the lock when this + If the calling task has not acquired the lock when this method is called, a RuntimeError is raised. This method releases the underlying lock, and then blocks until it is awakened by a notify() or notify_all() call for - the same condition variable in another coroutine. Once + the same condition variable in another task. Once awakened, it re-acquires the lock and returns True. + + This method may return spuriously, + which is why the caller should always + re-check the state and be prepared to wait() again. """ if not self.locked(): raise RuntimeError('cannot wait on un-acquired lock') + fut = self._get_loop().create_future() self.release() try: - fut = self._get_loop().create_future() - self._waiters.append(fut) try: - await fut - return True - finally: - self._waiters.remove(fut) - - finally: - # Must re-acquire lock even if wait is cancelled. - # We only catch CancelledError here, since we don't want any - # other (fatal) errors with the future to cause us to spin. - err = None - while True: - try: - await self.acquire() - break - except exceptions.CancelledError as e: - err = e - - if err: + self._waiters.append(fut) try: - raise err # Re-raise most recent exception instance. + await fut + return True finally: - err = None # Break reference cycles. + self._waiters.remove(fut) + + finally: + # Must re-acquire lock even if wait is cancelled. + # We only catch CancelledError here, since we don't want any + # other (fatal) errors with the future to cause us to spin. + err = None + while True: + try: + await self.acquire() + break + except exceptions.CancelledError as e: + err = e + + if err is not None: + try: + raise err # Re-raise most recent exception instance. + finally: + err = None # Break reference cycles. + except BaseException: + # Any error raised out of here _may_ have occurred after this Task + # believed to have been successfully notified. + # Make sure to notify another Task instead. This may result + # in a "spurious wakeup", which is allowed as part of the + # Condition Variable protocol. + self._notify(1) + raise async def wait_for(self, predicate): """Wait until a predicate becomes true. - The predicate should be a callable which result will be - interpreted as a boolean value. The final predicate value is + The predicate should be a callable whose result will be + interpreted as a boolean value. The method will repeatedly + wait() until it evaluates to true. The final predicate value is the return value. """ result = predicate() @@ -303,20 +315,22 @@ async def wait_for(self, predicate): return result def notify(self, n=1): - """By default, wake up one coroutine waiting on this condition, if any. - If the calling coroutine has not acquired the lock when this method + """By default, wake up one task waiting on this condition, if any. + If the calling task has not acquired the lock when this method is called, a RuntimeError is raised. - This method wakes up at most n of the coroutines waiting for the - condition variable; it is a no-op if no coroutines are waiting. + This method wakes up n of the tasks waiting for the condition + variable; if fewer than n are waiting, they are all awoken. - Note: an awakened coroutine does not actually return from its + Note: an awakened task does not actually return from its wait() call until it can reacquire the lock. Since notify() does not release the lock, its caller should. """ if not self.locked(): raise RuntimeError('cannot notify on un-acquired lock') + self._notify(n) + def _notify(self, n): idx = 0 for fut in self._waiters: if idx >= n: @@ -374,7 +388,7 @@ async def acquire(self): If the internal counter is larger than zero on entry, decrement it by one and return True immediately. If it is - zero on entry, block, waiting until some other coroutine has + zero on entry, block, waiting until some other task has called release() to make it larger than 0, and then return True. """ @@ -414,8 +428,8 @@ async def acquire(self): def release(self): """Release a semaphore, incrementing the internal counter by one. - When it was zero on entry and another coroutine is waiting for it to - become larger than zero again, wake up that coroutine. + When it was zero on entry and another task is waiting for it to + become larger than zero again, wake up that task. """ self._value += 1 self._wake_up_next() diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 9029efd2355b46f..a0884bffe6b0de1 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -816,6 +816,98 @@ async def func(): # originally raised. self.assertIs(err.exception, raised) + async def test_cancelled_wakeup(self): + # Test that a task cancelled at the "same" time as it is woken + # up as part of a Condition.notify() does not result in a lost wakeup. + # This test simulates a cancel while the target task is awaiting initial + # wakeup on the wakeup queue. + condition = asyncio.Condition() + state = 0 + async def consumer(): + nonlocal state + async with condition: + while True: + await condition.wait_for(lambda: state != 0) + if state < 0: + return + state -= 1 + + # create two consumers + c = [asyncio.create_task(consumer()) for _ in range(2)] + # wait for them to settle + await asyncio.sleep(0) + async with condition: + # produce one item and wake up one + state += 1 + condition.notify(1) + + # Cancel it while it is awaiting to be run. + # This cancellation could come from the outside + c[0].cancel() + + # now wait for the item to be consumed + # if it doesn't means that our "notify" didn"t take hold. + # because it raced with a cancel() + try: + async with asyncio.timeout(0.01): + await condition.wait_for(lambda: state == 0) + except TimeoutError: + pass + self.assertEqual(state, 0) + + # clean up + state = -1 + condition.notify_all() + await c[1] + + async def test_cancelled_wakeup_relock(self): + # Test that a task cancelled at the "same" time as it is woken + # up as part of a Condition.notify() does not result in a lost wakeup. + # This test simulates a cancel while the target task is acquiring the lock + # again. + condition = asyncio.Condition() + state = 0 + async def consumer(): + nonlocal state + async with condition: + while True: + await condition.wait_for(lambda: state != 0) + if state < 0: + return + state -= 1 + + # create two consumers + c = [asyncio.create_task(consumer()) for _ in range(2)] + # wait for them to settle + await asyncio.sleep(0) + async with condition: + # produce one item and wake up one + state += 1 + condition.notify(1) + + # now we sleep for a bit. This allows the target task to wake up and + # settle on re-aquiring the lock + await asyncio.sleep(0) + + # Cancel it while awaiting the lock + # This cancel could come the outside. + c[0].cancel() + + # now wait for the item to be consumed + # if it doesn't means that our "notify" didn"t take hold. + # because it raced with a cancel() + try: + async with asyncio.timeout(0.01): + await condition.wait_for(lambda: state == 0) + except TimeoutError: + pass + self.assertEqual(state, 0) + + # clean up + state = -1 + condition.notify_all() + await c[1] + class SemaphoreTests(unittest.IsolatedAsyncioTestCase): def test_initial_value_zero(self): diff --git a/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst b/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst new file mode 100644 index 000000000000000..9abde13bbf8571b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-12-09-35-07.gh-issue-112202.t_0V1m.rst @@ -0,0 +1 @@ +Ensure that a :func:`asyncio.Condition.notify` call does not get lost if the awakened ``Task`` is simultaneously cancelled or encounters any other error. From 94ec2b9c9ce898723c3fe61fbc64d6c8f4f68700 Mon Sep 17 00:00:00 2001 From: Travis Howse <tjhowse@gmail.com> Date: Sun, 4 Feb 2024 03:14:02 +1000 Subject: [PATCH 182/263] gh-114887 Reject only sockets of type SOCK_STREAM in create_datagram_endpoint() (#114893) Also improve exception message. Co-authored-by: Donghee Na <donghee.na92@gmail.com> --- Lib/asyncio/base_events.py | 4 ++-- Lib/test/test_asyncio/test_base_events.py | 2 +- .../2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index c60d7688ef8c772..aadc4f478f8b560 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -1340,9 +1340,9 @@ async def create_datagram_endpoint(self, protocol_factory, allow_broadcast=None, sock=None): """Create datagram connection.""" if sock is not None: - if sock.type != socket.SOCK_DGRAM: + if sock.type == socket.SOCK_STREAM: raise ValueError( - f'A UDP Socket was expected, got {sock!r}') + f'A datagram socket was expected, got {sock!r}') if (local_addr or remote_addr or family or proto or flags or reuse_port or allow_broadcast): diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index c2080977e9d5877..82071edb2525706 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1232,7 +1232,7 @@ def test_create_datagram_endpoint_wrong_sock(self): with sock: coro = self.loop.create_datagram_endpoint(MyProto, sock=sock) with self.assertRaisesRegex(ValueError, - 'A UDP Socket was expected'): + 'A datagram socket was expected'): self.loop.run_until_complete(coro) def test_create_connection_no_host_port_sock(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst new file mode 100644 index 000000000000000..b4d8cf4089d7238 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-04-07-18.gh-issue-114887.uLSFmN.rst @@ -0,0 +1,2 @@ +Changed socket type validation in :meth:`~asyncio.loop.create_datagram_endpoint` to accept all non-stream sockets. +This fixes a regression in compatibility with raw sockets. From a4c298c1494b602a9650b597aad50b48e3fa1f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= <stephane.bidoul@gmail.com> Date: Sat, 3 Feb 2024 18:45:09 +0100 Subject: [PATCH 183/263] gh-114965: Updated bundled pip to 24.0 (gh-114966) Updated bundled pip to 24.0 --- Lib/ensurepip/__init__.py | 2 +- ...none-any.whl => pip-24.0-py3-none-any.whl} | Bin 2109393 -> 2110226 bytes ...-02-03-17-54-17.gh-issue-114965.gHksCK.rst | 1 + Misc/sbom.spdx.json | 28 +++++++++--------- 4 files changed, 16 insertions(+), 15 deletions(-) rename Lib/ensurepip/_bundled/{pip-23.3.2-py3-none-any.whl => pip-24.0-py3-none-any.whl} (83%) create mode 100644 Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 80ee125cfd4ed32..e8dd253bb555209 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] -_PIP_VERSION = "23.3.2" +_PIP_VERSION = "24.0" # Directory of system wheel packages. Some Linux distribution packaging # policies recommend against bundling dependencies. For example, Fedora diff --git a/Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl similarity index 83% rename from Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl rename to Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl index ae78b8a6ce073797af05cd7a833ff23d14cff185..2e6aa9d2cb99232f70d3ffcd0ed8e0b8a730e3ab 100644 GIT binary patch delta 299723 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 delta 299082 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%0Dq*;SPLW^zV~GX;IsiZ5|>$a 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%0AVO0M@LEgMvA1@C_<QYLU^j`XgHD?OC8H* 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 diff --git a/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst b/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst new file mode 100644 index 000000000000000..d59ff991993792a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-03-17-54-17.gh-issue-114965.gHksCK.rst @@ -0,0 +1 @@ +Update bundled pip to 24.0 diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 94566772338b108..e94dcb83dd4e404 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1570,18 +1570,18 @@ "fileName": "Modules/_decimal/libmpdec/vcdiv64.asm" }, { - "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.2-py3-none-any.whl", + "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", "checksums": [ { "algorithm": "SHA1", - "checksumValue": "8e48f55ab2965ee64bd55cc91a8077d184a33e30" + "checksumValue": "e44313ae1e6af3c2bd3b60ab2fa8c34308d00555" }, { "algorithm": "SHA256", - "checksumValue": "5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76" + "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" } ], - "fileName": "Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl" + "fileName": "Lib/ensurepip/_bundled/pip-24.0-py3-none-any.whl" } ], "packages": [ @@ -1742,21 +1742,21 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + "checksumValue": "034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784" } ], - "downloadLocation": "https://files.pythonhosted.org/packages/76/cb/6bbd2b10170ed991cf64e8c8b85e01f2fb38f95d1bc77617569e0b0b26ac/distlib-0.3.6-py2.py3-none-any.whl", + "downloadLocation": "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", "externalRefs": [ { "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/distlib@0.3.6", + "referenceLocator": "pkg:pypi/distlib@0.3.8", "referenceType": "purl" } ], "licenseConcluded": "MIT", "name": "distlib", "primaryPackagePurpose": "SOURCE", - "versionInfo": "0.3.6" + "versionInfo": "0.3.8" }, { "SPDXID": "SPDXRef-PACKAGE-distro", @@ -2204,19 +2204,19 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76" + "checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc" } ], - "downloadLocation": "https://files.pythonhosted.org/packages/15/aa/3f4c7bcee2057a76562a5b33ecbd199be08cdb4443a02e26bd2c3cf6fc39/pip-23.3.2-py3-none-any.whl", + "downloadLocation": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:pypa:pip:23.3.2:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:pypa:pip:24.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" }, { "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pip@23.3.2", + "referenceLocator": "pkg:pypi/pip@24.0", "referenceType": "purl" } ], @@ -2224,7 +2224,7 @@ "name": "pip", "originator": "Organization: Python Packaging Authority", "primaryPackagePurpose": "SOURCE", - "versionInfo": "23.3.2" + "versionInfo": "24.0" } ], "relationships": [ @@ -2909,7 +2909,7 @@ "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" }, { - "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.2-py3-none-any.whl", + "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-24.0-py3-none-any.whl", "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-PACKAGE-pip" } From ab76d37948fd506af44762dc1c3e32f27d1327a8 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sat, 3 Feb 2024 12:45:49 -0600 Subject: [PATCH 184/263] gh-101100: Fix Sphinx reference warnings in the glossary (#114729) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/glossary.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 098bfffb104ef69..f656e32514c7178 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -341,7 +341,7 @@ Glossary docstring A string literal which appears as the first expression in a class, function or module. While ignored when the suite is executed, it is - recognized by the compiler and put into the :attr:`__doc__` attribute + recognized by the compiler and put into the :attr:`!__doc__` attribute of the enclosing class, function or module. Since it is available via introspection, it is the canonical place for documentation of the object. @@ -1104,10 +1104,12 @@ Glossary The :class:`collections.abc.Sequence` abstract base class defines a much richer interface that goes beyond just :meth:`~object.__getitem__` and :meth:`~object.__len__`, adding - :meth:`count`, :meth:`index`, :meth:`~object.__contains__`, and + :meth:`!count`, :meth:`!index`, :meth:`~object.__contains__`, and :meth:`~object.__reversed__`. Types that implement this expanded interface can be registered explicitly using - :func:`~abc.ABCMeta.register`. + :func:`~abc.ABCMeta.register`. For more documentation on sequence + methods generally, see + :ref:`Common Sequence Operations <typesseq-common>`. set comprehension A compact way to process all or part of the elements in an iterable and From 72d2d0f10d5623bceb98a2014926ea0b87594ecb Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 4 Feb 2024 00:55:38 +0300 Subject: [PATCH 185/263] gh-114803: Mention that `@dataclass` should not be applied on enums (GH-114891) Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Ethan Furman <ethan@stoneleaf.us> --- Doc/howto/enum.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 1e9ac9b6761b647..30be15230fc088e 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -497,13 +497,30 @@ the :meth:`~Enum.__repr__` omits the inherited class' name. For example:: >>> Creature.DOG <Creature.DOG: size='medium', legs=4> -Use the :func:`!dataclass` argument ``repr=False`` +Use the :func:`~dataclasses.dataclass` argument ``repr=False`` to use the standard :func:`repr`. .. versionchanged:: 3.12 Only the dataclass fields are shown in the value area, not the dataclass' name. +.. note:: + + Adding :func:`~dataclasses.dataclass` decorator to :class:`Enum` + and its subclasses is not supported. It will not raise any errors, + but it will produce very strange results at runtime, such as members + being equal to each other:: + + >>> @dataclass # don't do this: it does not make any sense + ... class Color(Enum): + ... RED = 1 + ... BLUE = 2 + ... + >>> Color.RED is Color.BLUE + False + >>> Color.RED == Color.BLUE # problem is here: they should not be equal + True + Pickling -------- From 1032326fe46afaef57c3e01160a4f889dadfee95 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Sat, 3 Feb 2024 17:16:03 -0600 Subject: [PATCH 186/263] gh-114883: Fix Makefile dependency tree for non-jit builds (GH-114884) --- Makefile.pre.in | 13 ++++++++++++- configure | 3 +++ configure.ac | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index fff3d3c4914e7a7..aad637876ead809 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2643,7 +2643,18 @@ config.status: $(srcdir)/configure Python/asm_trampoline.o: $(srcdir)/Python/asm_trampoline.S $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< -Python/jit.o: regen-jit + +JIT_DEPS = \ + $(srcdir)/Tools/jit/*.c \ + $(srcdir)/Tools/jit/*.py \ + $(srcdir)/Python/executor_cases.c.h \ + pyconfig.h + +jit_stencils.h: $(JIT_DEPS) + @REGEN_JIT_COMMAND@ + +Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@ + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $< .PHONY: regen-jit regen-jit: diff --git a/configure b/configure index 1d41d7ba66470d7..0375565c2945528 100755 --- a/configure +++ b/configure @@ -920,6 +920,7 @@ LLVM_AR PROFILE_TASK DEF_MAKE_RULE DEF_MAKE_ALL_RULE +JIT_STENCILS_H REGEN_JIT_COMMAND ABIFLAGS LN @@ -8019,12 +8020,14 @@ then : else $as_nop as_fn_append CFLAGS_NODIST " -D_Py_JIT" REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host" + JIT_STENCILS_H="jit_stencils.h" if test "x$Py_DEBUG" = xtrue then : as_fn_append REGEN_JIT_COMMAND " --debug" fi fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_experimental_jit" >&5 printf "%s\n" "$enable_experimental_jit" >&6; } diff --git a/configure.ac b/configure.ac index b29cd028f8f2004..e121e893a1d0d9e 100644 --- a/configure.ac +++ b/configure.ac @@ -1592,11 +1592,13 @@ AS_VAR_IF([enable_experimental_jit], [AS_VAR_APPEND([CFLAGS_NODIST], [" -D_Py_JIT"]) AS_VAR_SET([REGEN_JIT_COMMAND], ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py $host"]) + AS_VAR_SET([JIT_STENCILS_H], ["jit_stencils.h"]) AS_VAR_IF([Py_DEBUG], [true], [AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])], [])]) AC_SUBST([REGEN_JIT_COMMAND]) +AC_SUBST([JIT_STENCILS_H]) AC_MSG_RESULT([$enable_experimental_jit]) # Enable optimization flags From 80734a6872105de874a424478cd0001e23286098 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sat, 3 Feb 2024 18:16:30 -0600 Subject: [PATCH 187/263] Update README.md (#114974) Trivial edit Co-authored-by: Carol Willing <carolcode@willingconsulting.com> --- Tools/wasm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index beb857f69e40da1..23b38c8e93638ad 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -83,7 +83,7 @@ embuilder --pic build zlib bzip2 MINIMAL_PIC ``` -#### Compile a build Python interpreter +### Compile and build Python interpreter From within the container, run the following command: From 848c86786be588312bff948441929816cdd19e28 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:45:35 +0200 Subject: [PATCH 188/263] gh-101100: Fix Sphinx warnings from PEP 3108 stdlib re-organisation (#114327) * Fix Sphinx warnings from PEP 3108 stdblib re-organisation * Apply suggestions from code review Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> * Update Doc/whatsnew/2.2.rst Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> * Apply suggestions from code review Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/whatsnew/2.0.rst | 12 ++++++------ Doc/whatsnew/2.2.rst | 10 +++++----- Doc/whatsnew/2.4.rst | 16 ++++++++-------- Doc/whatsnew/2.5.rst | 18 +++++++++--------- Doc/whatsnew/2.6.rst | 42 +++++++++++++++++++++--------------------- Doc/whatsnew/2.7.rst | 44 ++++++++++++++++++++++---------------------- Doc/whatsnew/3.0.rst | 36 ++++++++++++++++++------------------ Doc/whatsnew/3.5.rst | 2 +- 8 files changed, 90 insertions(+), 90 deletions(-) diff --git a/Doc/whatsnew/2.0.rst b/Doc/whatsnew/2.0.rst index f4a9d23699de53c..af8171487fbcfa2 100644 --- a/Doc/whatsnew/2.0.rst +++ b/Doc/whatsnew/2.0.rst @@ -1039,12 +1039,12 @@ is an implementation of the Secure Socket Layer, which encrypts the data being sent over a socket. When compiling Python, you can edit :file:`Modules/Setup` to include SSL support, which adds an additional function to the :mod:`socket` module: ``socket.ssl(socket, keyfile, certfile)``, which takes a socket -object and returns an SSL socket. The :mod:`httplib` and :mod:`urllib` modules +object and returns an SSL socket. The :mod:`httplib <http>` and :mod:`urllib` modules were also changed to support ``https://`` URLs, though no one has implemented FTP or SMTP over SSL. -The :mod:`httplib` module has been rewritten by Greg Stein to support HTTP/1.1. -Backward compatibility with the 1.5 version of :mod:`httplib` is provided, +The :mod:`httplib <http>` module has been rewritten by Greg Stein to support HTTP/1.1. +Backward compatibility with the 1.5 version of :mod:`!httplib` is provided, though using HTTP/1.1 features such as pipelining will require rewriting code to use a different set of interfaces. @@ -1108,7 +1108,7 @@ module. * :mod:`pyexpat`: An interface to the Expat XML parser. (Contributed by Paul Prescod.) -* :mod:`robotparser`: Parse a :file:`robots.txt` file, which is used for writing +* :mod:`robotparser <urllib.robotparser>`: Parse a :file:`robots.txt` file, which is used for writing web spiders that politely avoid certain areas of a web site. The parser accepts the contents of a :file:`robots.txt` file, builds a set of rules from it, and can then answer questions about the fetchability of a given URL. (Contributed @@ -1129,10 +1129,10 @@ module. :file:`Tools/idle/BrowserControl.py`, and adapted for the standard library by Fred.) -* :mod:`_winreg`: An interface to the Windows registry. :mod:`_winreg` is an +* :mod:`_winreg <winreg>`: An interface to the Windows registry. :mod:`!_winreg` is an adaptation of functions that have been part of PythonWin since 1995, but has now been added to the core distribution, and enhanced to support Unicode. - :mod:`_winreg` was written by Bill Tutt and Mark Hammond. + :mod:`!_winreg` was written by Bill Tutt and Mark Hammond. * :mod:`zipfile`: A module for reading and writing ZIP-format archives. These are archives produced by :program:`PKZIP` on DOS/Windows or :program:`zip` on diff --git a/Doc/whatsnew/2.2.rst b/Doc/whatsnew/2.2.rst index 968bd7a126bdf0b..e6c13f957b8d54a 100644 --- a/Doc/whatsnew/2.2.rst +++ b/Doc/whatsnew/2.2.rst @@ -55,7 +55,7 @@ implemented in C. In particular, it's not possible to subclass built-in types, so you can't just subclass, say, lists in order to add a single useful method to them. The :mod:`!UserList` module provides a class that supports all of the methods of lists and that can be subclassed further, but there's lots of C code -that expects a regular Python list and won't accept a :class:`!UserList` +that expects a regular Python list and won't accept a :class:`~collections.UserList` instance. Python 2.2 fixes this, and in the process adds some exciting new capabilities. @@ -69,7 +69,7 @@ A brief summary: * It's also possible to automatically call methods on accessing or setting an instance attribute by using a new mechanism called :dfn:`properties`. Many uses - of :meth:`!__getattr__` can be rewritten to use properties instead, making the + of :meth:`~object.__getattr__` can be rewritten to use properties instead, making the resulting code simpler and faster. As a small side benefit, attributes can now have docstrings, too. @@ -933,7 +933,7 @@ anyway). New and Improved Modules ======================== -* The :mod:`!xmlrpclib` module was contributed to the standard library by Fredrik +* The :mod:`xmlrpclib <xmlrpc.client>` module was contributed to the standard library by Fredrik Lundh, providing support for writing XML-RPC clients. XML-RPC is a simple remote procedure call protocol built on top of HTTP and XML. For example, the following snippet retrieves a list of RSS channels from the O'Reilly Network, @@ -956,7 +956,7 @@ New and Improved Modules # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ] - The :mod:`!SimpleXMLRPCServer` module makes it easy to create straightforward + The :mod:`SimpleXMLRPCServer <xmlrpc.server>` module makes it easy to create straightforward XML-RPC servers. See http://xmlrpc.scripting.com/ for more information about XML-RPC. * The new :mod:`hmac` module implements the HMAC algorithm described by @@ -964,7 +964,7 @@ New and Improved Modules * Several functions that originally returned lengthy tuples now return pseudo-sequences that still behave like tuples but also have mnemonic attributes such - as :attr:`!memberst_mtime` or :attr:`!tm_year`. The enhanced functions include + as :attr:`!memberst_mtime` or :attr:`~time.struct_time.tm_year`. The enhanced functions include :func:`~os.stat`, :func:`~os.fstat`, :func:`~os.statvfs`, and :func:`~os.fstatvfs` in the :mod:`os` module, and :func:`~time.localtime`, :func:`~time.gmtime`, and :func:`~time.strptime` in the :mod:`time` module. diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index 15d4003622c5065..7e235d4370edaa9 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -1081,7 +1081,7 @@ complete list of changes, or look through the CVS logs for all the details. :func:`nsmallest` that use heaps to find the N largest or smallest values in a dataset without the expense of a full sort. (Contributed by Raymond Hettinger.) -* The :mod:`httplib` module now contains constants for HTTP status codes defined +* The :mod:`httplib <http>` module now contains constants for HTTP status codes defined in various HTTP-related RFC documents. Constants have names such as :const:`OK`, :const:`CREATED`, :const:`CONTINUE`, and :const:`MOVED_PERMANENTLY`; use pydoc to get a full list. (Contributed by @@ -1218,10 +1218,10 @@ complete list of changes, or look through the CVS logs for all the details. now include the string ``'%default'``, which will be replaced by the option's default value. (Contributed by Greg Ward.) -* The long-term plan is to deprecate the :mod:`rfc822` module in some future +* The long-term plan is to deprecate the :mod:`!rfc822` module in some future Python release in favor of the :mod:`email` package. To this end, the - :func:`email.Utils.formatdate` function has been changed to make it usable as a - replacement for :func:`rfc822.formatdate`. You may want to write new e-mail + :func:`email.Utils.formatdate <email.utils.formatdate>` function has been changed to make it usable as a + replacement for :func:`!rfc822.formatdate`. You may want to write new e-mail processing code with this in mind. (Change implemented by Anthony Baxter.) * A new ``urandom(n)`` function was added to the :mod:`os` module, returning @@ -1308,7 +1308,7 @@ complete list of changes, or look through the CVS logs for all the details. sockets, and regular expression pattern objects. (Contributed by Raymond Hettinger.) -* The :mod:`xmlrpclib` module now supports a multi-call extension for +* The :mod:`xmlrpclib <xmlrpc.client>` module now supports a multi-call extension for transmitting multiple XML-RPC calls in a single HTTP operation. (Contributed by Brian Quinlan.) @@ -1323,8 +1323,8 @@ complete list of changes, or look through the CVS logs for all the details. cookielib --------- -The :mod:`cookielib` library supports client-side handling for HTTP cookies, -mirroring the :mod:`Cookie` module's server-side cookie support. Cookies are +The :mod:`cookielib <http.cookiejar>` library supports client-side handling for HTTP cookies, +mirroring the :mod:`Cookie <http.cookies>` module's server-side cookie support. Cookies are stored in cookie jars; the library transparently stores cookies offered by the web server in the cookie jar, and fetches the cookie from the jar when connecting to the server. As in web browsers, policy objects control whether @@ -1335,7 +1335,7 @@ are provided: one that stores cookies in the Netscape format so applications can use the Mozilla or Lynx cookie files, and one that stores cookies in the same format as the Perl libwww library. -:mod:`urllib2` has been changed to interact with :mod:`cookielib`: +:mod:`urllib2 <urllib.request>` has been changed to interact with :mod:`cookielib <http.cookiejar>`: :class:`HTTPCookieProcessor` manages a cookie jar that is used when accessing URLs. diff --git a/Doc/whatsnew/2.5.rst b/Doc/whatsnew/2.5.rst index f45d70ea5a19a03..2ae26e7a106a0bf 100644 --- a/Doc/whatsnew/2.5.rst +++ b/Doc/whatsnew/2.5.rst @@ -1478,8 +1478,8 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch 790710 -* The :mod:`pickle` and :mod:`cPickle` modules no longer accept a return value - of ``None`` from the :meth:`__reduce__` method; the method must return a tuple +* The :mod:`pickle` and :mod:`!cPickle` modules no longer accept a return value + of ``None`` from the :meth:`~object.__reduce__` method; the method must return a tuple of arguments instead. The ability to return ``None`` was deprecated in Python 2.4, so this completes the removal of the feature. @@ -1519,7 +1519,7 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch #1472854 -* The :mod:`SimpleXMLRPCServer` and :mod:`DocXMLRPCServer` classes now have a +* The :mod:`SimpleXMLRPCServer <xmlrpc.server>` and :mod:`DocXMLRPCServer <xmlrpc.server>` classes now have a :attr:`rpc_paths` attribute that constrains XML-RPC operations to a limited set of URL paths; the default is to allow only ``'/'`` and ``'/RPC2'``. Setting :attr:`rpc_paths` to ``None`` or an empty tuple disables this path checking. @@ -1650,9 +1650,9 @@ complete list of changes, or look through the SVN logs for all the details. .. Patch #754022 -* The :mod:`xmlrpclib` module now supports returning :class:`~datetime.datetime` objects - for the XML-RPC date type. Supply ``use_datetime=True`` to the :func:`loads` - function or the :class:`Unmarshaller` class to enable this feature. (Contributed +* The :mod:`xmlrpclib <xmlrpc.client>` module now supports returning :class:`~datetime.datetime` objects + for the XML-RPC date type. Supply ``use_datetime=True`` to the :func:`~xmlrpc.client.loads` + function or the :class:`!Unmarshaller` class to enable this feature. (Contributed by Skip Montanaro.) .. Patch 1120353 @@ -2253,12 +2253,12 @@ code: appeared. In Python 2.5, the argument must be exactly one %char specifier with no surrounding text. -* Library: The :mod:`pickle` and :mod:`cPickle` modules no longer accept a - return value of ``None`` from the :meth:`__reduce__` method; the method must +* Library: The :mod:`pickle` and :mod:`!cPickle` modules no longer accept a + return value of ``None`` from the :meth:`~object.__reduce__` method; the method must return a tuple of arguments instead. The modules also no longer accept the deprecated *bin* keyword parameter. -* Library: The :mod:`SimpleXMLRPCServer` and :mod:`DocXMLRPCServer` classes now +* Library: The :mod:`SimpleXMLRPCServer <xmlrpc.server>` and :mod:`DocXMLRPCServer <xmlrpc.server>` classes now have a :attr:`rpc_paths` attribute that constrains XML-RPC operations to a limited set of URL paths; the default is to allow only ``'/'`` and ``'/RPC2'``. Setting :attr:`rpc_paths` to ``None`` or an empty tuple disables this path diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index c6bab93b7efdda1..7d3769a22286e28 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1082,7 +1082,7 @@ the :mod:`io` module: (In Python 2.6, :class:`io.StringIO` is implemented in pure Python, so it's pretty slow. You should therefore stick with the - existing :mod:`StringIO` module or :mod:`cStringIO` for now. At some + existing :mod:`!StringIO` module or :mod:`!cStringIO` for now. At some point Python 3.0's :mod:`io` module will be rewritten into C for speed, and perhaps the C implementation will be backported to the 2.x releases.) @@ -1807,7 +1807,7 @@ changes, or look through the Subversion logs for all the details. Nubis; :issue:`1817`.) The :func:`parse_qs` and :func:`parse_qsl` functions have been - relocated from the :mod:`!cgi` module to the :mod:`urlparse` module. + relocated from the :mod:`!cgi` module to the :mod:`urlparse <urllib.parse>` module. The versions still available in the :mod:`!cgi` module will trigger :exc:`PendingDeprecationWarning` messages in 2.6 (:issue:`600362`). @@ -1895,8 +1895,8 @@ changes, or look through the Subversion logs for all the details. (Contributed by Raymond Hettinger.) -* The :mod:`Cookie` module's :class:`Morsel` objects now support an - :attr:`httponly` attribute. In some browsers. cookies with this attribute +* The :mod:`Cookie <http.cookies>` module's :class:`~http.cookies.Morsel` objects now support an + :attr:`~http.cookies.Morsel.httponly` attribute. In some browsers. cookies with this attribute set cannot be accessed or manipulated by JavaScript code. (Contributed by Arvin Schnell; :issue:`1638033`.) @@ -1987,8 +1987,8 @@ changes, or look through the Subversion logs for all the details. (Contributed by Raymond Hettinger.) * An optional ``timeout`` parameter, specifying a timeout measured in - seconds, was added to the :class:`httplib.HTTPConnection` and - :class:`HTTPSConnection` class constructors. (Added by Facundo + seconds, was added to the :class:`httplib.HTTPConnection <http.client.HTTPConnection>` and + :class:`HTTPSConnection <http.client.HTTPSConnection>` class constructors. (Added by Facundo Batista.) * Most of the :mod:`inspect` module's functions, such as @@ -2371,10 +2371,10 @@ changes, or look through the Subversion logs for all the details. ``socket(socket.AF_INET, ...)`` may be all that's required to make your code work with IPv6. -* The base classes in the :mod:`SocketServer` module now support - calling a :meth:`handle_timeout` method after a span of inactivity - specified by the server's :attr:`timeout` attribute. (Contributed - by Michael Pomraning.) The :meth:`serve_forever` method +* The base classes in the :mod:`SocketServer <socketserver>` module now support + calling a :meth:`~socketserver.BaseServer.handle_timeout` method after a span of inactivity + specified by the server's :attr:`~socketserver.BaseServer.timeout` attribute. (Contributed + by Michael Pomraning.) The :meth:`~socketserver.BaseServer.serve_forever` method now takes an optional poll interval measured in seconds, controlling how often the server will check for a shutdown request. (Contributed by Pedro Werneck and Jeffrey Yasskin; @@ -2478,9 +2478,9 @@ changes, or look through the Subversion logs for all the details. ``with tempfile.NamedTemporaryFile() as tmp: ...``. (Contributed by Alexander Belopolsky; :issue:`2021`.) -* The :mod:`test.test_support` module gained a number +* The :mod:`test.test_support <test.support>` module gained a number of context managers useful for writing tests. - :func:`EnvironmentVarGuard` is a + :func:`~test.support.os_helper.EnvironmentVarGuard` is a context manager that temporarily changes environment variables and automatically restores them to their old values. @@ -2577,9 +2577,9 @@ changes, or look through the Subversion logs for all the details. (:issue:`1513695`) * An optional ``timeout`` parameter was added to the - :func:`urllib.urlopen` function and the + :func:`urllib.urlopen <urllib.request.urlopen>` function and the :class:`urllib.ftpwrapper` class constructor, as well as the - :func:`urllib2.urlopen` function. The parameter specifies a timeout + :func:`urllib2.urlopen <urllib.request.urlopen>` function. The parameter specifies a timeout measured in seconds. For example:: >>> u = urllib2.urlopen("http://slow.example.com", @@ -2604,7 +2604,7 @@ changes, or look through the Subversion logs for all the details. intended for testing purposes that lets you temporarily modify the warning filters and then restore their original values (:issue:`3781`). -* The XML-RPC :class:`SimpleXMLRPCServer` and :class:`DocXMLRPCServer` +* The XML-RPC :class:`SimpleXMLRPCServer <xmlrpc.server>` and :class:`DocXMLRPCServer <xmlrpc.server>` classes can now be prevented from immediately opening and binding to their socket by passing ``False`` as the *bind_and_activate* constructor parameter. This can be used to modify the instance's @@ -2621,11 +2621,11 @@ changes, or look through the Subversion logs for all the details. information. (Contributed by Alan McIntyre as part of his project for Google's Summer of Code 2007.) -* The :mod:`xmlrpclib` module no longer automatically converts +* The :mod:`xmlrpclib <xmlrpc.client>` module no longer automatically converts :class:`datetime.date` and :class:`datetime.time` to the - :class:`xmlrpclib.DateTime` type; the conversion semantics were + :class:`xmlrpclib.DateTime <xmlrpc.client.DateTime>` type; the conversion semantics were not necessarily correct for all applications. Code using - :mod:`xmlrpclib` should convert :class:`date` and :class:`~datetime.time` + :mod:`!xmlrpclib` should convert :class:`date` and :class:`~datetime.time` instances. (:issue:`1330538`) The code can also handle dates before 1900 (contributed by Ralf Schmitt; :issue:`2014`) and 64-bit integers represented by using ``<i8>`` in XML-RPC responses @@ -3274,11 +3274,11 @@ that may require changes to your code: :exc:`StandardError` but now it is, through :exc:`IOError`. (Implemented by Gregory P. Smith; :issue:`1706815`.) -* The :mod:`xmlrpclib` module no longer automatically converts +* The :mod:`xmlrpclib <xmlrpc.client>` module no longer automatically converts :class:`datetime.date` and :class:`datetime.time` to the - :class:`xmlrpclib.DateTime` type; the conversion semantics were + :class:`xmlrpclib.DateTime <xmlrpc.client.DateTime>` type; the conversion semantics were not necessarily correct for all applications. Code using - :mod:`xmlrpclib` should convert :class:`date` and :class:`~datetime.time` + :mod:`!xmlrpclib` should convert :class:`date` and :class:`~datetime.time` instances. (:issue:`1330538`) * (3.0-warning mode) The :class:`Exception` class now warns diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 524967b45242344..ada05aa22b46f66 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -915,7 +915,7 @@ used with the :option:`-W` switch, separated by commas. (Contributed by Brian Curtin; :issue:`7301`.) For example, the following setting will print warnings every time -they occur, but turn warnings from the :mod:`Cookie` module into an +they occur, but turn warnings from the :mod:`Cookie <http.cookies>` module into an error. (The exact syntax for setting an environment variable varies across operating systems and shells.) @@ -1012,12 +1012,12 @@ Several performance enhancements have been added: scan. This is sometimes faster by a factor of 10. (Added by Florent Xicluna; :issue:`7462` and :issue:`7622`.) -* The :mod:`pickle` and :mod:`cPickle` modules now automatically +* The :mod:`pickle` and :mod:`!cPickle` modules now automatically intern the strings used for attribute names, reducing memory usage of the objects resulting from unpickling. (Contributed by Jake McGuire; :issue:`5084`.) -* The :mod:`cPickle` module now special-cases dictionaries, +* The :mod:`!cPickle` module now special-cases dictionaries, nearly halving the time required to pickle them. (Contributed by Collin Winter; :issue:`5670`.) @@ -1163,7 +1163,7 @@ changes, or look through the Subversion logs for all the details. statement, has been deprecated, because the :keyword:`!with` statement now supports multiple context managers. -* The :mod:`cookielib` module now ignores cookies that have an invalid +* The :mod:`cookielib <http.cookiejar>` module now ignores cookies that have an invalid version field, one that doesn't contain an integer value. (Fixed by John J. Lee; :issue:`3924`.) @@ -1306,11 +1306,11 @@ changes, or look through the Subversion logs for all the details. ``('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')``. (Contributed by Carl Chenet; :issue:`7418`.) -* The default :class:`~httplib.HTTPResponse` class used by the :mod:`httplib` module now +* The default :class:`~http.client.HTTPResponse` class used by the :mod:`httplib <http>` module now supports buffering, resulting in much faster reading of HTTP responses. (Contributed by Kristján Valur Jónsson; :issue:`4879`.) - The :class:`~httplib.HTTPConnection` and :class:`~httplib.HTTPSConnection` classes + The :class:`~http.client.HTTPConnection` and :class:`~http.client.HTTPSConnection` classes now support a *source_address* parameter, a ``(host, port)`` 2-tuple giving the source address that will be used for the connection. (Contributed by Eldon Ziegler; :issue:`3972`.) @@ -1518,16 +1518,16 @@ changes, or look through the Subversion logs for all the details. the :class:`bytearray` and :class:`memoryview` objects. (Implemented by Antoine Pitrou; :issue:`8104`.) -* The :mod:`SocketServer` module's :class:`~SocketServer.TCPServer` class now +* The :mod:`SocketServer <socketserver>` module's :class:`~socketserver.TCPServer` class now supports socket timeouts and disabling the Nagle algorithm. - The :attr:`~SocketServer.TCPServer.disable_nagle_algorithm` class attribute + The :attr:`!disable_nagle_algorithm` class attribute defaults to ``False``; if overridden to be true, new request connections will have the TCP_NODELAY option set to prevent buffering many small sends into a single TCP packet. - The :attr:`~SocketServer.BaseServer.timeout` class attribute can hold + The :attr:`~socketserver.BaseServer.timeout` class attribute can hold a timeout in seconds that will be applied to the request socket; if - no request is received within that time, :meth:`~SocketServer.BaseServer.handle_timeout` - will be called and :meth:`~SocketServer.BaseServer.handle_request` will return. + no request is received within that time, :meth:`~socketserver.BaseServer.handle_timeout` + will be called and :meth:`~socketserver.BaseServer.handle_request` will return. (Contributed by Kristján Valur Jónsson; :issue:`6192` and :issue:`6267`.) * Updated module: the :mod:`sqlite3` module has been updated to @@ -1648,7 +1648,7 @@ changes, or look through the Subversion logs for all the details. and has been updated to version 5.2.0 (updated by Florent Xicluna; :issue:`8024`). -* The :mod:`urlparse` module's :func:`~urlparse.urlsplit` now handles +* The :mod:`urlparse <urllib.parse>` module's :func:`~urllib.parse.urlsplit` now handles unknown URL schemes in a fashion compliant with :rfc:`3986`: if the URL is of the form ``"<something>://..."``, the text before the ``://`` is treated as the scheme, even if it's a made-up scheme that @@ -1675,7 +1675,7 @@ changes, or look through the Subversion logs for all the details. (Python 2.7 actually produces slightly different output, since it returns a named tuple instead of a standard tuple.) - The :mod:`urlparse` module also supports IPv6 literal addresses as defined by + The :mod:`urlparse <urllib.parse>` module also supports IPv6 literal addresses as defined by :rfc:`2732` (contributed by Senthil Kumaran; :issue:`2987`). .. doctest:: @@ -1697,8 +1697,8 @@ changes, or look through the Subversion logs for all the details. or comment (which looks like ``<!-- comment -->``). (Patch by Neil Muller; :issue:`2746`.) -* The XML-RPC client and server, provided by the :mod:`xmlrpclib` and - :mod:`SimpleXMLRPCServer` modules, have improved performance by +* The XML-RPC client and server, provided by the :mod:`xmlrpclib <xmlrpc.client>` and + :mod:`SimpleXMLRPCServer <xmlrpc.server>` modules, have improved performance by supporting HTTP/1.1 keep-alive and by optionally using gzip encoding to compress the XML being exchanged. The gzip compression is controlled by the :attr:`encode_threshold` attribute of @@ -2334,11 +2334,11 @@ Port-Specific Changes: Windows and :data:`LIBRARIES_ASSEMBLY_NAME_PREFIX`. (Contributed by David Cournapeau; :issue:`4365`.) -* The :mod:`_winreg` module for accessing the registry now implements - the :func:`~_winreg.CreateKeyEx` and :func:`~_winreg.DeleteKeyEx` +* The :mod:`_winreg <winreg>` module for accessing the registry now implements + the :func:`~winreg.CreateKeyEx` and :func:`~winreg.DeleteKeyEx` functions, extended versions of previously supported functions that - take several extra arguments. The :func:`~_winreg.DisableReflectionKey`, - :func:`~_winreg.EnableReflectionKey`, and :func:`~_winreg.QueryReflectionKey` + take several extra arguments. The :func:`~winreg.DisableReflectionKey`, + :func:`~winreg.EnableReflectionKey`, and :func:`~winreg.QueryReflectionKey` were also tested and documented. (Implemented by Brian Curtin: :issue:`7347`.) @@ -2508,7 +2508,7 @@ In the standard library: which raises an exception if there's an error. (Changed by Lars Gustäbel; :issue:`7357`.) -* The :mod:`urlparse` module's :func:`~urlparse.urlsplit` now handles +* The :mod:`urlparse <urllib.parse>` module's :func:`~urllib.parse.urlsplit` now handles unknown URL schemes in a fashion compliant with :rfc:`3986`: if the URL is of the form ``"<something>://..."``, the text before the ``://`` is treated as the scheme, even if it's a made-up scheme that @@ -2711,8 +2711,8 @@ and :ref:`setuptools-index`. PEP 476: Enabling certificate verification by default for stdlib http clients ----------------------------------------------------------------------------- -:pep:`476` updated :mod:`httplib` and modules which use it, such as -:mod:`urllib2` and :mod:`xmlrpclib`, to now verify that the server +:pep:`476` updated :mod:`httplib <http>` and modules which use it, such as +:mod:`urllib2 <urllib.request>` and :mod:`xmlrpclib`, to now verify that the server presents a certificate which is signed by a Certificate Authority in the platform trust store and whose hostname matches the hostname being requested by default, significantly improving security for many applications. This diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst index 1df5209f22c6a5b..888e6279754fc29 100644 --- a/Doc/whatsnew/3.0.rst +++ b/Doc/whatsnew/3.0.rst @@ -337,7 +337,7 @@ changed. (However, the standard library remains ASCII-only with the exception of contributor names in comments.) -* The :mod:`StringIO` and :mod:`cStringIO` modules are gone. Instead, +* The :mod:`!StringIO` and :mod:`!cStringIO` modules are gone. Instead, import the :mod:`io` module and use :class:`io.StringIO` or :class:`io.BytesIO` for text and data respectively. @@ -563,7 +563,7 @@ review: removal in Python 3.0 due to lack of use or because a better replacement exists. See :pep:`3108` for an exhaustive list. -* The :mod:`bsddb3` package was removed because its presence in the +* The :mod:`!bsddb3` package was removed because its presence in the core standard library has proved over time to be a particular burden for the core developers due to testing instability and Berkeley DB's release schedule. However, the package is alive and well, @@ -588,40 +588,40 @@ review: * A common pattern in Python 2.x is to have one version of a module implemented in pure Python, with an optional accelerated version implemented as a C extension; for example, :mod:`pickle` and - :mod:`cPickle`. This places the burden of importing the accelerated + :mod:`!cPickle`. This places the burden of importing the accelerated version and falling back on the pure Python version on each user of these modules. In Python 3.0, the accelerated versions are considered implementation details of the pure Python versions. Users should always import the standard version, which attempts to import the accelerated version and falls back to the pure Python - version. The :mod:`pickle` / :mod:`cPickle` pair received this + version. The :mod:`pickle` / :mod:`!cPickle` pair received this treatment. The :mod:`profile` module is on the list for 3.1. The - :mod:`StringIO` module has been turned into a class in the :mod:`io` + :mod:`!StringIO` module has been turned into a class in the :mod:`io` module. * Some related modules have been grouped into packages, and usually the submodule names have been simplified. The resulting new packages are: - * :mod:`dbm` (:mod:`anydbm`, :mod:`dbhash`, :mod:`dbm`, - :mod:`dumbdbm`, :mod:`gdbm`, :mod:`whichdb`). + * :mod:`dbm` (:mod:`!anydbm`, :mod:`!dbhash`, :mod:`!dbm`, + :mod:`!dumbdbm`, :mod:`!gdbm`, :mod:`!whichdb`). - * :mod:`html` (:mod:`HTMLParser`, :mod:`htmlentitydefs`). + * :mod:`html` (:mod:`!HTMLParser`, :mod:`!htmlentitydefs`). - * :mod:`http` (:mod:`httplib`, :mod:`BaseHTTPServer`, - :mod:`CGIHTTPServer`, :mod:`SimpleHTTPServer`, :mod:`Cookie`, - :mod:`cookielib`). + * :mod:`http` (:mod:`!httplib`, :mod:`!BaseHTTPServer`, + :mod:`!CGIHTTPServer`, :mod:`!SimpleHTTPServer`, :mod:`!Cookie`, + :mod:`!cookielib`). * :mod:`tkinter` (all :mod:`Tkinter`-related modules except :mod:`turtle`). The target audience of :mod:`turtle` doesn't really care about :mod:`tkinter`. Also note that as of Python 2.6, the functionality of :mod:`turtle` has been greatly enhanced. - * :mod:`urllib` (:mod:`urllib`, :mod:`urllib2`, :mod:`urlparse`, - :mod:`robotparse`). + * :mod:`urllib` (:mod:`!urllib`, :mod:`!urllib2`, :mod:`!urlparse`, + :mod:`!robotparse`). - * :mod:`xmlrpc` (:mod:`xmlrpclib`, :mod:`DocXMLRPCServer`, - :mod:`SimpleXMLRPCServer`). + * :mod:`xmlrpc` (:mod:`!xmlrpclib`, :mod:`!DocXMLRPCServer`, + :mod:`!SimpleXMLRPCServer`). Some other changes to standard library modules, not covered by :pep:`3108`: @@ -642,9 +642,9 @@ Some other changes to standard library modules, not covered by * Cleanup of the :mod:`operator` module: removed :func:`sequenceIncludes` and :func:`isCallable`. -* Cleanup of the :mod:`thread` module: :func:`acquire_lock` and - :func:`release_lock` are gone; use :func:`acquire` and - :func:`release` instead. +* Cleanup of the :mod:`!thread` module: :func:`!acquire_lock` and + :func:`!release_lock` are gone; use :meth:`~threading.Lock.acquire` and + :meth:`~threading.Lock.release` instead. * Cleanup of the :mod:`random` module: removed the :func:`jumpahead` API. diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 1c7a9270af0aabc..5c2ec230441b426 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -2418,7 +2418,7 @@ Changes in the Python API (Contributed by Victor Stinner in :issue:`21205`.) * The deprecated "strict" mode and argument of :class:`~html.parser.HTMLParser`, - :meth:`HTMLParser.error`, and the :exc:`HTMLParserError` exception have been + :meth:`!HTMLParser.error`, and the :exc:`!HTMLParserError` exception have been removed. (Contributed by Ezio Melotti in :issue:`15114`.) The *convert_charrefs* argument of :class:`~html.parser.HTMLParser` is now ``True`` by default. (Contributed by Berker Peksag in :issue:`21047`.) From ec69e1d0ddc9906e0fb755a5234aeabdc96450ab Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Sun, 4 Feb 2024 08:24:24 -0600 Subject: [PATCH 189/263] gh-101100: Fix dangling references in pickle.rst (#114972) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Doc/library/pickle.rst | 51 +++++++++++++++++++++--------------------- Doc/tools/.nitignore | 1 - 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index 1b718abfa481a02..acada092afb679b 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -356,7 +356,7 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`, :func:`copyreg.pickle`. It is a mapping whose keys are classes and whose values are reduction functions. A reduction function takes a single argument of the associated class and should - conform to the same interface as a :meth:`__reduce__` + conform to the same interface as a :meth:`~object.__reduce__` method. By default, a pickler object will not have a @@ -376,7 +376,7 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`, Special reducer that can be defined in :class:`Pickler` subclasses. This method has priority over any reducer in the :attr:`dispatch_table`. It - should conform to the same interface as a :meth:`__reduce__` method, and + should conform to the same interface as a :meth:`~object.__reduce__` method, and can optionally return ``NotImplemented`` to fallback on :attr:`dispatch_table`-registered reducers to pickle ``obj``. @@ -516,7 +516,7 @@ The following types can be pickled: * classes accessible from the top level of a module; -* instances of such classes whose the result of calling :meth:`__getstate__` +* instances of such classes whose the result of calling :meth:`~object.__getstate__` is picklable (see section :ref:`pickle-inst` for details). Attempts to pickle unpicklable objects will raise the :exc:`PicklingError` @@ -552,7 +552,7 @@ purpose, so you can fix bugs in a class or add methods to the class and still load objects that were created with an earlier version of the class. If you plan to have long-lived objects that will see many versions of a class, it may be worthwhile to put a version number in the objects so that suitable -conversions can be made by the class's :meth:`__setstate__` method. +conversions can be made by the class's :meth:`~object.__setstate__` method. .. _pickle-inst: @@ -567,7 +567,7 @@ customize, and control how class instances are pickled and unpickled. In most cases, no additional code is needed to make instances picklable. By default, pickle will retrieve the class and the attributes of an instance via -introspection. When a class instance is unpickled, its :meth:`__init__` method +introspection. When a class instance is unpickled, its :meth:`~object.__init__` method is usually *not* invoked. The default behaviour first creates an uninitialized instance and then restores the saved attributes. The following code shows an implementation of this behaviour:: @@ -658,30 +658,30 @@ methods: Refer to the section :ref:`pickle-state` for more information about how to use -the methods :meth:`__getstate__` and :meth:`__setstate__`. +the methods :meth:`~object.__getstate__` and :meth:`~object.__setstate__`. .. note:: - At unpickling time, some methods like :meth:`__getattr__`, - :meth:`__getattribute__`, or :meth:`__setattr__` may be called upon the + At unpickling time, some methods like :meth:`~object.__getattr__`, + :meth:`~object.__getattribute__`, or :meth:`~object.__setattr__` may be called upon the instance. In case those methods rely on some internal invariant being - true, the type should implement :meth:`__new__` to establish such an - invariant, as :meth:`__init__` is not called when unpickling an + true, the type should implement :meth:`~object.__new__` to establish such an + invariant, as :meth:`~object.__init__` is not called when unpickling an instance. .. index:: pair: copy; protocol As we shall see, pickle does not use directly the methods described above. In fact, these methods are part of the copy protocol which implements the -:meth:`__reduce__` special method. The copy protocol provides a unified +:meth:`~object.__reduce__` special method. The copy protocol provides a unified interface for retrieving the data necessary for pickling and copying objects. [#]_ -Although powerful, implementing :meth:`__reduce__` directly in your classes is +Although powerful, implementing :meth:`~object.__reduce__` directly in your classes is error prone. For this reason, class designers should use the high-level -interface (i.e., :meth:`__getnewargs_ex__`, :meth:`__getstate__` and -:meth:`__setstate__`) whenever possible. We will show, however, cases where -using :meth:`__reduce__` is the only option or leads to more efficient pickling +interface (i.e., :meth:`~object.__getnewargs_ex__`, :meth:`~object.__getstate__` and +:meth:`~object.__setstate__`) whenever possible. We will show, however, cases where +using :meth:`!__reduce__` is the only option or leads to more efficient pickling or both. .. method:: object.__reduce__() @@ -716,8 +716,9 @@ or both. These items will be appended to the object either using ``obj.append(item)`` or, in batch, using ``obj.extend(list_of_items)``. This is primarily used for list subclasses, but may be used by other - classes as long as they have :meth:`append` and :meth:`extend` methods with - the appropriate signature. (Whether :meth:`append` or :meth:`extend` is + classes as long as they have + :ref:`append and extend methods <typesseq-common>` with + the appropriate signature. (Whether :meth:`!append` or :meth:`!extend` is used depends on which pickle protocol version is used as well as the number of items to append, so both must be supported.) @@ -793,8 +794,8 @@ any other code which depends on pickling, then one can create a pickler with a private dispatch table. The global dispatch table managed by the :mod:`copyreg` module is -available as :data:`copyreg.dispatch_table`. Therefore, one may -choose to use a modified copy of :data:`copyreg.dispatch_table` as a +available as :data:`!copyreg.dispatch_table`. Therefore, one may +choose to use a modified copy of :data:`!copyreg.dispatch_table` as a private dispatch table. For example :: @@ -833,12 +834,12 @@ Handling Stateful Objects single: __setstate__() (copy protocol) Here's an example that shows how to modify pickling behavior for a class. -The :class:`TextReader` class opens a text file, and returns the line number and +The :class:`!TextReader` class below opens a text file, and returns the line number and line contents each time its :meth:`!readline` method is called. If a -:class:`TextReader` instance is pickled, all attributes *except* the file object +:class:`!TextReader` instance is pickled, all attributes *except* the file object member are saved. When the instance is unpickled, the file is reopened, and -reading resumes from the last location. The :meth:`__setstate__` and -:meth:`__getstate__` methods are used to implement this behavior. :: +reading resumes from the last location. The :meth:`!__setstate__` and +:meth:`!__getstate__` methods are used to implement this behavior. :: class TextReader: """Print and number lines in a text file.""" @@ -903,7 +904,7 @@ functions and classes. For those cases, it is possible to subclass from the :class:`Pickler` class and implement a :meth:`~Pickler.reducer_override` method. This method can return an -arbitrary reduction tuple (see :meth:`__reduce__`). It can alternatively return +arbitrary reduction tuple (see :meth:`~object.__reduce__`). It can alternatively return ``NotImplemented`` to fallback to the traditional behavior. If both the :attr:`~Pickler.dispatch_table` and @@ -971,7 +972,7 @@ provided by pickle protocol 5 and higher. Provider API ^^^^^^^^^^^^ -The large data objects to be pickled must implement a :meth:`__reduce_ex__` +The large data objects to be pickled must implement a :meth:`~object.__reduce_ex__` method specialized for protocol 5 and higher, which returns a :class:`PickleBuffer` instance (instead of e.g. a :class:`bytes` object) for any large data. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7127f30f240ce7a..f96478b45e44c00 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -46,7 +46,6 @@ Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/optparse.rst Doc/library/os.rst -Doc/library/pickle.rst Doc/library/pickletools.rst Doc/library/platform.rst Doc/library/plistlib.rst From ff7588b729a2a414ea189a2012904da3fbd1401c Mon Sep 17 00:00:00 2001 From: Ethan Furman <ethan@stoneleaf.us> Date: Sun, 4 Feb 2024 07:22:55 -0800 Subject: [PATCH 190/263] gh-114071: [Enum] update docs and code for tuples/subclasses (GH-114871) Update documentation with `__new__` and `__init__` entries. Support use of `auto()` in tuple subclasses on member assignment lines. Previously, auto() was only supported on the member definition line either solo or as part of a tuple: RED = auto() BLUE = auto(), 'azul' However, since Python itself supports using tuple subclasses where tuples are expected, e.g.: from collections import namedtuple T = namedtuple('T', 'first second third') def test(one, two, three): print(one, two, three) test(*T(4, 5, 6)) # 4 5 6 it made sense to also support tuple subclasses in enum definitions. --- Doc/library/enum.rst | 29 ++++++++++++++-- Lib/enum.py | 10 ++++-- Lib/test/test_enum.py | 34 +++++++++++++++++++ ...-02-01-10-19-11.gh-issue-114071.vkm2G_.rst | 1 + 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 07b15e23b2c10a4..f31e6ea848f3b2e 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -337,6 +337,17 @@ Data Types >>> PowersOfThree.SECOND.value 9 + .. method:: Enum.__init__(self, \*args, \**kwds) + + By default, does nothing. If multiple values are given in the member + assignment, those values become separate arguments to ``__init__``; e.g. + + >>> from enum import Enum + >>> class Weekday(Enum): + ... MONDAY = 1, 'Mon' + + ``Weekday.__init__()`` would be called as ``Weekday.__init__(self, 1, 'Mon')`` + .. method:: Enum.__init_subclass__(cls, \**kwds) A *classmethod* that is used to further configure subsequent subclasses. @@ -364,6 +375,18 @@ Data Types >>> Build('deBUG') <Build.DEBUG: 'debug'> + .. method:: Enum.__new__(cls, \*args, \**kwds) + + By default, doesn't exist. If specified, either in the enum class + definition or in a mixin class (such as ``int``), all values given + in the member assignment will be passed; e.g. + + >>> from enum import Enum + >>> class MyIntEnum(Enum): + ... SEVENTEEN = '1a', 16 + + results in the call ``int('1a', 16)`` and a value of ``17`` for the member. + .. method:: Enum.__repr__(self) Returns the string used for *repr()* calls. By default, returns the @@ -477,9 +500,9 @@ Data Types .. class:: Flag - *Flag* members support the bitwise operators ``&`` (*AND*), ``|`` (*OR*), - ``^`` (*XOR*), and ``~`` (*INVERT*); the results of those operators are members - of the enumeration. + ``Flag`` is the same as :class:`Enum`, but its members support the bitwise + operators ``&`` (*AND*), ``|`` (*OR*), ``^`` (*XOR*), and ``~`` (*INVERT*); + the results of those operators are members of the enumeration. .. method:: __contains__(self, value) diff --git a/Lib/enum.py b/Lib/enum.py index a8a50a583803758..98a8966f5eb1599 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -409,10 +409,11 @@ def __setitem__(self, key, value): if isinstance(value, auto): single = True value = (value, ) - if type(value) is tuple and any(isinstance(v, auto) for v in value): + if isinstance(value, tuple) and any(isinstance(v, auto) for v in value): # insist on an actual tuple, no subclasses, in keeping with only supporting # top-level auto() usage (not contained in any other data structure) auto_valued = [] + t = type(value) for v in value: if isinstance(v, auto): non_auto_store = False @@ -427,7 +428,12 @@ def __setitem__(self, key, value): if single: value = auto_valued[0] else: - value = tuple(auto_valued) + try: + # accepts iterable as multiple arguments? + value = t(auto_valued) + except TypeError: + # then pass them in singlely + value = t(*auto_valued) self._member_names[key] = None if non_auto_store: self._last_values.append(value) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index d045739efa46b8b..39c1ae0ad5a0787 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2344,6 +2344,40 @@ class SomeTuple(tuple, Enum): globals()['SomeTuple'] = SomeTuple test_pickle_dump_load(self.assertIs, SomeTuple.first) + def test_tuple_subclass_with_auto_1(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(T, Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = auto(), 'for the money' + second = auto(), 'for the show' + third = auto(), 'for the music' + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.index, 1) + self.assertEqual(SomeEnum.second.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + + def test_tuple_subclass_with_auto_2(self): + from collections import namedtuple + T = namedtuple('T', 'index desc') + class SomeEnum(Enum): + __qualname__ = 'SomeEnum' # needed for pickle protocol 4 + first = T(auto(), 'for the money') + second = T(auto(), 'for the show') + third = T(auto(), 'for the music') + self.assertIs(type(SomeEnum.first), SomeEnum) + self.assertEqual(SomeEnum.third.value, (3, 'for the music')) + self.assertIsInstance(SomeEnum.third.value, T) + self.assertEqual(SomeEnum.first.value.index, 1) + self.assertEqual(SomeEnum.second.value.desc, 'for the show') + globals()['SomeEnum'] = SomeEnum + globals()['T'] = T + test_pickle_dump_load(self.assertIs, SomeEnum.first) + def test_duplicate_values_give_unique_enum_items(self): class AutoNumber(Enum): first = () diff --git a/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst b/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst new file mode 100644 index 000000000000000..587ce4d21576371 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-01-10-19-11.gh-issue-114071.vkm2G_.rst @@ -0,0 +1 @@ +Support tuple subclasses using auto() for enum member value. From fc060969117f5a5dc96c220eb91b1e2f863d71cf Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:23:26 +0200 Subject: [PATCH 191/263] gh-83383: Always mark the dbm.dumb database as unmodified after open() and sync() (GH-114560) The directory file for a newly created database is now created immediately after opening instead of deferring this until synchronizing or closing. --- Lib/dbm/dumb.py | 4 +- Lib/test/test_dbm_dumb.py | 72 +++++++++++++++++++ ...4-01-25-19-22-17.gh-issue-83383.3GwO9v.rst | 5 ++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py index 754624ccc8f5008..def120ffc3778b6 100644 --- a/Lib/dbm/dumb.py +++ b/Lib/dbm/dumb.py @@ -98,7 +98,8 @@ def _update(self, flag): except OSError: if flag not in ('c', 'n'): raise - self._modified = True + with self._io.open(self._dirfile, 'w', encoding="Latin-1") as f: + self._chmod(self._dirfile) else: with f: for line in f: @@ -134,6 +135,7 @@ def _commit(self): # position; UTF-8, though, does care sometimes. entry = "%r, %r\n" % (key.decode('Latin-1'), pos_and_siz_pair) f.write(entry) + self._modified = False sync = _commit diff --git a/Lib/test/test_dbm_dumb.py b/Lib/test/test_dbm_dumb.py index a481175b3bfdbde..672f9092207cf62 100644 --- a/Lib/test/test_dbm_dumb.py +++ b/Lib/test/test_dbm_dumb.py @@ -246,9 +246,27 @@ def test_missing_data(self): _delete_files() with self.assertRaises(FileNotFoundError): dumbdbm.open(_fname, value) + self.assertFalse(os.path.exists(_fname + '.dat')) self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + for value in ('c', 'n'): + _delete_files() + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dat')) + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + def test_missing_index(self): with dumbdbm.open(_fname, 'n') as f: pass @@ -259,6 +277,60 @@ def test_missing_index(self): self.assertFalse(os.path.exists(_fname + '.dir')) self.assertFalse(os.path.exists(_fname + '.bak')) + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + + def test_sync_empty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + + def test_sync_nonempty_unmodified(self): + with dumbdbm.open(_fname, 'n') as f: + pass + os.unlink(_fname + '.dir') + for value in ('c', 'n'): + with dumbdbm.open(_fname, value) as f: + f['key'] = 'value' + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + f.sync() + self.assertTrue(os.path.exists(_fname + '.dir')) + self.assertTrue(os.path.exists(_fname + '.bak')) + os.unlink(_fname + '.dir') + os.unlink(_fname + '.bak') + f.sync() + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + self.assertFalse(os.path.exists(_fname + '.dir')) + self.assertFalse(os.path.exists(_fname + '.bak')) + def test_invalid_flag(self): for flag in ('x', 'rf', None): with self.assertRaisesRegex(ValueError, diff --git a/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst new file mode 100644 index 000000000000000..e6336204dfa2369 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-25-19-22-17.gh-issue-83383.3GwO9v.rst @@ -0,0 +1,5 @@ +Synchronization of the :mod:`dbm.dumb` database is now no-op if there was no +modification since opening or last synchronization. +The directory file for a newly created empty :mod:`dbm.dumb` database is now +created immediately after opening instead of deferring this until +synchronizing or closing. From ca715e56a13feabc15c368898df6511613d18987 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:25:21 +0200 Subject: [PATCH 192/263] gh-69893: Add the close() method for xml.etree.ElementTree.iterparse() iterator (GH-114534) --- Doc/library/xml.etree.elementtree.rst | 5 ++ Doc/whatsnew/3.13.rst | 8 ++ Lib/test/test_xml_etree.py | 85 ++++++++++++++++++- Lib/xml/etree/ElementTree.py | 9 +- ...4-01-24-17-25-18.gh-issue-69893.PQq5fR.rst | 2 + 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index bb6773c361a9b41..75a7915c15240d2 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -625,6 +625,8 @@ Functions target. Returns an :term:`iterator` providing ``(event, elem)`` pairs; it has a ``root`` attribute that references the root element of the resulting XML tree once *source* is fully read. + The iterator has the :meth:`!close` method that closes the internal + file object if *source* is a filename. Note that while :func:`iterparse` builds the tree incrementally, it issues blocking reads on *source* (or the file it names). As such, it's unsuitable @@ -647,6 +649,9 @@ Functions .. versionchanged:: 3.8 The ``comment`` and ``pi`` events were added. + .. versionchanged:: 3.13 + Added the :meth:`!close` method. + .. function:: parse(source, parser=None) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index f17c6ec0775beff..77f4fce6c321fee 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -472,6 +472,14 @@ warnings warning may also be emitted when a decorated function or class is used at runtime. See :pep:`702`. (Contributed by Jelle Zijlstra in :gh:`104003`.) +xml.etree.ElementTree +--------------------- + +* Add the :meth:`!close` method for the iterator returned by + :func:`~xml.etree.ElementTree.iterparse` for explicit cleaning up. + (Contributed by Serhiy Storchaka in :gh:`69893`.) + + Optimizations ============= diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 221545b315fa44d..a435ec7822ea0cb 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -555,6 +555,17 @@ def test_iterparse(self): ('end', '{namespace}root'), ]) + with open(SIMPLE_XMLFILE, 'rb') as source: + context = iterparse(source) + action, elem = next(context) + self.assertEqual((action, elem.tag), ('end', 'element')) + self.assertEqual([(action, elem.tag) for action, elem in context], [ + ('end', 'element'), + ('end', 'empty-element'), + ('end', 'root'), + ]) + self.assertEqual(context.root.tag, 'root') + events = () context = iterparse(SIMPLE_XMLFILE, events) self.assertEqual([(action, elem.tag) for action, elem in context], []) @@ -646,12 +657,81 @@ def test_iterparse(self): # Not exhausting the iterator still closes the resource (bpo-43292) with warnings_helper.check_no_resource_warning(self): - it = iterparse(TESTFN) + it = iterparse(SIMPLE_XMLFILE) del it + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + it.close() + del it + + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + del it, elem + + with warnings_helper.check_no_resource_warning(self): + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + it.close() + self.assertEqual((action, elem.tag), ('end', 'element')) + del it, elem + with self.assertRaises(FileNotFoundError): iterparse("nonexistent") + def test_iterparse_close(self): + iterparse = ET.iterparse + + it = iterparse(SIMPLE_XMLFILE) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + it = iterparse(SIMPLE_XMLFILE) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + action, elem = next(it) + self.assertEqual((action, elem.tag), ('end', 'element')) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + it = iterparse(SIMPLE_XMLFILE) + list(it) + it.close() + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + + with open(SIMPLE_XMLFILE, 'rb') as source: + it = iterparse(source) + list(it) + it.close() + self.assertFalse(source.closed) + with self.assertRaises(StopIteration): + next(it) + it.close() # idempotent + def test_writefile(self): elem = ET.Element("tag") elem.text = "text" @@ -3044,8 +3124,7 @@ def test_basic(self): # With an explicit parser too (issue #9708) sourcefile = serialize(doc, to_string=False) parser = ET.XMLParser(target=ET.TreeBuilder()) - self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], - 'end') + self.assertEqual(next(ET.iterparse(sourcefile, parser=parser))[0], 'end') tree = ET.ElementTree(None) self.assertRaises(AttributeError, tree.iter) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index bb7362d1634a726..a37fead41b750e4 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1248,10 +1248,17 @@ def iterator(source): if close_source: source.close() + gen = iterator(source) class IterParseIterator(collections.abc.Iterator): - __next__ = iterator(source).__next__ + __next__ = gen.__next__ + def close(self): + if close_source: + source.close() + gen.close() def __del__(self): + # TODO: Emit a ResourceWarning if it was not explicitly closed. + # (When the close() method will be supported in all maintained Python versions.) if close_source: source.close() diff --git a/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst b/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst new file mode 100644 index 000000000000000..1ebf434c33187bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-17-25-18.gh-issue-69893.PQq5fR.rst @@ -0,0 +1,2 @@ +Add the :meth:`!close` method for the iterator returned by +:func:`xml.etree.ElementTree.iterparse`. From ecabff98c41453f15ecd26ac255d531b571b9bc1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:27:42 +0200 Subject: [PATCH 193/263] gh-113267: Revert "gh-106584: Fix exit code for unittest in Python 3.12 (#106588)" (GH-114470) This reverts commit 8fc071345b50dd3de61ebeeaa287ccef21d061b2. --- Lib/test/test_unittest/test_discovery.py | 2 +- Lib/test/test_unittest/test_skipping.py | 12 ++++++------ Lib/unittest/case.py | 4 +--- Lib/unittest/result.py | 10 ++++------ .../2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst | 2 ++ 5 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst diff --git a/Lib/test/test_unittest/test_discovery.py b/Lib/test/test_unittest/test_discovery.py index dcb72d73efceabb..004898ed4318348 100644 --- a/Lib/test/test_unittest/test_discovery.py +++ b/Lib/test/test_unittest/test_discovery.py @@ -571,7 +571,7 @@ def _get_module_from_name(name): result = unittest.TestResult() suite.run(result) self.assertEqual(len(result.skipped), 1) - self.assertEqual(result.testsRun, 0) + self.assertEqual(result.testsRun, 1) self.assertEqual(import_calls, ['my_package']) # Check picklability diff --git a/Lib/test/test_unittest/test_skipping.py b/Lib/test/test_unittest/test_skipping.py index 1a6af06d32b4339..f146dcac18ecc09 100644 --- a/Lib/test/test_unittest/test_skipping.py +++ b/Lib/test/test_unittest/test_skipping.py @@ -103,16 +103,16 @@ def test_dont_skip(self): pass result = LoggingResult(events) self.assertIs(suite.run(result), result) self.assertEqual(len(result.skipped), 1) - expected = ['addSkip', 'stopTest', 'startTest', - 'addSuccess', 'stopTest'] + expected = ['startTest', 'addSkip', 'stopTest', + 'startTest', 'addSuccess', 'stopTest'] self.assertEqual(events, expected) - self.assertEqual(result.testsRun, 1) + self.assertEqual(result.testsRun, 2) self.assertEqual(result.skipped, [(test_do_skip, "testing")]) self.assertTrue(result.wasSuccessful()) events = [] result = test_do_skip.run() - self.assertEqual(events, ['startTestRun', 'addSkip', + self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip', 'stopTest', 'stopTestRun']) self.assertEqual(result.skipped, [(test_do_skip, "testing")]) @@ -135,13 +135,13 @@ def test_1(self): test = Foo("test_1") suite = unittest.TestSuite([test]) self.assertIs(suite.run(result), result) - self.assertEqual(events, ['addSkip', 'stopTest']) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) events = [] result = test.run() - self.assertEqual(events, ['startTestRun', 'addSkip', + self.assertEqual(events, ['startTestRun', 'startTest', 'addSkip', 'stopTest', 'stopTestRun']) self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 811557498bb30ed..001b640dc43ad69 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -606,6 +606,7 @@ def run(self, result=None): else: stopTestRun = None + result.startTest(self) try: testMethod = getattr(self, self._testMethodName) if (getattr(self.__class__, "__unittest_skip__", False) or @@ -616,9 +617,6 @@ def run(self, result=None): _addSkip(result, self, skip_why) return result - # Increase the number of tests only if it hasn't been skipped - result.startTest(self) - expecting_failure = ( getattr(self, "__unittest_expecting_failure__", False) or getattr(testMethod, "__unittest_expecting_failure__", False) diff --git a/Lib/unittest/result.py b/Lib/unittest/result.py index 9e56f658027f4de..3ace0a5b7bf2efb 100644 --- a/Lib/unittest/result.py +++ b/Lib/unittest/result.py @@ -97,12 +97,10 @@ def _restoreStdout(self): sys.stdout = self._original_stdout sys.stderr = self._original_stderr - if self._stdout_buffer is not None: - self._stdout_buffer.seek(0) - self._stdout_buffer.truncate() - if self._stderr_buffer is not None: - self._stderr_buffer.seek(0) - self._stderr_buffer.truncate() + self._stdout_buffer.seek(0) + self._stdout_buffer.truncate() + self._stderr_buffer.seek(0) + self._stderr_buffer.truncate() def stopTestRun(self): """Called once after all tests are executed. diff --git a/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst b/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst new file mode 100644 index 000000000000000..ad8aaf9250f6d8e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-23-11-04-21.gh-issue-113267.xe_Pxe.rst @@ -0,0 +1,2 @@ +Revert changes in :gh:`106584` which made calls of ``TestResult`` methods +``startTest()`` and ``stopTest()`` unbalanced. From 0ea366240b75380ed7568acbe95d72e481a734f7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:28:07 +0200 Subject: [PATCH 194/263] gh-113280: Always close socket if SSLSocket creation failed (GH-114659) Co-authored-by: Thomas Grainger <tagrain@gmail.com> --- Lib/ssl.py | 107 +++++++++--------- Lib/test/test_ssl.py | 33 ++++-- ...-01-27-20-11-24.gh-issue-113280.CZPQMf.rst | 2 + 3 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst diff --git a/Lib/ssl.py b/Lib/ssl.py index 74a9d2d8fd4fb01..03d0121891ff4cb 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -994,71 +994,67 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, if context.check_hostname and not server_hostname: raise ValueError("check_hostname requires server_hostname") + sock_timeout = sock.gettimeout() kwargs = dict( family=sock.family, type=sock.type, proto=sock.proto, fileno=sock.fileno() ) self = cls.__new__(cls, **kwargs) super(SSLSocket, self).__init__(**kwargs) - sock_timeout = sock.gettimeout() sock.detach() - - self._context = context - self._session = session - self._closed = False - self._sslobj = None - self.server_side = server_side - self.server_hostname = context._encode_hostname(server_hostname) - self.do_handshake_on_connect = do_handshake_on_connect - self.suppress_ragged_eofs = suppress_ragged_eofs - - # See if we are connected + # Now SSLSocket is responsible for closing the file descriptor. try: - self.getpeername() - except OSError as e: - if e.errno != errno.ENOTCONN: - raise - connected = False - blocking = self.getblocking() - self.setblocking(False) + self._context = context + self._session = session + self._closed = False + self._sslobj = None + self.server_side = server_side + self.server_hostname = context._encode_hostname(server_hostname) + self.do_handshake_on_connect = do_handshake_on_connect + self.suppress_ragged_eofs = suppress_ragged_eofs + + # See if we are connected try: - # We are not connected so this is not supposed to block, but - # testing revealed otherwise on macOS and Windows so we do - # the non-blocking dance regardless. Our raise when any data - # is found means consuming the data is harmless. - notconn_pre_handshake_data = self.recv(1) + self.getpeername() except OSError as e: - # EINVAL occurs for recv(1) on non-connected on unix sockets. - if e.errno not in (errno.ENOTCONN, errno.EINVAL): + if e.errno != errno.ENOTCONN: raise - notconn_pre_handshake_data = b'' - self.setblocking(blocking) - if notconn_pre_handshake_data: - # This prevents pending data sent to the socket before it was - # closed from escaping to the caller who could otherwise - # presume it came through a successful TLS connection. - reason = "Closed before TLS handshake with data in recv buffer." - notconn_pre_handshake_data_error = SSLError(e.errno, reason) - # Add the SSLError attributes that _ssl.c always adds. - notconn_pre_handshake_data_error.reason = reason - notconn_pre_handshake_data_error.library = None - try: - self.close() - except OSError: - pass + connected = False + blocking = self.getblocking() + self.setblocking(False) try: - raise notconn_pre_handshake_data_error - finally: - # Explicitly break the reference cycle. - notconn_pre_handshake_data_error = None - else: - connected = True + # We are not connected so this is not supposed to block, but + # testing revealed otherwise on macOS and Windows so we do + # the non-blocking dance regardless. Our raise when any data + # is found means consuming the data is harmless. + notconn_pre_handshake_data = self.recv(1) + except OSError as e: + # EINVAL occurs for recv(1) on non-connected on unix sockets. + if e.errno not in (errno.ENOTCONN, errno.EINVAL): + raise + notconn_pre_handshake_data = b'' + self.setblocking(blocking) + if notconn_pre_handshake_data: + # This prevents pending data sent to the socket before it was + # closed from escaping to the caller who could otherwise + # presume it came through a successful TLS connection. + reason = "Closed before TLS handshake with data in recv buffer." + notconn_pre_handshake_data_error = SSLError(e.errno, reason) + # Add the SSLError attributes that _ssl.c always adds. + notconn_pre_handshake_data_error.reason = reason + notconn_pre_handshake_data_error.library = None + try: + raise notconn_pre_handshake_data_error + finally: + # Explicitly break the reference cycle. + notconn_pre_handshake_data_error = None + else: + connected = True - self.settimeout(sock_timeout) # Must come after setblocking() calls. - self._connected = connected - if connected: - # create the SSL object - try: + self.settimeout(sock_timeout) # Must come after setblocking() calls. + self._connected = connected + if connected: + # create the SSL object self._sslobj = self._context._wrap_socket( self, server_side, self.server_hostname, owner=self, session=self._session, @@ -1069,9 +1065,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True, # non-blocking raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets") self.do_handshake() - except (OSError, ValueError): + except: + try: self.close() - raise + except OSError: + pass + raise return self @property diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 3fdfa2960503b8f..1b18230d83577dc 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -2206,14 +2206,15 @@ def _test_get_server_certificate(test, host, port, cert=None): sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port ,pem)) def _test_get_server_certificate_fail(test, host, port): - try: - pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) - except ssl.SSLError as x: - #should fail - if support.verbose: - sys.stdout.write("%s\n" % x) - else: - test.fail("Got server certificate %s for %s:%s!" % (pem, host, port)) + with warnings_helper.check_no_resource_warning(test): + try: + pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) + except ssl.SSLError as x: + #should fail + if support.verbose: + sys.stdout.write("%s\n" % x) + else: + test.fail("Got server certificate %s for %s:%s!" % (pem, host, port)) from test.ssl_servers import make_https_server @@ -3026,6 +3027,16 @@ def test_check_hostname_idn(self): server_hostname="python.example.org") as s: with self.assertRaises(ssl.CertificateError): s.connect((HOST, server.port)) + with ThreadedEchoServer(context=server_context, chatty=True) as server: + with warnings_helper.check_no_resource_warning(self): + with self.assertRaises(UnicodeError): + context.wrap_socket(socket.socket(), + server_hostname='.pythontest.net') + with ThreadedEchoServer(context=server_context, chatty=True) as server: + with warnings_helper.check_no_resource_warning(self): + with self.assertRaises(UnicodeDecodeError): + context.wrap_socket(socket.socket(), + server_hostname=b'k\xf6nig.idn.pythontest.net') def test_wrong_cert_tls12(self): """Connecting when the server rejects the client's certificate @@ -4983,7 +4994,8 @@ def call_after_accept(conn_to_client): self.assertIsNone(wrap_error.library, msg="attr must exist") finally: # gh-108342: Explicitly break the reference cycle - wrap_error = None + with warnings_helper.check_no_resource_warning(self): + wrap_error = None server = None def test_https_client_non_tls_response_ignored(self): @@ -5032,7 +5044,8 @@ def call_after_accept(conn_to_client): # socket; that fails if the connection is broken. It may seem pointless # to test this. It serves as an illustration of something that we never # want to happen... properly not happening. - with self.assertRaises(OSError): + with warnings_helper.check_no_resource_warning(self), \ + self.assertRaises(OSError): connection.request("HEAD", "/test", headers={"Host": "localhost"}) response = connection.getresponse() diff --git a/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst b/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst new file mode 100644 index 000000000000000..3dcdbcf0995616a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-27-20-11-24.gh-issue-113280.CZPQMf.rst @@ -0,0 +1,2 @@ +Fix a leak of open socket in rare cases when error occurred in +:class:`ssl.SSLSocket` creation. From 3ddc5152550ea62280124c37d0b4339030ff7df4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:32:25 +0200 Subject: [PATCH 195/263] gh-114388: Fix warnings when assign an unsigned integer member (GH-114391) * Fix a RuntimeWarning emitted when assign an integer-like value that is not an instance of int to an attribute that corresponds to a C struct member of type T_UINT and T_ULONG. * Fix a double RuntimeWarning emitted when assign a negative integer value to an attribute that corresponds to a C struct member of type T_UINT. --- Lib/test/test_capi/test_structmembers.py | 37 +++++++++ ...-01-21-17-29-32.gh-issue-114388.UVGO4K.rst | 5 ++ Python/structmember.c | 83 ++++++++++++------- 3 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index 2cf46b203478dc2..415b8033bd16b3c 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -14,6 +14,13 @@ PY_SSIZE_T_MAX, PY_SSIZE_T_MIN, ) + +class Index: + def __init__(self, value): + self.value = value + def __index__(self): + return self.value + # There are two classes: one using <structmember.h> and another using # `Py_`-prefixed API. They should behave the same in Python @@ -72,6 +79,10 @@ def test_int(self): self.assertEqual(ts.T_INT, INT_MIN) ts.T_UINT = UINT_MAX self.assertEqual(ts.T_UINT, UINT_MAX) + ts.T_UINT = Index(0) + self.assertEqual(ts.T_UINT, 0) + ts.T_UINT = Index(INT_MAX) + self.assertEqual(ts.T_UINT, INT_MAX) def test_long(self): ts = self.ts @@ -81,6 +92,10 @@ def test_long(self): self.assertEqual(ts.T_LONG, LONG_MIN) ts.T_ULONG = ULONG_MAX self.assertEqual(ts.T_ULONG, ULONG_MAX) + ts.T_ULONG = Index(0) + self.assertEqual(ts.T_ULONG, 0) + ts.T_ULONG = Index(LONG_MAX) + self.assertEqual(ts.T_ULONG, LONG_MAX) def test_py_ssize_t(self): ts = self.ts @@ -173,6 +188,28 @@ def test_ushort_max(self): with warnings_helper.check_warnings(('', RuntimeWarning)): ts.T_USHORT = USHRT_MAX+1 + def test_int(self): + ts = self.ts + if LONG_MIN < INT_MIN: + with self.assertWarns(RuntimeWarning): + ts.T_INT = INT_MIN-1 + if LONG_MAX > INT_MAX: + with self.assertWarns(RuntimeWarning): + ts.T_INT = INT_MAX+1 + + def test_uint(self): + ts = self.ts + with self.assertWarns(RuntimeWarning): + ts.T_UINT = -1 + if ULONG_MAX > UINT_MAX: + with self.assertWarns(RuntimeWarning): + ts.T_UINT = UINT_MAX+1 + + def test_ulong(self): + ts = self.ts + with self.assertWarns(RuntimeWarning): + ts.T_ULONG = -1 + class TestWarnings_OldAPI(TestWarnings, unittest.TestCase): cls = _test_structmembersType_OldAPI diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst new file mode 100644 index 000000000000000..52c2742001d9ca6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-21-17-29-32.gh-issue-114388.UVGO4K.rst @@ -0,0 +1,5 @@ +Fix a :exc:`RuntimeWarning` emitted when assign an integer-like value that +is not an instance of :class:`int` to an attribute that corresponds to a C +struct member of :ref:`type <PyMemberDef-types>` T_UINT and T_ULONG. Fix a +double :exc:`RuntimeWarning` emitted when assign a negative integer value to +an attribute that corresponds to a C struct member of type T_UINT. diff --git a/Python/structmember.c b/Python/structmember.c index 7a5a6a49d231167..18bd486952419b7 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -197,45 +197,72 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) WARN("Truncation of value to int"); break; } - case Py_T_UINT:{ - unsigned long ulong_val = PyLong_AsUnsignedLong(v); - if ((ulong_val == (unsigned long)-1) && PyErr_Occurred()) { - /* XXX: For compatibility, accept negative int values - as well. */ - PyErr_Clear(); - ulong_val = PyLong_AsLong(v); - if ((ulong_val == (unsigned long)-1) && - PyErr_Occurred()) + case Py_T_UINT: { + /* XXX: For compatibility, accept negative int values + as well. */ + int overflow; + long long_val = PyLong_AsLongAndOverflow(v, &overflow); + if (long_val == -1 && PyErr_Occurred()) { + return -1; + } + if (overflow < 0) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C long"); + } + else if (!overflow) { + *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; + if (long_val < 0) { + WARN("Writing negative value into unsigned field"); + } + else if ((unsigned long)long_val > UINT_MAX) { + WARN("Truncation of value to unsigned short"); + } + } + else { + unsigned long ulong_val = PyLong_AsUnsignedLong(v); + if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; - *(unsigned int *)addr = (unsigned int)ulong_val; - WARN("Writing negative value into unsigned field"); - } else - *(unsigned int *)addr = (unsigned int)ulong_val; - if (ulong_val > UINT_MAX) - WARN("Truncation of value to unsigned int"); - break; + } + *(unsigned int*)addr = (unsigned int)ulong_val; + if (ulong_val > UINT_MAX) { + WARN("Truncation of value to unsigned int"); + } } + break; + } case Py_T_LONG:{ *(long*)addr = PyLong_AsLong(v); if ((*(long*)addr == -1) && PyErr_Occurred()) return -1; break; } - case Py_T_ULONG:{ - *(unsigned long*)addr = PyLong_AsUnsignedLong(v); - if ((*(unsigned long*)addr == (unsigned long)-1) - && PyErr_Occurred()) { - /* XXX: For compatibility, accept negative int values - as well. */ - PyErr_Clear(); - *(unsigned long*)addr = PyLong_AsLong(v); - if ((*(unsigned long*)addr == (unsigned long)-1) - && PyErr_Occurred()) + case Py_T_ULONG: { + /* XXX: For compatibility, accept negative int values + as well. */ + int overflow; + long long_val = PyLong_AsLongAndOverflow(v, &overflow); + if (long_val == -1 && PyErr_Occurred()) { + return -1; + } + if (overflow < 0) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C long"); + } + else if (!overflow) { + *(unsigned long *)addr = (unsigned long)long_val; + if (long_val < 0) { + WARN("Writing negative value into unsigned field"); + } + } + else { + unsigned long ulong_val = PyLong_AsUnsignedLong(v); + if (ulong_val == (unsigned long)-1 && PyErr_Occurred()) { return -1; - WARN("Writing negative value into unsigned field"); + } + *(unsigned long*)addr = ulong_val; } break; - } + } case Py_T_PYSSIZET:{ *(Py_ssize_t*)addr = PyLong_AsSsize_t(v); if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1) From 7e42fddf608337e83b30401910d76fd75d5cf20a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 17:49:42 +0200 Subject: [PATCH 196/263] gh-113951: Tkinter: "tag_unbind(tag, sequence, funcid)" now only unbinds "funcid" (GH-113955) Previously, "tag_unbind(tag, sequence, funcid)" methods of Text and Canvas widgets destroyed the current binding for "sequence", leaving "sequence" unbound, and deleted the "funcid" command. Now they remove only "funcid" from the binding for "sequence", keeping other commands, and delete the "funcid" command. They leave "sequence" unbound only if "funcid" was the last bound command. --- Lib/test/test_tkinter/test_misc.py | 95 +++++++++++++++++++ Lib/tkinter/__init__.py | 26 ++--- ...-01-11-20-47-49.gh-issue-113951.AzlqFK.rst | 7 ++ 3 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index dc8a810235fc9bd..71553503005c48b 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -706,6 +706,101 @@ def test3(e): pass self.assertCommandExist(funcid2) self.assertCommandExist(funcid3) + def _test_tag_bind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-a>' + w.pack() + self.assertRaises(TypeError, w.tag_bind) + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + if isinstance(w, tkinter.Text): + self.assertRaises(TypeError, w.tag_bind, tag) + self.assertRaises(TypeError, w.tag_bind, tag, event) + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + + funcid = w.tag_bind(tag, event, test1) + self.assertEqual(tag_bind(tag), (event,)) + script = tag_bind(tag, event) + self.assertIn(funcid, script) + self.assertCommandExist(funcid) + + funcid2 = w.tag_bind(tag, event, test2, add=True) + script = tag_bind(tag, event) + self.assertIn(funcid, script) + self.assertIn(funcid2, script) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid2) + + def _test_tag_unbind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-b>' + w.pack() + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + + funcid = w.tag_bind(tag, event, test1) + funcid2 = w.tag_bind(tag, event, test2, add=True) + + self.assertRaises(TypeError, w.tag_unbind, tag) + w.tag_unbind(tag, event) + self.assertEqual(tag_bind(tag, event), '') + self.assertEqual(tag_bind(tag), ()) + + def _test_tag_bind_rebind(self, w): + tag = 'sel' + event = '<Control-Alt-Key-d>' + w.pack() + tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind + self.assertEqual(tag_bind(tag), ()) + self.assertEqual(tag_bind(tag, event), '') + def test1(e): pass + def test2(e): pass + def test3(e): pass + + funcid = w.tag_bind(tag, event, test1) + funcid2 = w.tag_bind(tag, event, test2, add=True) + script = tag_bind(tag, event) + self.assertIn(funcid2, script) + self.assertIn(funcid, script) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid2) + + funcid3 = w.tag_bind(tag, event, test3) + script = tag_bind(tag, event) + self.assertNotIn(funcid, script) + self.assertNotIn(funcid2, script) + self.assertIn(funcid3, script) + self.assertCommandExist(funcid3) + + def test_canvas_tag_bind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_bind(c) + + def test_canvas_tag_unbind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_unbind(c) + + def test_canvas_tag_bind_rebind(self): + c = tkinter.Canvas(self.frame) + self._test_tag_bind_rebind(c) + + def test_text_tag_bind(self): + t = tkinter.Text(self.frame) + self._test_tag_bind(t) + + def test_text_tag_unbind(self): + t = tkinter.Text(self.frame) + self._test_tag_unbind(t) + + def test_text_tag_bind_rebind(self): + t = tkinter.Text(self.frame) + self._test_tag_bind_rebind(t) + def test_bindtags(self): f = self.frame self.assertEqual(self.root.bindtags(), ('.', 'Tk', 'all')) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index e0db41dd915ece9..a1567d332ae6efc 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1537,16 +1537,19 @@ def unbind(self, sequence, funcid=None): Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE unbound. """ + self._unbind(('bind', self._w, sequence), funcid) + + def _unbind(self, what, funcid=None): if funcid is None: - self.tk.call('bind', self._w, sequence, '') + self.tk.call(*what, '') else: - lines = self.tk.call('bind', self._w, sequence).split('\n') + lines = self.tk.call(what).split('\n') prefix = f'if {{"[{funcid} ' keep = '\n'.join(line for line in lines if not line.startswith(prefix)) if not keep.strip(): keep = '' - self.tk.call('bind', self._w, sequence, keep) + self.tk.call(*what, keep) self.deletecommand(funcid) def bind_all(self, sequence=None, func=None, add=None): @@ -1558,7 +1561,7 @@ def bind_all(self, sequence=None, func=None, add=None): def unbind_all(self, sequence): """Unbind for all widgets for event SEQUENCE all functions.""" - self.tk.call('bind', 'all' , sequence, '') + self._root()._unbind(('bind', 'all', sequence)) def bind_class(self, className, sequence=None, func=None, add=None): """Bind to widgets with bindtag CLASSNAME at event @@ -1573,7 +1576,7 @@ def bind_class(self, className, sequence=None, func=None, add=None): def unbind_class(self, className, sequence): """Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE all functions.""" - self.tk.call('bind', className , sequence, '') + self._root()._unbind(('bind', className, sequence)) def mainloop(self, n=0): """Call the mainloop of Tk.""" @@ -2885,9 +2888,7 @@ def bbox(self, *args): def tag_unbind(self, tagOrId, sequence, funcid=None): """Unbind for all items with TAGORID for event SEQUENCE the function identified with FUNCID.""" - self.tk.call(self._w, 'bind', tagOrId, sequence, '') - if funcid: - self.deletecommand(funcid) + self._unbind((self._w, 'bind', tagOrId, sequence), funcid) def tag_bind(self, tagOrId, sequence=None, func=None, add=None): """Bind to all items with TAGORID at event SEQUENCE a call to function FUNC. @@ -3997,9 +3998,7 @@ def tag_add(self, tagName, index1, *args): def tag_unbind(self, tagName, sequence, funcid=None): """Unbind for all characters with TAGNAME for event SEQUENCE the function identified with FUNCID.""" - self.tk.call(self._w, 'tag', 'bind', tagName, sequence, '') - if funcid: - self.deletecommand(funcid) + return self._unbind((self._w, 'tag', 'bind', tagName, sequence), funcid) def tag_bind(self, tagName, sequence, func, add=None): """Bind to all characters with TAGNAME at event SEQUENCE a call to function FUNC. @@ -4010,6 +4009,11 @@ def tag_bind(self, tagName, sequence, func, add=None): return self._bind((self._w, 'tag', 'bind', tagName), sequence, func, add) + def _tag_bind(self, tagName, sequence=None, func=None, add=None): + # For tests only + return self._bind((self._w, 'tag', 'bind', tagName), + sequence, func, add) + def tag_cget(self, tagName, option): """Return the value of OPTION for tag TAGNAME.""" if option[:1] != '-': diff --git a/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst new file mode 100644 index 000000000000000..e683472e59b8a49 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-11-20-47-49.gh-issue-113951.AzlqFK.rst @@ -0,0 +1,7 @@ +Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and +:class:`tkinter.Canvas` classes with three arguments. Previously, +``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding +for *sequence*, leaving *sequence* unbound, and deleted the *funcid* +command. Now it removes only *funcid* from the binding for *sequence*, +keeping other commands, and deletes the *funcid* command. It leaves +*sequence* unbound only if *funcid* was the last bound command. From d466052ad48091a00a50c5298f33238aff591028 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 19:06:22 +0200 Subject: [PATCH 197/263] gh-114388: Fix an error in GH-114391 (GH-115000) --- Python/structmember.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/structmember.c b/Python/structmember.c index 18bd486952419b7..c9f03a464078d0b 100644 --- a/Python/structmember.c +++ b/Python/structmember.c @@ -208,6 +208,7 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) if (overflow < 0) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C long"); + return -1; } else if (!overflow) { *(unsigned int *)addr = (unsigned int)(unsigned long)long_val; @@ -247,6 +248,7 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v) if (overflow < 0) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C long"); + return -1; } else if (!overflow) { *(unsigned long *)addr = (unsigned long)long_val; From da8f9fb2ea65cc2784c2400fc39ad8c800a67a42 Mon Sep 17 00:00:00 2001 From: Dai Wentao <dwt136@gmail.com> Date: Mon, 5 Feb 2024 02:42:58 +0800 Subject: [PATCH 198/263] gh-113803: Fix inaccurate documentation for shutil.move when dst is an existing directory (#113837) * fix the usage of dst and destination in shutil.move doc * update shutil.move doc --- Doc/library/shutil.rst | 25 ++++++++++++++----------- Lib/shutil.py | 10 +++++----- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 7a7dd23177e6721..ff8c9a189ab3de2 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -360,21 +360,24 @@ Directory and files operations .. function:: move(src, dst, copy_function=copy2) - Recursively move a file or directory (*src*) to another location (*dst*) - and return the destination. + Recursively move a file or directory (*src*) to another location and return + the destination. - If the destination is an existing directory, then *src* is moved inside that - directory. If the destination already exists but is not a directory, it may - be overwritten depending on :func:`os.rename` semantics. + If *dst* is an existing directory or a symlink to a directory, then *src* + is moved inside that directory. The destination path in that directory must + not already exist. + + If *dst* already exists but is not a directory, it may be overwritten + depending on :func:`os.rename` semantics. If the destination is on the current filesystem, then :func:`os.rename` is - used. Otherwise, *src* is copied to *dst* using *copy_function* and then - removed. In case of symlinks, a new symlink pointing to the target of *src* - will be created in or as *dst* and *src* will be removed. + used. Otherwise, *src* is copied to the destination using *copy_function* + and then removed. In case of symlinks, a new symlink pointing to the target + of *src* will be created as the destination and *src* will be removed. - If *copy_function* is given, it must be a callable that takes two arguments - *src* and *dst*, and will be used to copy *src* to *dst* if - :func:`os.rename` cannot be used. If the source is a directory, + If *copy_function* is given, it must be a callable that takes two arguments, + *src* and the destination, and will be used to copy *src* to the destination + if :func:`os.rename` cannot be used. If the source is a directory, :func:`copytree` is called, passing it the *copy_function*. The default *copy_function* is :func:`copy2`. Using :func:`~shutil.copy` as the *copy_function* allows the move to succeed when it is not possible to also diff --git a/Lib/shutil.py b/Lib/shutil.py index acc9419be4dfca6..c19ea0607208afa 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -861,12 +861,12 @@ def move(src, dst, copy_function=copy2): similar to the Unix "mv" command. Return the file or directory's destination. - If the destination is a directory or a symlink to a directory, the source - is moved inside the directory. The destination path must not already - exist. + If dst is an existing directory or a symlink to a directory, then src is + moved inside that directory. The destination path in that directory must + not already exist. - If the destination already exists but is not a directory, it may be - overwritten depending on os.rename() semantics. + If dst already exists but is not a directory, it may be overwritten + depending on os.rename() semantics. If the destination is on our current filesystem, then rename() is used. Otherwise, src is copied to the destination and then removed. Symlinks are From 929d44e15a5667151beadb2d3a2528cd641639d6 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sun, 4 Feb 2024 22:16:43 +0300 Subject: [PATCH 199/263] gh-114685: PyBuffer_FillInfo() now raises on PyBUF_{READ,WRITE} (GH-114802) --- Lib/test/test_buffer.py | 21 +++++++++++++++++++ ...-01-31-15-43-35.gh-issue-114685.n7aRmX.rst | 3 +++ Modules/_testcapimodule.c | 21 +++++++++++++++++++ Objects/abstract.c | 16 +++++++++----- 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 535b795f508a242..5b1b95b9c82064c 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -4591,6 +4591,27 @@ def test_c_buffer_invalid_flags(self): self.assertRaises(SystemError, buf.__buffer__, PyBUF_READ) self.assertRaises(SystemError, buf.__buffer__, PyBUF_WRITE) + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_invalid_flags(self): + # PyBuffer_FillInfo + source = b"abc" + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + source, 0, PyBUF_READ) + self.assertRaises(SystemError, _testcapi.buffer_fill_info, + source, 0, PyBUF_WRITE) + + @unittest.skipIf(_testcapi is None, "requires _testcapi") + def test_c_fill_buffer_readonly_and_writable(self): + source = b"abc" + with _testcapi.buffer_fill_info(source, 1, PyBUF_SIMPLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertTrue(m.readonly) + with _testcapi.buffer_fill_info(source, 0, PyBUF_WRITABLE) as m: + self.assertEqual(bytes(m), b"abc") + self.assertFalse(m.readonly) + self.assertRaises(BufferError, _testcapi.buffer_fill_info, + source, 1, PyBUF_WRITABLE) + def test_inheritance(self): class A(bytearray): def __buffer__(self, flags): diff --git a/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst new file mode 100644 index 000000000000000..76ff00645fe57d2 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-01-31-15-43-35.gh-issue-114685.n7aRmX.rst @@ -0,0 +1,3 @@ +:c:func:`PyBuffer_FillInfo` now raises a :exc:`SystemError` if called with +:c:macro:`PyBUF_READ` or :c:macro:`PyBUF_WRITE` as flags. These flags should +only be used with the ``PyMemoryView_*`` C API. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 6def680190b1a63..e67de3eeb6e17eb 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -1261,6 +1261,26 @@ make_memoryview_from_NULL_pointer(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyMemoryView_FromBuffer(&info); } +static PyObject * +buffer_fill_info(PyObject *self, PyObject *args) +{ + Py_buffer info; + const char *data; + Py_ssize_t size; + int readonly; + int flags; + + if (!PyArg_ParseTuple(args, "s#ii:buffer_fill_info", + &data, &size, &readonly, &flags)) { + return NULL; + } + + if (PyBuffer_FillInfo(&info, NULL, (void *)data, size, readonly, flags) < 0) { + return NULL; + } + return PyMemoryView_FromBuffer(&info); +} + static PyObject * test_from_contiguous(PyObject* self, PyObject *Py_UNUSED(ignored)) { @@ -3314,6 +3334,7 @@ static PyMethodDef TestMethods[] = { {"eval_code_ex", eval_eval_code_ex, METH_VARARGS}, {"make_memoryview_from_NULL_pointer", make_memoryview_from_NULL_pointer, METH_NOARGS}, + {"buffer_fill_info", buffer_fill_info, METH_VARARGS}, {"crash_no_current_thread", crash_no_current_thread, METH_NOARGS}, {"test_current_tstate_matches", test_current_tstate_matches, METH_NOARGS}, {"run_in_subinterp", run_in_subinterp, METH_VARARGS}, diff --git a/Objects/abstract.c b/Objects/abstract.c index daf04eb4ab2cda3..07d4b89fe188c84 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -767,11 +767,17 @@ PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, return -1; } - if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && - (readonly == 1)) { - PyErr_SetString(PyExc_BufferError, - "Object is not writable."); - return -1; + if (flags != PyBUF_SIMPLE) { /* fast path */ + if (flags == PyBUF_READ || flags == PyBUF_WRITE) { + PyErr_BadInternalCall(); + return -1; + } + if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && + (readonly == 1)) { + PyErr_SetString(PyExc_BufferError, + "Object is not writable."); + return -1; + } } view->obj = Py_XNewRef(obj); From 15f6f048a6ecdf0f6f4fc076d013be3d110f8ed6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sun, 4 Feb 2024 22:19:06 +0200 Subject: [PATCH 200/263] gh-114392: Improve test_capi.test_structmembers (GH-114393) Test all integer member types with extreme values and values outside of the valid range. Test support of integer-like objects. Test warnings for wrapped out values. --- Lib/test/test_capi/test_structmembers.py | 217 ++++++++++------------- 1 file changed, 93 insertions(+), 124 deletions(-) diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py index 415b8033bd16b3c..a294c3b13a5c30c 100644 --- a/Lib/test/test_capi/test_structmembers.py +++ b/Lib/test/test_capi/test_structmembers.py @@ -45,83 +45,115 @@ class ReadWriteTests: def setUp(self): self.ts = _make_test_object(self.cls) + def _test_write(self, name, value, expected=None): + if expected is None: + expected = value + ts = self.ts + setattr(ts, name, value) + self.assertEqual(getattr(ts, name), expected) + + def _test_warn(self, name, value, expected=None): + ts = self.ts + self.assertWarns(RuntimeWarning, setattr, ts, name, value) + if expected is not None: + self.assertEqual(getattr(ts, name), expected) + + def _test_overflow(self, name, value): + ts = self.ts + self.assertRaises(OverflowError, setattr, ts, name, value) + + def _test_int_range(self, name, minval, maxval, *, hardlimit=None, + indexlimit=None): + if hardlimit is None: + hardlimit = (minval, maxval) + ts = self.ts + self._test_write(name, minval) + self._test_write(name, maxval) + hardminval, hardmaxval = hardlimit + self._test_overflow(name, hardminval-1) + self._test_overflow(name, hardmaxval+1) + self._test_overflow(name, 2**1000) + self._test_overflow(name, -2**1000) + if hardminval < minval: + self._test_warn(name, hardminval) + self._test_warn(name, minval-1, maxval) + if maxval < hardmaxval: + self._test_warn(name, maxval+1, minval) + self._test_warn(name, hardmaxval) + + if indexlimit is None: + indexlimit = hardlimit + if not indexlimit: + self.assertRaises(TypeError, setattr, ts, name, Index(minval)) + self.assertRaises(TypeError, setattr, ts, name, Index(maxval)) + else: + hardminindexval, hardmaxindexval = indexlimit + self._test_write(name, Index(minval), minval) + if minval < hardminindexval: + self._test_write(name, Index(hardminindexval), hardminindexval) + if maxval < hardmaxindexval: + self._test_write(name, Index(maxval), maxval) + else: + self._test_write(name, Index(hardmaxindexval), hardmaxindexval) + self._test_overflow(name, Index(hardminindexval-1)) + if name in ('T_UINT', 'T_ULONG'): + self.assertRaises(TypeError, setattr, self.ts, name, + Index(hardmaxindexval+1)) + self.assertRaises(TypeError, setattr, self.ts, name, + Index(2**1000)) + else: + self._test_overflow(name, Index(hardmaxindexval+1)) + self._test_overflow(name, Index(2**1000)) + self._test_overflow(name, Index(-2**1000)) + if hardminindexval < minval and name != 'T_ULONGLONG': + self._test_warn(name, Index(hardminindexval)) + self._test_warn(name, Index(minval-1)) + if maxval < hardmaxindexval: + self._test_warn(name, Index(maxval+1)) + self._test_warn(name, Index(hardmaxindexval)) + def test_bool(self): ts = self.ts ts.T_BOOL = True - self.assertEqual(ts.T_BOOL, True) + self.assertIs(ts.T_BOOL, True) ts.T_BOOL = False - self.assertEqual(ts.T_BOOL, False) + self.assertIs(ts.T_BOOL, False) self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 1) + self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 0) + self.assertRaises(TypeError, setattr, ts, 'T_BOOL', None) def test_byte(self): - ts = self.ts - ts.T_BYTE = CHAR_MAX - self.assertEqual(ts.T_BYTE, CHAR_MAX) - ts.T_BYTE = CHAR_MIN - self.assertEqual(ts.T_BYTE, CHAR_MIN) - ts.T_UBYTE = UCHAR_MAX - self.assertEqual(ts.T_UBYTE, UCHAR_MAX) + self._test_int_range('T_BYTE', CHAR_MIN, CHAR_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_UBYTE', 0, UCHAR_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) def test_short(self): - ts = self.ts - ts.T_SHORT = SHRT_MAX - self.assertEqual(ts.T_SHORT, SHRT_MAX) - ts.T_SHORT = SHRT_MIN - self.assertEqual(ts.T_SHORT, SHRT_MIN) - ts.T_USHORT = USHRT_MAX - self.assertEqual(ts.T_USHORT, USHRT_MAX) + self._test_int_range('T_SHORT', SHRT_MIN, SHRT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_USHORT', 0, USHRT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) def test_int(self): - ts = self.ts - ts.T_INT = INT_MAX - self.assertEqual(ts.T_INT, INT_MAX) - ts.T_INT = INT_MIN - self.assertEqual(ts.T_INT, INT_MIN) - ts.T_UINT = UINT_MAX - self.assertEqual(ts.T_UINT, UINT_MAX) - ts.T_UINT = Index(0) - self.assertEqual(ts.T_UINT, 0) - ts.T_UINT = Index(INT_MAX) - self.assertEqual(ts.T_UINT, INT_MAX) + self._test_int_range('T_INT', INT_MIN, INT_MAX, + hardlimit=(LONG_MIN, LONG_MAX)) + self._test_int_range('T_UINT', 0, UINT_MAX, + hardlimit=(LONG_MIN, ULONG_MAX), + indexlimit=(LONG_MIN, LONG_MAX)) def test_long(self): - ts = self.ts - ts.T_LONG = LONG_MAX - self.assertEqual(ts.T_LONG, LONG_MAX) - ts.T_LONG = LONG_MIN - self.assertEqual(ts.T_LONG, LONG_MIN) - ts.T_ULONG = ULONG_MAX - self.assertEqual(ts.T_ULONG, ULONG_MAX) - ts.T_ULONG = Index(0) - self.assertEqual(ts.T_ULONG, 0) - ts.T_ULONG = Index(LONG_MAX) - self.assertEqual(ts.T_ULONG, LONG_MAX) + self._test_int_range('T_LONG', LONG_MIN, LONG_MAX) + self._test_int_range('T_ULONG', 0, ULONG_MAX, + hardlimit=(LONG_MIN, ULONG_MAX), + indexlimit=(LONG_MIN, LONG_MAX)) def test_py_ssize_t(self): - ts = self.ts - ts.T_PYSSIZET = PY_SSIZE_T_MAX - self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MAX) - ts.T_PYSSIZET = PY_SSIZE_T_MIN - self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MIN) + self._test_int_range('T_PYSSIZET', PY_SSIZE_T_MIN, PY_SSIZE_T_MAX, indexlimit=False) def test_longlong(self): - ts = self.ts - if not hasattr(ts, "T_LONGLONG"): - self.skipTest("long long not present") - - ts.T_LONGLONG = LLONG_MAX - self.assertEqual(ts.T_LONGLONG, LLONG_MAX) - ts.T_LONGLONG = LLONG_MIN - self.assertEqual(ts.T_LONGLONG, LLONG_MIN) - - ts.T_ULONGLONG = ULLONG_MAX - self.assertEqual(ts.T_ULONGLONG, ULLONG_MAX) - - ## make sure these will accept a plain int as well as a long - ts.T_LONGLONG = 3 - self.assertEqual(ts.T_LONGLONG, 3) - ts.T_ULONGLONG = 4 - self.assertEqual(ts.T_ULONGLONG, 4) + self._test_int_range('T_LONGLONG', LLONG_MIN, LLONG_MAX) + self._test_int_range('T_ULONGLONG', 0, ULLONG_MAX, + indexlimit=(LONG_MIN, LONG_MAX)) def test_bad_assignments(self): ts = self.ts @@ -131,10 +163,9 @@ def test_bad_assignments(self): 'T_SHORT', 'T_USHORT', 'T_INT', 'T_UINT', 'T_LONG', 'T_ULONG', + 'T_LONGLONG', 'T_ULONGLONG', 'T_PYSSIZET' ] - if hasattr(ts, 'T_LONGLONG'): - integer_attributes.extend(['T_LONGLONG', 'T_ULONGLONG']) # issue8014: this produced 'bad argument to internal function' # internal error @@ -154,68 +185,6 @@ class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase): class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase): cls = _test_structmembersType_NewAPI -class TestWarnings: - def setUp(self): - self.ts = _make_test_object(self.cls) - - def test_byte_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_BYTE = CHAR_MAX+1 - - def test_byte_min(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_BYTE = CHAR_MIN-1 - - def test_ubyte_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_UBYTE = UCHAR_MAX+1 - - def test_short_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_SHORT = SHRT_MAX+1 - - def test_short_min(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_SHORT = SHRT_MIN-1 - - def test_ushort_max(self): - ts = self.ts - with warnings_helper.check_warnings(('', RuntimeWarning)): - ts.T_USHORT = USHRT_MAX+1 - - def test_int(self): - ts = self.ts - if LONG_MIN < INT_MIN: - with self.assertWarns(RuntimeWarning): - ts.T_INT = INT_MIN-1 - if LONG_MAX > INT_MAX: - with self.assertWarns(RuntimeWarning): - ts.T_INT = INT_MAX+1 - - def test_uint(self): - ts = self.ts - with self.assertWarns(RuntimeWarning): - ts.T_UINT = -1 - if ULONG_MAX > UINT_MAX: - with self.assertWarns(RuntimeWarning): - ts.T_UINT = UINT_MAX+1 - - def test_ulong(self): - ts = self.ts - with self.assertWarns(RuntimeWarning): - ts.T_ULONG = -1 - -class TestWarnings_OldAPI(TestWarnings, unittest.TestCase): - cls = _test_structmembersType_OldAPI - -class TestWarnings_NewAPI(TestWarnings, unittest.TestCase): - cls = _test_structmembersType_NewAPI - if __name__ == "__main__": unittest.main() From 391659b3da570bfa28fed5fbdb6f2d9c26ab3dd0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee <russell@keith-magee.com> Date: Mon, 5 Feb 2024 08:04:57 +0800 Subject: [PATCH 201/263] gh-114099: Add test exclusions to support running the test suite on iOS (#114889) Add test annotations required to run the test suite on iOS (PEP 730). The majority of the change involve annotating tests that use subprocess, but are skipped on Emscripten/WASI for other reasons, and including iOS/tvOS/watchOS under the same umbrella as macOS/darwin checks. `is_apple` and `is_apple_mobile` test helpers have been added to identify *any* Apple platform, and "any Apple platform except macOS", respectively. --- Lib/test/support/__init__.py | 26 ++++- Lib/test/support/os_helper.py | 8 +- Lib/test/test_asyncio/test_events.py | 14 +++ Lib/test/test_asyncio/test_streams.py | 3 +- Lib/test/test_asyncio/test_subprocess.py | 2 + Lib/test/test_asyncio/test_unix_events.py | 2 +- Lib/test/test_cmd_line_script.py | 16 +-- Lib/test/test_code_module.py | 1 + Lib/test/test_fcntl.py | 12 ++- Lib/test/test_ftplib.py | 3 + Lib/test/test_genericpath.py | 22 ++-- Lib/test/test_httpservers.py | 18 ++-- Lib/test/test_io.py | 16 ++- Lib/test/test_marshal.py | 4 +- Lib/test/test_mmap.py | 4 +- Lib/test/test_os.py | 4 +- Lib/test/test_pdb.py | 102 +++++++++--------- Lib/test/test_platform.py | 3 +- Lib/test/test_posix.py | 15 +-- Lib/test/test_pty.py | 18 ++-- Lib/test/test_selectors.py | 5 +- Lib/test/test_shutil.py | 2 + Lib/test/test_signal.py | 9 +- Lib/test/test_socket.py | 24 +++-- Lib/test/test_sqlite3/test_dbapi.py | 6 +- Lib/test/test_stat.py | 5 +- Lib/test/test_sys_settrace.py | 3 +- Lib/test/test_unicode_file_functions.py | 14 +-- Lib/test/test_urllib2.py | 2 + Lib/test/test_venv.py | 10 +- ...-02-02-13-18-55.gh-issue-114099.C_ycWg.rst | 1 + 31 files changed, 224 insertions(+), 150 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index f2e6af078a5f291..5b091fb2fd32dc6 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -43,7 +43,7 @@ "requires_limited_api", "requires_specialization", # sys "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", - "check_impl_detail", "unix_shell", "setswitchinterval", + "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", # os "get_pagesize", # network @@ -522,7 +522,7 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_android = hasattr(sys, 'getandroidapilevel') -if sys.platform not in ('win32', 'vxworks'): +if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None @@ -532,19 +532,35 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" -has_fork_support = hasattr(os, "fork") and not is_emscripten and not is_wasi +# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not +# have subprocess or fork support. +is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} +is_apple = is_apple_mobile or sys.platform == "darwin" + +has_fork_support = hasattr(os, "fork") and not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") -has_subprocess_support = not is_emscripten and not is_wasi +has_subprocess_support = not ( + is_emscripten + or is_wasi + or is_apple_mobile +) def requires_subprocess(): """Used for subprocess, os.spawn calls, fd inheritance""" return unittest.skipUnless(has_subprocess_support, "requires subprocess support") # Emscripten's socket emulation and WASI sockets have limitations. -has_socket_support = not is_emscripten and not is_wasi +has_socket_support = not ( + is_emscripten + or is_wasi +) def requires_working_socket(*, module=False): """Skip tests or modules that require working sockets diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 20f38fd36a8876e..22787e32b5f3abe 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -22,8 +22,8 @@ # TESTFN_UNICODE is a non-ascii filename TESTFN_UNICODE = TESTFN_ASCII + "-\xe0\xf2\u0258\u0141\u011f" -if sys.platform == 'darwin': - # In Mac OS X's VFS API file names are, by definition, canonically +if support.is_apple: + # On Apple's VFS API file names are, by definition, canonically # decomposed Unicode, encoded using UTF-8. See QA1173: # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html import unicodedata @@ -48,8 +48,8 @@ 'encoding (%s). Unicode filename tests may not be effective' % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) TESTFN_UNENCODABLE = None -# macOS and Emscripten deny unencodable filenames (invalid utf-8) -elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: +# Apple and Emscripten deny unencodable filenames (invalid utf-8) +elif not support.is_apple and sys.platform not in {"emscripten", "wasi"}: try: # ascii and utf-8 cannot encode the byte 0xff b'\xff'.decode(sys.getfilesystemencoding()) diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index b25c0975736e20e..c92c88bd5b2429c 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -1815,6 +1815,7 @@ def check_killed(self, returncode): else: self.assertEqual(-signal.SIGKILL, returncode) + @support.requires_subprocess() def test_subprocess_exec(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1836,6 +1837,7 @@ def test_subprocess_exec(self): self.check_killed(proto.returncode) self.assertEqual(b'Python The Winner', proto.data[1]) + @support.requires_subprocess() def test_subprocess_interactive(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1863,6 +1865,7 @@ def test_subprocess_interactive(self): self.loop.run_until_complete(proto.completed) self.check_killed(proto.returncode) + @support.requires_subprocess() def test_subprocess_shell(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1879,6 +1882,7 @@ def test_subprocess_shell(self): self.assertEqual(proto.data[2], b'') transp.close() + @support.requires_subprocess() def test_subprocess_exitcode(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1890,6 +1894,7 @@ def test_subprocess_exitcode(self): self.assertEqual(7, proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_close_after_finish(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), @@ -1904,6 +1909,7 @@ def test_subprocess_close_after_finish(self): self.assertEqual(7, proto.returncode) self.assertIsNone(transp.close()) + @support.requires_subprocess() def test_subprocess_kill(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1920,6 +1926,7 @@ def test_subprocess_kill(self): self.check_killed(proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_terminate(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1937,6 +1944,7 @@ def test_subprocess_terminate(self): transp.close() @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") + @support.requires_subprocess() def test_subprocess_send_signal(self): # bpo-31034: Make sure that we get the default signal handler (killing # the process). The parent process may have decided to ignore SIGHUP, @@ -1961,6 +1969,7 @@ def test_subprocess_send_signal(self): finally: signal.signal(signal.SIGHUP, old_handler) + @support.requires_subprocess() def test_subprocess_stderr(self): prog = os.path.join(os.path.dirname(__file__), 'echo2.py') @@ -1982,6 +1991,7 @@ def test_subprocess_stderr(self): self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) self.assertEqual(0, proto.returncode) + @support.requires_subprocess() def test_subprocess_stderr_redirect_to_stdout(self): prog = os.path.join(os.path.dirname(__file__), 'echo2.py') @@ -2007,6 +2017,7 @@ def test_subprocess_stderr_redirect_to_stdout(self): transp.close() self.assertEqual(0, proto.returncode) + @support.requires_subprocess() def test_subprocess_close_client_stream(self): prog = os.path.join(os.path.dirname(__file__), 'echo3.py') @@ -2041,6 +2052,7 @@ def test_subprocess_close_client_stream(self): self.loop.run_until_complete(proto.completed) self.check_killed(proto.returncode) + @support.requires_subprocess() def test_subprocess_wait_no_same_group(self): # start the new process in a new session connect = self.loop.subprocess_shell( @@ -2053,6 +2065,7 @@ def test_subprocess_wait_no_same_group(self): self.assertEqual(7, proto.returncode) transp.close() + @support.requires_subprocess() def test_subprocess_exec_invalid_args(self): async def connect(**kwds): await self.loop.subprocess_exec( @@ -2066,6 +2079,7 @@ async def connect(**kwds): with self.assertRaises(ValueError): self.loop.run_until_complete(connect(shell=True)) + @support.requires_subprocess() def test_subprocess_shell_invalid_args(self): async def connect(cmd=None, **kwds): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 3c8cc5f3649180d..210990593adfa9b 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -10,7 +10,6 @@ import unittest from unittest import mock import warnings -from test.support import socket_helper try: import ssl except ImportError: @@ -18,6 +17,7 @@ import asyncio from test.test_asyncio import utils as test_utils +from test.support import requires_subprocess, socket_helper def tearDownModule(): @@ -770,6 +770,7 @@ async def client(addr): self.assertEqual(msg2, b"hello world 2!\n") @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") + @requires_subprocess() def test_read_all_from_pipe_reader(self): # See asyncio issue 168. This test is derived from the example # subprocess_attach_read_pipe.py, but we configure the diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 808b21c66175511..f50a9ebc031ba8a 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -47,6 +47,7 @@ def _start(self, *args, **kwargs): self._proc.pid = -1 +@support.requires_subprocess() class SubprocessTransportTests(test_utils.TestCase): def setUp(self): super().setUp() @@ -110,6 +111,7 @@ def test_subprocess_repr(self): transport.close() +@support.requires_subprocess() class SubprocessMixin: def test_stdin_stdout(self): diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index d2c8cba6acfa31c..59ef9f5f58cabc3 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1874,7 +1874,7 @@ async def runner(): wsock.close() -@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()') +@support.requires_fork() class TestFork(unittest.IsolatedAsyncioTestCase): async def test_fork_not_share_event_loop(self): diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 48754d5a63da3b1..3a5a8abf81e43d4 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -14,8 +14,7 @@ import textwrap from test import support -from test.support import import_helper -from test.support import os_helper +from test.support import import_helper, is_apple, os_helper from test.support.script_helper import ( make_pkg, make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, spawn_python, kill_python) @@ -557,12 +556,17 @@ def test_pep_409_verbiage(self): self.assertTrue(text[3].startswith('NameError')) def test_non_ascii(self): - # Mac OS X denies the creation of a file with an invalid UTF-8 name. + # Apple platforms deny the creation of a file with an invalid UTF-8 name. # Windows allows creating a name with an arbitrary bytes name, but # Python cannot a undecodable bytes argument to a subprocess. - # WASI does not permit invalid UTF-8 names. - if (os_helper.TESTFN_UNDECODABLE - and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): + # Emscripten/WASI does not permit invalid UTF-8 names. + if ( + os_helper.TESTFN_UNDECODABLE + and sys.platform not in { + "win32", "emscripten", "wasi" + } + and not is_apple + ): name = os.fsdecode(os_helper.TESTFN_UNDECODABLE) elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 747c0f9683c19c7..259778a5cade98f 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -160,6 +160,7 @@ def setUp(self): self.console = code.InteractiveConsole(local_exit=True) self.mock_sys() + @unittest.skipIf(sys.flags.no_site, "exit() isn't defined unless there's a site module") def test_exit(self): # default exit message self.infunc.side_effect = ["exit()"] diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 203dd6fe57dcd99..6d734d052454d34 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -6,7 +6,9 @@ import struct import sys import unittest -from test.support import verbose, cpython_only, get_pagesize +from test.support import ( + cpython_only, get_pagesize, is_apple, requires_subprocess, verbose +) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink @@ -56,8 +58,10 @@ def get_lockdata(): else: start_len = "qq" - if (sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) - or sys.platform == 'darwin'): + if ( + sys.platform.startswith(('netbsd', 'freebsd', 'openbsd')) + or is_apple + ): if struct.calcsize('l') == 8: off_t = 'l' pid_t = 'i' @@ -157,6 +161,7 @@ def test_flock(self): self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") + @requires_subprocess() def test_lockf_exclusive(self): self.f = open(TESTFN, 'wb+') cmd = fcntl.LOCK_EX | fcntl.LOCK_NB @@ -169,6 +174,7 @@ def test_lockf_exclusive(self): self.assertEqual(p.exitcode, 0) @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") + @requires_subprocess() def test_lockf_share(self): self.f = open(TESTFN, 'wb+') cmd = fcntl.LOCK_SH | fcntl.LOCK_NB diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 2f191ea7a44c161..81115e9db888cf2 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -18,6 +18,7 @@ from unittest import TestCase, skipUnless from test import support +from test.support import requires_subprocess from test.support import threading_helper from test.support import socket_helper from test.support import warnings_helper @@ -900,6 +901,7 @@ def retr(): @skipUnless(ssl, "SSL not available") +@requires_subprocess() class TestTLS_FTPClassMixin(TestFTPClass): """Repeat TestFTPClass tests starting the TLS layer for both control and data connections first. @@ -916,6 +918,7 @@ def setUp(self, encoding=DEFAULT_ENCODING): @skipUnless(ssl, "SSL not available") +@requires_subprocess() class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index 4f311c2d498e9f4..b77cd4c67d6b2ae 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -7,9 +7,9 @@ import sys import unittest import warnings -from test.support import is_emscripten -from test.support import os_helper -from test.support import warnings_helper +from test.support import ( + is_apple, is_emscripten, os_helper, warnings_helper +) from test.support.script_helper import assert_python_ok from test.support.os_helper import FakePath @@ -483,12 +483,16 @@ def test_abspath_issue3426(self): self.assertIsInstance(abspath(path), str) def test_nonascii_abspath(self): - if (os_helper.TESTFN_UNDECODABLE - # macOS and Emscripten deny the creation of a directory with an - # invalid UTF-8 name. Windows allows creating a directory with an - # arbitrary bytes name, but fails to enter this directory - # (when the bytes name is used). - and sys.platform not in ('win32', 'darwin', 'emscripten', 'wasi')): + if ( + os_helper.TESTFN_UNDECODABLE + # Apple platforms and Emscripten/WASI deny the creation of a + # directory with an invalid UTF-8 name. Windows allows creating a + # directory with an arbitrary bytes name, but fails to enter this + # directory (when the bytes name is used). + and sys.platform not in { + "win32", "emscripten", "wasi" + } and not is_apple + ): name = os_helper.TESTFN_UNDECODABLE elif os_helper.TESTFN_NONASCII: name = os_helper.TESTFN_NONASCII diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 9fa6ecf9c08e279..d762ec6102ab8aa 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -30,8 +30,9 @@ import unittest from test import support -from test.support import os_helper -from test.support import threading_helper +from test.support import ( + is_apple, os_helper, requires_subprocess, threading_helper +) support.requires_working_socket(module=True) @@ -410,8 +411,8 @@ def close_conn(): reader.close() return body - @unittest.skipIf(sys.platform == 'darwin', - 'undecodable name cannot always be decoded on macOS') + @unittest.skipIf(is_apple, + 'undecodable name cannot always be decoded on Apple platforms') @unittest.skipIf(sys.platform == 'win32', 'undecodable name cannot be decoded on win32') @unittest.skipUnless(os_helper.TESTFN_UNDECODABLE, @@ -422,11 +423,11 @@ def test_undecodable_filename(self): with open(os.path.join(self.tempdir, filename), 'wb') as f: f.write(os_helper.TESTFN_UNDECODABLE) response = self.request(self.base_url + '/') - if sys.platform == 'darwin': - # On Mac OS the HFS+ filesystem replaces bytes that aren't valid - # UTF-8 into a percent-encoded value. + if is_apple: + # On Apple platforms the HFS+ filesystem replaces bytes that + # aren't valid UTF-8 into a percent-encoded value. for name in os.listdir(self.tempdir): - if name != 'test': # Ignore a filename created in setUp(). + if name != 'test': # Ignore a filename created in setUp(). filename = name break body = self.check_status_and_reason(response, HTTPStatus.OK) @@ -697,6 +698,7 @@ def test_html_escape_filename(self): @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") +@requires_subprocess() class CGIHTTPServerTestCase(BaseTestCase): class request_handler(NoLogRequestHandler, CGIHTTPRequestHandler): _test_case_self = None # populated by each setUp() method call. diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 9e28b936e00bd57..73669ecc7927763 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -39,11 +39,9 @@ from test import support from test.support.script_helper import ( assert_python_ok, assert_python_failure, run_python_until_end) -from test.support import import_helper -from test.support import os_helper -from test.support import threading_helper -from test.support import warnings_helper -from test.support import skip_if_sanitizer +from test.support import ( + import_helper, is_apple, os_helper, skip_if_sanitizer, threading_helper, warnings_helper +) from test.support.os_helper import FakePath import codecs @@ -606,10 +604,10 @@ def test_raw_bytes_io(self): self.read_ops(f, True) def test_large_file_ops(self): - # On Windows and Mac OSX this test consumes large resources; It takes - # a long time to build the >2 GiB file and takes >2 GiB of disk space - # therefore the resource must be enabled to run this test. - if sys.platform[:3] == 'win' or sys.platform == 'darwin': + # On Windows and Apple platforms this test consumes large resources; It + # takes a long time to build the >2 GiB file and takes >2 GiB of disk + # space therefore the resource must be enabled to run this test. + if sys.platform[:3] == 'win' or is_apple: support.requires( 'largefile', 'test requires %s bytes and a long time to run' % self.LARGE) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 6e17e010e7f355d..615568e6af21028 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -1,5 +1,5 @@ from test import support -from test.support import os_helper, requires_debug_ranges +from test.support import is_apple_mobile, os_helper, requires_debug_ranges from test.support.script_helper import assert_python_ok import array import io @@ -286,7 +286,7 @@ def test_recursion_limit(self): #if os.name == 'nt' and support.Py_DEBUG: if os.name == 'nt': MAX_MARSHAL_STACK_DEPTH = 1000 - elif sys.platform == 'wasi': + elif sys.platform == 'wasi' or is_apple_mobile: MAX_MARSHAL_STACK_DEPTH = 1500 else: MAX_MARSHAL_STACK_DEPTH = 2000 diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index b89621e08577be0..ac759757d24659c 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1,5 +1,5 @@ from test.support import ( - requires, _2G, _4G, gc_collect, cpython_only, is_emscripten + requires, _2G, _4G, gc_collect, cpython_only, is_emscripten, is_apple, ) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink @@ -1067,7 +1067,7 @@ def tearDown(self): unlink(TESTFN) def _make_test_file(self, num_zeroes, tail): - if sys.platform[:3] == 'win' or sys.platform == 'darwin': + if sys.platform[:3] == 'win' or is_apple: requires('largefile', 'test requires %s bytes and a long time to run' % str(0x180000000)) f = open(TESTFN, 'w+b') diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index ed79a2c24ef30bf..86af1a8ed8ee154 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -3848,6 +3848,7 @@ def test_does_not_crash(self): self.assertGreaterEqual(size.columns, 0) self.assertGreaterEqual(size.lines, 0) + @support.requires_subprocess() def test_stty_match(self): """Check if stty returns the same results @@ -4577,7 +4578,8 @@ def test_posix_pty_functions(self): self.addCleanup(os.close, son_fd) self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd)) - @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.openpty()") + @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.spawnl()") + @support.requires_subprocess() def test_pipe_spawnl(self): # gh-77046: On Windows, os.pipe() file descriptors must be created with # _O_NOINHERIT to make them non-inheritable. UCRT has no public API to diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index b2283cff6cb4622..2b0795cdad707e1 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -779,58 +779,62 @@ def test_pdb_where_command(): (Pdb) continue """ -def test_pdb_interact_command(): - """Test interact command - >>> g = 0 - >>> dict_g = {} +# skip this test if sys.flags.no_site = True; +# exit() isn't defined unless there's a site module. +if not sys.flags.no_site: + def test_pdb_interact_command(): + """Test interact command - >>> def test_function(): - ... x = 1 - ... lst_local = [] - ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + >>> g = 0 + >>> dict_g = {} - >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE - ... 'interact', - ... 'x', - ... 'g', - ... 'x = 2', - ... 'g = 3', - ... 'dict_g["a"] = True', - ... 'lst_local.append(x)', - ... 'exit()', - ... 'p x', - ... 'p g', - ... 'p dict_g', - ... 'p lst_local', - ... 'continue', - ... ]): - ... test_function() - --Return-- - > <doctest test.test_pdb.test_pdb_interact_command[2]>(4)test_function()->None - -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() - (Pdb) interact - *pdb interact start* - ... x - 1 - ... g - 0 - ... x = 2 - ... g = 3 - ... dict_g["a"] = True - ... lst_local.append(x) - ... exit() - *exit from pdb interact command* - (Pdb) p x - 1 - (Pdb) p g - 0 - (Pdb) p dict_g - {'a': True} - (Pdb) p lst_local - [2] - (Pdb) continue - """ + >>> def test_function(): + ... x = 1 + ... lst_local = [] + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + + >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + ... 'interact', + ... 'x', + ... 'g', + ... 'x = 2', + ... 'g = 3', + ... 'dict_g["a"] = True', + ... 'lst_local.append(x)', + ... 'exit()', + ... 'p x', + ... 'p g', + ... 'p dict_g', + ... 'p lst_local', + ... 'continue', + ... ]): + ... test_function() + --Return-- + > <doctest test.test_pdb.test_pdb_interact_command[2]>(4)test_function()->None + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) interact + *pdb interact start* + ... x + 1 + ... g + 0 + ... x = 2 + ... g = 3 + ... dict_g["a"] = True + ... lst_local.append(x) + ... exit() + *exit from pdb interact command* + (Pdb) p x + 1 + (Pdb) p g + 0 + (Pdb) p dict_g + {'a': True} + (Pdb) p lst_local + [2] + (Pdb) continue + """ def test_convenience_variables(): """Test convenience variables diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 216973350319fe6..648e18d0150ef08 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -472,7 +472,8 @@ def test_macos(self): 'root:xnu-4570.71.2~1/RELEASE_X86_64'), 'x86_64', 'i386') arch = ('64bit', '') - with mock.patch.object(platform, 'uname', return_value=uname), \ + with mock.patch.object(sys, "platform", "darwin"), \ + mock.patch.object(platform, 'uname', return_value=uname), \ mock.patch.object(platform, 'architecture', return_value=arch): for mac_ver, expected_terse, expected in [ # darwin: mac_ver() returns empty strings diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 9c382ace806e0f8..72e348fbbdcbc14 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1,7 +1,7 @@ "Test posix functions" from test import support -from test.support import import_helper +from test.support import is_apple from test.support import os_helper from test.support import warnings_helper from test.support.script_helper import assert_python_ok @@ -781,9 +781,10 @@ def check_stat(uid, gid): check_stat(uid, gid) self.assertRaises(OSError, chown_func, first_param, 0, -1) check_stat(uid, gid) - if 0 not in os.getgroups(): - self.assertRaises(OSError, chown_func, first_param, -1, 0) - check_stat(uid, gid) + if hasattr(os, 'getgroups'): + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) # test illegal types for t in str, float: self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) @@ -1256,8 +1257,8 @@ def test_sched_priority(self): self.assertIsInstance(lo, int) self.assertIsInstance(hi, int) self.assertGreaterEqual(hi, lo) - # OSX evidently just returns 15 without checking the argument. - if sys.platform != "darwin": + # Apple plaforms return 15 without checking the argument. + if not is_apple: self.assertRaises(OSError, posix.sched_get_priority_min, -23) self.assertRaises(OSError, posix.sched_get_priority_max, -23) @@ -2028,11 +2029,13 @@ def test_dup2(self): @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn") +@support.requires_subprocess() class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawn', None) @unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp") +@support.requires_subprocess() class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): spawn_func = getattr(posix, 'posix_spawnp', None) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 51e3a46d0df1780..3f2bac0155fd9e8 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,12 +1,17 @@ -from test.support import verbose, reap_children -from test.support.os_helper import TESTFN, unlink +import sys +import unittest +from test.support import ( + is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose +) from test.support.import_helper import import_module +from test.support.os_helper import TESTFN, unlink -# Skip these tests if termios or fcntl are not available +# Skip these tests if termios is not available import_module('termios') -# fcntl is a proxy for not being one of the wasm32 platforms even though we -# don't use this module... a proper check for what crashes those is needed. -import_module("fcntl") + +# Skip tests on WASM platforms, plus iOS/tvOS/watchOS +if is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest(f"pty tests not required on {sys.platform}") import errno import os @@ -17,7 +22,6 @@ import signal import socket import io # readline -import unittest import warnings TEST_STRING_1 = b"I wish to buy a fish license.\n" diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index 677349c2bfca93d..643775597c56c67 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -6,8 +6,7 @@ import socket import sys from test import support -from test.support import os_helper -from test.support import socket_helper +from test.support import is_apple, os_helper, socket_helper from time import sleep import unittest import unittest.mock @@ -526,7 +525,7 @@ def test_above_fd_setsize(self): try: fds = s.select() except OSError as e: - if e.errno == errno.EINVAL and sys.platform == 'darwin': + if e.errno == errno.EINVAL and is_apple: # unexplainable errors on macOS don't need to fail the test self.skipTest("Invalid argument error calling poll()") raise diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 8edd75e9907ec02..d96dad4eb9475d2 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2148,6 +2148,7 @@ def check_chown(path, uid=None, gid=None): check_chown(dirname, uid, gid) +@support.requires_subprocess() class TestWhich(BaseTest, unittest.TestCase): def setUp(self): @@ -3181,6 +3182,7 @@ def test_bad_environ(self): self.assertGreaterEqual(size.lines, 0) @unittest.skipUnless(os.isatty(sys.__stdout__.fileno()), "not on tty") + @support.requires_subprocess() @unittest.skipUnless(hasattr(os, 'get_terminal_size'), 'need os.get_terminal_size()') def test_stty_match(self): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 637a0ca3b369726..61fb047caf6dab6 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -13,9 +13,10 @@ import time import unittest from test import support -from test.support import os_helper +from test.support import ( + is_apple, is_apple_mobile, os_helper, threading_helper +) from test.support.script_helper import assert_python_ok, spawn_python -from test.support import threading_helper try: import _testcapi except ImportError: @@ -832,7 +833,7 @@ def test_itimer_real(self): self.assertEqual(self.hndl_called, True) # Issue 3864, unknown if this affects earlier versions of freebsd also - @unittest.skipIf(sys.platform in ('netbsd5',), + @unittest.skipIf(sys.platform in ('netbsd5',) or is_apple_mobile, 'itimer not reliable (does not mix well with threading) on some BSDs.') def test_itimer_virtual(self): self.itimer = signal.ITIMER_VIRTUAL @@ -1344,7 +1345,7 @@ def handler(signum, frame): # Python handler self.assertEqual(len(sigs), N, "Some signals were lost") - @unittest.skipIf(sys.platform == "darwin", "crashes due to system bug (FB13453490)") + @unittest.skipIf(is_apple, "crashes due to system bug (FB13453490)") @unittest.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1") @threading_helper.requires_working_threading() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 231448c75f01db0..179642349920627 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1,9 +1,8 @@ import unittest from test import support -from test.support import os_helper -from test.support import socket_helper -from test.support import threading_helper -from test.support import refleak_helper +from test.support import ( + is_apple, os_helper, refleak_helper, socket_helper, threading_helper +) import _thread as thread import array @@ -1196,8 +1195,11 @@ def testGetServBy(self): # Find one service that exists, then check all the related interfaces. # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. - if (sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) - or sys.platform in ('linux', 'darwin')): + if ( + sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) + or sys.platform == 'linux' + or is_apple + ): # avoid the 'echo' service on this platform, as there is an # assumption breaking non-standard port/protocol entry services = ('daytime', 'qotd', 'domain') @@ -3708,7 +3710,7 @@ def testFDPassCMSG_LEN(self): def _testFDPassCMSG_LEN(self): self.createAndSendFDs(1) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparate(self): @@ -3719,7 +3721,7 @@ def testFDPassSeparate(self): maxcmsgs=2) @testFDPassSeparate.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparate(self): fd0, fd1 = self.newFDs(2) @@ -3732,7 +3734,7 @@ def _testFDPassSeparate(self): array.array("i", [fd1]))]), len(MSG)) - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") @requireAttrs(socket, "CMSG_SPACE") def testFDPassSeparateMinSpace(self): @@ -3746,7 +3748,7 @@ def testFDPassSeparateMinSpace(self): maxcmsgs=2, ignoreflags=socket.MSG_CTRUNC) @testFDPassSeparateMinSpace.client_skip - @unittest.skipIf(sys.platform == "darwin", "skipping, see issue #12958") + @unittest.skipIf(is_apple, "skipping, see issue #12958") @unittest.skipIf(AIX, "skipping, see issue #22397") def _testFDPassSeparateMinSpace(self): fd0, fd1 = self.newFDs(2) @@ -3770,7 +3772,7 @@ def sendAncillaryIfPossible(self, msg, ancdata): nbytes = self.sendmsgToServer([msg]) self.assertEqual(nbytes, len(msg)) - @unittest.skipIf(sys.platform == "darwin", "see issue #24725") + @unittest.skipIf(is_apple, "skipping, see issue #12958") def testFDPassEmpty(self): # Try to pass an empty FD array. Can receive either no array # or an empty array. diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index f3efe0f52f4fd73..588272448bbfda6 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -31,7 +31,7 @@ from test.support import ( SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, - is_emscripten, is_wasi + is_apple, is_emscripten, is_wasi ) from test.support import gc_collect from test.support import threading_helper @@ -667,7 +667,7 @@ def test_open_with_path_like_object(self): cx.execute(self._sql) @unittest.skipIf(sys.platform == "win32", "skipped on Windows") - @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") + @unittest.skipIf(is_apple, "skipped on Apple platforms") @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") def test_open_with_undecodable_path(self): @@ -713,7 +713,7 @@ def test_open_uri_readonly(self): cx.execute(self._sql) @unittest.skipIf(sys.platform == "win32", "skipped on Windows") - @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") + @unittest.skipIf(is_apple, "skipped on Apple platforms") @unittest.skipIf(is_emscripten or is_wasi, "not supported on Emscripten/WASI") @unittest.skipUnless(TESTFN_UNDECODABLE, "only works if there are undecodable paths") def test_open_undecodable_uri(self): diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index d6b6dd6e7417002..49013a4bcd8af6b 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -2,8 +2,7 @@ import os import socket import sys -from test.support import os_helper -from test.support import socket_helper +from test.support import is_apple, os_helper, socket_helper from test.support.import_helper import import_fresh_module from test.support.os_helper import TESTFN @@ -247,7 +246,7 @@ def test_flags_consistent(self): for flag in self.file_flags: if flag.startswith("UF"): self.assertTrue(getattr(self.statmod, flag) & self.statmod.UF_SETTABLE, f"{flag} not in UF_SETTABLE") - elif sys.platform == 'darwin' and self.statmod is c_stat and flag == 'SF_DATALESS': + elif is_apple and self.statmod is c_stat and flag == 'SF_DATALESS': self.assertTrue(self.statmod.SF_DATALESS & self.statmod.SF_SYNTHETIC, "SF_DATALESS not in SF_SYNTHETIC") self.assertFalse(self.statmod.SF_DATALESS & self.statmod.SF_SETTABLE, "SF_DATALESS in SF_SETTABLE") else: diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index ae6e192a7ab6eff..125f40227118f63 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -7,7 +7,7 @@ import gc from functools import wraps import asyncio -from test.support import import_helper +from test.support import import_helper, requires_subprocess import contextlib import os import tempfile @@ -1810,6 +1810,7 @@ def compare_events(self, line_offset, events, expected_events): def make_tracer(): return Tracer(trace_opcode_events=True) + @requires_subprocess() def test_trace_opcodes_after_settrace(self): """Make sure setting f_trace_opcodes after starting trace works even if it's the first time f_trace_opcodes is being set. GH-103615""" diff --git a/Lib/test/test_unicode_file_functions.py b/Lib/test/test_unicode_file_functions.py index 47619c8807bafe3..25c16e3a0b7e430 100644 --- a/Lib/test/test_unicode_file_functions.py +++ b/Lib/test/test_unicode_file_functions.py @@ -5,7 +5,7 @@ import unittest import warnings from unicodedata import normalize -from test.support import os_helper +from test.support import is_apple, os_helper from test import support @@ -23,13 +23,13 @@ '10_\u1fee\u1ffd', ] -# Mac OS X decomposes Unicode names, using Normal Form D. +# Apple platforms decompose Unicode names, using Normal Form D. # http://developer.apple.com/mac/library/qa/qa2001/qa1173.html # "However, most volume formats do not follow the exact specification for # these normal forms. For example, HFS Plus uses a variant of Normal Form D # in which U+2000 through U+2FFF, U+F900 through U+FAFF, and U+2F800 through # U+2FAFF are not decomposed." -if sys.platform != 'darwin': +if not is_apple: filenames.extend([ # Specific code points: NFC(fn), NFD(fn), NFKC(fn) and NFKD(fn) all different '11_\u0385\u03d3\u03d4', @@ -119,11 +119,11 @@ def test_open(self): os.stat(name) self._apply_failure(os.listdir, name, self._listdir_failure) - # Skip the test on darwin, because darwin does normalize the filename to + # Skip the test on Apple platforms, because they don't normalize the filename to # NFD (a variant of Unicode NFD form). Normalize the filename to NFC, NFKC, # NFKD in Python is useless, because darwin will normalize it later and so # open(), os.stat(), etc. don't raise any exception. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') @unittest.skipIf( support.is_emscripten or support.is_wasi, "test fails on Emscripten/WASI when host platform is macOS." @@ -142,10 +142,10 @@ def test_normalize(self): self._apply_failure(os.remove, name) self._apply_failure(os.listdir, name) - # Skip the test on darwin, because darwin uses a normalization different + # Skip the test on Apple platforms, because they use a normalization different # than Python NFD normalization: filenames are different even if we use # Python NFD normalization. - @unittest.skipIf(sys.platform == 'darwin', 'irrelevant test on Mac OS X') + @unittest.skipIf(is_apple, 'irrelevant test on Apple platforms') def test_listdir(self): sf0 = set(self.files) with warnings.catch_warnings(): diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 99c9e24994732fa..fa528a675892b5e 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1,6 +1,7 @@ import unittest from test import support from test.support import os_helper +from test.support import requires_subprocess from test.support import warnings_helper from test import test_urllib from unittest import mock @@ -998,6 +999,7 @@ def test_http_body_fileobj(self): file_obj.close() + @requires_subprocess() def test_http_body_pipe(self): # A file reading from a pipe. # A pipe cannot be seek'ed. There is no way to determine the diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 6dda00efd7bbb69..ba31beb81e80b09 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,8 +19,8 @@ import tempfile from test.support import (captured_stdout, captured_stderr, skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_emscripten, is_wasi, - requires_venv_with_pip, TEST_HOME_DIR, + requires_subprocess, is_apple_mobile, is_emscripten, + is_wasi, requires_venv_with_pip, TEST_HOME_DIR, requires_resource, copy_python_src_ignore) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) import unittest @@ -39,8 +39,10 @@ or sys._base_executable != sys.executable, 'cannot run venv.create from within a venv on this platform') -if is_emscripten or is_wasi: - raise unittest.SkipTest("venv is not available on Emscripten/WASI.") +# Skip tests on WASM platforms, plus iOS/tvOS/watchOS +if is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest(f"venv tests not required on {sys.platform}") + @requires_subprocess() def check_output(cmd, encoding=None): diff --git a/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst b/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst new file mode 100644 index 000000000000000..487cd5062fc75b5 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-02-02-13-18-55.gh-issue-114099.C_ycWg.rst @@ -0,0 +1 @@ +Added test exclusions required to run the test suite on iOS. From e207cc181fbb0ceb30542fd0d68140c916305f57 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Sun, 4 Feb 2024 20:57:54 -0500 Subject: [PATCH 202/263] gh-114628: Display csv.Error without context (#115005) When cvs.Error is raised when TypeError is caught, the TypeError display and 'During handling' note is just noise with duplicate information. Suppress with 'from None'. --- Lib/csv.py | 4 ++-- .../Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst diff --git a/Lib/csv.py b/Lib/csv.py index a079279b8b8cbce..75e35b23236795f 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -113,8 +113,8 @@ def _validate(self): try: _Dialect(self) except TypeError as e: - # We do this for compatibility with py2.3 - raise Error(str(e)) + # Re-raise to get a traceback showing more user code. + raise Error(str(e)) from None class excel(Dialect): """Describe the usual properties of Excel-generated CSV files.""" diff --git a/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst b/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst new file mode 100644 index 000000000000000..8138adc62c95f32 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-04-13-17-33.gh-issue-114628.WJpqqS.rst @@ -0,0 +1,2 @@ +When csv.Error is raised when handling TypeError, do not print the TypeError +traceback. From 39ec7fbba84663ab760853da2ac422c2e988d189 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy <tjreedy@udel.edu> Date: Sun, 4 Feb 2024 23:11:31 -0500 Subject: [PATCH 203/263] Remove bogus syntax error marker in csv doc (#115017) --- Doc/library/csv.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index fd62b225fcebb80..4ee7820585d3a21 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -244,7 +244,6 @@ The :mod:`csv` module defines the following classes: with open('students.csv', 'w', newline='') as csvfile: writer = csv.writer(csvfile, dialect='unix') - ^^^^^^^^^^^^^^ .. class:: excel() From f71bdd34085d31a826148b2e5da57e0302655056 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Mon, 5 Feb 2024 13:20:34 +0300 Subject: [PATCH 204/263] gh-115020: Remove a debugging print in test_frame (GH-115021) --- Lib/test/test_frame.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 244ce8af7cdf088..baed03d92b9e561 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -72,7 +72,6 @@ def inner(): except ZeroDivisionError as exc: support.gc_collect() self.assertIsNotNone(wr()) - print(exc.__traceback__.tb_next.tb_frame.f_locals) exc.__traceback__.tb_next.tb_frame.clear() support.gc_collect() self.assertIsNone(wr()) From 87cd20a567aca56369010689e22a524bc1f1ac03 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Mon, 5 Feb 2024 13:45:09 +0300 Subject: [PATCH 205/263] gh-115026: Argument Clinic: handle PyBuffer_FillInfo errors in generated code (#115027) --- Modules/_sqlite/clinic/connection.c.h | 6 ++++-- Modules/clinic/_codecsmodule.c.h | 18 +++++++++++++----- Modules/clinic/_ssl.c.h | 6 ++++-- Tools/clinic/clinic.py | 4 +++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index db5eb77891e52e5..f2cff6a7b421f3b 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1551,7 +1551,9 @@ deserialize(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1818,4 +1820,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=90b5b9c14261b8d7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=99299d3ee2c247ab input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_codecsmodule.c.h b/Modules/clinic/_codecsmodule.c.h index 12fea806ab52093..1c0f37442ab3509 100644 --- a/Modules/clinic/_codecsmodule.c.h +++ b/Modules/clinic/_codecsmodule.c.h @@ -297,7 +297,9 @@ _codecs_escape_decode(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1099,7 +1101,9 @@ _codecs_unicode_escape_decode(PyObject *module, PyObject *const *args, Py_ssize_ if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1175,7 +1179,9 @@ _codecs_raw_unicode_escape_decode(PyObject *module, PyObject *const *args, Py_ss if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -1644,7 +1650,9 @@ _codecs_readbuffer_encode(PyObject *module, PyObject *const *args, Py_ssize_t na if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&data, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { @@ -2738,4 +2746,4 @@ _codecs_lookup_error(PyObject *module, PyObject *arg) #ifndef _CODECS_CODE_PAGE_ENCODE_METHODDEF #define _CODECS_CODE_PAGE_ENCODE_METHODDEF #endif /* !defined(_CODECS_CODE_PAGE_ENCODE_METHODDEF) */ -/*[clinic end generated code: output=d8d9e372f7ccba35 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e50d5fdf65bd45fa input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 19c0f619b92f45c..2940f16a2cb7f6a 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -1297,7 +1297,9 @@ _ssl_RAND_add(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (ptr == NULL) { goto exit; } - PyBuffer_FillInfo(&view, args[0], (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&view, args[0], (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) { + goto exit; + } } else { /* any bytes-like object */ if (PyObject_GetBuffer(args[0], &view, PyBUF_SIMPLE) != 0) { @@ -1662,4 +1664,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=6342ea0062ab16c7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fd1c3378fbba5240 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 770878a3f8d2c78..c1df83a72bd8cea 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -4368,7 +4368,9 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st if (ptr == NULL) {{{{ goto exit; }}}} - PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, 0); + if (PyBuffer_FillInfo(&{paramname}, {argname}, (void *)ptr, len, 1, PyBUF_SIMPLE) < 0) {{{{ + goto exit; + }}}} }}}} else {{{{ /* any bytes-like object */ if (PyObject_GetBuffer({argname}, &{paramname}, PyBUF_SIMPLE) != 0) {{{{ From 992446dd5bd3fff92ea0f8064fb19eebfe105cef Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Mon, 5 Feb 2024 16:20:54 +0000 Subject: [PATCH 206/263] GH-113462: Limit the number of versions that a single class can use. (GH-114900) --- Include/cpython/object.h | 1 + Lib/test/test_type_cache.py | 13 +++++++++++++ .../2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst | 2 ++ Objects/typeobject.c | 7 ++++++- 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst diff --git a/Include/cpython/object.h b/Include/cpython/object.h index c93931634fee051..7512bb70c760fdd 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -229,6 +229,7 @@ struct _typeobject { /* bitset of which type-watchers care about this type */ unsigned char tp_watched; + uint16_t tp_versions_used; }; /* This struct is used by the specializer diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 295df78a17374a3..58572c6f4d31578 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -79,6 +79,19 @@ class C: _clear_type_cache() + def test_per_class_limit(self): + class C: + x = 0 + + type_assign_version(C) + orig_version = type_get_version(C) + for i in range(1001): + C.x = i + type_assign_version(C) + + new_version = type_get_version(C) + self.assertEqual(new_version, 0) + @support.cpython_only class TypeCacheWithSpecializationTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst new file mode 100644 index 000000000000000..1a401ecebf019aa --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-02-05-27-48.gh-issue-113462.VMml8q.rst @@ -0,0 +1,2 @@ +Limit the number of versions that a single class can use. Prevents a few +wayward classes using up all the version numbers. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a850473cad813dd..e220d10ce563c29 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -908,6 +908,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { } } +#define MAX_VERSIONS_PER_CLASS 1000 + static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) { @@ -922,7 +924,10 @@ assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) { return 0; } - + if (type->tp_versions_used >= MAX_VERSIONS_PER_CLASS) { + return 0; + } + type->tp_versions_used++; if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) { /* static types */ if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) { From b4ba0f73d6eef3da321bb96aafd09dfbc572e95d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 18:24:54 +0200 Subject: [PATCH 207/263] gh-43457: Tkinter: fix design flaws in wm_attributes() (GH-111404) * When called with a single argument to get a value, it allow to omit the minus prefix. * It can be called with keyword arguments to set attributes. * w.wm_attributes(return_python_dict=True) returns a dict instead of a tuple (it will be the default in future). * Setting wantobjects to 0 no longer affects the result. --- Doc/whatsnew/3.13.rst | 9 +++ Lib/test/test_tkinter/support.py | 2 +- Lib/test/test_tkinter/test_misc.py | 55 +++++++++++++++++++ Lib/tkinter/__init__.py | 51 ++++++++++------- Lib/tkinter/simpledialog.py | 2 +- ...3-10-27-19-24-58.gh-issue-43457.84lx9H.rst | 8 +++ 6 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 77f4fce6c321fee..c25d41351c2f3cd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -421,6 +421,15 @@ tkinter :meth:`!tk_busy_current`, and :meth:`!tk_busy_status`. (Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.) +* The :mod:`tkinter` widget method :meth:`!wm_attributes` now accepts + the attribute name without the minus prefix to get window attributes, + e.g. ``w.wm_attributes('alpha')`` and allows to specify attributes and + values to set as keyword arguments, e.g. ``w.wm_attributes(alpha=0.5)``. + Add new optional keyword-only parameter *return_python_dict*: calling + ``w.wm_attributes(return_python_dict=True)`` returns the attributes as + a dict instead of a tuple. + (Contributed by Serhiy Storchaka in :gh:`43457`.) + * Add support of the "vsapi" element type in the :meth:`~tkinter.ttk.Style.element_create` method of :class:`tkinter.ttk.Style`. diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index a37705f0ae6febb..ebb9e00ff91bf0e 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -14,7 +14,7 @@ def setUpClass(cls): # Some window managers can maximize new windows. cls.root.wm_state('normal') try: - cls.root.wm_attributes('-zoomed', False) + cls.root.wm_attributes(zoomed=False) except tkinter.TclError: pass diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 71553503005c48b..81a20b698a72eb4 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -437,6 +437,61 @@ def test_info_patchlevel(self): self.assertTrue(str(vi).startswith(f'{vi.major}.{vi.minor}')) +class WmTest(AbstractTkTest, unittest.TestCase): + + def test_wm_attribute(self): + w = self.root + attributes = w.wm_attributes(return_python_dict=True) + self.assertIsInstance(attributes, dict) + attributes2 = w.wm_attributes() + self.assertIsInstance(attributes2, tuple) + self.assertEqual(attributes2[::2], + tuple('-' + k for k in attributes)) + self.assertEqual(attributes2[1::2], tuple(attributes.values())) + # silently deprecated + attributes3 = w.wm_attributes(None) + if self.wantobjects: + self.assertEqual(attributes3, attributes2) + else: + self.assertIsInstance(attributes3, str) + + for name in attributes: + self.assertEqual(w.wm_attributes(name), attributes[name]) + # silently deprecated + for name in attributes: + self.assertEqual(w.wm_attributes('-' + name), attributes[name]) + + self.assertIn('alpha', attributes) + self.assertIn('fullscreen', attributes) + self.assertIn('topmost', attributes) + if w._windowingsystem == "win32": + self.assertIn('disabled', attributes) + self.assertIn('toolwindow', attributes) + self.assertIn('transparentcolor', attributes) + if w._windowingsystem == "aqua": + self.assertIn('modified', attributes) + self.assertIn('notify', attributes) + self.assertIn('titlepath', attributes) + self.assertIn('transparent', attributes) + if w._windowingsystem == "x11": + self.assertIn('type', attributes) + self.assertIn('zoomed', attributes) + + w.wm_attributes(alpha=0.5) + self.assertEqual(w.wm_attributes('alpha'), + 0.5 if self.wantobjects else '0.5') + w.wm_attributes(alpha=1.0) + self.assertEqual(w.wm_attributes('alpha'), + 1.0 if self.wantobjects else '1.0') + # silently deprecated + w.wm_attributes('-alpha', 0.5) + self.assertEqual(w.wm_attributes('alpha'), + 0.5 if self.wantobjects else '0.5') + w.wm_attributes(alpha=1.0) + self.assertEqual(w.wm_attributes('alpha'), + 1.0 if self.wantobjects else '1.0') + + class BindTest(AbstractTkTest, unittest.TestCase): def setUp(self): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a1567d332ae6efc..2be9da2cfb92993 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -2108,26 +2108,39 @@ def wm_aspect(self, aspect = wm_aspect - def wm_attributes(self, *args): - """This subcommand returns or sets platform specific attributes - - The first form returns a list of the platform specific flags and - their values. The second form returns the value for the specific - option. The third form sets one or more of the values. The values - are as follows: - - On Windows, -disabled gets or sets whether the window is in a - disabled state. -toolwindow gets or sets the style of the window - to toolwindow (as defined in the MSDN). -topmost gets or sets - whether this is a topmost window (displays above all other - windows). - - On Macintosh, XXXXX - - On Unix, there are currently no special attribute values. + def wm_attributes(self, *args, return_python_dict=False, **kwargs): + """Return or sets platform specific attributes. + + When called with a single argument return_python_dict=True, + return a dict of the platform specific attributes and their values. + When called without arguments or with a single argument + return_python_dict=False, return a tuple containing intermixed + attribute names with the minus prefix and their values. + + When called with a single string value, return the value for the + specific option. When called with keyword arguments, set the + corresponding attributes. """ - args = ('wm', 'attributes', self._w) + args - return self.tk.call(args) + if not kwargs: + if not args: + res = self.tk.call('wm', 'attributes', self._w) + if return_python_dict: + return _splitdict(self.tk, res) + else: + return self.tk.splitlist(res) + if len(args) == 1 and args[0] is not None: + option = args[0] + if option[0] == '-': + # TODO: deprecate + option = option[1:] + return self.tk.call('wm', 'attributes', self._w, '-' + option) + # TODO: deprecate + return self.tk.call('wm', 'attributes', self._w, *args) + elif args: + raise TypeError('wm_attribute() options have been specified as ' + 'positional and keyword arguments') + else: + self.tk.call('wm', 'attributes', self._w, *self._options(kwargs)) attributes = wm_attributes diff --git a/Lib/tkinter/simpledialog.py b/Lib/tkinter/simpledialog.py index 538bbfc318d7044..0f0dc66460f7987 100644 --- a/Lib/tkinter/simpledialog.py +++ b/Lib/tkinter/simpledialog.py @@ -262,7 +262,7 @@ def _setup_dialog(w): w.tk.call("::tk::unsupported::MacWindowStyle", "style", w, "moveableModal", "") elif w._windowingsystem == "x11": - w.wm_attributes("-type", "dialog") + w.wm_attributes(type="dialog") # -------------------------------------------------------------------- # convenience dialogues diff --git a/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst b/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst new file mode 100644 index 000000000000000..401a532ce03e777 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-27-19-24-58.gh-issue-43457.84lx9H.rst @@ -0,0 +1,8 @@ +Fix the :mod:`tkinter` widget method :meth:`!wm_attributes`. It now +accepts the attribute name without the minus prefix to get window attributes +and allows to specify attributes and values to set as keyword arguments. +Add new optional keyword argument *return_python_dict*: calling +``w.wm_attributes(return_python_dict=True)`` returns the attributes as +a dict instead of a tuple. +Calling ``w.wm_attributes()`` now returns a tuple instead of string if +*wantobjects* was set to 0. From 36518e69d74607e5f094ce55286188e4545a947d Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Mon, 5 Feb 2024 18:28:51 +0000 Subject: [PATCH 208/263] GH-108362: Incremental GC implementation (GH-108038) --- Doc/whatsnew/3.13.rst | 34 + Include/internal/pycore_gc.h | 42 +- Include/internal/pycore_object.h | 17 +- Include/internal/pycore_runtime_init.h | 8 +- Lib/test/test_gc.py | 22 +- ...-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst | 13 + Modules/gcmodule.c | 23 +- Objects/object.c | 15 + Objects/structseq.c | 5 +- Python/gc.c | 824 +++++++++++------- Python/gc_free_threading.c | 27 +- Python/import.c | 2 +- Tools/gdb/libpython.py | 7 +- 13 files changed, 647 insertions(+), 392 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c25d41351c2f3cd..0770e28d230b4b3 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -92,6 +92,10 @@ Interpreter improvements: New Features ============ +* The cyclic garbage collector is now incremental. + This means that maximum pause times are reduced, + by an order of magnitude or more for larger heaps. + Improved Error Messages ----------------------- @@ -101,6 +105,13 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) +Incremental Garbage Collection +------------------------------ + +* The cycle garbage collector is now incremental. + This means that maximum pause times are reduced + by an order of magnitude or more for larger heaps. + Other Language Changes ====================== @@ -232,6 +243,29 @@ fractions sign handling, minimum width and grouping. (Contributed by Mark Dickinson in :gh:`111320`.) +gc +-- +* The cyclic garbage collector is now incremental, which changes the meanings + of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as + well as :meth:`gc.get_count` and :meth:`gc.get_stats`. +* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility, + the first value is the threshold for young collections, as before, the second + value determines the rate at which the old collection is scanned; the + default is 10 and higher values mean that the old collection is scanned more slowly. + The third value is meangless and is always zero. +* :meth:`gc.set_threshold` ignores any items after the second. +* :meth:`gc.get_count` and :meth:`gc.get_stats`. + These functions return the same format of results as before. + The only difference is that instead of the results refering to + the young, aging and old generations, the results refer to the + young generation and the aging and collecting spaces of the old generation. + +In summary, code that attempted to manipulate the behavior of the cycle GC may +not work as well as intended, but it is very unlikely to harmful. +All other code will work just fine. +Uses should avoid calling :meth:`gc.collect` unless their workload is episodic, +but that has always been the case to some extent. + glob ---- diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index ca1d9fdf5253b8d..d2f5c69b45ee399 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -71,11 +71,15 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ -#define _PyGC_PREV_MASK_FINALIZED (1) +#define _PyGC_PREV_MASK_FINALIZED 1 /* Bit 1 is set when the object is in generation which is GCed currently. */ -#define _PyGC_PREV_MASK_COLLECTING (2) +#define _PyGC_PREV_MASK_COLLECTING 2 + +/* Bit 0 is set if the object belongs to old space 1 */ +#define _PyGC_NEXT_MASK_OLD_SPACE_1 1 + /* The (N-2) most significant bits contain the real address. */ -#define _PyGC_PREV_SHIFT (2) +#define _PyGC_PREV_SHIFT 2 #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) /* set for debugging information */ @@ -101,11 +105,13 @@ typedef enum { // Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) { - uintptr_t next = gc->_gc_next; + uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK; return (PyGC_Head*)next; } static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) { - gc->_gc_next = (uintptr_t)next; + uintptr_t unext = (uintptr_t)next; + assert((unext & ~_PyGC_PREV_MASK) == 0); + gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext; } // Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags. @@ -113,6 +119,7 @@ static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) { uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK); return (PyGC_Head*)prev; } + static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) { uintptr_t uprev = (uintptr_t)prev; assert((uprev & ~_PyGC_PREV_MASK) == 0); @@ -198,6 +205,13 @@ struct gc_generation { generations */ }; +struct gc_collection_stats { + /* number of collected objects */ + Py_ssize_t collected; + /* total number of uncollectable objects (put into gc.garbage) */ + Py_ssize_t uncollectable; +}; + /* Running stats per generation */ struct gc_generation_stats { /* total number of collections */ @@ -219,8 +233,8 @@ struct _gc_runtime_state { int enabled; int debug; /* linked lists of container objects */ - struct gc_generation generations[NUM_GENERATIONS]; - PyGC_Head *generation0; + struct gc_generation young; + struct gc_generation old[2]; /* a permanent generation which won't be collected */ struct gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; @@ -233,22 +247,20 @@ struct _gc_runtime_state { /* This is the number of objects that survived the last full collection. It approximates the number of long lived objects tracked by the GC. - (by "full collection", we mean a collection of the oldest generation). */ Py_ssize_t long_lived_total; - /* This is the number of objects that survived all "non-full" - collections, and are awaiting to undergo a full collection for - the first time. */ - Py_ssize_t long_lived_pending; + + Py_ssize_t work_to_do; + /* Which of the old spaces is the visited space */ + int visited_space; }; extern void _PyGC_InitState(struct _gc_runtime_state *); -extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, - _PyGC_Reason reason); -extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate); +extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason); +extern void _PyGC_CollectNoFail(PyThreadState *tstate); /* Freeze objects tracked by the GC and ignore them in future collections. */ extern void _PyGC_Freeze(PyInterpreterState *interp); diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 34a83ea228e8b10..efa712c4a0b4588 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -125,19 +125,7 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) } #define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) -static inline void _Py_SetImmortal(PyObject *op) -{ - if (op) { -#ifdef Py_GIL_DISABLED - op->ob_tid = _Py_UNOWNED_TID; - op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; - op->ob_ref_shared = 0; -#else - op->ob_refcnt = _Py_IMMORTAL_REFCNT; -#endif - } -} -#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) +extern void _Py_SetImmortal(PyObject *op); // Makes an immortal object mortal again with the specified refcnt. Should only // be used during runtime finalization. @@ -325,11 +313,12 @@ static inline void _PyObject_GC_TRACK( filename, lineno, __func__); PyInterpreterState *interp = _PyInterpreterState_GET(); - PyGC_Head *generation0 = interp->gc.generation0; + PyGC_Head *generation0 = &interp->gc.young.head; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); + assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0); generation0->_gc_prev = (uintptr_t)gc; #endif } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 0a5c92bb84b524f..4370ad05bdc0584 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -160,12 +160,12 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .gc = { \ .enabled = 1, \ - .generations = { \ - /* .head is set in _PyGC_InitState(). */ \ - { .threshold = 700, }, \ - { .threshold = 10, }, \ + .young = { .threshold = 2000, }, \ + .old = { \ { .threshold = 10, }, \ + { .threshold = 0, }, \ }, \ + .work_to_do = -5000, \ }, \ .object_state = _py_object_state_INIT(INTERP), \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index b01f344cb14a1a7..0002852fce96438 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -383,19 +383,11 @@ def test_collect_generations(self): # each call to collect(N) x = [] gc.collect(0) - # x is now in gen 1 + # x is now in the old gen a, b, c = gc.get_count() - gc.collect(1) - # x is now in gen 2 - d, e, f = gc.get_count() - gc.collect(2) - # x is now in gen 3 - g, h, i = gc.get_count() - # We don't check a, d, g since their exact values depends on + # We don't check a since its exact values depends on # internal implementation details of the interpreter. self.assertEqual((b, c), (1, 0)) - self.assertEqual((e, f), (0, 1)) - self.assertEqual((h, i), (0, 0)) def test_trashcan(self): class Ouch: @@ -846,16 +838,6 @@ def test_get_objects_generations(self): self.assertFalse( any(l is element for element in gc.get_objects(generation=2)) ) - gc.collect(generation=1) - self.assertFalse( - any(l is element for element in gc.get_objects(generation=0)) - ) - self.assertFalse( - any(l is element for element in gc.get_objects(generation=1)) - ) - self.assertTrue( - any(l is element for element in gc.get_objects(generation=2)) - ) gc.collect(generation=2) self.assertFalse( any(l is element for element in gc.get_objects(generation=0)) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst new file mode 100644 index 000000000000000..1fe4e0f41e12951 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst @@ -0,0 +1,13 @@ +Implements an incremental cyclic garbage collector. By collecting the old +generation in increments, there is no need for a full heap scan. This can +hugely reduce maximum pause time for programs with large heaps. + +Reduces the number of generations from three to two. The old generation is +split into two spaces, "aging" and "collecting". + +Collection happens in two steps:: * First, the young generation is scanned +and the survivors moved to the end of the aging space. * Then objects are +taken from the collecting space, at such a rate that all cycles are +collected eventually. Those objects are then scanned and the survivors +moved to the end of the aging space. When the collecting space becomes +empty, the two spaces are swapped. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index ffddef34ecce7a5..3b63dd7a9a83533 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -158,17 +158,12 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1, { GCState *gcstate = get_gc_state(); - gcstate->generations[0].threshold = threshold0; + gcstate->young.threshold = threshold0; if (group_right_1) { - gcstate->generations[1].threshold = threshold1; + gcstate->old[0].threshold = threshold1; } if (group_right_2) { - gcstate->generations[2].threshold = threshold2; - - /* generations higher than 2 get the same threshold */ - for (int i = 3; i < NUM_GENERATIONS; i++) { - gcstate->generations[i].threshold = gcstate->generations[2].threshold; - } + gcstate->old[1].threshold = threshold2; } Py_RETURN_NONE; } @@ -185,9 +180,9 @@ gc_get_threshold_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->generations[0].threshold, - gcstate->generations[1].threshold, - gcstate->generations[2].threshold); + gcstate->young.threshold, + gcstate->old[0].threshold, + 0); } /*[clinic input] @@ -202,9 +197,9 @@ gc_get_count_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->generations[0].count, - gcstate->generations[1].count, - gcstate->generations[2].count); + gcstate->young.count, + gcstate->old[gcstate->visited_space].count, + gcstate->old[gcstate->visited_space^1].count); } /*[clinic input] diff --git a/Objects/object.c b/Objects/object.c index bbf7f98ae3daf92..7247eb21df6b6eb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2387,6 +2387,21 @@ _Py_NewReferenceNoTotal(PyObject *op) new_reference(op); } +void +_Py_SetImmortal(PyObject *op) +{ + if (PyObject_IS_GC(op) && _PyObject_GC_IS_TRACKED(op)) { + _PyObject_GC_UNTRACK(op); + } +#ifdef Py_GIL_DISABLED + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; +#else + op->ob_refcnt = _Py_IMMORTAL_REFCNT; +#endif +} + void _Py_ResurrectReference(PyObject *op) { diff --git a/Objects/structseq.c b/Objects/structseq.c index 581d6ad240885a0..661d96a968fb809 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -603,6 +603,9 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, PyStructSequence_Desc *desc, unsigned long tp_flags) { + if (Py_TYPE(type) == NULL) { + Py_SET_TYPE(type, &PyType_Type); + } Py_ssize_t n_unnamed_members; Py_ssize_t n_members = count_members(desc, &n_unnamed_members); PyMemberDef *members = NULL; @@ -618,7 +621,7 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, } initialize_static_fields(type, desc, members, tp_flags); - _Py_SetImmortal(type); + _Py_SetImmortal((PyObject *)type); } #ifndef NDEBUG else { diff --git a/Python/gc.c b/Python/gc.c index 466467602915264..cda12ff7fbc982f 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -45,7 +45,7 @@ typedef struct _gc_runtime_state GCState; // move_legacy_finalizers() removes this flag instead. // Between them, unreachable list is not normal list and we can not use // most gc_list_* functions for it. -#define NEXT_MASK_UNREACHABLE (1) +#define NEXT_MASK_UNREACHABLE 2 #define AS_GC(op) _Py_AS_GC(op) #define FROM_GC(gc) _Py_FROM_GC(gc) @@ -95,9 +95,48 @@ gc_decref(PyGC_Head *g) g->_gc_prev -= 1 << _PyGC_PREV_SHIFT; } +static inline int +gc_old_space(PyGC_Head *g) +{ + return g->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1; +} -#define GEN_HEAD(gcstate, n) (&(gcstate)->generations[n].head) +static inline int +flip_old_space(int space) +{ + assert(space == 0 || space == 1); + return space ^ _PyGC_NEXT_MASK_OLD_SPACE_1; +} +static inline void +gc_flip_old_space(PyGC_Head *g) +{ + g->_gc_next ^= _PyGC_NEXT_MASK_OLD_SPACE_1; +} + +static inline void +gc_set_old_space(PyGC_Head *g, int space) +{ + assert(space == 0 || space == _PyGC_NEXT_MASK_OLD_SPACE_1); + g->_gc_next &= ~_PyGC_NEXT_MASK_OLD_SPACE_1; + g->_gc_next |= space; +} + +static PyGC_Head * +GEN_HEAD(GCState *gcstate, int n) +{ + assert((gcstate->visited_space & (~1)) == 0); + switch(n) { + case 0: + return &gcstate->young.head; + case 1: + return &gcstate->old[gcstate->visited_space].head; + case 2: + return &gcstate->old[gcstate->visited_space^1].head; + default: + Py_UNREACHABLE(); + } +} static GCState * get_gc_state(void) @@ -116,11 +155,12 @@ _PyGC_InitState(GCState *gcstate) GEN.head._gc_prev = (uintptr_t)&GEN.head; \ } while (0) - for (int i = 0; i < NUM_GENERATIONS; i++) { - assert(gcstate->generations[i].count == 0); - INIT_HEAD(gcstate->generations[i]); - }; - gcstate->generation0 = GEN_HEAD(gcstate, 0); + assert(gcstate->young.count == 0); + assert(gcstate->old[0].count == 0); + assert(gcstate->old[1].count == 0); + INIT_HEAD(gcstate->young); + INIT_HEAD(gcstate->old[0]); + INIT_HEAD(gcstate->old[1]); INIT_HEAD(gcstate->permanent_generation); #undef INIT_HEAD @@ -218,6 +258,7 @@ gc_list_is_empty(PyGC_Head *list) static inline void gc_list_append(PyGC_Head *node, PyGC_Head *list) { + assert((list->_gc_prev & ~_PyGC_PREV_MASK) == 0); PyGC_Head *last = (PyGC_Head *)list->_gc_prev; // last <-> node @@ -275,6 +316,8 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to) PyGC_Head *from_tail = GC_PREV(from); assert(from_head != from); assert(from_tail != from); + assert(gc_list_is_empty(to) || + gc_old_space(to_tail) == gc_old_space(from_tail)); _PyGCHead_SET_NEXT(to_tail, from_head); _PyGCHead_SET_PREV(from_head, to_tail); @@ -343,8 +386,8 @@ enum flagstates {collecting_clear_unreachable_clear, static void validate_list(PyGC_Head *head, enum flagstates flags) { - assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0); - assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0); + assert((head->_gc_prev & ~_PyGC_PREV_MASK) == 0); + assert((head->_gc_next & ~_PyGC_PREV_MASK) == 0); uintptr_t prev_value = 0, next_value = 0; switch (flags) { case collecting_clear_unreachable_clear: @@ -366,7 +409,7 @@ validate_list(PyGC_Head *head, enum flagstates flags) PyGC_Head *gc = GC_NEXT(head); while (gc != head) { PyGC_Head *trueprev = GC_PREV(gc); - PyGC_Head *truenext = (PyGC_Head *)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); + PyGC_Head *truenext = GC_NEXT(gc); assert(truenext != NULL); assert(trueprev == prev); assert((gc->_gc_prev & PREV_MASK_COLLECTING) == prev_value); @@ -376,8 +419,44 @@ validate_list(PyGC_Head *head, enum flagstates flags) } assert(prev == GC_PREV(head)); } + +static void +validate_old(GCState *gcstate) +{ + for (int space = 0; space < 2; space++) { + PyGC_Head *head = &gcstate->old[space].head; + PyGC_Head *gc = GC_NEXT(head); + while (gc != head) { + PyGC_Head *next = GC_NEXT(gc); + assert(gc_old_space(gc) == space); + gc = next; + } + } +} + +static void +validate_consistent_old_space(PyGC_Head *head) +{ + PyGC_Head *prev = head; + PyGC_Head *gc = GC_NEXT(head); + if (gc == head) { + return; + } + int old_space = gc_old_space(gc); + while (gc != head) { + PyGC_Head *truenext = GC_NEXT(gc); + assert(truenext != NULL); + assert(gc_old_space(gc) == old_space); + prev = gc; + gc = truenext; + } + assert(prev == GC_PREV(head)); +} + #else #define validate_list(x, y) do{}while(0) +#define validate_old(g) do{}while(0) +#define validate_consistent_old_space(l) do{}while(0) #endif /*** end of list stuff ***/ @@ -394,15 +473,7 @@ update_refs(PyGC_Head *containers) while (gc != containers) { next = GC_NEXT(gc); - /* Move any object that might have become immortal to the - * permanent generation as the reference count is not accurately - * reflecting the actual number of live references to this object - */ - if (_Py_IsImmortal(FROM_GC(gc))) { - gc_list_move(gc, &get_gc_state()->permanent_generation.head); - gc = next; - continue; - } + assert(!_Py_IsImmortal(FROM_GC(gc))); gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been @@ -500,12 +571,13 @@ visit_reachable(PyObject *op, void *arg) // Manually unlink gc from unreachable list because the list functions // don't work right in the presence of NEXT_MASK_UNREACHABLE flags. PyGC_Head *prev = GC_PREV(gc); - PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); + PyGC_Head *next = GC_NEXT(gc); _PyObject_ASSERT(FROM_GC(prev), prev->_gc_next & NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(next), next->_gc_next & NEXT_MASK_UNREACHABLE); - prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE + prev->_gc_next = gc->_gc_next; // copy flag bits + gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; _PyGCHead_SET_PREV(next, prev); gc_list_append(gc, reachable); @@ -557,6 +629,9 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) * or to the right have been scanned yet. */ + validate_consistent_old_space(young); + /* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */ + uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1); while (gc != young) { if (gc_get_refs(gc)) { /* gc is definitely reachable from outside the @@ -602,17 +677,18 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) // But this may pollute the unreachable list head's 'next' pointer // too. That's semantically senseless but expedient here - the // damage is repaired when this function ends. - last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc); + last->_gc_next = flags | (uintptr_t)gc; _PyGCHead_SET_PREV(gc, last); - gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable); + gc->_gc_next = flags | (uintptr_t)unreachable; unreachable->_gc_prev = (uintptr_t)gc; } - gc = (PyGC_Head*)prev->_gc_next; + gc = _PyGCHead_NEXT(prev); } // young->_gc_prev must be last element remained in the list. young->_gc_prev = (uintptr_t)prev; + young->_gc_next &= _PyGC_PREV_MASK; // don't let the pollution of the list head's next pointer leak - unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE; + unreachable->_gc_next &= _PyGC_PREV_MASK; } static void @@ -669,8 +745,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) PyObject *op = FROM_GC(gc); _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE); + next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; - next = (PyGC_Head*)gc->_gc_next; if (has_legacy_finalizer(op)) { gc_clear_collecting(gc); @@ -689,8 +765,8 @@ clear_unreachable_mask(PyGC_Head *unreachable) assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { _PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE); + next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; - next = (PyGC_Head*)gc->_gc_next; } validate_list(unreachable, collecting_set_unreachable_clear); } @@ -1023,25 +1099,6 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, } -// Show stats for objects in each generations -static void -show_stats_each_generations(GCState *gcstate) -{ - char buf[100]; - size_t pos = 0; - - for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { - pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, - " %zd", - gc_list_size(GEN_HEAD(gcstate, i))); - } - - PySys_FormatStderr( - "gc: objects in each generation:%s\n" - "gc: objects in permanent generation: %zd\n", - buf, gc_list_size(&gcstate->permanent_generation.head)); -} - /* Deduce which objects among "base" are unreachable from outside the list and move them to 'unreachable'. The process consist in the following steps: @@ -1115,7 +1172,6 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) { * the reachable objects instead. But this is a one-time cost, probably not * worth complicating the code to speed just a little. */ - gc_list_init(unreachable); move_unreachable(base, unreachable); // gc_prev is pointer again validate_list(base, collecting_clear_unreachable_clear); validate_list(unreachable, collecting_set_unreachable_set); @@ -1154,219 +1210,272 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, } -/* Invoke progress callbacks to notify clients that garbage collection - * is starting or stopping - */ +#define UNTRACK_TUPLES 1 +#define UNTRACK_DICTS 2 + static void -invoke_gc_callback(PyThreadState *tstate, const char *phase, - int generation, Py_ssize_t collected, - Py_ssize_t uncollectable) -{ - assert(!_PyErr_Occurred(tstate)); +gc_collect_region(PyThreadState *tstate, + PyGC_Head *from, + PyGC_Head *to, + int untrack, + struct gc_collection_stats *stats); - /* we may get called very early */ - GCState *gcstate = &tstate->interp->gc; - if (gcstate->callbacks == NULL) { - return; +static inline Py_ssize_t +gc_list_set_space(PyGC_Head *list, int space) +{ + Py_ssize_t size = 0; + PyGC_Head *gc; + for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { + gc_set_old_space(gc, space); + size++; } + return size; +} - /* The local variable cannot be rebound, check it for sanity */ - assert(PyList_CheckExact(gcstate->callbacks)); - PyObject *info = NULL; - if (PyList_GET_SIZE(gcstate->callbacks) != 0) { - info = Py_BuildValue("{sisnsn}", - "generation", generation, - "collected", collected, - "uncollectable", uncollectable); - if (info == NULL) { - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + +static void +add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats) +{ + gcstate->generation_stats[gen].collected += stats->collected; + gcstate->generation_stats[gen].uncollectable += stats->uncollectable; + gcstate->generation_stats[gen].collections += 1; +} + + +/* Multiply by 4 so that the default incremental threshold of 10 + * scans objects at 40% the rate that the young gen tenures them. */ +#define SCAN_RATE_MULTIPLIER 4 + + +static void +gc_collect_young(PyThreadState *tstate, + struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + PyGC_Head *young = &gcstate->young.head; + PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; +#ifdef Py_STATS + { + Py_ssize_t count = 0; + PyGC_Head *gc; + for (gc = GC_NEXT(young); gc != young; gc = GC_NEXT(gc)) { + count++; } } +#endif - PyObject *phase_obj = PyUnicode_FromString(phase); - if (phase_obj == NULL) { - Py_XDECREF(info); - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + PyGC_Head survivors; + gc_list_init(&survivors); + gc_collect_region(tstate, young, &survivors, UNTRACK_TUPLES, stats); + Py_ssize_t survivor_count = 0; + if (gcstate->visited_space) { + /* objects in visited space have bit set, so we set it here */ + survivor_count = gc_list_set_space(&survivors, 1); } - - PyObject *stack[] = {phase_obj, info}; - for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { - PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); - Py_INCREF(cb); /* make sure cb doesn't go away */ - r = PyObject_Vectorcall(cb, stack, 2, NULL); - if (r == NULL) { - PyErr_WriteUnraisable(cb); - } - else { - Py_DECREF(r); + else { + PyGC_Head *gc; + for (gc = GC_NEXT(&survivors); gc != &survivors; gc = GC_NEXT(gc)) { +#ifdef GC_DEBUG + assert(gc_old_space(gc) == 0); +#endif + survivor_count++; } - Py_DECREF(cb); } - Py_DECREF(phase_obj); - Py_XDECREF(info); - assert(!_PyErr_Occurred(tstate)); + gc_list_merge(&survivors, visited); + validate_old(gcstate); + gcstate->young.count = 0; + gcstate->old[gcstate->visited_space].count++; + Py_ssize_t scale_factor = gcstate->old[0].threshold; + if (scale_factor < 1) { + scale_factor = 1; + } + gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor; + add_stats(gcstate, 0, stats); +} + +static inline int +is_in_visited(PyGC_Head *gc, int visited_space) +{ + assert(visited_space == 0 || flip_old_space(visited_space) == 0); + return gc_old_space(gc) == visited_space; } +struct container_and_flag { + PyGC_Head *container; + int visited_space; +}; -/* Find the oldest generation (highest numbered) where the count - * exceeds the threshold. Objects in the that generation and - * generations younger than it will be collected. */ +/* A traversal callback for adding to container) */ static int -gc_select_generation(GCState *gcstate) -{ - for (int i = NUM_GENERATIONS-1; i >= 0; i--) { - if (gcstate->generations[i].count > gcstate->generations[i].threshold) { - /* Avoid quadratic performance degradation in number - of tracked objects (see also issue #4074): - - To limit the cost of garbage collection, there are two strategies; - - make each collection faster, e.g. by scanning fewer objects - - do less collections - This heuristic is about the latter strategy. - - In addition to the various configurable thresholds, we only trigger a - full collection if the ratio - - long_lived_pending / long_lived_total - - is above a given value (hardwired to 25%). - - The reason is that, while "non-full" collections (i.e., collections of - the young and middle generations) will always examine roughly the same - number of objects -- determined by the aforementioned thresholds --, - the cost of a full collection is proportional to the total number of - long-lived objects, which is virtually unbounded. - - Indeed, it has been remarked that doing a full collection every - <constant number> of object creations entails a dramatic performance - degradation in workloads which consist in creating and storing lots of - long-lived objects (e.g. building a large list of GC-tracked objects would - show quadratic performance, instead of linear as expected: see issue #4074). - - Using the above ratio, instead, yields amortized linear performance in - the total number of objects (the effect of which can be summarized - thusly: "each full garbage collection is more and more costly as the - number of objects grows, but we do fewer and fewer of them"). - - This heuristic was suggested by Martin von Löwis on python-dev in - June 2008. His original analysis and proposal can be found at: - http://mail.python.org/pipermail/python-dev/2008-June/080579.html - */ - if (i == NUM_GENERATIONS - 1 - && gcstate->long_lived_pending < gcstate->long_lived_total / 4) - { - continue; - } - return i; +visit_add_to_container(PyObject *op, void *arg) +{ + OBJECT_STAT_INC(object_visits); + struct container_and_flag *cf = (struct container_and_flag *)arg; + int visited = cf->visited_space; + assert(visited == get_gc_state()->visited_space); + if (_PyObject_IS_GC(op)) { + PyGC_Head *gc = AS_GC(op); + if (_PyObject_GC_IS_TRACKED(op) && + gc_old_space(gc) != visited) { + assert(!_Py_IsImmortal(op)); + gc_flip_old_space(gc); + gc_list_move(gc, cf->container); } } - return -1; + return 0; } - -/* This is the main function. Read this to understand how the - * collection process works. */ -static Py_ssize_t -gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) +static uintptr_t +expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCState *gcstate) { - int i; - Py_ssize_t m = 0; /* # objects collected */ - Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ - PyGC_Head *young; /* the generation we are examining */ - PyGC_Head *old; /* next older generation */ - PyGC_Head unreachable; /* non-problematic unreachable trash */ - PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ - PyGC_Head *gc; - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ - GCState *gcstate = &tstate->interp->gc; - - // gc_collect_main() must not be called before _PyGC_Init - // or after _PyGC_Fini() - assert(gcstate->garbage != NULL); - assert(!_PyErr_Occurred(tstate)); + validate_list(container, collecting_clear_unreachable_clear); + struct container_and_flag arg = { + .container = container, + .visited_space = gcstate->visited_space, + }; + uintptr_t size = 0; + assert(GC_NEXT(gc) == container); + while (gc != container) { + /* Survivors will be moved to visited space, so they should + * have been marked as visited */ + assert(is_in_visited(gc, gcstate->visited_space)); + PyObject *op = FROM_GC(gc); + if (_Py_IsImmortal(op)) { + PyGC_Head *next = GC_NEXT(gc); + gc_list_move(gc, &get_gc_state()->permanent_generation.head); + gc = next; + continue; + } + traverseproc traverse = Py_TYPE(op)->tp_traverse; + (void) traverse(op, + visit_add_to_container, + &arg); + gc = GC_NEXT(gc); + size++; + } + return size; +} - int expected = 0; - if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { - // Don't start a garbage collection if one is already in progress. - return 0; +/* Do bookkeeping for a completed GC cycle */ +static void +completed_cycle(GCState *gcstate) +{ + assert(gc_list_is_empty(&gcstate->old[gcstate->visited_space^1].head)); + assert(gc_list_is_empty(&gcstate->young.head)); + gcstate->visited_space = flip_old_space(gcstate->visited_space); + if (gcstate->work_to_do > 0) { + gcstate->work_to_do = 0; } +} - if (generation == GENERATION_AUTO) { - // Select the oldest generation that needs collecting. We will collect - // objects from that generation and all generations younger than it. - generation = gc_select_generation(gcstate); - if (generation < 0) { - // No generation needs to be collected. - _Py_atomic_store_int(&gcstate->collecting, 0); - return 0; +static void +gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + if (gcstate->work_to_do <= 0) { + /* No work to do */ + return; + } + PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head; + PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; + PyGC_Head increment; + gc_list_init(&increment); + if (gc_list_is_empty(not_visited)) { + completed_cycle(gcstate); + return; + } + Py_ssize_t region_size = 0; + while (region_size < gcstate->work_to_do) { + if (gc_list_is_empty(not_visited)) { + break; } + PyGC_Head *gc = _PyGCHead_NEXT(not_visited); + gc_list_move(gc, &increment); + gc_set_old_space(gc, gcstate->visited_space); + region_size += expand_region_transitively_reachable(&increment, gc, gcstate); } - - assert(generation >= 0 && generation < NUM_GENERATIONS); - -#ifdef Py_STATS - if (_Py_stats) { - _Py_stats->object_stats.object_visits = 0; + assert(region_size == gc_list_size(&increment)); + PyGC_Head survivors; + gc_list_init(&survivors); + gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats); + gc_list_merge(&survivors, visited); + assert(gc_list_is_empty(&increment)); + gcstate->work_to_do -= region_size; + validate_old(gcstate); + add_stats(gcstate, 1, stats); + if (gc_list_is_empty(not_visited)) { + completed_cycle(gcstate); } -#endif - GC_STAT_ADD(generation, collections, 1); +} - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(tstate, "start", generation, 0, 0); - } - if (gcstate->debug & _PyGC_DEBUG_STATS) { - PySys_WriteStderr("gc: collecting generation %d...\n", generation); - show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); +static void +gc_collect_full(PyThreadState *tstate, + struct gc_collection_stats *stats) +{ + GCState *gcstate = &tstate->interp->gc; + validate_old(gcstate); + PyGC_Head *young = &gcstate->young.head; + PyGC_Head *old0 = &gcstate->old[0].head; + PyGC_Head *old1 = &gcstate->old[1].head; + /* merge all generations into old0 */ + gc_list_merge(young, old0); + gcstate->young.count = 0; + PyGC_Head *gc = GC_NEXT(old1); + while (gc != old1) { + PyGC_Head *next = GC_NEXT(gc); + gc_set_old_space(gc, 0); + gc = next; } + gc_list_merge(old1, old0); - if (PyDTrace_GC_START_ENABLED()) { - PyDTrace_GC_START(generation); - } + gc_collect_region(tstate, old0, old0, + UNTRACK_TUPLES | UNTRACK_DICTS, + stats); + gcstate->visited_space = 1; + gcstate->young.count = 0; + gcstate->old[0].count = 0; + gcstate->old[1].count = 0; - /* update collection and allocation counters */ - if (generation+1 < NUM_GENERATIONS) { - gcstate->generations[generation+1].count += 1; - } - for (i = 0; i <= generation; i++) { - gcstate->generations[i].count = 0; - } + gcstate->work_to_do = - gcstate->young.threshold * 2; - /* merge younger generations with one we are currently collecting */ - for (i = 0; i < generation; i++) { - gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); - } + _PyGC_ClearAllFreeLists(tstate->interp); + validate_old(gcstate); + add_stats(gcstate, 2, stats); +} - /* handy references */ - young = GEN_HEAD(gcstate, generation); - if (generation < NUM_GENERATIONS-1) { - old = GEN_HEAD(gcstate, generation+1); - } - else { - old = young; - } - validate_list(old, collecting_clear_unreachable_clear); +/* This is the main function. Read this to understand how the + * collection process works. */ +static void +gc_collect_region(PyThreadState *tstate, + PyGC_Head *from, + PyGC_Head *to, + int untrack, + struct gc_collection_stats *stats) +{ + PyGC_Head unreachable; /* non-problematic unreachable trash */ + PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ + PyGC_Head *gc; /* initialize to prevent a compiler warning */ + GCState *gcstate = &tstate->interp->gc; - deduce_unreachable(young, &unreachable); + assert(gcstate->garbage != NULL); + assert(!_PyErr_Occurred(tstate)); - untrack_tuples(young); - /* Move reachable objects to next generation. */ - if (young != old) { - if (generation == NUM_GENERATIONS - 2) { - gcstate->long_lived_pending += gc_list_size(young); - } - gc_list_merge(young, old); + gc_list_init(&unreachable); + deduce_unreachable(from, &unreachable); + validate_consistent_old_space(from); + if (untrack & UNTRACK_TUPLES) { + untrack_tuples(from); } - else { - /* We only un-track dicts in full collections, to avoid quadratic - dict build-up. See issue #14775. */ - untrack_dicts(young); - gcstate->long_lived_pending = 0; - gcstate->long_lived_total = gc_list_size(young); + if (untrack & UNTRACK_DICTS) { + untrack_dicts(from); } + validate_consistent_old_space(to); + if (from != to) { + gc_list_merge(from, to); + } + validate_consistent_old_space(to); + /* Move reachable objects to next generation. */ /* All objects in unreachable are trash, but objects reachable from * legacy finalizers (e.g. tp_del) can't safely be deleted. @@ -1380,10 +1489,8 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) * and we move those into the finalizers list too. */ move_legacy_finalizer_reachable(&finalizers); - validate_list(&finalizers, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); - /* Print debugging information. */ if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) { for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) { @@ -1392,89 +1499,99 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) } /* Clear weakrefs and invoke callbacks as necessary. */ - m += handle_weakrefs(&unreachable, old); - - validate_list(old, collecting_clear_unreachable_clear); + stats->collected += handle_weakrefs(&unreachable, to); + validate_list(to, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); /* Call tp_finalize on objects which have one. */ finalize_garbage(tstate, &unreachable); - /* Handle any objects that may have resurrected after the call * to 'finalize_garbage' and continue the collection with the * objects that are still unreachable */ PyGC_Head final_unreachable; - handle_resurrected_objects(&unreachable, &final_unreachable, old); + gc_list_init(&final_unreachable); + handle_resurrected_objects(&unreachable, &final_unreachable, to); /* Call tp_clear on objects in the final_unreachable set. This will cause * the reference cycles to be broken. It may also cause some objects * in finalizers to be freed. */ - m += gc_list_size(&final_unreachable); - delete_garbage(tstate, gcstate, &final_unreachable, old); + stats->collected += gc_list_size(&final_unreachable); + delete_garbage(tstate, gcstate, &final_unreachable, to); /* Collect statistics on uncollectable objects found and print * debugging information. */ + Py_ssize_t n = 0; for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) { n++; if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) debug_cycle("uncollectable", FROM_GC(gc)); } - if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); - PySys_WriteStderr( - "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", - n+m, n, d); - } - + stats->uncollectable = n; /* Append instances in the uncollectable set to a Python * reachable list of garbage. The programmer has to deal with * this if they insist on creating this type of structure. */ - handle_legacy_finalizers(tstate, gcstate, &finalizers, old); - validate_list(old, collecting_clear_unreachable_clear); + handle_legacy_finalizers(tstate, gcstate, &finalizers, to); + validate_list(to, collecting_clear_unreachable_clear); +} - /* Clear free list only during the collection of the highest - * generation */ - if (generation == NUM_GENERATIONS-1) { - _PyGC_ClearAllFreeLists(tstate->interp); - } +/* Invoke progress callbacks to notify clients that garbage collection + * is starting or stopping + */ +static void +do_gc_callback(GCState *gcstate, const char *phase, + int generation, struct gc_collection_stats *stats) +{ + assert(!PyErr_Occurred()); - if (_PyErr_Occurred(tstate)) { - if (reason == _Py_GC_REASON_SHUTDOWN) { - _PyErr_Clear(tstate); - } - else { - PyErr_FormatUnraisable("Exception ignored in garbage collection"); + /* The local variable cannot be rebound, check it for sanity */ + assert(PyList_CheckExact(gcstate->callbacks)); + PyObject *info = NULL; + if (PyList_GET_SIZE(gcstate->callbacks) != 0) { + info = Py_BuildValue("{sisnsn}", + "generation", generation, + "collected", stats->collected, + "uncollectable", stats->uncollectable); + if (info == NULL) { + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } } - /* Update stats */ - struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; - stats->collections++; - stats->collected += m; - stats->uncollectable += n; - - GC_STAT_ADD(generation, objects_collected, m); -#ifdef Py_STATS - if (_Py_stats) { - GC_STAT_ADD(generation, object_visits, - _Py_stats->object_stats.object_visits); - _Py_stats->object_stats.object_visits = 0; + PyObject *phase_obj = PyUnicode_FromString(phase); + if (phase_obj == NULL) { + Py_XDECREF(info); + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } -#endif - if (PyDTrace_GC_DONE_ENABLED()) { - PyDTrace_GC_DONE(n + m); + PyObject *stack[] = {phase_obj, info}; + for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { + PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); + Py_INCREF(cb); /* make sure cb doesn't go away */ + r = PyObject_Vectorcall(cb, stack, 2, NULL); + if (r == NULL) { + PyErr_WriteUnraisable(cb); + } + else { + Py_DECREF(r); + } + Py_DECREF(cb); } + Py_DECREF(phase_obj); + Py_XDECREF(info); + assert(!PyErr_Occurred()); +} - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(tstate, "stop", generation, m, n); +static void +invoke_gc_callback(GCState *gcstate, const char *phase, + int generation, struct gc_collection_stats *stats) +{ + if (gcstate->callbacks == NULL) { + return; } - - assert(!_PyErr_Occurred(tstate)); - _Py_atomic_store_int(&gcstate->collecting, 0); - return n + m; + do_gc_callback(gcstate, phase, generation, stats); } static int @@ -1549,7 +1666,7 @@ _PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) } } else { - if (append_objects(result, GEN_HEAD(gcstate, generation))) { + if (append_objects(result, GEN_HEAD(gcstate, (int)generation))) { goto error; } } @@ -1564,10 +1681,16 @@ void _PyGC_Freeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; - for (int i = 0; i < NUM_GENERATIONS; ++i) { - gc_list_merge(GEN_HEAD(gcstate, i), &gcstate->permanent_generation.head); - gcstate->generations[i].count = 0; - } + gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head); + gcstate->young.count = 0; + PyGC_Head*old0 = &gcstate->old[0].head; + PyGC_Head*old1 = &gcstate->old[1].head; + gc_list_merge(old0, &gcstate->permanent_generation.head); + gcstate->old[0].count = 0; + gc_list_set_space(old1, 0); + gc_list_merge(old1, &gcstate->permanent_generation.head); + gcstate->old[1].count = 0; + validate_old(gcstate); } void @@ -1575,7 +1698,8 @@ _PyGC_Unfreeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; gc_list_merge(&gcstate->permanent_generation.head, - GEN_HEAD(gcstate, NUM_GENERATIONS-1)); + &gcstate->old[0].head); + validate_old(gcstate); } Py_ssize_t @@ -1611,32 +1735,100 @@ PyGC_IsEnabled(void) return gcstate->enabled; } -/* Public API to invoke gc.collect() from C */ +// Show stats for objects in each generations +static void +show_stats_each_generations(GCState *gcstate) +{ + char buf[100]; + size_t pos = 0; + + for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { + pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, + " %zd", + gc_list_size(GEN_HEAD(gcstate, i))); + } + + PySys_FormatStderr( + "gc: objects in each generation:%s\n" + "gc: objects in permanent generation: %zd\n", + buf, gc_list_size(&gcstate->permanent_generation.head)); +} + Py_ssize_t -PyGC_Collect(void) +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) { - PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - if (!gcstate->enabled) { + int expected = 0; + if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { + // Don't start a garbage collection if one is already in progress. return 0; } - Py_ssize_t n; + struct gc_collection_stats stats = { 0 }; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(gcstate, "start", generation, &stats); + } + _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ + if (gcstate->debug & _PyGC_DEBUG_STATS) { + PySys_WriteStderr("gc: collecting generation %d...\n", generation); + show_stats_each_generations(gcstate); + t1 = _PyTime_GetPerfCounter(); + } + if (PyDTrace_GC_START_ENABLED()) { + PyDTrace_GC_START(generation); + } + GC_STAT_ADD(generation, collections, 1); PyObject *exc = _PyErr_GetRaisedException(tstate); - n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); + switch(generation) { + case 0: + gc_collect_young(tstate, &stats); + break; + case 1: + gc_collect_young(tstate, &stats); + gc_collect_increment(tstate, &stats); + break; + case 2: + gc_collect_full(tstate, &stats); + break; + default: + Py_UNREACHABLE(); + } + if (PyDTrace_GC_DONE_ENABLED()) { + PyDTrace_GC_DONE(stats.uncollectable + stats.collected); + } + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(gcstate, "stop", generation, &stats); + } _PyErr_SetRaisedException(tstate, exc); + GC_STAT_ADD(generation, objects_collected, stats.collected); +#ifdef Py_STATS + if (_Py_stats) { + GC_STAT_ADD(generation, object_visits, + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; + } +#endif + validate_old(gcstate); + if (gcstate->debug & _PyGC_DEBUG_STATS) { + double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + PySys_WriteStderr( + "gc: done, %zd collected, %zd uncollectable, %.4fs elapsed\n", + stats.collected, stats.uncollectable, d); + } - return n; + _Py_atomic_store_int(&gcstate->collecting, 0); + return stats.uncollectable + stats.collected; } +/* Public API to invoke gc.collect() from C */ Py_ssize_t -_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +PyGC_Collect(void) { - return gc_collect_main(tstate, generation, reason); + return _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_MANUAL); } -Py_ssize_t +void _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1645,7 +1837,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_SHUTDOWN); } void @@ -1780,10 +1972,10 @@ _PyObject_GC_Link(PyObject *op) GCState *gcstate = &tstate->interp->gc; g->_gc_next = 0; g->_gc_prev = 0; - gcstate->generations[0].count++; /* number of allocated GC objects */ - if (gcstate->generations[0].count > gcstate->generations[0].threshold && + gcstate->young.count++; /* number of allocated GC objects */ + if (gcstate->young.count > gcstate->young.threshold && gcstate->enabled && - gcstate->generations[0].threshold && + gcstate->young.threshold && !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { @@ -1794,7 +1986,9 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); + if (tstate->interp->gc.enabled) { + _PyGC_Collect(tstate, 1, _Py_GC_REASON_HEAP); + } } static PyObject * @@ -1897,8 +2091,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->generations[0].count > 0) { - gcstate->generations[0].count--; + if (gcstate->young.count > 0) { + gcstate->young.count--; } PyObject_Free(((char *)op)-presize); } @@ -1921,26 +2115,36 @@ PyObject_GC_IsFinalized(PyObject *obj) return 0; } +static int +visit_generation(gcvisitobjects_t callback, void *arg, struct gc_generation *gen) +{ + PyGC_Head *gc_list, *gc; + gc_list = &gen->head; + for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { + PyObject *op = FROM_GC(gc); + Py_INCREF(op); + int res = callback(op, arg); + Py_DECREF(op); + if (!res) { + return -1; + } + } + return 0; +} + void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) { - size_t i; GCState *gcstate = get_gc_state(); int origenstate = gcstate->enabled; gcstate->enabled = 0; - for (i = 0; i < NUM_GENERATIONS; i++) { - PyGC_Head *gc_list, *gc; - gc_list = GEN_HEAD(gcstate, i); - for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { - PyObject *op = FROM_GC(gc); - Py_INCREF(op); - int res = callback(op, arg); - Py_DECREF(op); - if (!res) { - goto done; - } - } + if (visit_generation(callback, arg, &gcstate->young)) { + goto done; + } + if (visit_generation(callback, arg, &gcstate->old[0])) { + goto done; } + visit_generation(callback, arg, &gcstate->old[1]); done: gcstate->enabled = origenstate; } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 8fbcdb15109b76c..1c4da726866e4e1 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -616,7 +616,7 @@ void _PyGC_InitState(GCState *gcstate) { // TODO: move to pycore_runtime_init.h once the incremental GC lands. - gcstate->generations[0].threshold = 2000; + gcstate->young.threshold = 2000; } @@ -911,8 +911,8 @@ cleanup_worklist(struct worklist *worklist) static bool gc_should_collect(GCState *gcstate) { - int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); - int threshold = gcstate->generations[0].threshold; + int count = _Py_atomic_load_int_relaxed(&gcstate->young.count); + int threshold = gcstate->young.threshold; if (count <= threshold || threshold == 0 || !gcstate->enabled) { return false; } @@ -920,7 +920,7 @@ gc_should_collect(GCState *gcstate) // objects. A few tests rely on immediate scheduling of the GC so we ignore // the scaled threshold if generations[1].threshold is set to zero. return (count > gcstate->long_lived_total / 4 || - gcstate->generations[1].threshold == 0); + gcstate->old[0].threshold == 0); } static void @@ -1031,10 +1031,15 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* update collection and allocation counters */ if (generation+1 < NUM_GENERATIONS) { - gcstate->generations[generation+1].count += 1; + gcstate->old[generation].count += 1; } for (i = 0; i <= generation; i++) { - gcstate->generations[i].count = 0; + if (i == 0) { + gcstate->young.count = 0; + } + else { + gcstate->old[i-1].count = 0; + } } PyInterpreterState *interp = tstate->interp; @@ -1357,7 +1362,7 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) return gc_collect_main(tstate, generation, reason); } -Py_ssize_t +void _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1366,7 +1371,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1490,7 +1495,7 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->generations[0].count++; + gcstate->young.count++; if (gc_should_collect(gcstate) && !_Py_atomic_load_int_relaxed(&gcstate->collecting)) @@ -1605,8 +1610,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->generations[0].count > 0) { - gcstate->generations[0].count--; + if (gcstate->young.count > 0) { + gcstate->young.count--; } PyObject_Free(((char *)op)-presize); } diff --git a/Python/import.c b/Python/import.c index 2fd0c08a6bb5aec..dfc5ec1f2f29272 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1030,7 +1030,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) if (!already_set) { /* We assume that all module defs are statically allocated and will never be freed. Otherwise, we would incref here. */ - _Py_SetImmortal(def); + _Py_SetImmortal((PyObject *)def); } res = 0; diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 483f28b46dfec74..96b891481d9f462 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -1753,8 +1753,11 @@ def is_waiting_for_gil(self): return (name == 'take_gil') def is_gc_collect(self): - '''Is this frame gc_collect_main() within the garbage-collector?''' - return self._gdbframe.name() in ('collect', 'gc_collect_main') + '''Is this frame a collector within the garbage-collector?''' + return self._gdbframe.name() in ( + 'collect', 'gc_collect_full', 'gc_collect_main', + 'gc_collect_young', 'gc_collect_increment' + ) def get_pyop(self): try: From bcccf1fb63870c1b7f8abe246e27b7fff343abd7 Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Mon, 5 Feb 2024 10:35:59 -0800 Subject: [PATCH 209/263] gh-112075: Add gc shared bits (#114931) Add GC shared flags for objects to the GC bit states in free-threaded builds --- Include/internal/pycore_gc.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index d2f5c69b45ee399..aeb07238fc83455 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -43,6 +43,7 @@ static inline PyObject* _Py_FROM_GC(PyGC_Head *gc) { # define _PyGC_BITS_FINALIZED (2) # define _PyGC_BITS_UNREACHABLE (4) # define _PyGC_BITS_FROZEN (8) +# define _PyGC_BITS_SHARED (16) #endif /* True if the object is currently tracked by the GC. */ @@ -68,6 +69,22 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { return 1; } +#ifdef Py_GIL_DISABLED + +/* True if an object is shared between multiple threads and + * needs special purpose when freeing to do the possibility + * of in-flight lock-free reads occuring */ +static inline int _PyObject_GC_IS_SHARED(PyObject *op) { + return (op->ob_gc_bits & _PyGC_BITS_SHARED) != 0; +} +#define _PyObject_GC_IS_SHARED(op) _PyObject_GC_IS_SHARED(_Py_CAST(PyObject*, op)) + +static inline void _PyObject_GC_SET_SHARED(PyObject *op) { + op->ob_gc_bits |= _PyGC_BITS_SHARED; +} +#define _PyObject_GC_SET_SHARED(op) _PyObject_GC_SET_SHARED(_Py_CAST(PyObject*, op)) + +#endif /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ From 750489cc774df44daa2c0d23e8a404fe62be93d1 Mon Sep 17 00:00:00 2001 From: HarryLHW <123lhw321@gmail.com> Date: Tue, 6 Feb 2024 04:22:57 +0800 Subject: [PATCH 210/263] gh-114967: Fix "Built-in Exceptions" documentation ambiguous wording (#114968) Change the somewhat vague "listed below" to "listed in this chapter" in Doc/library/exceptions.rst. The exceptions are listed in multiple sections after two intermediate sections. --------- Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> --- Doc/library/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index f821776c2861331..3191315049ad5a4 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -16,7 +16,7 @@ equivalent, even if they have the same name. .. index:: pair: statement; raise -The built-in exceptions listed below can be generated by the interpreter or +The built-in exceptions listed in this chapter can be generated by the interpreter or built-in functions. Except where mentioned, they have an "associated value" indicating the detailed cause of the error. This may be a string or a tuple of several items of information (e.g., an error code and a string explaining the From 4aa4f0906df9fc9c6c6f6657f2c521468c6b1688 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 22:42:43 +0200 Subject: [PATCH 211/263] gh-109475: Fix support of explicit option value "--" in argparse (GH-114814) For example "--option=--". --- Lib/argparse.py | 2 +- Lib/test/test_argparse.py | 16 ++++++++++++++++ ...024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst diff --git a/Lib/argparse.py b/Lib/argparse.py index 9e19f39fadd87bc..2131d729746d41e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2485,7 +2485,7 @@ def parse_known_intermixed_args(self, args=None, namespace=None): # ======================== def _get_values(self, action, arg_strings): # for everything but PARSER, REMAINDER args, strip out first '--' - if action.nargs not in [PARSER, REMAINDER]: + if not action.option_strings and action.nargs not in [PARSER, REMAINDER]: try: arg_strings.remove('--') except ValueError: diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 940d7e95f96e20a..d1f3d40000140d3 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5405,6 +5405,22 @@ def test_zero_or_more_optional(self): args = parser.parse_args([]) self.assertEqual(NS(x=[]), args) + def test_double_dash(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', nargs='*') + parser.add_argument('bar', nargs='*') + + args = parser.parse_args(['--foo=--']) + self.assertEqual(NS(foo=['--'], bar=[]), args) + args = parser.parse_args(['--foo', '--']) + self.assertEqual(NS(foo=[], bar=[]), args) + args = parser.parse_args(['-f--']) + self.assertEqual(NS(foo=['--'], bar=[]), args) + args = parser.parse_args(['-f', '--']) + self.assertEqual(NS(foo=[], bar=[]), args) + args = parser.parse_args(['--foo', 'a', 'b', '--', 'c', 'd']) + self.assertEqual(NS(foo=['a', 'b'], bar=['c', 'd']), args) + # =========================== # parse_intermixed_args tests diff --git a/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst b/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst new file mode 100644 index 000000000000000..7582cb2bcd76298 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-31-20-07-11.gh-issue-109475.lmTb9S.rst @@ -0,0 +1,2 @@ +Fix support of explicit option value "--" in :mod:`argparse` (e.g. +``--option=--``). From 09096a1647913526a3d4fa69a9d2056ec82a8f37 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Mon, 5 Feb 2024 21:49:17 +0100 Subject: [PATCH 212/263] gh-115015: Argument Clinic: fix generated code for METH_METHOD methods without params (#115016) --- Lib/test/clinic.test.c | 4 +- Lib/test/test_clinic.py | 20 ++++++++++ ...-02-05-02-45-51.gh-issue-115015.rgtiDB.rst | 5 +++ Modules/_io/clinic/bufferedio.c.h | 4 +- Modules/_io/clinic/bytesio.c.h | 4 +- Modules/_io/clinic/fileio.c.h | 4 +- Modules/_io/clinic/iobase.c.h | 4 +- Modules/_io/clinic/textio.c.h | 4 +- Modules/_io/clinic/winconsoleio.c.h | 4 +- Modules/_sre/clinic/sre.c.h | 6 +-- Modules/_testclinic.c | 37 +++++++++++++++++++ Modules/cjkcodecs/clinic/multibytecodec.c.h | 4 +- Modules/clinic/_asynciomodule.c.h | 6 +-- Modules/clinic/_curses_panel.c.h | 12 +++--- Modules/clinic/_dbmmodule.c.h | 6 +-- Modules/clinic/_elementtree.c.h | 6 +-- Modules/clinic/_gdbmmodule.c.h | 12 +++--- Modules/clinic/_lsprof.c.h | 4 +- Modules/clinic/_pickle.c.h | 4 +- Modules/clinic/_queuemodule.c.h | 4 +- Modules/clinic/_testclinic.c.h | 24 +++++++++++- Modules/clinic/_testmultiphase.c.h | 8 ++-- Modules/clinic/arraymodule.c.h | 4 +- Modules/clinic/md5module.c.h | 4 +- Modules/clinic/posixmodule.c.h | 4 +- Modules/clinic/sha1module.c.h | 4 +- Modules/clinic/sha2module.c.h | 6 +-- Modules/clinic/zlibmodule.c.h | 10 ++--- Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 + Tools/clinic/clinic.py | 2 +- 30 files changed, 153 insertions(+), 68 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index b15aeb898d35a12..168f6f73f6186fa 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4625,7 +4625,7 @@ Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls); static PyObject * Test_cls_no_params(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "cls_no_params() takes no arguments"); return NULL; } @@ -4634,7 +4634,7 @@ Test_cls_no_params(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_s static PyObject * Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls) -/*[clinic end generated code: output=cc8845f22cff3dcb input=e7e2e4e344e96a11]*/ +/*[clinic end generated code: output=4d68b4652c144af3 input=e7e2e4e344e96a11]*/ /*[clinic input] diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 7323bdd801f4be0..e987ce546054979 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3288,6 +3288,26 @@ def test_cloned_func_with_converter_exception_message(self): func = getattr(ac_tester, name) self.assertEqual(func(), name) + def test_meth_method_no_params(self): + obj = ac_tester.TestClass() + meth = obj.meth_method_no_params + check = partial(self.assertRaisesRegex, TypeError, "no arguments") + check(meth, 1) + check(meth, a=1) + + def test_meth_method_no_params_capi(self): + from _testcapi import pyobject_vectorcall + obj = ac_tester.TestClass() + meth = obj.meth_method_no_params + pyobject_vectorcall(meth, None, None) + pyobject_vectorcall(meth, (), None) + pyobject_vectorcall(meth, (), ()) + pyobject_vectorcall(meth, None, ()) + + check = partial(self.assertRaisesRegex, TypeError, "no arguments") + check(pyobject_vectorcall, meth, (1,), None) + check(pyobject_vectorcall, meth, (1,), ("a",)) + def test_depr_star_new(self): cls = ac_tester.DeprStarNew cls() diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst new file mode 100644 index 000000000000000..d8739d28eb2b73c --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-02-45-51.gh-issue-115015.rgtiDB.rst @@ -0,0 +1,5 @@ +Fix a bug in Argument Clinic that generated incorrect code for methods with +no parameters that use the :ref:`METH_METHOD | METH_FASTCALL | METH_KEYWORDS +<METH_METHOD-METH_FASTCALL-METH_KEYWORDS>` calling convention. Only the +positional parameter count was checked; any keyword argument passed would be +silently accepted. diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index d5bec5f71f5be88..64eddcd314a803e 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -96,7 +96,7 @@ _io__BufferedIOBase_detach_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__BufferedIOBase_detach(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "detach() takes no arguments"); return NULL; } @@ -1245,4 +1245,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=442b05b9a117df6c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4249187a725a3b3e input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h index 37023e49087647e..620e9e3b84ea192 100644 --- a/Modules/_io/clinic/bytesio.c.h +++ b/Modules/_io/clinic/bytesio.c.h @@ -96,7 +96,7 @@ _io_BytesIO_getbuffer_impl(bytesio *self, PyTypeObject *cls); static PyObject * _io_BytesIO_getbuffer(bytesio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getbuffer() takes no arguments"); return NULL; } @@ -534,4 +534,4 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=2be0e05a8871b7e2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ef116925b8b9e535 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/fileio.c.h b/Modules/_io/clinic/fileio.c.h index cf3ba28b066cf7f..5b5487d63eba900 100644 --- a/Modules/_io/clinic/fileio.c.h +++ b/Modules/_io/clinic/fileio.c.h @@ -27,7 +27,7 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls); static PyObject * _io_FileIO_close(fileio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "close() takes no arguments"); return NULL; } @@ -528,4 +528,4 @@ _io_FileIO_isatty(fileio *self, PyObject *Py_UNUSED(ignored)) #ifndef _IO_FILEIO_TRUNCATE_METHODDEF #define _IO_FILEIO_TRUNCATE_METHODDEF #endif /* !defined(_IO_FILEIO_TRUNCATE_METHODDEF) */ -/*[clinic end generated code: output=1c0f4a36f76b0c6a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e3d9446b4087020e input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/iobase.c.h b/Modules/_io/clinic/iobase.c.h index 6bdfa1444015acc..bae80a265fab075 100644 --- a/Modules/_io/clinic/iobase.c.h +++ b/Modules/_io/clinic/iobase.c.h @@ -262,7 +262,7 @@ _io__IOBase_fileno_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__IOBase_fileno(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "fileno() takes no arguments"); return NULL; } @@ -438,4 +438,4 @@ _io__RawIOBase_readall(PyObject *self, PyObject *Py_UNUSED(ignored)) { return _io__RawIOBase_readall_impl(self); } -/*[clinic end generated code: output=5a22bc5db0ecaacb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e7326fbefc52bfba input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 23b3cc8d71e098c..f04ee729abc9edc 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -27,7 +27,7 @@ _io__TextIOBase_detach_impl(PyObject *self, PyTypeObject *cls); static PyObject * _io__TextIOBase_detach(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "detach() takes no arguments"); return NULL; } @@ -1292,4 +1292,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED return return_value; } -/*[clinic end generated code: output=d01aa598647c1385 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=93a5a91a22100a28 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/winconsoleio.c.h b/Modules/_io/clinic/winconsoleio.c.h index 6cab295c44611d4..4696ecc5c843e6e 100644 --- a/Modules/_io/clinic/winconsoleio.c.h +++ b/Modules/_io/clinic/winconsoleio.c.h @@ -29,7 +29,7 @@ _io__WindowsConsoleIO_close_impl(winconsoleio *self, PyTypeObject *cls); static PyObject * _io__WindowsConsoleIO_close(winconsoleio *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "close() takes no arguments"); return NULL; } @@ -457,4 +457,4 @@ _io__WindowsConsoleIO_isatty(winconsoleio *self, PyObject *Py_UNUSED(ignored)) #ifndef _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF #define _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF #endif /* !defined(_IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF) */ -/*[clinic end generated code: output=04108fc26b187386 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2c2bc86713b21dd6 input=a9049054013a1b77]*/ diff --git a/Modules/_sre/clinic/sre.c.h b/Modules/_sre/clinic/sre.c.h index cd3fbbc720bdf1e..48336c7a2fca262 100644 --- a/Modules/_sre/clinic/sre.c.h +++ b/Modules/_sre/clinic/sre.c.h @@ -1434,7 +1434,7 @@ _sre_SRE_Scanner_match_impl(ScannerObject *self, PyTypeObject *cls); static PyObject * _sre_SRE_Scanner_match(ScannerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "match() takes no arguments"); return NULL; } @@ -1455,10 +1455,10 @@ _sre_SRE_Scanner_search_impl(ScannerObject *self, PyTypeObject *cls); static PyObject * _sre_SRE_Scanner_search(ScannerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "search() takes no arguments"); return NULL; } return _sre_SRE_Scanner_search_impl(self, cls); } -/*[clinic end generated code: output=ad513f31b99505fa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c3e711f0b2f43d66 input=a9049054013a1b77]*/ diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 15e0093f15ba1e6..fb0936bbccd3183 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -1213,6 +1213,40 @@ clone_with_conv_f2_impl(PyObject *module, custom_t path) } +/*[clinic input] +class _testclinic.TestClass "PyObject *" "PyObject" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=668a591c65bec947]*/ + +/*[clinic input] +_testclinic.TestClass.meth_method_no_params + cls: defining_class + / +[clinic start generated code]*/ + +static PyObject * +_testclinic_TestClass_meth_method_no_params_impl(PyObject *self, + PyTypeObject *cls) +/*[clinic end generated code: output=c140f100080c2fc8 input=6bd34503d11c63c1]*/ +{ + Py_RETURN_NONE; +} + +static struct PyMethodDef test_class_methods[] = { + _TESTCLINIC_TESTCLASS_METH_METHOD_NO_PARAMS_METHODDEF + {NULL, NULL} +}; + +static PyTypeObject TestClass = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_testclinic.TestClass", + .tp_basicsize = sizeof(PyObject), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_methods = test_class_methods, +}; + + /*[clinic input] output push destination deprstar new file '{dirname}/clinic/_testclinic_depr.c.h' @@ -1906,6 +1940,9 @@ PyInit__testclinic(void) if (m == NULL) { return NULL; } + if (PyModule_AddType(m, &TestClass) < 0) { + goto error; + } if (PyModule_AddType(m, &DeprStarNew) < 0) { goto error; } diff --git a/Modules/cjkcodecs/clinic/multibytecodec.c.h b/Modules/cjkcodecs/clinic/multibytecodec.c.h index 305ade17b1f1aa8..b5639d5cf10a22a 100644 --- a/Modules/cjkcodecs/clinic/multibytecodec.c.h +++ b/Modules/cjkcodecs/clinic/multibytecodec.c.h @@ -668,7 +668,7 @@ _multibytecodec_MultibyteStreamWriter_reset_impl(MultibyteStreamWriterObject *se static PyObject * _multibytecodec_MultibyteStreamWriter_reset(MultibyteStreamWriterObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "reset() takes no arguments"); return NULL; } @@ -682,4 +682,4 @@ PyDoc_STRVAR(_multibytecodec___create_codec__doc__, #define _MULTIBYTECODEC___CREATE_CODEC_METHODDEF \ {"__create_codec", (PyCFunction)_multibytecodec___create_codec, METH_O, _multibytecodec___create_codec__doc__}, -/*[clinic end generated code: output=219a363662d2fbff input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ee767a6d93c7108a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index d941c280a4300ba..6a9c8ff6d8fdd9e 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -120,7 +120,7 @@ _asyncio_Future_exception_impl(FutureObj *self, PyTypeObject *cls); static PyObject * _asyncio_Future_exception(FutureObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "exception() takes no arguments"); return NULL; } @@ -453,7 +453,7 @@ _asyncio_Future_get_loop_impl(FutureObj *self, PyTypeObject *cls); static PyObject * _asyncio_Future_get_loop(FutureObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_loop() takes no arguments"); return NULL; } @@ -1487,4 +1487,4 @@ _asyncio_current_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=f3864d8e2af7635f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b26155080c82c472 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_curses_panel.c.h b/Modules/clinic/_curses_panel.c.h index 7945d93b5433f7d..457f71370afda9f 100644 --- a/Modules/clinic/_curses_panel.c.h +++ b/Modules/clinic/_curses_panel.c.h @@ -19,7 +19,7 @@ _curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_bottom(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "bottom() takes no arguments"); return NULL; } @@ -43,7 +43,7 @@ _curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_hide(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "hide() takes no arguments"); return NULL; } @@ -65,7 +65,7 @@ _curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_show(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "show() takes no arguments"); return NULL; } @@ -87,7 +87,7 @@ _curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls); static PyObject * _curses_panel_panel_top(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "top() takes no arguments"); return NULL; } @@ -327,7 +327,7 @@ _curses_panel_panel_userptr_impl(PyCursesPanelObject *self, static PyObject * _curses_panel_panel_userptr(PyCursesPanelObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "userptr() takes no arguments"); return NULL; } @@ -418,4 +418,4 @@ _curses_panel_update_panels(PyObject *module, PyObject *Py_UNUSED(ignored)) { return _curses_panel_update_panels_impl(module); } -/*[clinic end generated code: output=636beecf71d96ff1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7bac14e9a1194c87 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_dbmmodule.c.h b/Modules/clinic/_dbmmodule.c.h index 5a4aba2825e03a6..d06271e18a49b25 100644 --- a/Modules/clinic/_dbmmodule.c.h +++ b/Modules/clinic/_dbmmodule.c.h @@ -37,7 +37,7 @@ _dbm_dbm_keys_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_keys(dbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); return NULL; } @@ -149,7 +149,7 @@ _dbm_dbm_clear_impl(dbmobject *self, PyTypeObject *cls); static PyObject * _dbm_dbm_clear(dbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); return NULL; } @@ -218,4 +218,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=96fdd4bd7bd256c5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=743ce0cea116747e input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_elementtree.c.h b/Modules/clinic/_elementtree.c.h index 02375c8a61e73e4..9622591a1aa8552 100644 --- a/Modules/clinic/_elementtree.c.h +++ b/Modules/clinic/_elementtree.c.h @@ -87,7 +87,7 @@ _elementtree_Element___copy___impl(ElementObject *self, PyTypeObject *cls); static PyObject * _elementtree_Element___copy__(ElementObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -644,7 +644,7 @@ _elementtree_Element_itertext_impl(ElementObject *self, PyTypeObject *cls); static PyObject * _elementtree_Element_itertext(ElementObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "itertext() takes no arguments"); return NULL; } @@ -1219,4 +1219,4 @@ _elementtree_XMLParser__setevents(XMLParserObject *self, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=8fdaa17d3262800a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=218ec9e6a889f796 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_gdbmmodule.c.h b/Modules/clinic/_gdbmmodule.c.h index c7164e519d0e7de..626e4678809d4f5 100644 --- a/Modules/clinic/_gdbmmodule.c.h +++ b/Modules/clinic/_gdbmmodule.c.h @@ -106,7 +106,7 @@ _gdbm_gdbm_keys_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_keys(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "keys() takes no arguments"); return NULL; } @@ -132,7 +132,7 @@ _gdbm_gdbm_firstkey_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_firstkey(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "firstkey() takes no arguments"); return NULL; } @@ -211,7 +211,7 @@ _gdbm_gdbm_reorganize_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_reorganize(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "reorganize() takes no arguments"); return NULL; } @@ -236,7 +236,7 @@ _gdbm_gdbm_sync_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_sync(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "sync() takes no arguments"); return NULL; } @@ -258,7 +258,7 @@ _gdbm_gdbm_clear_impl(gdbmobject *self, PyTypeObject *cls); static PyObject * _gdbm_gdbm_clear(gdbmobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "clear() takes no arguments"); return NULL; } @@ -340,4 +340,4 @@ dbmopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=c5ee922363d5a81f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6b4c19905ac9967d input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_lsprof.c.h b/Modules/clinic/_lsprof.c.h index dfc003eb54774ca..b3b7fda5660bfd5 100644 --- a/Modules/clinic/_lsprof.c.h +++ b/Modules/clinic/_lsprof.c.h @@ -39,10 +39,10 @@ _lsprof_Profiler_getstats_impl(ProfilerObject *self, PyTypeObject *cls); static PyObject * _lsprof_Profiler_getstats(ProfilerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getstats() takes no arguments"); return NULL; } return _lsprof_Profiler_getstats_impl(self, cls); } -/*[clinic end generated code: output=0615a53cce828f06 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5c9d87d89863dc83 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_pickle.c.h b/Modules/clinic/_pickle.c.h index fb086925e3941d6..5a6ae7be6b6ea77 100644 --- a/Modules/clinic/_pickle.c.h +++ b/Modules/clinic/_pickle.c.h @@ -328,7 +328,7 @@ _pickle_Unpickler_load_impl(UnpicklerObject *self, PyTypeObject *cls); static PyObject * _pickle_Unpickler_load(UnpicklerObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "load() takes no arguments"); return NULL; } @@ -1077,4 +1077,4 @@ _pickle_loads(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec exit: return return_value; } -/*[clinic end generated code: output=ebe78653233827a6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=bd63c85a8737b0aa input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_queuemodule.c.h b/Modules/clinic/_queuemodule.c.h index b3b6b8e96c135e4..6f4c715c7229651 100644 --- a/Modules/clinic/_queuemodule.c.h +++ b/Modules/clinic/_queuemodule.c.h @@ -278,7 +278,7 @@ _queue_SimpleQueue_get_nowait(simplequeueobject *self, PyTypeObject *cls, PyObje { PyObject *return_value = NULL; - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_nowait() takes no arguments"); goto exit; } @@ -349,4 +349,4 @@ _queue_SimpleQueue_qsize(simplequeueobject *self, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=242950edc8f7dfd7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=44a718f40072018a input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index fea30e778381dea..bb516be37ec3f09 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -3141,4 +3141,26 @@ clone_with_conv_f2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py exit: return return_value; } -/*[clinic end generated code: output=90743ac900d60f9f input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_testclinic_TestClass_meth_method_no_params__doc__, +"meth_method_no_params($self, /)\n" +"--\n" +"\n"); + +#define _TESTCLINIC_TESTCLASS_METH_METHOD_NO_PARAMS_METHODDEF \ + {"meth_method_no_params", _PyCFunction_CAST(_testclinic_TestClass_meth_method_no_params), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _testclinic_TestClass_meth_method_no_params__doc__}, + +static PyObject * +_testclinic_TestClass_meth_method_no_params_impl(PyObject *self, + PyTypeObject *cls); + +static PyObject * +_testclinic_TestClass_meth_method_no_params(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "meth_method_no_params() takes no arguments"); + return NULL; + } + return _testclinic_TestClass_meth_method_no_params_impl(self, cls); +} +/*[clinic end generated code: output=6520c1ca5392a3f0 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testmultiphase.c.h b/Modules/clinic/_testmultiphase.c.h index c0a00954c16cbe2..7ac361ece1acc3e 100644 --- a/Modules/clinic/_testmultiphase.c.h +++ b/Modules/clinic/_testmultiphase.c.h @@ -27,7 +27,7 @@ _testmultiphase_StateAccessType_get_defining_module_impl(StateAccessTypeObject * static PyObject * _testmultiphase_StateAccessType_get_defining_module(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_defining_module() takes no arguments"); return NULL; } @@ -50,7 +50,7 @@ _testmultiphase_StateAccessType_getmodulebydef_bad_def_impl(StateAccessTypeObjec static PyObject * _testmultiphase_StateAccessType_getmodulebydef_bad_def(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "getmodulebydef_bad_def() takes no arguments"); return NULL; } @@ -156,10 +156,10 @@ _testmultiphase_StateAccessType_get_count_impl(StateAccessTypeObject *self, static PyObject * _testmultiphase_StateAccessType_get_count(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "get_count() takes no arguments"); return NULL; } return _testmultiphase_StateAccessType_get_count_impl(self, cls); } -/*[clinic end generated code: output=d8c262af27b3b98d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2c199bad52e9cda7 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/arraymodule.c.h b/Modules/clinic/arraymodule.c.h index dbce03135416499..0b764e43e194375 100644 --- a/Modules/clinic/arraymodule.c.h +++ b/Modules/clinic/arraymodule.c.h @@ -652,7 +652,7 @@ array_arrayiterator___reduce___impl(arrayiterobject *self, PyTypeObject *cls); static PyObject * array_arrayiterator___reduce__(arrayiterobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__reduce__() takes no arguments"); return NULL; } @@ -667,4 +667,4 @@ PyDoc_STRVAR(array_arrayiterator___setstate____doc__, #define ARRAY_ARRAYITERATOR___SETSTATE___METHODDEF \ {"__setstate__", (PyCFunction)array_arrayiterator___setstate__, METH_O, array_arrayiterator___setstate____doc__}, -/*[clinic end generated code: output=bf086c01e7e482bf input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3be987238a4bb431 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/md5module.c.h b/Modules/clinic/md5module.c.h index 7d4d3108dab9b6c..ee7fb3d7c613f22 100644 --- a/Modules/clinic/md5module.c.h +++ b/Modules/clinic/md5module.c.h @@ -23,7 +23,7 @@ MD5Type_copy_impl(MD5object *self, PyTypeObject *cls); static PyObject * MD5Type_copy(MD5object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -148,4 +148,4 @@ _md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw exit: return return_value; } -/*[clinic end generated code: output=bfadda44914804a8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4dbca39332d3f52f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 1373bdef03ba5e5..b49d64d4281889e 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -11212,7 +11212,7 @@ os_DirEntry_is_symlink(DirEntry *self, PyTypeObject *defining_class, PyObject *c PyObject *return_value = NULL; int _return_value; - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "is_symlink() takes no arguments"); goto exit; } @@ -12588,4 +12588,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=43e4e557c771358a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=268af5cbc8baa9d4 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/sha1module.c.h b/Modules/clinic/sha1module.c.h index ee391656fb67c31..b89c7e505c788e9 100644 --- a/Modules/clinic/sha1module.c.h +++ b/Modules/clinic/sha1module.c.h @@ -23,7 +23,7 @@ SHA1Type_copy_impl(SHA1object *self, PyTypeObject *cls); static PyObject * SHA1Type_copy(SHA1object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -148,4 +148,4 @@ _sha1_sha1(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * exit: return return_value; } -/*[clinic end generated code: output=41fc7579213b57b4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=af5a640df662066f input=a9049054013a1b77]*/ diff --git a/Modules/clinic/sha2module.c.h b/Modules/clinic/sha2module.c.h index ec31d5545be4c15..cf4b88d52856b86 100644 --- a/Modules/clinic/sha2module.c.h +++ b/Modules/clinic/sha2module.c.h @@ -23,7 +23,7 @@ SHA256Type_copy_impl(SHA256object *self, PyTypeObject *cls); static PyObject * SHA256Type_copy(SHA256object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -45,7 +45,7 @@ SHA512Type_copy_impl(SHA512object *self, PyTypeObject *cls); static PyObject * SHA512Type_copy(SHA512object *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -437,4 +437,4 @@ _sha2_sha384(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject exit: return return_value; } -/*[clinic end generated code: output=1482d9de086e45c4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b46da764024b1764 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/zlibmodule.c.h b/Modules/clinic/zlibmodule.c.h index 6b09abe309bf486..7ff3edf5a557f80 100644 --- a/Modules/clinic/zlibmodule.c.h +++ b/Modules/clinic/zlibmodule.c.h @@ -637,7 +637,7 @@ zlib_Compress_copy_impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Compress_copy(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -662,7 +662,7 @@ zlib_Compress___copy___impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Compress___copy__(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -735,7 +735,7 @@ zlib_Decompress_copy_impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Decompress_copy(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "copy() takes no arguments"); return NULL; } @@ -760,7 +760,7 @@ zlib_Decompress___copy___impl(compobject *self, PyTypeObject *cls); static PyObject * zlib_Decompress___copy__(compobject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - if (nargs) { + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { PyErr_SetString(PyExc_TypeError, "__copy__() takes no arguments"); return NULL; } @@ -1098,4 +1098,4 @@ zlib_crc32(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #ifndef ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF #define ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF #endif /* !defined(ZLIB_DECOMPRESS___DEEPCOPY___METHODDEF) */ -/*[clinic end generated code: output=6dd97dc851c39031 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8bb840fb6af43dd4 input=a9049054013a1b77]*/ diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 0b02ad01d399836..1d9576d083d8dc7 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -334,6 +334,7 @@ Modules/_testclinic.c - DeprStarNew - Modules/_testclinic.c - DeprKwdInit - Modules/_testclinic.c - DeprKwdInitNoInline - Modules/_testclinic.c - DeprKwdNew - +Modules/_testclinic.c - TestClass - ################################## diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c1df83a72bd8cea..db57d17899af939 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -960,7 +960,7 @@ def parser_body( return_error = ('return NULL;' if simple_return else 'goto exit;') parser_code = [libclinic.normalize_snippet(""" - if (nargs) {{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) {{ PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments"); %s }} From 652fbf88c4c422ff17fefd4dcb5e06b5c0e26e74 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Mon, 5 Feb 2024 22:51:11 +0200 Subject: [PATCH 213/263] gh-82626: Emit a warning when bool is used as a file descriptor (GH-111275) --- Doc/whatsnew/3.13.rst | 5 +++++ Lib/_pyio.py | 5 +++++ Lib/test/test_fileio.py | 8 ++++++++ Lib/test/test_genericpath.py | 6 ++++++ Lib/test/test_os.py | 14 ++++++++++++++ Lib/test/test_posix.py | 7 +++++++ .../2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst | 2 ++ Modules/_io/fileio.c | 7 +++++++ Modules/faulthandler.c | 7 +++++++ Modules/posixmodule.c | 7 +++++++ Objects/fileobject.c | 7 +++++++ 11 files changed, 75 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 0770e28d230b4b3..9bac36ba0bffb8c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -145,6 +145,11 @@ Other Language Changes is rejected when the global is used in the :keyword:`else` block. (Contributed by Irit Katriel in :gh:`111123`.) +* Many functions now emit a warning if a boolean value is passed as + a file descriptor argument. + This can help catch some errors earlier. + (Contributed by Serhiy Storchaka in :gh:`82626`.) + * Added a new environment variable :envvar:`PYTHON_FROZEN_MODULES`. It determines whether or not frozen modules are ignored by the import machinery, equivalent of the :option:`-X frozen_modules <-X>` command-line option. diff --git a/Lib/_pyio.py b/Lib/_pyio.py index df2c29bfa9caeed..8a0d0dc4b1a0b85 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1495,6 +1495,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None): if isinstance(file, float): raise TypeError('integer argument expected, got float') if isinstance(file, int): + if isinstance(file, bool): + import warnings + warnings.warn("bool is used as a file descriptor", + RuntimeWarning, stacklevel=2) + file = int(file) fd = file if fd < 0: raise ValueError('negative file descriptor') diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 06d9b454add34cd..06d5a8abf320835 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -484,6 +484,14 @@ def testInvalidFd(self): import msvcrt self.assertRaises(OSError, msvcrt.get_osfhandle, make_bad_fd()) + def testBooleanFd(self): + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + f = self.FileIO(fd, closefd=False) + f.close() + self.assertEqual(cm.filename, __file__) + def testBadModeArgument(self): # verify that we get a sensible error message for bad mode argument bad_mode = "qwerty" diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index b77cd4c67d6b2ae..f407ee3caf154c2 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -165,6 +165,12 @@ def test_exists_fd(self): os.close(w) self.assertFalse(self.pathmodule.exists(r)) + def test_exists_bool(self): + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor'): + self.pathmodule.exists(fd) + def test_isdir(self): filename = os_helper.TESTFN bfilename = os.fsencode(filename) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 86af1a8ed8ee154..2c8823ae47c726e 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2195,12 +2195,15 @@ def test_chmod(self): class TestInvalidFD(unittest.TestCase): singles = ["fchdir", "dup", "fdatasync", "fstat", "fstatvfs", "fsync", "tcgetpgrp", "ttyname"] + singles_fildes = {"fchdir", "fdatasync", "fsync"} #singles.append("close") #We omit close because it doesn't raise an exception on some platforms def get_single(f): def helper(self): if hasattr(os, f): self.check(getattr(os, f)) + if f in self.singles_fildes: + self.check_bool(getattr(os, f)) return helper for f in singles: locals()["test_"+f] = get_single(f) @@ -2214,8 +2217,16 @@ def check(self, f, *args, **kwargs): self.fail("%r didn't raise an OSError with a bad file descriptor" % f) + def check_bool(self, f, *args, **kwargs): + with warnings.catch_warnings(): + warnings.simplefilter("error", RuntimeWarning) + for fd in False, True: + with self.assertRaises(RuntimeWarning): + f(fd, *args, **kwargs) + def test_fdopen(self): self.check(os.fdopen, encoding="utf-8") + self.check_bool(os.fdopen, encoding="utf-8") @unittest.skipUnless(hasattr(os, 'isatty'), 'test needs os.isatty()') def test_isatty(self): @@ -2277,11 +2288,14 @@ def test_fchown(self): def test_fpathconf(self): self.check(os.pathconf, "PC_NAME_MAX") self.check(os.fpathconf, "PC_NAME_MAX") + self.check_bool(os.pathconf, "PC_NAME_MAX") + self.check_bool(os.fpathconf, "PC_NAME_MAX") @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): self.check(os.truncate, 0) self.check(os.ftruncate, 0) + self.check_bool(os.truncate, 0) @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 72e348fbbdcbc14..a45f620e18dc1d0 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1514,6 +1514,13 @@ def test_stat_dir_fd(self): self.assertRaises(OverflowError, posix.stat, name, dir_fd=10**20) + for fd in False, True: + with self.assertWarnsRegex(RuntimeWarning, + 'bool is used as a file descriptor') as cm: + with self.assertRaises(OSError): + posix.stat('nonexisting', dir_fd=fd) + self.assertEqual(cm.filename, __file__) + @unittest.skipUnless(os.utime in os.supports_dir_fd, "test needs dir_fd support in os.utime()") def test_utime_dir_fd(self): with self.prepare_file() as (dir_fd, name, fullname): diff --git a/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst b/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst new file mode 100644 index 000000000000000..92a66b5bf0f635c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-24-19-19-54.gh-issue-82626._hfLRf.rst @@ -0,0 +1,2 @@ +Many functions now emit a warning if a boolean value is passed as a file +descriptor argument. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 9cf268ca0b26c81..6bb156e41fe43cb 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -269,6 +269,13 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode, self->fd = -1; } + if (PyBool_Check(nameobj)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(nameobj); if (fd < 0) { if (!PyErr_Occurred()) { diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index a2e3c2300b3ce85..95d646c9c65b3c4 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -119,6 +119,13 @@ faulthandler_get_fileno(PyObject **file_ptr) } } else if (PyLong_Check(file)) { + if (PyBool_Check(file)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(file); if (fd == -1 && PyErr_Occurred()) return -1; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 40ff131b119d66f..22891135bde0af7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -969,6 +969,13 @@ _fd_converter(PyObject *o, int *p) int overflow; long long_value; + if (PyBool_Check(o)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return 0; + } + } PyObject *index = _PyNumber_Index(o); if (index == NULL) { return 0; diff --git a/Objects/fileobject.c b/Objects/fileobject.c index 5522eba34eace9b..e30ab952dff571b 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -174,6 +174,13 @@ PyObject_AsFileDescriptor(PyObject *o) PyObject *meth; if (PyLong_Check(o)) { + if (PyBool_Check(o)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(o); } else if (PyObject_GetOptionalAttr(o, &_Py_ID(fileno), &meth) < 0) { From c32bae52904723d99e1f98e2ef54570268d86467 Mon Sep 17 00:00:00 2001 From: mpage <mpage@meta.com> Date: Mon, 5 Feb 2024 13:48:37 -0800 Subject: [PATCH 214/263] gh-114944: Fix race between `_PyParkingLot_Park` and `_PyParkingLot_UnparkAll` when handling interrupts (#114945) Fix race between `_PyParkingLot_Park` and `_PyParkingLot_UnparkAll` when handling interrupts There is a potential race when `_PyParkingLot_UnparkAll` is executing in one thread and another thread is unblocked because of an interrupt in `_PyParkingLot_Park`. Consider the following scenario: 1. Thread T0 is blocked[^1] in `_PyParkingLot_Park` on address `A`. 2. Thread T1 executes `_PyParkingLot_UnparkAll` on address `A`. It finds the `wait_entry` for `T0` and unlinks[^2] its list node. 3. Immediately after (2), T0 is woken up due to an interrupt. It then segfaults trying to unlink[^3] the node that was previously unlinked in (2). To fix this we mark each waiter as unparking before releasing the bucket lock. `_PyParkingLot_Park` will wait to handle the coming wakeup, and not attempt to unlink the node, when this field is set. `_PyParkingLot_Unpark` does this already, presumably to handle this case. --- .../2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst | 1 + Python/parking_lot.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst new file mode 100644 index 000000000000000..fb41caf7c5f4fae --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-03-01-48-38.gh-issue-114944.4J5ELD.rst @@ -0,0 +1 @@ +Fixes a race between ``PyParkingLot_Park`` and ``_PyParkingLot_UnparkAll``. diff --git a/Python/parking_lot.c b/Python/parking_lot.c index c83d7443e289c56..8ba50fc1353ebdd 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -244,6 +244,7 @@ dequeue(Bucket *bucket, const void *address) if (wait->addr == (uintptr_t)address) { llist_remove(node); --bucket->num_waiters; + wait->is_unparking = true; return wait; } } @@ -262,6 +263,7 @@ dequeue_all(Bucket *bucket, const void *address, struct llist_node *dst) llist_remove(node); llist_insert_tail(dst, node); --bucket->num_waiters; + wait->is_unparking = true; } } } @@ -337,8 +339,6 @@ _PyParkingLot_Unpark(const void *addr, _Py_unpark_fn_t *fn, void *arg) _PyRawMutex_Lock(&bucket->mutex); struct wait_entry *waiter = dequeue(bucket, addr); if (waiter) { - waiter->is_unparking = true; - int has_more_waiters = (bucket->num_waiters > 0); fn(arg, waiter->park_arg, has_more_waiters); } From bb57ffdb38e9e8df8f9ea71f1430dbbe4bf2d3ac Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Tue, 6 Feb 2024 00:41:34 +0200 Subject: [PATCH 215/263] gh-83648: Support deprecation of options, arguments and subcommands in argparse (GH-114086) --- Doc/library/argparse.rst | 47 +++++- Doc/whatsnew/3.13.rst | 9 ++ Lib/argparse.py | 92 +++++++++--- Lib/test/test_argparse.py | 139 +++++++++++++++++- ...4-01-15-20-21-33.gh-issue-83648.HzD_fY.rst | 2 + 5 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 1395d457f874b0c..952643a46416d21 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -777,6 +777,8 @@ The add_argument() method * dest_ - The name of the attribute to be added to the object returned by :meth:`parse_args`. + * deprecated_ - Whether or not use of the argument is deprecated. + The following sections describe how each of these are used. @@ -1439,6 +1441,34 @@ behavior:: >>> parser.parse_args('--foo XXX'.split()) Namespace(bar='XXX') + +.. _deprecated: + +deprecated +^^^^^^^^^^ + +During a project's lifetime, some arguments may need to be removed from the +command line. Before removing them, you should inform +your users that the arguments are deprecated and will be removed. +The ``deprecated`` keyword argument of +:meth:`~ArgumentParser.add_argument`, which defaults to ``False``, +specifies if the argument is deprecated and will be removed +in the future. +For arguments, if ``deprecated`` is ``True``, then a warning will be +printed to standard error when the argument is used:: + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='snake.py') + >>> parser.add_argument('--legs', default=0, type=int, deprecated=True) + >>> parser.parse_args([]) + Namespace(legs=0) + >>> parser.parse_args(['--legs', '4']) # doctest: +SKIP + snake.py: warning: option '--legs' is deprecated + Namespace(legs=4) + +.. versionchanged:: 3.13 + + Action classes ^^^^^^^^^^^^^^ @@ -1842,7 +1872,8 @@ Sub-commands {foo,bar} additional help - Furthermore, ``add_parser`` supports an additional ``aliases`` argument, + Furthermore, :meth:`~_SubParsersAction.add_parser` supports an additional + *aliases* argument, which allows multiple strings to refer to the same subparser. This example, like ``svn``, aliases ``co`` as a shorthand for ``checkout``:: @@ -1853,6 +1884,20 @@ Sub-commands >>> parser.parse_args(['co', 'bar']) Namespace(foo='bar') + :meth:`~_SubParsersAction.add_parser` supports also an additional + *deprecated* argument, which allows to deprecate the subparser. + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='chicken.py') + >>> subparsers = parser.add_subparsers() + >>> run = subparsers.add_parser('run') + >>> fly = subparsers.add_parser('fly', deprecated=True) + >>> parser.parse_args(['fly']) # doctest: +SKIP + chicken.py: warning: command 'fly' is deprecated + Namespace() + + .. versionadded:: 3.13 + One particularly effective way of handling sub-commands is to combine the use of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so that each subparser knows which Python function it should execute. For diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 9bac36ba0bffb8c..5e5f1e295f4d706 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -169,6 +169,15 @@ New Modules Improved Modules ================ +argparse +-------- + +* Add parameter *deprecated* in methods + :meth:`~argparse.ArgumentParser.add_argument` and :meth:`!add_parser` + which allows to deprecate command-line options, positional arguments and + subcommands. + (Contributed by Serhiy Storchaka in :gh:`83648`). + array ----- diff --git a/Lib/argparse.py b/Lib/argparse.py index 2131d729746d41e..04ee3b19aca755e 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -843,7 +843,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): self.option_strings = option_strings self.dest = dest self.nargs = nargs @@ -854,6 +855,7 @@ def __init__(self, self.required = required self.help = help self.metavar = metavar + self.deprecated = deprecated def _get_kwargs(self): names = [ @@ -867,6 +869,7 @@ def _get_kwargs(self): 'required', 'help', 'metavar', + 'deprecated', ] return [(name, getattr(self, name)) for name in names] @@ -889,7 +892,8 @@ def __init__(self, choices=_deprecated_default, required=False, help=None, - metavar=_deprecated_default): + metavar=_deprecated_default, + deprecated=False): _option_strings = [] for option_string in option_strings: @@ -927,7 +931,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): @@ -950,7 +955,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for store actions must be != 0; if you ' 'have nothing to store, actions such as store ' @@ -967,7 +973,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) @@ -982,7 +989,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_StoreConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -990,7 +998,8 @@ def __init__(self, const=const, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) @@ -1003,14 +1012,16 @@ def __init__(self, dest, default=False, required=False, - help=None): + help=None, + deprecated=False): super(_StoreTrueAction, self).__init__( option_strings=option_strings, dest=dest, const=True, - default=default, + deprecated=deprecated, required=required, - help=help) + help=help, + default=default) class _StoreFalseAction(_StoreConstAction): @@ -1020,14 +1031,16 @@ def __init__(self, dest, default=True, required=False, - help=None): + help=None, + deprecated=False): super(_StoreFalseAction, self).__init__( option_strings=option_strings, dest=dest, const=False, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) class _AppendAction(Action): @@ -1042,7 +1055,8 @@ def __init__(self, choices=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): if nargs == 0: raise ValueError('nargs for append actions must be != 0; if arg ' 'strings are not supplying the value to append, ' @@ -1059,7 +1073,8 @@ def __init__(self, choices=choices, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1077,7 +1092,8 @@ def __init__(self, default=None, required=False, help=None, - metavar=None): + metavar=None, + deprecated=False): super(_AppendConstAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1086,7 +1102,8 @@ def __init__(self, default=default, required=required, help=help, - metavar=metavar) + metavar=metavar, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) @@ -1102,14 +1119,16 @@ def __init__(self, dest, default=None, required=False, - help=None): + help=None, + deprecated=False): super(_CountAction, self).__init__( option_strings=option_strings, dest=dest, nargs=0, default=default, required=required, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): count = getattr(namespace, self.dest, None) @@ -1124,13 +1143,15 @@ def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, - help=None): + help=None, + deprecated=False): super(_HelpAction, self).__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, - help=help) + help=help, + deprecated=deprecated) def __call__(self, parser, namespace, values, option_string=None): parser.print_help() @@ -1144,7 +1165,8 @@ def __init__(self, version=None, dest=SUPPRESS, default=SUPPRESS, - help="show program's version number and exit"): + help="show program's version number and exit", + deprecated=False): super(_VersionAction, self).__init__( option_strings=option_strings, dest=dest, @@ -1188,6 +1210,7 @@ def __init__(self, self._parser_class = parser_class self._name_parser_map = {} self._choices_actions = [] + self._deprecated = set() super(_SubParsersAction, self).__init__( option_strings=option_strings, @@ -1198,7 +1221,7 @@ def __init__(self, help=help, metavar=metavar) - def add_parser(self, name, **kwargs): + def add_parser(self, name, *, deprecated=False, **kwargs): # set prog from the existing prefix if kwargs.get('prog') is None: kwargs['prog'] = '%s %s' % (self._prog_prefix, name) @@ -1226,6 +1249,10 @@ def add_parser(self, name, **kwargs): for alias in aliases: self._name_parser_map[alias] = parser + if deprecated: + self._deprecated.add(name) + self._deprecated.update(aliases) + return parser def _get_subactions(self): @@ -1241,13 +1268,17 @@ def __call__(self, parser, namespace, values, option_string=None): # select the parser try: - parser = self._name_parser_map[parser_name] + subparser = self._name_parser_map[parser_name] except KeyError: args = {'parser_name': parser_name, 'choices': ', '.join(self._name_parser_map)} msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args raise ArgumentError(self, msg) + if parser_name in self._deprecated: + parser._warning(_("command '%(parser_name)s' is deprecated") % + {'parser_name': parser_name}) + # parse all the remaining options into the namespace # store any unrecognized options on the object, so that the top # level parser can decide what to do with them @@ -1255,7 +1286,7 @@ def __call__(self, parser, namespace, values, option_string=None): # In case this subparser defines new defaults, we parse them # in a new namespace object and then update the original # namespace for the relevant parts. - subnamespace, arg_strings = parser.parse_known_args(arg_strings, None) + subnamespace, arg_strings = subparser.parse_known_args(arg_strings, None) for key, value in vars(subnamespace).items(): setattr(namespace, key, value) @@ -1975,6 +2006,7 @@ def _parse_known_args(self, arg_strings, namespace): # converts arg strings to the appropriate and then takes the action seen_actions = set() seen_non_default_actions = set() + warned = set() def take_action(action, argument_strings, option_string=None): seen_actions.add(action) @@ -2070,6 +2102,10 @@ def consume_optional(start_index): # the Optional's string args stopped assert action_tuples for action, args, option_string in action_tuples: + if action.deprecated and option_string not in warned: + self._warning(_("option '%(option)s' is deprecated") % + {'option': option_string}) + warned.add(option_string) take_action(action, args, option_string) return stop @@ -2089,6 +2125,10 @@ def consume_positionals(start_index): for action, arg_count in zip(positionals, arg_counts): args = arg_strings[start_index: start_index + arg_count] start_index += arg_count + if args and action.deprecated and action.dest not in warned: + self._warning(_("argument '%(argument_name)s' is deprecated") % + {'argument_name': action.dest}) + warned.add(action.dest) take_action(action, args) # slice off the Positionals that we just parsed and return the @@ -2650,3 +2690,7 @@ def error(self, message): self.print_usage(_sys.stderr) args = {'prog': self.prog, 'message': message} self.exit(2, _('%(prog)s: error: %(message)s\n') % args) + + def _warning(self, message): + args = {'prog': self.prog, 'message': message} + self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index d1f3d40000140d3..86d6e81a71642b7 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -5099,7 +5099,8 @@ def test_optional(self): string = ( "Action(option_strings=['--foo', '-a', '-b'], dest='b', " "nargs='+', const=None, default=42, type='int', " - "choices=[1, 2, 3], required=False, help='HELP', metavar='METAVAR')") + "choices=[1, 2, 3], required=False, help='HELP', " + "metavar='METAVAR', deprecated=False)") self.assertStringEqual(option, string) def test_argument(self): @@ -5116,7 +5117,8 @@ def test_argument(self): string = ( "Action(option_strings=[], dest='x', nargs='?', " "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " - "required=True, help='H HH H', metavar='MV MV MV')" % float) + "required=True, help='H HH H', metavar='MV MV MV', " + "deprecated=False)" % float) self.assertStringEqual(argument, string) def test_namespace(self): @@ -5308,6 +5310,139 @@ def spam(string_to_convert): args = parser.parse_args('--foo spam!'.split()) self.assertEqual(NS(foo='foo_converted'), args) + +# ============================================== +# Check that deprecated arguments output warning +# ============================================== + +class TestDeprecatedArguments(TestCase): + + def test_deprecated_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f', 'spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '-f', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', 'spam', '--foo', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_boolean_option(self): + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--foo', action=argparse.BooleanOptionalAction, deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args(['--foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['-f']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '-f' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['--foo', '--no-foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: option '--foo' is deprecated") + self.assertRegex(stderr, "warning: option '--no-foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_arguments(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='?', deprecated=True) + parser.add_argument('bar', nargs='?', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertRegex(stderr, "warning: argument 'bar' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 2) + + def test_deprecated_varargument(self): + parser = argparse.ArgumentParser() + parser.add_argument('foo', nargs='*', deprecated=True) + + with captured_stderr() as stderr: + parser.parse_args([]) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['spam']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['spam', 'ham']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: argument 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + def test_deprecated_subparser(self): + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + subparsers.add_parser('foo', aliases=['baz'], deprecated=True) + subparsers.add_parser('bar') + + with captured_stderr() as stderr: + parser.parse_args(['bar']) + stderr = stderr.getvalue() + self.assertEqual(stderr.count('is deprecated'), 0) + + with captured_stderr() as stderr: + parser.parse_args(['foo']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: command 'foo' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + with captured_stderr() as stderr: + parser.parse_args(['baz']) + stderr = stderr.getvalue() + self.assertRegex(stderr, "warning: command 'baz' is deprecated") + self.assertEqual(stderr.count('is deprecated'), 1) + + # ================================================================== # Check semantics regarding the default argument and type conversion # ================================================================== diff --git a/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst new file mode 100644 index 000000000000000..bd3e27b4be0cf58 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-20-21-33.gh-issue-83648.HzD_fY.rst @@ -0,0 +1,2 @@ +Support deprecation of options, positional arguments and subcommands in +:mod:`argparse`. From 01dceba13e872e9ca24b8e00a2b75db3d0d6c1a3 Mon Sep 17 00:00:00 2001 From: Zachary Ware <zach@python.org> Date: Mon, 5 Feb 2024 17:10:55 -0600 Subject: [PATCH 216/263] gh-109991: Update Windows build to use OpenSSL 3.0.13 (#115043) --- .../Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst | 1 + PCbuild/get_externals.bat | 4 ++-- PCbuild/python.props | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst b/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst new file mode 100644 index 000000000000000..d9923c35c2726e6 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-05-16-53-12.gh-issue-109991.YqjnDz.rst @@ -0,0 +1 @@ +Update Windows build to use OpenSSL 3.0.13. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index de73d923d8f4df1..0989bd46a580f77 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -53,7 +53,7 @@ echo.Fetching external libraries... set libraries= set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 -if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.11 +if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.13 set libraries=%libraries% sqlite-3.44.2.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 @@ -76,7 +76,7 @@ echo.Fetching external binaries... set binaries= if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4 -if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.11 +if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.0.13 if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.13.1 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 diff --git a/PCbuild/python.props b/PCbuild/python.props index 2cb16693e546b1b..54553db40572888 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -74,8 +74,8 @@ <libffiDir Condition="$(libffiDir) == ''">$(ExternalsDir)libffi-3.4.4\</libffiDir> <libffiOutDir Condition="$(libffiOutDir) == ''">$(libffiDir)$(ArchName)\</libffiOutDir> <libffiIncludeDir Condition="$(libffiIncludeDir) == ''">$(libffiOutDir)include</libffiIncludeDir> - <opensslDir Condition="$(opensslDir) == ''">$(ExternalsDir)openssl-3.0.11\</opensslDir> - <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.11\$(ArchName)\</opensslOutDir> + <opensslDir Condition="$(opensslDir) == ''">$(ExternalsDir)openssl-3.0.13\</opensslDir> + <opensslOutDir Condition="$(opensslOutDir) == ''">$(ExternalsDir)openssl-bin-3.0.13\$(ArchName)\</opensslOutDir> <opensslIncludeDir Condition="$(opensslIncludeDir) == ''">$(opensslOutDir)include</opensslIncludeDir> <nasmDir Condition="$(nasmDir) == ''">$(ExternalsDir)\nasm-2.11.06\</nasmDir> <zlibDir Condition="$(zlibDir) == ''">$(ExternalsDir)\zlib-1.3.1\</zlibDir> From 638e811a3c54a81d8af5a4c08b9497d210823f78 Mon Sep 17 00:00:00 2001 From: Ned Deily <nad@python.org> Date: Mon, 5 Feb 2024 20:59:25 -0500 Subject: [PATCH 217/263] gh-109991: Update macOS installer to use OpenSSL 3.0.13. (GH-115052) --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 32de56bcf130863..9000fb8973659db 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.0.11", - url="https://www.openssl.org/source/openssl-3.0.11.tar.gz", - checksum='b3425d3bb4a2218d0697eb41f7fc0cdede016ed19ca49d168b78e8d947887f55', + name="OpenSSL 3.0.13", + url="https://www.openssl.org/source/openssl-3.0.13.tar.gz", + checksum='88525753f79d3bec27d2fa7c66aa0b92b3aa9498dafd93d7cfa4b3780cdae313', buildrecipe=build_universal_openssl, configure=None, install=None, diff --git a/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst b/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst new file mode 100644 index 000000000000000..79b45e7d51da3f5 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-02-05-18-30-27.gh-issue-109991.tun6Yu.rst @@ -0,0 +1 @@ +Update macOS installer to use OpenSSL 3.0.13. From 299e16ca0f303a1e00bd0e04679862a5d4db5ab2 Mon Sep 17 00:00:00 2001 From: Ned Deily <nad@python.org> Date: Mon, 5 Feb 2024 21:10:11 -0500 Subject: [PATCH 218/263] gh-109991: Update GitHub CI workflows to use OpenSSL 3.0.13. (#115050) Also update multissltests to use 1.1.1w, 3.0.13, 3.1.5, and 3.2.1. --- .github/workflows/build.yml | 6 +++--- .github/workflows/reusable-ubuntu.yml | 2 +- .../2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst | 2 ++ Tools/ssl/multissltests.py | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 949c4ae95da07f0..0a2f6da50ed8a09 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -250,7 +250,7 @@ jobs: strategy: fail-fast: false matrix: - openssl_ver: [1.1.1w, 3.0.11, 3.1.3] + openssl_ver: [1.1.1w, 3.0.13, 3.1.5, 3.2.1] env: OPENSSL_VER: ${{ matrix.openssl_ver }} MULTISSL_DIR: ${{ github.workspace }}/multissl @@ -304,7 +304,7 @@ jobs: needs: check_source if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true' env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@v4 @@ -415,7 +415,7 @@ jobs: needs: check_source if: needs.check_source.outputs.run_tests == 'true' env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index ef52d99c15191b9..0cbad57f0c6572e 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 60 runs-on: ubuntu-20.04 env: - OPENSSL_VER: 3.0.11 + OPENSSL_VER: 3.0.13 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@v4 diff --git a/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst new file mode 100644 index 000000000000000..4eb4d39629b9bcd --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2024-02-05-19-00-32.gh-issue-109991.yJSEkw.rst @@ -0,0 +1,2 @@ +Update GitHub CI workflows to use OpenSSL 3.0.13 and multissltests to use +1.1.1w, 3.0.13, 3.1.5, and 3.2.1. diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 120e3883adc795d..baa16102068aa08 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -47,8 +47,9 @@ OPENSSL_RECENT_VERSIONS = [ "1.1.1w", - "3.0.11", - "3.1.3", + "3.0.13", + "3.1.5", + "3.2.1", ] LIBRESSL_OLD_VERSIONS = [ From 1b1f8398d0ffe3c8ba2cca79d0c0f19a6a34e72a Mon Sep 17 00:00:00 2001 From: Barney Gale <barney.gale@gmail.com> Date: Tue, 6 Feb 2024 02:48:18 +0000 Subject: [PATCH 219/263] GH-106747: Make pathlib ABC globbing more consistent with `glob.glob()` (#115056) When expanding `**` wildcards, ensure we add a trailing slash to the topmost directory path. This matches `glob.glob()` behaviour: >>> glob.glob('dirA/**', recursive=True) ['dirA/', 'dirA/dirB', 'dirA/dirB/dirC'] This does not affect `pathlib.Path.glob()`, because trailing slashes aren't supported in pathlib proper. --- Lib/pathlib/_abc.py | 2 +- Lib/test/test_pathlib/test_pathlib_abc.py | 34 +++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 91f5cd6c01e9d08..e4b1201a3703c32 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -95,7 +95,7 @@ def _select_recursive(parent_paths, dir_only, follow_symlinks): if follow_symlinks is None: follow_symlinks = False for parent_path in parent_paths: - paths = [parent_path] + paths = [parent_path._make_child_relpath('')] while paths: path = paths.pop() yield path diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 207579ccbf443b7..1d30deca8f7a1bb 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1791,25 +1791,25 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."]) _check(p, "dir*/**", [ - "dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", - "dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", - "dirE"]) + "dirA/", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB", + "dirB/", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB", + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE/"]) _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", "dirB/linkD/..", "dirA/linkC/linkD/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) _check(p, "dir*/*/**", [ - "dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", - "dirB/linkD", "dirB/linkD/fileB", - "dirC/dirD", "dirC/dirD/fileD"]) + "dirA/linkC/", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB", + "dirB/linkD/", "dirB/linkD/fileB", + "dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..", "dirB/linkD/..", "dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) @needs_symlinks @@ -1827,19 +1827,19 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) _check(p, "dir*/*/..", ["dirC/dirD/.."]) _check(p, "dir*/**", [ - "dirA", "dirA/linkC", - "dirB", "dirB/fileB", "dirB/linkD", - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", - "dirE"]) + "dirA/", "dirA/linkC", + "dirB/", "dirB/fileB", "dirB/linkD", + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt", + "dirE/"]) _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "dir*/*/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "dir*/*/**/", ["dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"]) + _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD/", "dirC/dirD/../dirD/fileD"]) _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_rglob_common(self): @@ -1876,13 +1876,13 @@ def _check(glob, expected): "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"]) + _check(p.rglob("dir*/**"), ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) _check(p.rglob("*/"), ["dirC/dirD/"]) _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) _check(p.rglob("**"), [ - "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) + "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p.rglob("*.txt"), ["dirC/novel.txt"]) From 13eb5215c9de9dd302f116ef0bca4ae23b02842b Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 6 Feb 2024 11:04:35 +0100 Subject: [PATCH 220/263] gh-115009: Update macOS installer to use SQLite 3.45.1 (#115066) Co-authored-by: Ned Deily <nad@python.org> --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 9000fb8973659db..0af90563cbbb2b7 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -359,9 +359,9 @@ def library_recipes(): ), ), dict( - name="SQLite 3.44.2", - url="https://sqlite.org/2023/sqlite-autoconf-3440200.tar.gz", - checksum="c02f40fd4f809ced95096250adc5764a", + name="SQLite 3.45.1", + url="https://sqlite.org/2024/sqlite-autoconf-3450100.tar.gz", + checksum="cd9c27841b7a5932c9897651e20b86c701dd740556989b01ca596fcfa3d49a0a", extra_cflags=('-Os ' '-DSQLITE_ENABLE_FTS5 ' '-DSQLITE_ENABLE_FTS4 ' diff --git a/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst b/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst new file mode 100644 index 000000000000000..47ec488c3cced2d --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2024-02-06-09-01-10.gh-issue-115009.ysau7e.rst @@ -0,0 +1 @@ +Update macOS installer to use SQLite 3.45.1. From 4bf41879d03b1da3c6d38c39a04331e3ae2e7545 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson <seth@python.org> Date: Tue, 6 Feb 2024 04:25:58 -0600 Subject: [PATCH 221/263] gh-112302: Change 'licenseConcluded' field to 'NOASSERTION' (#115038) --- Misc/sbom.spdx.json | 60 ++++++++++++++++++------------------ Tools/build/generate_sbom.py | 12 +++++--- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index e94dcb83dd4e404..d783d14255e66f1 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1601,7 +1601,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "expat", "originator": "Organization: Expat development team", "primaryPackagePurpose": "SOURCE", @@ -1623,7 +1623,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "Apache-2.0", + "licenseConcluded": "NOASSERTION", "name": "hacl-star", "originator": "Organization: HACL* Developers", "primaryPackagePurpose": "SOURCE", @@ -1645,7 +1645,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "CC0-1.0", + "licenseConcluded": "NOASSERTION", "name": "libb2", "originator": "Organization: BLAKE2 - fast secure hashing", "primaryPackagePurpose": "SOURCE", @@ -1667,7 +1667,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "macholib", "originator": "Person: Ronald Oussoren (ronaldoussoren@mac.com)", "primaryPackagePurpose": "SOURCE", @@ -1689,7 +1689,7 @@ "referenceType": "cpe23Type" } ], - "licenseConcluded": "BSD-2-Clause", + "licenseConcluded": "NOASSERTION", "name": "mpdecimal", "originator": "Organization: bytereef.org", "primaryPackagePurpose": "SOURCE", @@ -1711,7 +1711,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "cachecontrol", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.13.1" @@ -1732,7 +1732,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "colorama", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.4.6" @@ -1753,7 +1753,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "distlib", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.3.8" @@ -1774,7 +1774,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "distro", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.8.0" @@ -1795,7 +1795,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "msgpack", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.5" @@ -1816,7 +1816,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "packaging", "primaryPackagePurpose": "SOURCE", "versionInfo": "21.3" @@ -1837,7 +1837,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "platformdirs", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.8.1" @@ -1858,7 +1858,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pyparsing", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.1.0" @@ -1879,7 +1879,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pyproject-hooks", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.0" @@ -1900,7 +1900,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "requests", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.31.0" @@ -1921,7 +1921,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "certifi", "primaryPackagePurpose": "SOURCE", "versionInfo": "2023.7.22" @@ -1942,7 +1942,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "chardet", "primaryPackagePurpose": "SOURCE", "versionInfo": "5.1.0" @@ -1963,7 +1963,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "idna", "primaryPackagePurpose": "SOURCE", "versionInfo": "3.4" @@ -1984,7 +1984,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "rich", "primaryPackagePurpose": "SOURCE", "versionInfo": "13.4.2" @@ -2005,7 +2005,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pygments", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.15.1" @@ -2026,7 +2026,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "typing_extensions", "primaryPackagePurpose": "SOURCE", "versionInfo": "4.7.1" @@ -2047,7 +2047,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "resolvelib", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.0.1" @@ -2068,7 +2068,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "setuptools", "primaryPackagePurpose": "SOURCE", "versionInfo": "68.0.0" @@ -2089,7 +2089,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "six", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.16.0" @@ -2110,7 +2110,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "tenacity", "primaryPackagePurpose": "SOURCE", "versionInfo": "8.2.2" @@ -2131,7 +2131,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "tomli", "primaryPackagePurpose": "SOURCE", "versionInfo": "2.0.1" @@ -2152,7 +2152,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "truststore", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.8.0" @@ -2173,7 +2173,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "webencodings", "primaryPackagePurpose": "SOURCE", "versionInfo": "0.5.1" @@ -2194,7 +2194,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "urllib3", "primaryPackagePurpose": "SOURCE", "versionInfo": "1.26.17" @@ -2220,7 +2220,7 @@ "referenceType": "purl" } ], - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "name": "pip", "originator": "Organization: Python Packaging Authority", "primaryPackagePurpose": "SOURCE", diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index aceb13f141cba40..442487f2d2546b4 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -338,7 +338,7 @@ def discover_pip_sbom_package(sbom_data: dict[str, typing.Any]) -> None: "name": "pip", "versionInfo": pip_version, "originator": "Organization: Python Packaging Authority", - "licenseConcluded": "MIT", + "licenseConcluded": "NOASSERTION", "downloadLocation": pip_download_url, "checksums": [ {"algorithm": "SHA256", "checksumValue": pip_checksum_sha256} @@ -383,9 +383,11 @@ def main() -> None: discover_pip_sbom_package(sbom_data) # Ensure all packages in this tool are represented also in the SBOM file. + actual_names = {package["name"] for package in sbom_data["packages"]} + expected_names = set(PACKAGE_TO_FILES) error_if( - {package["name"] for package in sbom_data["packages"]} != set(PACKAGE_TO_FILES), - "Packages defined in SBOM tool don't match those defined in SBOM file.", + actual_names != expected_names, + f"Packages defined in SBOM tool don't match those defined in SBOM file: {actual_names}, {expected_names}", ) # Make a bunch of assertions about the SBOM data to ensure it's consistent. @@ -422,8 +424,8 @@ def main() -> None: # License must be on the approved list for SPDX. license_concluded = package["licenseConcluded"] error_if( - license_concluded not in ALLOWED_LICENSE_EXPRESSIONS, - f"License identifier '{license_concluded}' not in SBOM tool allowlist" + license_concluded != "NOASSERTION", + f"License identifier must be 'NOASSERTION'" ) # We call 'sorted()' here a lot to avoid filesystem scan order issues. From 1a10437a14b13100bdf41cbdab819c33258deb65 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak <felisiak.mariusz@gmail.com> Date: Tue, 6 Feb 2024 12:34:56 +0100 Subject: [PATCH 222/263] gh-91602: Add iterdump() support for filtering database objects (#114501) Add optional 'filter' parameter to iterdump() that allows a "LIKE" pattern for filtering database objects to dump. Co-authored-by: Erlend E. Aasland <erlend@python.org> --- Doc/library/sqlite3.rst | 11 ++- Doc/whatsnew/3.13.rst | 4 ++ .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + Lib/sqlite3/dump.py | 19 +++-- Lib/test/test_sqlite3/test_dump.py | 70 +++++++++++++++++++ ...4-01-24-20-51-49.gh-issue-91602.8fOH8l.rst | 3 + Modules/_sqlite/clinic/connection.c.h | 60 ++++++++++++++-- Modules/_sqlite/connection.c | 20 ++++-- 11 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index c3406b166c3d89d..87d5ef1e42ca3ac 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1137,12 +1137,19 @@ Connection objects .. _Loading an Extension: https://www.sqlite.org/loadext.html#loading_an_extension_ - .. method:: iterdump + .. method:: iterdump(*, filter=None) Return an :term:`iterator` to dump the database as SQL source code. Useful when saving an in-memory database for later restoration. Similar to the ``.dump`` command in the :program:`sqlite3` shell. + :param filter: + + An optional ``LIKE`` pattern for database objects to dump, e.g. ``prefix_%``. + If ``None`` (the default), all database objects will be included. + + :type filter: str | None + Example: .. testcode:: @@ -1158,6 +1165,8 @@ Connection objects :ref:`sqlite3-howto-encoding` + .. versionchanged:: 3.13 + Added the *filter* parameter. .. method:: backup(target, *, pages=-1, progress=None, name="main", sleep=0.250) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 5e5f1e295f4d706..372757759b986f9 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -438,6 +438,10 @@ sqlite3 object is not :meth:`closed <sqlite3.Connection.close>` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +* Add *filter* keyword-only parameter to :meth:`sqlite3.Connection.iterdump` + for filtering database objects to dump. + (Contributed by Mariusz Felisiak in :gh:`91602`.) + subprocess ---------- diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index dd09ff40f39fe6f..932738c3049882b 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -940,6 +940,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fileno)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filepath)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fillvalue)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filter)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filters)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(final)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(find_class)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 79d6509abcdfd91..da62b4f0a951ff8 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -429,6 +429,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(fileno) STRUCT_FOR_ID(filepath) STRUCT_FOR_ID(fillvalue) + STRUCT_FOR_ID(filter) STRUCT_FOR_ID(filters) STRUCT_FOR_ID(final) STRUCT_FOR_ID(find_class) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index f3c55acfb3c282f..68fbbcb4378e17f 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -938,6 +938,7 @@ extern "C" { INIT_ID(fileno), \ INIT_ID(filepath), \ INIT_ID(fillvalue), \ + INIT_ID(filter), \ INIT_ID(filters), \ INIT_ID(final), \ INIT_ID(find_class), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 2e9572382fe0332..c8458b4e36ccc93 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1128,6 +1128,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(fillvalue); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(filter); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(filters); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/sqlite3/dump.py b/Lib/sqlite3/dump.py index 719dfc8947697d4..9dcce7dc76ced48 100644 --- a/Lib/sqlite3/dump.py +++ b/Lib/sqlite3/dump.py @@ -15,7 +15,7 @@ def _quote_value(value): return "'{0}'".format(value.replace("'", "''")) -def _iterdump(connection): +def _iterdump(connection, *, filter=None): """ Returns an iterator to the dump of the database in an SQL text format. @@ -32,15 +32,23 @@ def _iterdump(connection): yield('PRAGMA foreign_keys=OFF;') yield('BEGIN TRANSACTION;') + if filter: + # Return database objects which match the filter pattern. + filter_name_clause = 'AND "name" LIKE ?' + params = [filter] + else: + filter_name_clause = "" + params = [] # sqlite_master table contains the SQL CREATE statements for the database. - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" == 'table' + {filter_name_clause} ORDER BY "name" """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) sqlite_sequence = [] for table_name, type, sql in schema_res.fetchall(): if table_name == 'sqlite_sequence': @@ -82,13 +90,14 @@ def _iterdump(connection): yield("{0};".format(row[0])) # Now when the type is 'index', 'trigger', or 'view' - q = """ + q = f""" SELECT "name", "type", "sql" FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" IN ('index', 'trigger', 'view') + {filter_name_clause} """ - schema_res = cu.execute(q) + schema_res = cu.execute(q, params) for name, type, sql in schema_res.fetchall(): yield('{0};'.format(sql)) diff --git a/Lib/test/test_sqlite3/test_dump.py b/Lib/test/test_sqlite3/test_dump.py index 2e1f0b80c10f46e..7261b7f0dc93d0a 100644 --- a/Lib/test/test_sqlite3/test_dump.py +++ b/Lib/test/test_sqlite3/test_dump.py @@ -54,6 +54,76 @@ def test_table_dump(self): [self.assertEqual(expected_sqls[i], actual_sqls[i]) for i in range(len(expected_sqls))] + def test_table_dump_filter(self): + all_table_sqls = [ + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + ] + all_views_sqls = [ + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", + ] + # Create database structure. + for sql in [*all_table_sqls, *all_views_sqls]: + self.cu.execute(sql) + # %_table_% matches all tables. + dump_sqls = list(self.cx.iterdump(filter="%_table_%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, "COMMIT;"], + ) + # view_% matches all views. + dump_sqls = list(self.cx.iterdump(filter="view_%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_views_sqls, "COMMIT;"], + ) + # %_1 matches tables and views with the _1 suffix. + dump_sqls = list(self.cx.iterdump(filter="%_1")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "test_table_1" ("id_2" INTEGER);""", + """INSERT INTO "test_table_1" VALUES(1);""", + """INSERT INTO "test_table_1" VALUES(2);""", + """CREATE VIEW "view_1" AS SELECT * FROM "some_table_2";""", + "COMMIT;" + ], + ) + # some_% matches some_table_2. + dump_sqls = list(self.cx.iterdump(filter="some_%")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE TABLE "some_table_2" ("id_1" INTEGER);""", + """INSERT INTO "some_table_2" VALUES(3);""", + """INSERT INTO "some_table_2" VALUES(4);""", + "COMMIT;" + ], + ) + # Only single object. + dump_sqls = list(self.cx.iterdump(filter="view_2")) + self.assertEqual( + dump_sqls, + [ + "BEGIN TRANSACTION;", + """CREATE VIEW "view_2" AS SELECT * FROM "test_table_1";""", + "COMMIT;" + ], + ) + # % matches all objects. + dump_sqls = list(self.cx.iterdump(filter="%")) + self.assertEqual( + dump_sqls, + ["BEGIN TRANSACTION;", *all_table_sqls, *all_views_sqls, "COMMIT;"], + ) + def test_dump_autoincrement(self): expected = [ 'CREATE TABLE "t1" (id integer primary key autoincrement);', diff --git a/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst new file mode 100644 index 000000000000000..21d39df43e035b6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-24-20-51-49.gh-issue-91602.8fOH8l.rst @@ -0,0 +1,3 @@ +Add *filter* keyword-only parameter to +:meth:`sqlite3.Connection.iterdump` for filtering database objects to dump. +Patch by Mariusz Felisiak. diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index f2cff6a7b421f3b..811314b5cd8aed8 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1204,21 +1204,67 @@ pysqlite_connection_interrupt(pysqlite_Connection *self, PyObject *Py_UNUSED(ign } PyDoc_STRVAR(pysqlite_connection_iterdump__doc__, -"iterdump($self, /)\n" +"iterdump($self, /, *, filter=None)\n" "--\n" "\n" -"Returns iterator to the dump of the database in an SQL text format."); +"Returns iterator to the dump of the database in an SQL text format.\n" +"\n" +" filter\n" +" An optional LIKE pattern for database objects to dump"); #define PYSQLITE_CONNECTION_ITERDUMP_METHODDEF \ - {"iterdump", (PyCFunction)pysqlite_connection_iterdump, METH_NOARGS, pysqlite_connection_iterdump__doc__}, + {"iterdump", _PyCFunction_CAST(pysqlite_connection_iterdump), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_iterdump__doc__}, static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self); +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter); static PyObject * -pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *Py_UNUSED(ignored)) +pysqlite_connection_iterdump(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return pysqlite_connection_iterdump_impl(self); + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(filter), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"filter", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "iterdump", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *filter = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + filter = args[0]; +skip_optional_kwonly: + return_value = pysqlite_connection_iterdump_impl(self, filter); + +exit: + return return_value; } PyDoc_STRVAR(pysqlite_connection_backup__doc__, @@ -1820,4 +1866,4 @@ getconfig(pysqlite_Connection *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=99299d3ee2c247ab input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3c6d0b748fac016f input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 0a6633972cc5ef7..f97afcf5fcf16ef 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -1979,12 +1979,17 @@ pysqlite_connection_interrupt_impl(pysqlite_Connection *self) /*[clinic input] _sqlite3.Connection.iterdump as pysqlite_connection_iterdump + * + filter: object = None + An optional LIKE pattern for database objects to dump + Returns iterator to the dump of the database in an SQL text format. [clinic start generated code]*/ static PyObject * -pysqlite_connection_iterdump_impl(pysqlite_Connection *self) -/*[clinic end generated code: output=586997aaf9808768 input=1911ca756066da89]*/ +pysqlite_connection_iterdump_impl(pysqlite_Connection *self, + PyObject *filter) +/*[clinic end generated code: output=fd81069c4bdeb6b0 input=4ae6d9a898f108df]*/ { if (!pysqlite_check_connection(self)) { return NULL; @@ -1998,9 +2003,16 @@ pysqlite_connection_iterdump_impl(pysqlite_Connection *self) } return NULL; } - - PyObject *retval = PyObject_CallOneArg(iterdump, (PyObject *)self); + PyObject *args[3] = {NULL, (PyObject *)self, filter}; + PyObject *kwnames = Py_BuildValue("(s)", "filter"); + if (!kwnames) { + Py_DECREF(iterdump); + return NULL; + } + Py_ssize_t nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET; + PyObject *retval = PyObject_Vectorcall(iterdump, args + 1, nargsf, kwnames); Py_DECREF(iterdump); + Py_DECREF(kwnames); return retval; } From d7334e2c2012defaf7aae920d6a56689464509d1 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Tue, 6 Feb 2024 16:08:56 +0300 Subject: [PATCH 223/263] gh-106233: Fix stacklevel in zoneinfo.InvalidTZPathWarning (GH-106234) --- Lib/test/test_zoneinfo/test_zoneinfo.py | 17 +++++++++++-- Lib/zoneinfo/_tzpath.py | 24 ++++++++++++------- ...-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst | 2 ++ 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 18eab5b33540c9e..8414721555731eb 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -20,7 +20,7 @@ from test.support import MISSING_C_DOCSTRINGS from test.test_zoneinfo import _support as test_support from test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase -from test.support.import_helper import import_module +from test.support.import_helper import import_module, CleanImport lzma = import_module('lzma') py_zoneinfo, c_zoneinfo = test_support.get_modules() @@ -1720,13 +1720,26 @@ def test_env_variable_relative_paths(self): with self.subTest("warning", path_var=path_var): # Note: Per PEP 615 the warning is implementation-defined # behavior, other implementations need not warn. - with self.assertWarns(self.module.InvalidTZPathWarning): + with self.assertWarns(self.module.InvalidTZPathWarning) as w: self.module.reset_tzpath() + self.assertEqual(w.warnings[0].filename, __file__) tzpath = self.module.TZPATH with self.subTest("filtered", path_var=path_var): self.assertSequenceEqual(tzpath, expected_paths) + def test_env_variable_relative_paths_warning_location(self): + path_var = "path/to/somewhere" + + with self.python_tzpath_context(path_var): + with CleanImport("zoneinfo", "zoneinfo._tzpath"): + with self.assertWarns(RuntimeWarning) as w: + import zoneinfo + InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning + self.assertIsInstance(w.warnings[0].message, InvalidTZPathWarning) + # It should represent the current file: + self.assertEqual(w.warnings[0].filename, __file__) + def test_reset_tzpath_kwarg(self): self.module.reset_tzpath(to=[f"{DRIVE}/a/b/c"]) diff --git a/Lib/zoneinfo/_tzpath.py b/Lib/zoneinfo/_tzpath.py index 4985dce2dc36d0e..5db17bea045d8c8 100644 --- a/Lib/zoneinfo/_tzpath.py +++ b/Lib/zoneinfo/_tzpath.py @@ -2,7 +2,7 @@ import sysconfig -def reset_tzpath(to=None): +def _reset_tzpath(to=None, stacklevel=4): global TZPATH tzpaths = to @@ -18,17 +18,22 @@ def reset_tzpath(to=None): base_tzpath = tzpaths else: env_var = os.environ.get("PYTHONTZPATH", None) - if env_var is not None: - base_tzpath = _parse_python_tzpath(env_var) - else: - base_tzpath = _parse_python_tzpath( - sysconfig.get_config_var("TZPATH") - ) + if env_var is None: + env_var = sysconfig.get_config_var("TZPATH") + base_tzpath = _parse_python_tzpath(env_var, stacklevel) TZPATH = tuple(base_tzpath) -def _parse_python_tzpath(env_var): +def reset_tzpath(to=None): + """Reset global TZPATH.""" + # We need `_reset_tzpath` helper function because it produces a warning, + # it is used as both a module-level call and a public API. + # This is how we equalize the stacklevel for both calls. + _reset_tzpath(to) + + +def _parse_python_tzpath(env_var, stacklevel): if not env_var: return () @@ -45,6 +50,7 @@ def _parse_python_tzpath(env_var): "Invalid paths specified in PYTHONTZPATH environment variable. " + msg, InvalidTZPathWarning, + stacklevel=stacklevel, ) return new_tzpath @@ -172,4 +178,4 @@ class InvalidTZPathWarning(RuntimeWarning): TZPATH = () -reset_tzpath() +_reset_tzpath(stacklevel=5) diff --git a/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst new file mode 100644 index 000000000000000..345c8b20815c951 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-29-14-26-56.gh-issue-106233.Aqw2HI.rst @@ -0,0 +1,2 @@ +Fix stacklevel in ``InvalidTZPathWarning`` during :mod:`zoneinfo` module +import. From 0e2ab73dc31e0b8ea1827ec24bae93ae2644c617 Mon Sep 17 00:00:00 2001 From: da-woods <dw-git@d-woods.co.uk> Date: Tue, 6 Feb 2024 15:55:44 +0000 Subject: [PATCH 224/263] gh-114756: Update FAQ section on removing the GIL (#114957) Update FAQ section on removing the GIL to reflect recent progress on PEP 703 and PEP 684. Co-authored-by: AN Long <aisk@users.noreply.github.com> --- Doc/faq/library.rst | 52 +++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/Doc/faq/library.rst b/Doc/faq/library.rst index 476a43d9c288f18..e2f8004c7e3aea5 100644 --- a/Doc/faq/library.rst +++ b/Doc/faq/library.rst @@ -405,22 +405,37 @@ lists. When in doubt, use a mutex! Can't we get rid of the Global Interpreter Lock? ------------------------------------------------ -.. XXX link to dbeazley's talk about GIL? - The :term:`global interpreter lock` (GIL) is often seen as a hindrance to Python's deployment on high-end multiprocessor server machines, because a multi-threaded Python program effectively only uses one CPU, due to the insistence that (almost) all Python code can only run while the GIL is held. -Back in the days of Python 1.5, Greg Stein actually implemented a comprehensive +With the approval of :pep:`703` work is now underway to remove the GIL from the +CPython implementation of Python. Initially it will be implemented as an +optional compiler flag when building the interpreter, and so separate +builds will be available with and without the GIL. Long-term, the hope is +to settle on a single build, once the performance implications of removing the +GIL are fully understood. Python 3.13 is likely to be the first release +containing this work, although it may not be completely functional in this +release. + +The current work to remove the GIL is based on a +`fork of Python 3.9 with the GIL removed <https://github.com/colesbury/nogil>`_ +by Sam Gross. +Prior to that, +in the days of Python 1.5, Greg Stein actually implemented a comprehensive patch set (the "free threading" patches) that removed the GIL and replaced it -with fine-grained locking. Adam Olsen recently did a similar experiment +with fine-grained locking. Adam Olsen did a similar experiment in his `python-safethread <https://code.google.com/archive/p/python-safethread>`_ -project. Unfortunately, both experiments exhibited a sharp drop in single-thread +project. Unfortunately, both of these earlier experiments exhibited a sharp +drop in single-thread performance (at least 30% slower), due to the amount of fine-grained locking -necessary to compensate for the removal of the GIL. +necessary to compensate for the removal of the GIL. The Python 3.9 fork +is the first attempt at removing the GIL with an acceptable performance +impact. -This doesn't mean that you can't make good use of Python on multi-CPU machines! +The presence of the GIL in current Python releases +doesn't mean that you can't make good use of Python on multi-CPU machines! You just have to be creative with dividing the work up between multiple *processes* rather than multiple *threads*. The :class:`~concurrent.futures.ProcessPoolExecutor` class in the new @@ -434,22 +449,13 @@ thread of execution is in the C code and allow other threads to get some work done. Some standard library modules such as :mod:`zlib` and :mod:`hashlib` already do this. -It has been suggested that the GIL should be a per-interpreter-state lock rather -than truly global; interpreters then wouldn't be able to share objects. -Unfortunately, this isn't likely to happen either. It would be a tremendous -amount of work, because many object implementations currently have global state. -For example, small integers and short strings are cached; these caches would -have to be moved to the interpreter state. Other object types have their own -free list; these free lists would have to be moved to the interpreter state. -And so on. - -And I doubt that it can even be done in finite time, because the same problem -exists for 3rd party extensions. It is likely that 3rd party extensions are -being written at a faster rate than you can convert them to store all their -global state in the interpreter state. - -And finally, once you have multiple interpreters not sharing any state, what -have you gained over running each interpreter in a separate process? +An alternative approach to reducing the impact of the GIL is +to make the GIL a per-interpreter-state lock rather than truly global. +This was :ref:`first implemented in Python 3.12 <whatsnew312-pep684>` and is +available in the C API. A Python interface to it is expected in Python 3.13. +The main limitation to it at the moment is likely to be 3rd party extension +modules, since these must be written with multiple interpreters in mind in +order to be usable, so many older extension modules will not be usable. Input and Output From de61d4bd4db868ce49a729a283763b94f2fda961 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 11:36:23 -0500 Subject: [PATCH 225/263] gh-112066: Add `PyDict_SetDefaultRef` function. (#112123) The `PyDict_SetDefaultRef` function is similar to `PyDict_SetDefault`, but returns a strong reference through the optional `**result` pointer instead of a borrowed reference. Co-authored-by: Petr Viktorin <encukou@gmail.com> --- Doc/c-api/dict.rst | 20 ++++ Doc/whatsnew/3.13.rst | 6 ++ Include/cpython/dictobject.h | 10 ++ Lib/test/test_capi/test_dict.py | 22 +++++ ...-11-15-13-47-48.gh-issue-112066.22WsqR.rst | 5 + Modules/_testcapi/dict.c | 26 ++++++ Objects/dictobject.c | 91 +++++++++++++++---- 7 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index 8471c98d0448721..03f3d28187bfe9a 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -174,6 +174,26 @@ Dictionary Objects .. versionadded:: 3.4 +.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result) + + Inserts *default_value* into the dictionary *p* with a key of *key* if the + key is not already present in the dictionary. If *result* is not ``NULL``, + then *\*result* is set to a :term:`strong reference` to either + *default_value*, if the key was not present, or the existing value, if *key* + was already present in the dictionary. + Returns ``1`` if the key was present and *default_value* was not inserted, + or ``0`` if the key was not present and *default_value* was inserted. + On failure, returns ``-1``, sets an exception, and sets ``*result`` + to ``NULL``. + + For clarity: if you have a strong reference to *default_value* before + calling this function, then after it returns, you hold a strong reference + to both *default_value* and *\*result* (if it's not ``NULL``). + These may refer to the same object: in that case you hold two separate + references to it. + .. versionadded:: 3.13 + + .. c:function:: int PyDict_Pop(PyObject *p, PyObject *key, PyObject **result) Remove *key* from dictionary *p* and optionally return the removed value. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 372757759b986f9..e034d34c5fb5abc 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1440,6 +1440,12 @@ New Features not needed. (Contributed by Victor Stinner in :gh:`106004`.) +* Added :c:func:`PyDict_SetDefaultRef`, which is similar to + :c:func:`PyDict_SetDefault` but returns a :term:`strong reference` instead of + a :term:`borrowed reference`. This function returns ``-1`` on error, ``0`` on + insertion, and ``1`` if the key was already present in the dictionary. + (Contributed by Sam Gross in :gh:`112066`.) + * Add :c:func:`PyDict_ContainsString` function: same as :c:func:`PyDict_Contains`, but *key* is specified as a :c:expr:`const char*` UTF-8 encoded bytes string, rather than a :c:expr:`PyObject*`. diff --git a/Include/cpython/dictobject.h b/Include/cpython/dictobject.h index 1720fe6f01ea37d..35b6a822a0dfffd 100644 --- a/Include/cpython/dictobject.h +++ b/Include/cpython/dictobject.h @@ -41,6 +41,16 @@ PyAPI_FUNC(PyObject *) _PyDict_GetItemStringWithError(PyObject *, const char *); PyAPI_FUNC(PyObject *) PyDict_SetDefault( PyObject *mp, PyObject *key, PyObject *defaultobj); +// Inserts `key` with a value `default_value`, if `key` is not already present +// in the dictionary. If `result` is not NULL, then the value associated +// with `key` is returned in `*result` (either the existing value, or the now +// inserted `default_value`). +// Returns: +// -1 on error +// 0 if `key` was not present and `default_value` was inserted +// 1 if `key` was present and `default_value` was not inserted +PyAPI_FUNC(int) PyDict_SetDefaultRef(PyObject *mp, PyObject *key, PyObject *default_value, PyObject **result); + /* Get the number of items of a dictionary. */ static inline Py_ssize_t PyDict_GET_SIZE(PyObject *op) { PyDictObject *mp; diff --git a/Lib/test/test_capi/test_dict.py b/Lib/test/test_capi/test_dict.py index 57a7238588eae09..cca6145bc90c047 100644 --- a/Lib/test/test_capi/test_dict.py +++ b/Lib/test/test_capi/test_dict.py @@ -339,6 +339,28 @@ def test_dict_setdefault(self): # CRASHES setdefault({}, 'a', NULL) # CRASHES setdefault(NULL, 'a', 5) + def test_dict_setdefaultref(self): + setdefault = _testcapi.dict_setdefaultref + dct = {} + self.assertEqual(setdefault(dct, 'a', 5), 5) + self.assertEqual(dct, {'a': 5}) + self.assertEqual(setdefault(dct, 'a', 8), 5) + self.assertEqual(dct, {'a': 5}) + + dct2 = DictSubclass() + self.assertEqual(setdefault(dct2, 'a', 5), 5) + self.assertEqual(dct2, {'a': 5}) + self.assertEqual(setdefault(dct2, 'a', 8), 5) + self.assertEqual(dct2, {'a': 5}) + + self.assertRaises(TypeError, setdefault, {}, [], 5) # unhashable + self.assertRaises(SystemError, setdefault, UserDict(), 'a', 5) + self.assertRaises(SystemError, setdefault, [1], 0, 5) + self.assertRaises(SystemError, setdefault, 42, 'a', 5) + # CRASHES setdefault({}, NULL, 5) + # CRASHES setdefault({}, 'a', NULL) + # CRASHES setdefault(NULL, 'a', 5) + def test_mapping_keys_valuesitems(self): class BadMapping(dict): def keys(self): diff --git a/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst b/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst new file mode 100644 index 000000000000000..ae2b8b2444de972 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-15-13-47-48.gh-issue-112066.22WsqR.rst @@ -0,0 +1,5 @@ +Add :c:func:`PyDict_SetDefaultRef`: insert a key and value into a dictionary +if the key is not already present. This is similar to +:meth:`dict.setdefault`, but returns an integer value indicating if the key +was already present. It is also similar to :c:func:`PyDict_SetDefault`, but +returns a strong reference instead of a borrowed reference. diff --git a/Modules/_testcapi/dict.c b/Modules/_testcapi/dict.c index 42e056b7d07a31f..fe03c24f75e196b 100644 --- a/Modules/_testcapi/dict.c +++ b/Modules/_testcapi/dict.c @@ -225,6 +225,31 @@ dict_setdefault(PyObject *self, PyObject *args) return PyDict_SetDefault(mapping, key, defaultobj); } +static PyObject * +dict_setdefaultref(PyObject *self, PyObject *args) +{ + PyObject *obj, *key, *default_value, *result = UNINITIALIZED_PTR; + if (!PyArg_ParseTuple(args, "OOO", &obj, &key, &default_value)) { + return NULL; + } + NULLABLE(obj); + NULLABLE(key); + NULLABLE(default_value); + switch (PyDict_SetDefaultRef(obj, key, default_value, &result)) { + case -1: + assert(result == NULL); + return NULL; + case 0: + assert(result == default_value); + return result; + case 1: + return result; + default: + Py_FatalError("PyDict_SetDefaultRef() returned invalid code"); + Py_UNREACHABLE(); + } +} + static PyObject * dict_delitem(PyObject *self, PyObject *args) { @@ -433,6 +458,7 @@ static PyMethodDef test_methods[] = { {"dict_delitem", dict_delitem, METH_VARARGS}, {"dict_delitemstring", dict_delitemstring, METH_VARARGS}, {"dict_setdefault", dict_setdefault, METH_VARARGS}, + {"dict_setdefaultref", dict_setdefaultref, METH_VARARGS}, {"dict_keys", dict_keys, METH_O}, {"dict_values", dict_values, METH_O}, {"dict_items", dict_items, METH_O}, diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 4bb818b90a4a72d..11b388d9f4adb05 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3355,8 +3355,9 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) return Py_NewRef(val); } -PyObject * -PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) +static int +dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result, int incref_result) { PyDictObject *mp = (PyDictObject *)d; PyObject *value; @@ -3365,41 +3366,64 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) if (!PyDict_Check(d)) { PyErr_BadInternalCall(); - return NULL; + if (result) { + *result = NULL; + } + return -1; } if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { hash = PyObject_Hash(key); - if (hash == -1) - return NULL; + if (hash == -1) { + if (result) { + *result = NULL; + } + return -1; + } } if (mp->ma_keys == Py_EMPTY_KEYS) { if (insert_to_emptydict(interp, mp, Py_NewRef(key), hash, - Py_NewRef(defaultobj)) < 0) { - return NULL; + Py_NewRef(default_value)) < 0) { + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = incref_result ? Py_NewRef(default_value) : default_value; } - return defaultobj; + return 0; } if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) { if (insertion_resize(interp, mp, 0) < 0) { - return NULL; + if (result) { + *result = NULL; + } + return -1; } } Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value); - if (ix == DKIX_ERROR) - return NULL; + if (ix == DKIX_ERROR) { + if (result) { + *result = NULL; + } + return -1; + } if (ix == DKIX_EMPTY) { uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_ADDED, mp, key, defaultobj); + interp, PyDict_EVENT_ADDED, mp, key, default_value); mp->ma_keys->dk_version = 0; - value = defaultobj; + value = default_value; if (mp->ma_keys->dk_usable <= 0) { if (insertion_resize(interp, mp, 1) < 0) { - return NULL; + if (result) { + *result = NULL; + } + return -1; } } Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash); @@ -3431,11 +3455,16 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) mp->ma_keys->dk_usable--; mp->ma_keys->dk_nentries++; assert(mp->ma_keys->dk_usable >= 0); + ASSERT_CONSISTENT(mp); + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 0; } else if (value == NULL) { uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_ADDED, mp, key, defaultobj); - value = defaultobj; + interp, PyDict_EVENT_ADDED, mp, key, default_value); + value = default_value; assert(_PyDict_HasSplitTable(mp)); assert(mp->ma_values->values[ix] == NULL); MAINTAIN_TRACKING(mp, key, value); @@ -3443,10 +3472,33 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) _PyDictValues_AddToInsertionOrder(mp->ma_values, ix); mp->ma_used++; mp->ma_version_tag = new_version; + ASSERT_CONSISTENT(mp); + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 0; } ASSERT_CONSISTENT(mp); - return value; + if (result) { + *result = incref_result ? Py_NewRef(value) : value; + } + return 1; +} + +int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + return dict_setdefault_ref(d, key, default_value, result, 1); +} + +PyObject * +PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) +{ + PyObject *result; + dict_setdefault_ref(d, key, defaultobj, &result, 0); + return result; } /*[clinic input] @@ -3467,9 +3519,8 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key, /*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/ { PyObject *val; - - val = PyDict_SetDefault((PyObject *)self, key, default_value); - return Py_XNewRef(val); + PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val); + return val; } From f7a22a7055d97c05406512577bdfcb6d3f134b91 Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Wed, 7 Feb 2024 01:41:18 +0900 Subject: [PATCH 226/263] gh-112087: Make list_{count, index, contains} to be thread-safe. (gh-114916) --- Objects/listobject.c | 52 ++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 82a4ba952de07dc..307b8f1bd76cac8 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -272,6 +272,15 @@ PyList_GetItemRef(PyObject *op, Py_ssize_t i) return Py_NewRef(PyList_GET_ITEM(op, i)); } +static inline PyObject* +list_get_item_ref(PyListObject *op, Py_ssize_t i) +{ + if (!valid_index(i, Py_SIZE(op))) { + return NULL; + } + return Py_NewRef(PyList_GET_ITEM(op, i)); +} + int PyList_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) @@ -478,18 +487,20 @@ list_length(PyObject *a) static int list_contains(PyObject *aa, PyObject *el) { - PyListObject *a = (PyListObject *)aa; - PyObject *item; - Py_ssize_t i; - int cmp; - for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i) { - item = PyList_GET_ITEM(a, i); - Py_INCREF(item); - cmp = PyObject_RichCompareBool(item, el, Py_EQ); + for (Py_ssize_t i = 0; ; i++) { + PyObject *item = list_get_item_ref((PyListObject *)aa, i); + if (item == NULL) { + // out-of-bounds + return 0; + } + int cmp = PyObject_RichCompareBool(item, el, Py_EQ); Py_DECREF(item); + if (cmp != 0) { + return cmp; + } } - return cmp; + return 0; } static PyObject * @@ -2724,8 +2735,6 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start, Py_ssize_t stop) /*[clinic end generated code: output=ec51b88787e4e481 input=40ec5826303a0eb1]*/ { - Py_ssize_t i; - if (start < 0) { start += Py_SIZE(self); if (start < 0) @@ -2736,9 +2745,12 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start, if (stop < 0) stop = 0; } - for (i = start; i < stop && i < Py_SIZE(self); i++) { - PyObject *obj = self->ob_item[i]; - Py_INCREF(obj); + for (Py_ssize_t i = start; i < stop; i++) { + PyObject *obj = list_get_item_ref(self, i); + if (obj == NULL) { + // out-of-bounds + break; + } int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) @@ -2764,15 +2776,17 @@ list_count(PyListObject *self, PyObject *value) /*[clinic end generated code: output=b1f5d284205ae714 input=3bdc3a5e6f749565]*/ { Py_ssize_t count = 0; - Py_ssize_t i; - - for (i = 0; i < Py_SIZE(self); i++) { - PyObject *obj = self->ob_item[i]; + for (Py_ssize_t i = 0; ; i++) { + PyObject *obj = list_get_item_ref(self, i); + if (obj == NULL) { + // out-of-bounds + break; + } if (obj == value) { count++; + Py_DECREF(obj); continue; } - Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) From 7fdd4235d790559372bbb1bf0c2384191a9bb5f3 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 11:45:42 -0500 Subject: [PATCH 227/263] gh-112529: Stop the world around gc.get_referents (#114823) We do not want to add locking in `tp_traverse` slot implementations. Instead, stop the world when calling `gc.get_referents`. Note that the the stop the world call is a no-op in the default build. Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> --- Modules/gcmodule.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 3b63dd7a9a83533..3a42654b41b2acb 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -230,6 +230,26 @@ referentsvisit(PyObject *obj, void *arg) return PyList_Append(list, obj) < 0; } +static int +append_referrents(PyObject *result, PyObject *args) +{ + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { + PyObject *obj = PyTuple_GET_ITEM(args, i); + if (!_PyObject_IS_GC(obj)) { + continue; + } + + traverseproc traverse = Py_TYPE(obj)->tp_traverse; + if (!traverse) { + continue; + } + if (traverse(obj, referentsvisit, result)) { + return -1; + } + } + return 0; +} + /*[clinic input] gc.get_referents @@ -242,29 +262,24 @@ static PyObject * gc_get_referents_impl(PyObject *module, PyObject *args) /*[clinic end generated code: output=d47dc02cefd06fe8 input=b3ceab0c34038cbf]*/ { - Py_ssize_t i; if (PySys_Audit("gc.get_referents", "(O)", args) < 0) { return NULL; } + PyInterpreterState *interp = _PyInterpreterState_GET(); PyObject *result = PyList_New(0); if (result == NULL) return NULL; - for (i = 0; i < PyTuple_GET_SIZE(args); i++) { - traverseproc traverse; - PyObject *obj = PyTuple_GET_ITEM(args, i); + // NOTE: stop the world is a no-op in default build + _PyEval_StopTheWorld(interp); + int err = append_referrents(result, args); + _PyEval_StartTheWorld(interp); - if (!_PyObject_IS_GC(obj)) - continue; - traverse = Py_TYPE(obj)->tp_traverse; - if (! traverse) - continue; - if (traverse(obj, referentsvisit, result)) { - Py_DECREF(result); - return NULL; - } + if (err < 0) { + Py_CLEAR(result); } + return result; } From 76108b8b05040fc49a6bc50eb2e990576595c57c Mon Sep 17 00:00:00 2001 From: Matthieu Caneill <matthieucan@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:44:12 +0100 Subject: [PATCH 228/263] #gh-75705: Set unixfrom envelope in mailbox._mboxMMDF (GH-107117) --- Lib/mailbox.py | 5 +++-- Lib/test/test_mailbox.py | 12 +++++++++++- .../2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst | 1 + 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 81ea210cf815a48..746811bd559412d 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -830,10 +830,11 @@ def get_message(self, key): """Return a Message representation or raise a KeyError.""" start, stop = self._lookup(key) self._file.seek(start) - from_line = self._file.readline().replace(linesep, b'') + from_line = self._file.readline().replace(linesep, b'').decode('ascii') string = self._file.read(stop - self._file.tell()) msg = self._message_factory(string.replace(linesep, b'\n')) - msg.set_from(from_line[5:].decode('ascii')) + msg.set_unixfrom(from_line) + msg.set_from(from_line[5:]) return msg def get_string(self, key, from_=False): diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index d84faad0eb34069..c52c014185bec7a 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -1127,12 +1127,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_from_bytes(self): # Add a byte string starting with 'From ' to the mailbox key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo@bar blah') + self.assertEqual(self._box[key].get_unixfrom(), 'From foo@bar blah') self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): @@ -1667,18 +1669,23 @@ def test_initialize_with_unixfrom(self): msg = mailbox.Message(_sample_message) msg.set_unixfrom('From foo@bar blah') msg = mailbox.mboxMessage(msg) - self.assertEqual(msg.get_from(), 'foo@bar blah', msg.get_from()) + self.assertEqual(msg.get_from(), 'foo@bar blah') + self.assertEqual(msg.get_unixfrom(), 'From foo@bar blah') def test_from(self): # Get and set "From " line msg = mailbox.mboxMessage(_sample_message) self._check_from(msg) + self.assertIsNone(msg.get_unixfrom()) msg.set_from('foo bar') self.assertEqual(msg.get_from(), 'foo bar') + self.assertIsNone(msg.get_unixfrom()) msg.set_from('foo@bar', True) self._check_from(msg, 'foo@bar') + self.assertIsNone(msg.get_unixfrom()) msg.set_from('blah@temp', time.localtime()) self._check_from(msg, 'blah@temp') + self.assertIsNone(msg.get_unixfrom()) def test_flags(self): # Use get_flags(), set_flags(), add_flag(), remove_flag() @@ -1866,6 +1873,7 @@ def test_maildir_to_mboxmmdf(self): self.assertEqual(msg.get_flags(), result) self.assertEqual(msg.get_from(), 'MAILER-DAEMON %s' % time.asctime(time.gmtime(0.0))) + self.assertIsNone(msg.get_unixfrom()) msg_maildir.set_subdir('cur') self.assertEqual(class_(msg_maildir).get_flags(), 'RODFA') @@ -1914,10 +1922,12 @@ def test_mboxmmdf_to_mboxmmdf(self): msg_mboxMMDF = class_(_sample_message) msg_mboxMMDF.set_flags('RODFA') msg_mboxMMDF.set_from('foo@bar') + self.assertIsNone(msg_mboxMMDF.get_unixfrom()) for class2_ in (mailbox.mboxMessage, mailbox.MMDFMessage): msg2 = class2_(msg_mboxMMDF) self.assertEqual(msg2.get_flags(), 'RODFA') self.assertEqual(msg2.get_from(), 'foo@bar') + self.assertIsNone(msg2.get_unixfrom()) def test_mboxmmdf_to_mh(self): # Convert mboxMessage and MMDFMessage to MHMessage diff --git a/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst b/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst new file mode 100644 index 000000000000000..272e31d64cfbd9b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-07-23-12-28-26.gh-issue-75705.aB2-Ww.rst @@ -0,0 +1 @@ +Set unixfrom envelope in :class:`mailbox.mbox` and :class:`mailbox.MMDF`. From 71239d50b54c90afd3fdde260848e0c6d73a5c27 Mon Sep 17 00:00:00 2001 From: Artem Mukhin <artem.m.mukhin@gmail.com> Date: Tue, 6 Feb 2024 20:32:07 +0100 Subject: [PATCH 229/263] gh-103224: Resolve paths properly in test_sysconfig (GH-103292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To pass tests when executed through a Python symlink. Co-authored-by: Miro Hrončok <miro@hroncok.cz> --- Lib/test/test_sysconfig.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index be609a0abd29c87..bb87bf00dc2d1a7 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -154,17 +154,21 @@ def test_posix_venv_scheme(self): 'python%d.%d' % sys.version_info[:2], 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv/ directory + binpath = os.path.join('venv', binpath) + incpath = os.path.join('venv', incpath) + libpath = os.path.join('venv', libpath) - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv')) + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} + + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv', vars=vars)) # The include directory on POSIX isn't exactly the same as before, # but it is "within" - sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv') + sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv', vars=vars) self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep)) def test_nt_venv_scheme(self): @@ -174,14 +178,19 @@ def test_nt_venv_scheme(self): incpath = 'Include' libpath = os.path.join('Lib', 'site-packages') - # Resolve the paths in prefix - binpath = os.path.join(sys.prefix, binpath) - incpath = os.path.join(sys.prefix, incpath) - libpath = os.path.join(sys.prefix, libpath) + # Resolve the paths in an imaginary venv\ directory + venv = 'venv' + binpath = os.path.join(venv, binpath) + incpath = os.path.join(venv, incpath) + libpath = os.path.join(venv, libpath) + + # Mimic the venv module, set all bases to the venv directory + bases = ('base', 'platbase', 'installed_base', 'installed_platbase') + vars = {base: 'venv' for base in bases} - self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv')) - self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv')) - self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv')) + self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv', vars=vars)) + self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv', vars=vars)) + self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv', vars=vars)) def test_venv_scheme(self): if sys.platform == 'win32': From b6228b521b4692b2de1c1c12f4aa5623f8319084 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Tue, 6 Feb 2024 14:45:04 -0500 Subject: [PATCH 230/263] gh-115035: Mark ThreadHandles as non-joinable earlier after forking (#115042) This marks dead ThreadHandles as non-joinable earlier in `PyOS_AfterFork_Child()` before we execute any Python code. The handles are stored in a global linked list in `_PyRuntimeState` because `fork()` affects the entire process. --- Include/internal/pycore_pythread.h | 15 +++++--- Include/internal/pycore_runtime_init.h | 2 + Lib/threading.py | 5 +-- Modules/_threadmodule.c | 53 +++++++++++++++++--------- Python/pystate.c | 2 + Python/thread_nt.h | 4 -- Python/thread_pthread.h | 10 ----- 7 files changed, 50 insertions(+), 41 deletions(-) diff --git a/Include/internal/pycore_pythread.h b/Include/internal/pycore_pythread.h index 9c9a09f60f34418..265299d7574838b 100644 --- a/Include/internal/pycore_pythread.h +++ b/Include/internal/pycore_pythread.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "dynamic_annotations.h" // _Py_ANNOTATE_PURE_HAPPENS_BEFORE_MUTEX +#include "pycore_llist.h" // struct llist_node // Get _POSIX_THREADS and _POSIX_SEMAPHORES macros if available #if (defined(HAVE_UNISTD_H) && !defined(_POSIX_THREADS) \ @@ -75,14 +76,22 @@ struct _pythread_runtime_state { struct py_stub_tls_entry tls_entries[PTHREAD_KEYS_MAX]; } stubs; #endif + + // Linked list of ThreadHandleObjects + struct llist_node handles; }; +#define _pythread_RUNTIME_INIT(pythread) \ + { \ + .handles = LLIST_INIT(pythread.handles), \ + } #ifdef HAVE_FORK /* Private function to reinitialize a lock at fork in the child process. Reset the lock to the unlocked state. Return 0 on success, return -1 on error. */ extern int _PyThread_at_fork_reinit(PyThread_type_lock *lock); +extern void _PyThread_AfterFork(struct _pythread_runtime_state *state); #endif /* HAVE_FORK */ @@ -143,12 +152,6 @@ PyAPI_FUNC(int) PyThread_join_thread(PyThread_handle_t); */ PyAPI_FUNC(int) PyThread_detach_thread(PyThread_handle_t); -/* - * Obtain the new thread ident and handle in a forked child process. - */ -PyAPI_FUNC(void) PyThread_update_thread_after_fork(PyThread_ident_t* ident, - PyThread_handle_t* handle); - #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 4370ad05bdc0584..2ad1347ad48a596 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -16,6 +16,7 @@ extern "C" { #include "pycore_parser.h" // _parser_runtime_state_INIT #include "pycore_pyhash.h" // pyhash_state_INIT #include "pycore_pymem_init.h" // _pymem_allocators_standard_INIT +#include "pycore_pythread.h" // _pythread_RUNTIME_INIT #include "pycore_runtime_init_generated.h" // _Py_bytes_characters_INIT #include "pycore_signal.h" // _signals_RUNTIME_INIT #include "pycore_tracemalloc.h" // _tracemalloc_runtime_state_INIT @@ -90,6 +91,7 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .obmalloc = _obmalloc_global_state_INIT, \ .pyhash_state = pyhash_state_INIT, \ + .threads = _pythread_RUNTIME_INIT(runtime.threads), \ .signals = _signals_RUNTIME_INIT, \ .interpreters = { \ /* This prevents interpreters from getting created \ diff --git a/Lib/threading.py b/Lib/threading.py index 75a08e5aac97d60..b6ff00acadd58fe 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -949,7 +949,6 @@ def _after_fork(self, new_ident=None): # This thread is alive. self._ident = new_ident if self._handle is not None: - self._handle.after_fork_alive() assert self._handle.ident == new_ident # bpo-42350: If the fork happens when the thread is already stopped # (ex: after threading._shutdown() has been called), _tstate_lock @@ -965,9 +964,7 @@ def _after_fork(self, new_ident=None): self._is_stopped = True self._tstate_lock = None self._join_lock = None - if self._handle is not None: - self._handle.after_fork_dead() - self._handle = None + self._handle = None def __repr__(self): assert self._initialized, "Thread.__init__() was not called" diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 5cceb84658deb78..df02b023012fbde 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -44,6 +44,7 @@ get_thread_state(PyObject *module) typedef struct { PyObject_HEAD + struct llist_node node; // linked list node (see _pythread_runtime_state) PyThread_ident_t ident; PyThread_handle_t handle; char joinable; @@ -59,6 +60,11 @@ new_thread_handle(thread_module_state* state) self->ident = 0; self->handle = 0; self->joinable = 0; + + HEAD_LOCK(&_PyRuntime); + llist_insert_tail(&_PyRuntime.threads.handles, &self->node); + HEAD_UNLOCK(&_PyRuntime); + return self; } @@ -66,6 +72,14 @@ static void ThreadHandle_dealloc(ThreadHandleObject *self) { PyObject *tp = (PyObject *) Py_TYPE(self); + + // Remove ourself from the global list of handles + HEAD_LOCK(&_PyRuntime); + if (self->node.next != NULL) { + llist_remove(&self->node); + } + HEAD_UNLOCK(&_PyRuntime); + if (self->joinable) { int ret = PyThread_detach_thread(self->handle); if (ret) { @@ -77,6 +91,28 @@ ThreadHandle_dealloc(ThreadHandleObject *self) Py_DECREF(tp); } +void +_PyThread_AfterFork(struct _pythread_runtime_state *state) +{ + // gh-115035: We mark ThreadHandles as not joinable early in the child's + // after-fork handler. We do this before calling any Python code to ensure + // that it happens before any ThreadHandles are deallocated, such as by a + // GC cycle. + PyThread_ident_t current = PyThread_get_thread_ident_ex(); + + struct llist_node *node; + llist_for_each_safe(node, &state->handles) { + ThreadHandleObject *hobj = llist_data(node, ThreadHandleObject, node); + if (hobj->ident == current) { + continue; + } + + // Disallow calls to detach() and join() as they could crash. + hobj->joinable = 0; + llist_remove(node); + } +} + static PyObject * ThreadHandle_repr(ThreadHandleObject *self) { @@ -91,21 +127,6 @@ ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored) } -static PyObject * -ThreadHandle_after_fork_alive(ThreadHandleObject *self, void* ignored) -{ - PyThread_update_thread_after_fork(&self->ident, &self->handle); - Py_RETURN_NONE; -} - -static PyObject * -ThreadHandle_after_fork_dead(ThreadHandleObject *self, void* ignored) -{ - // Disallow calls to detach() and join() as they could crash. - self->joinable = 0; - Py_RETURN_NONE; -} - static PyObject * ThreadHandle_detach(ThreadHandleObject *self, void* ignored) { @@ -157,8 +178,6 @@ static PyGetSetDef ThreadHandle_getsetlist[] = { static PyMethodDef ThreadHandle_methods[] = { - {"after_fork_alive", (PyCFunction)ThreadHandle_after_fork_alive, METH_NOARGS}, - {"after_fork_dead", (PyCFunction)ThreadHandle_after_fork_dead, METH_NOARGS}, {"detach", (PyCFunction)ThreadHandle_detach, METH_NOARGS}, {"join", (PyCFunction)ThreadHandle_join, METH_NOARGS}, {0, 0} diff --git a/Python/pystate.c b/Python/pystate.c index 7836c172bbfb618..e77e5bfa7e2df86 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -517,6 +517,8 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime) return _PyStatus_NO_MEMORY(); } + _PyThread_AfterFork(&runtime->threads); + return _PyStatus_OK(); } #endif diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 044e9fa111e9797..ad467e0e7840e7b 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -242,10 +242,6 @@ PyThread_detach_thread(PyThread_handle_t handle) { return (CloseHandle(hThread) == 0); } -void -PyThread_update_thread_after_fork(PyThread_ident_t* ident, PyThread_handle_t* handle) { -} - /* * Return the thread Id instead of a handle. The Id is said to uniquely identify the * thread in the system diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index fb3b79fc160502f..556e3de0b071f81 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -339,16 +339,6 @@ PyThread_detach_thread(PyThread_handle_t th) { return pthread_detach((pthread_t) th); } -void -PyThread_update_thread_after_fork(PyThread_ident_t* ident, PyThread_handle_t* handle) { - // The thread id might have been updated in the forked child - pthread_t th = pthread_self(); - *ident = (PyThread_ident_t) th; - *handle = (PyThread_handle_t) th; - assert(th == (pthread_t) *ident); - assert(th == (pthread_t) *handle); -} - /* XXX This implementation is considered (to quote Tim Peters) "inherently hosed" because: - It does not guarantee the promise that a non-zero integer is returned. From 92abb0124037e5bc938fa870461a26f64c56095b Mon Sep 17 00:00:00 2001 From: Dino Viehland <dinoviehland@meta.com> Date: Tue, 6 Feb 2024 14:03:43 -0800 Subject: [PATCH 231/263] gh-112075: Add critical sections for most dict APIs (#114508) Starts adding thread safety to dict objects. Use @critical_section for APIs which are exposed via argument clinic and don't directly correlate with a public C API which needs to acquire the lock Use a _lock_held suffix for keeping changes to complicated functions simple and just wrapping them with a critical section Acquire and release the lock in an existing function where it won't be overly disruptive to the existing logic --- Include/internal/pycore_critical_section.h | 46 ++ Modules/_sre/sre.c | 27 +- Objects/clinic/dictobject.c.h | 30 +- Objects/dictobject.c | 866 +++++++++++++++------ Objects/odictobject.c | 19 +- Objects/setobject.c | 78 +- 6 files changed, 782 insertions(+), 284 deletions(-) diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index bf2bbfffc38bd0f..38ed8cd69804ba9 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -104,12 +104,37 @@ extern "C" { # define Py_END_CRITICAL_SECTION2() \ _PyCriticalSection2_End(&_cs2); \ } + +// Asserts that the mutex is locked. The mutex must be held by the +// top-most critical section otherwise there's the possibility +// that the mutex would be swalled out in some code paths. +#define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) \ + _PyCriticalSection_AssertHeld(mutex) + +// Asserts that the mutex for the given object is locked. The mutex must +// be held by the top-most critical section otherwise there's the +// possibility that the mutex would be swalled out in some code paths. +#ifdef Py_DEBUG + +#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \ + if (Py_REFCNT(op) != 1) { \ + _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \ + } + +#else /* Py_DEBUG */ + +#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) + +#endif /* Py_DEBUG */ + #else /* !Py_GIL_DISABLED */ // The critical section APIs are no-ops with the GIL. # define Py_BEGIN_CRITICAL_SECTION(op) # define Py_END_CRITICAL_SECTION() # define Py_BEGIN_CRITICAL_SECTION2(a, b) # define Py_END_CRITICAL_SECTION2() +# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) +# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) #endif /* !Py_GIL_DISABLED */ typedef struct { @@ -236,6 +261,27 @@ _PyCriticalSection2_End(_PyCriticalSection2 *c) PyAPI_FUNC(void) _PyCriticalSection_SuspendAll(PyThreadState *tstate); +#ifdef Py_GIL_DISABLED + +static inline void +_PyCriticalSection_AssertHeld(PyMutex *mutex) { +#ifdef Py_DEBUG + PyThreadState *tstate = _PyThreadState_GET(); + uintptr_t prev = tstate->critical_section; + if (prev & _Py_CRITICAL_SECTION_TWO_MUTEXES) { + _PyCriticalSection2 *cs = (_PyCriticalSection2 *)(prev & ~_Py_CRITICAL_SECTION_MASK); + assert(cs != NULL && (cs->base.mutex == mutex || cs->mutex2 == mutex)); + } + else { + _PyCriticalSection *cs = (_PyCriticalSection *)(tstate->critical_section & ~_Py_CRITICAL_SECTION_MASK); + assert(cs != NULL && cs->mutex == mutex); + } + +#endif +} + +#endif + #ifdef __cplusplus } #endif diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c index d451974b9cf81e6..00fbd9674b8cdda 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -39,13 +39,14 @@ static const char copyright[] = " SRE 2.2.2 Copyright (c) 1997-2002 by Secret Labs AB "; #include "Python.h" -#include "pycore_dict.h" // _PyDict_Next() -#include "pycore_long.h" // _PyLong_GetZero() -#include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION +#include "pycore_dict.h" // _PyDict_Next() +#include "pycore_long.h" // _PyLong_GetZero() +#include "pycore_moduleobject.h" // _PyModule_GetState() -#include "sre.h" // SRE_CODE +#include "sre.h" // SRE_CODE -#include <ctype.h> // tolower(), toupper(), isalnum() +#include <ctype.h> // tolower(), toupper(), isalnum() #define SRE_CODE_BITS (8 * sizeof(SRE_CODE)) @@ -2349,26 +2350,28 @@ _sre_SRE_Match_groupdict_impl(MatchObject *self, PyObject *default_value) if (!result || !self->pattern->groupindex) return result; + Py_BEGIN_CRITICAL_SECTION(self->pattern->groupindex); while (_PyDict_Next(self->pattern->groupindex, &pos, &key, &value, &hash)) { int status; Py_INCREF(key); value = match_getslice(self, key, default_value); if (!value) { Py_DECREF(key); - goto failed; + Py_CLEAR(result); + goto exit; } status = _PyDict_SetItem_KnownHash(result, key, value, hash); Py_DECREF(value); Py_DECREF(key); - if (status < 0) - goto failed; + if (status < 0) { + Py_CLEAR(result); + goto exit; + } } +exit: + Py_END_CRITICAL_SECTION(); return result; - -failed: - Py_DECREF(result); - return NULL; } /*[clinic input] diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index 8f532f454156dec..daaef211b1db494 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -2,6 +2,7 @@ preserve [clinic start generated code]*/ +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(dict_fromkeys__doc__, @@ -65,6 +66,21 @@ PyDoc_STRVAR(dict___contains____doc__, #define DICT___CONTAINS___METHODDEF \ {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, +static PyObject * +dict___contains___impl(PyDictObject *self, PyObject *key); + +static PyObject * +dict___contains__(PyDictObject *self, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = dict___contains___impl(self, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(dict_get__doc__, "get($self, key, default=None, /)\n" "--\n" @@ -93,7 +109,9 @@ dict_get(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = dict_get_impl(self, key, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -130,7 +148,9 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) } default_value = args[1]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = dict_setdefault_impl(self, key, default_value); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -209,7 +229,13 @@ dict_popitem_impl(PyDictObject *self); static PyObject * dict_popitem(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { - return dict_popitem_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = dict_popitem_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(dict___sizeof____doc__, @@ -301,4 +327,4 @@ dict_values(PyDictObject *self, PyObject *Py_UNUSED(ignored)) { return dict_values_impl(self); } -/*[clinic end generated code: output=f3ac47dfbf341b23 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c8fda06bac5b05f3 input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 11b388d9f4adb05..2df95e977a180fa 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -113,18 +113,19 @@ As a consequence of this, split keys have a maximum size of 16. #define PyDict_MINSIZE 8 #include "Python.h" -#include "pycore_bitutils.h" // _Py_bit_length -#include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_code.h" // stats -#include "pycore_dict.h" // export _PyDict_SizeOf() -#include "pycore_freelist.h" // _PyFreeListState_GET() -#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() -#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() -#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() -#include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_setobject.h" // _PySet_NextEntry() -#include "stringlib/eq.h" // unicode_eq() +#include "pycore_bitutils.h" // _Py_bit_length +#include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_code.h" // stats +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION +#include "pycore_dict.h" // export _PyDict_SizeOf() +#include "pycore_freelist.h" // _PyFreeListState_GET() +#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED() +#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() +#include "pycore_pyerrors.h" // _PyErr_GetRaisedException() +#include "pycore_pystate.h" // _PyThreadState_GET() +#include "pycore_setobject.h" // _PySet_NextEntry() +#include "stringlib/eq.h" // unicode_eq() #include <stdbool.h> @@ -141,6 +142,21 @@ To avoid slowing down lookups on a near-full table, we resize the table when it's USABLE_FRACTION (currently two-thirds) full. */ +#ifdef Py_GIL_DISABLED + +static inline void +ASSERT_DICT_LOCKED(PyObject *op) +{ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op); +} +#define ASSERT_DICT_LOCKED(op) ASSERT_DICT_LOCKED(_Py_CAST(PyObject*, op)) + +#else + +#define ASSERT_DICT_LOCKED(op) + +#endif + #define PERTURB_SHIFT 5 /* @@ -240,6 +256,16 @@ static int dictresize(PyInterpreterState *interp, PyDictObject *mp, static PyObject* dict_iter(PyObject *dict); +static int +contains_lock_held(PyDictObject *mp, PyObject *key); +static int +contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash); +static int +setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value); +static int +dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result, int incref_result); + #include "clinic/dictobject.c.h" @@ -789,6 +815,8 @@ clone_combined_dict_keys(PyDictObject *orig) assert(orig->ma_keys != Py_EMPTY_KEYS); assert(orig->ma_keys->dk_refcnt == 1); + ASSERT_DICT_LOCKED(orig); + size_t keys_size = _PyDict_KeysSize(orig->ma_keys); PyDictKeysObject *keys = PyMem_Malloc(keys_size); if (keys == NULL) { @@ -1230,6 +1258,8 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp, { PyObject *old_value; + ASSERT_DICT_LOCKED(mp); + if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) { if (insertion_resize(interp, mp, 0) < 0) goto Fail; @@ -1326,6 +1356,7 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { assert(mp->ma_keys == Py_EMPTY_KEYS); + ASSERT_DICT_LOCKED(mp); uint64_t new_version = _PyDict_NotifyEvent( interp, PyDict_EVENT_ADDED, mp, key, value); @@ -1419,6 +1450,8 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, PyDictKeysObject *oldkeys; PyDictValues *oldvalues; + ASSERT_DICT_LOCKED(mp); + if (log2_newsize >= SIZEOF_SIZE_T*8) { PyErr_NoMemory(); return -1; @@ -1613,7 +1646,7 @@ _PyDict_FromItems(PyObject *const *keys, Py_ssize_t keys_offset, for (Py_ssize_t i = 0; i < length; i++) { PyObject *key = *ks; PyObject *value = *vs; - if (PyDict_SetItem(dict, key, value) < 0) { + if (setitem_lock_held((PyDictObject *)dict, key, value) < 0) { Py_DECREF(dict); return NULL; } @@ -1688,6 +1721,7 @@ PyDict_GetItem(PyObject *op, PyObject *key) Py_ssize_t _PyDict_LookupIndex(PyDictObject *mp, PyObject *key) { + // TODO: Thread safety PyObject *value; assert(PyDict_CheckExact((PyObject*)mp)); assert(PyUnicode_CheckExact(key)); @@ -1864,9 +1898,11 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key) } /* Consumes references to key and value */ -int -_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) +static int +setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) { + ASSERT_DICT_LOCKED(mp); + assert(key); assert(value); assert(PyDict_Check(mp)); @@ -1879,7 +1915,9 @@ _PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) return -1; } } + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (mp->ma_keys == Py_EMPTY_KEYS) { return insert_to_emptydict(interp, mp, key, hash, value); } @@ -1887,6 +1925,16 @@ _PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) return insertdict(interp, mp, key, hash, value); } +int +_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(mp); + res = setitem_take2_lock_held(mp, key, value); + Py_END_CRITICAL_SECTION(); + return res; +} + /* CAUTION: PyDict_SetItem() must guarantee that it won't resize the * dictionary if it's merely replacing the value for an existing key. * This means that it's safe to loop over a dictionary with PyDict_Next() @@ -1906,6 +1954,16 @@ PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) Py_NewRef(key), Py_NewRef(value)); } +static int +setitem_lock_held(PyDictObject *mp, PyObject *key, PyObject *value) +{ + assert(key); + assert(value); + return setitem_take2_lock_held(mp, + Py_NewRef(key), Py_NewRef(value)); +} + + int _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, Py_hash_t hash) @@ -1921,12 +1979,21 @@ _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value, assert(hash != -1); mp = (PyDictObject *)op; + int res; PyInterpreterState *interp = _PyInterpreterState_GET(); + + Py_BEGIN_CRITICAL_SECTION(mp); + if (mp->ma_keys == Py_EMPTY_KEYS) { - return insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + res = insert_to_emptydict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); } - /* insertdict() handles any resizing that might be necessary */ - return insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + else { + /* insertdict() handles any resizing that might be necessary */ + res = insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value)); + } + + Py_END_CRITICAL_SECTION(); + return res; } static void @@ -1951,6 +2018,8 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, { PyObject *old_key; + ASSERT_DICT_LOCKED(mp); + Py_ssize_t hashpos = lookdict_index(mp->ma_keys, hash, ix); assert(hashpos >= 0); @@ -2002,8 +2071,8 @@ PyDict_DelItem(PyObject *op, PyObject *key) return _PyDict_DelItem_KnownHash(op, key, hash); } -int -_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) +static int +delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash) { Py_ssize_t ix; PyDictObject *mp; @@ -2013,6 +2082,9 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) PyErr_BadInternalCall(); return -1; } + + ASSERT_DICT_LOCKED(op); + assert(key); assert(hash != -1); mp = (PyDictObject *)op; @@ -2030,13 +2102,19 @@ _PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) return delitem_common(mp, hash, ix, old_value, new_version); } -/* This function promises that the predicate -> deletion sequence is atomic - * (i.e. protected by the GIL), assuming the predicate itself doesn't - * release the GIL. - */ int -_PyDict_DelItemIf(PyObject *op, PyObject *key, - int (*predicate)(PyObject *value)) +_PyDict_DelItem_KnownHash(PyObject *op, PyObject *key, Py_hash_t hash) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = delitem_knownhash_lock_held(op, key, hash); + Py_END_CRITICAL_SECTION(); + return res; +} + +static int +delitemif_lock_held(PyObject *op, PyObject *key, + int (*predicate)(PyObject *value)) { Py_ssize_t hashpos, ix; PyDictObject *mp; @@ -2044,6 +2122,8 @@ _PyDict_DelItemIf(PyObject *op, PyObject *key, PyObject *old_value; int res; + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) { PyErr_BadInternalCall(); return -1; @@ -2077,16 +2157,32 @@ _PyDict_DelItemIf(PyObject *op, PyObject *key, return 0; } } +/* This function promises that the predicate -> deletion sequence is atomic + * (i.e. protected by the GIL or the per-dict mutex in free threaded builds), + * assuming the predicate itself doesn't release the GIL (or cause re-entrancy + * which would release the per-dict mutex) + */ +int +_PyDict_DelItemIf(PyObject *op, PyObject *key, + int (*predicate)(PyObject *value)) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = delitemif_lock_held(op, key, predicate); + Py_END_CRITICAL_SECTION(); + return res; +} - -void -PyDict_Clear(PyObject *op) +static void +clear_lock_held(PyObject *op) { PyDictObject *mp; PyDictKeysObject *oldkeys; PyDictValues *oldvalues; Py_ssize_t i, n; + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) return; mp = ((PyDictObject *)op); @@ -2119,6 +2215,14 @@ PyDict_Clear(PyObject *op) ASSERT_CONSISTENT(mp); } +void +PyDict_Clear(PyObject *op) +{ + Py_BEGIN_CRITICAL_SECTION(op); + clear_lock_held(op); + Py_END_CRITICAL_SECTION(); +} + /* Internal version of PyDict_Next that returns a hash value in addition * to the key and value. * Return 1 on success, return 0 when the reached the end of the dictionary @@ -2135,6 +2239,9 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, if (!PyDict_Check(op)) return 0; + + ASSERT_DICT_LOCKED(op); + mp = (PyDictObject *)op; i = *ppos; if (mp->ma_values) { @@ -2208,7 +2315,11 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, int PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue) { - return _PyDict_Next(op, ppos, pkey, pvalue, NULL); + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = _PyDict_Next(op, ppos, pkey, pvalue, NULL); + Py_END_CRITICAL_SECTION(); + return res; } @@ -2219,6 +2330,8 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, { assert(PyDict_Check(mp)); + ASSERT_DICT_LOCKED(mp); + if (mp->ma_used == 0) { if (result) { *result = NULL; @@ -2258,10 +2371,11 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, return 1; } - -int -PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) +static int +pop_lock_held(PyObject *op, PyObject *key, PyObject **result) { + ASSERT_DICT_LOCKED(op); + if (!PyDict_Check(op)) { if (result) { *result = NULL; @@ -2291,6 +2405,17 @@ PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) return _PyDict_Pop_KnownHash(dict, key, hash, result); } +int +PyDict_Pop(PyObject *op, PyObject *key, PyObject **result) +{ + int err; + Py_BEGIN_CRITICAL_SECTION(op); + err = pop_lock_held(op, key, result); + Py_END_CRITICAL_SECTION(); + + return err; +} + int PyDict_PopString(PyObject *op, const char *key, PyObject **result) @@ -2323,6 +2448,55 @@ _PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value) return result; } +static PyDictObject * +dict_dict_fromkeys(PyInterpreterState *interp, PyDictObject *mp, + PyObject *iterable, PyObject *value) +{ + PyObject *oldvalue; + Py_ssize_t pos = 0; + PyObject *key; + Py_hash_t hash; + int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); + uint8_t new_size = Py_MAX( + estimate_log2_keysize(PyDict_GET_SIZE(iterable)), + DK_LOG_SIZE(mp->ma_keys)); + if (dictresize(interp, mp, new_size, unicode)) { + Py_DECREF(mp); + return NULL; + } + + while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { + if (insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value))) { + Py_DECREF(mp); + return NULL; + } + } + return mp; +} + +static PyDictObject * +dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp, + PyObject *iterable, PyObject *value) +{ + Py_ssize_t pos = 0; + PyObject *key; + Py_hash_t hash; + + if (dictresize(interp, mp, + estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) { + Py_DECREF(mp); + return NULL; + } + + while (_PySet_NextEntry(iterable, &pos, &key, &hash)) { + if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) { + Py_DECREF(mp); + return NULL; + } + } + return mp; +} /* Internal version of dict.from_keys(). It is subclass-friendly. */ PyObject * @@ -2338,49 +2512,22 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) if (d == NULL) return NULL; - if (PyDict_CheckExact(d) && ((PyDictObject *)d)->ma_used == 0) { + + if (PyDict_CheckExact(d)) { if (PyDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; - PyObject *oldvalue; - Py_ssize_t pos = 0; - PyObject *key; - Py_hash_t hash; - - int unicode = DK_IS_UNICODE(((PyDictObject*)iterable)->ma_keys); - if (dictresize(interp, mp, - estimate_log2_keysize(PyDict_GET_SIZE(iterable)), - unicode)) { - Py_DECREF(d); - return NULL; - } - while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { - if (insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value))) { - Py_DECREF(d); - return NULL; - } - } + Py_BEGIN_CRITICAL_SECTION2(d, iterable); + d = (PyObject *)dict_dict_fromkeys(interp, mp, iterable, value); + Py_END_CRITICAL_SECTION2(); return d; } - if (PyAnySet_CheckExact(iterable)) { + else if (PyAnySet_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; - Py_ssize_t pos = 0; - PyObject *key; - Py_hash_t hash; - - if (dictresize(interp, mp, - estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) { - Py_DECREF(d); - return NULL; - } - while (_PySet_NextEntry(iterable, &pos, &key, &hash)) { - if (insertdict(interp, mp, Py_NewRef(key), hash, Py_NewRef(value))) { - Py_DECREF(d); - return NULL; - } - } + Py_BEGIN_CRITICAL_SECTION2(d, iterable); + d = (PyObject *)dict_set_fromkeys(interp, mp, iterable, value); + Py_END_CRITICAL_SECTION2(); return d; } } @@ -2392,12 +2539,17 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) } if (PyDict_CheckExact(d)) { + Py_BEGIN_CRITICAL_SECTION(d); while ((key = PyIter_Next(it)) != NULL) { - status = PyDict_SetItem(d, key, value); + status = setitem_lock_held((PyDictObject *)d, key, value); Py_DECREF(key); - if (status < 0) - goto Fail; + if (status < 0) { + assert(PyErr_Occurred()); + goto dict_iter_exit; + } } +dict_iter_exit: + Py_END_CRITICAL_SECTION(); } else { while ((key = PyIter_Next(it)) != NULL) { status = PyObject_SetItem(d, key, value); @@ -2468,7 +2620,7 @@ dict_dealloc(PyObject *self) static PyObject * -dict_repr(PyObject *self) +dict_repr_lock_held(PyObject *self) { PyDictObject *mp = (PyDictObject *)self; Py_ssize_t i; @@ -2498,7 +2650,7 @@ dict_repr(PyObject *self) Note that repr may mutate the dict. */ i = 0; first = 1; - while (PyDict_Next((PyObject *)mp, &i, &key, &value)) { + while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) { PyObject *s; int res; @@ -2551,15 +2703,25 @@ dict_repr(PyObject *self) return NULL; } +static PyObject * +dict_repr(PyObject *self) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(self); + res = dict_repr_lock_held(self); + Py_END_CRITICAL_SECTION(); + return res; +} + static Py_ssize_t dict_length(PyObject *self) { PyDictObject *mp = (PyDictObject *)self; - return mp->ma_used; + return _Py_atomic_load_ssize_relaxed(&mp->ma_used); } static PyObject * -dict_subscript(PyObject *self, PyObject *key) +dict_subscript_lock_held(PyObject *self, PyObject *key) { PyDictObject *mp = (PyDictObject *)self; Py_ssize_t ix; @@ -2594,6 +2756,16 @@ dict_subscript(PyObject *self, PyObject *key) return Py_NewRef(value); } +static PyObject * +dict_subscript(PyObject *self, PyObject *key) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(self); + res = dict_subscript_lock_held(self, key); + Py_END_CRITICAL_SECTION(); + return res; +} + static int dict_ass_sub(PyObject *mp, PyObject *v, PyObject *w) { @@ -2609,9 +2781,11 @@ static PyMappingMethods dict_as_mapping = { dict_ass_sub, /*mp_ass_subscript*/ }; -PyObject * -PyDict_Keys(PyObject *dict) +static PyObject * +keys_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2646,8 +2820,21 @@ PyDict_Keys(PyObject *dict) } PyObject * -PyDict_Values(PyObject *dict) +PyDict_Keys(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = keys_lock_held(dict); + Py_END_CRITICAL_SECTION(); + + return res; +} + +static PyObject * +values_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2682,8 +2869,20 @@ PyDict_Values(PyObject *dict) } PyObject * -PyDict_Items(PyObject *dict) +PyDict_Values(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = values_lock_held(dict); + Py_END_CRITICAL_SECTION(); + return res; +} + +static PyObject * +items_lock_held(PyObject *dict) { + ASSERT_DICT_LOCKED(dict); + if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; @@ -2732,6 +2931,17 @@ PyDict_Items(PyObject *dict) return v; } +PyObject * +PyDict_Items(PyObject *dict) +{ + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(dict); + res = items_lock_held(dict); + Py_END_CRITICAL_SECTION(); + + return res; +} + /*[clinic input] @classmethod dict.fromkeys @@ -2810,8 +3020,8 @@ dict_update(PyObject *self, PyObject *args, PyObject *kwds) producing iterable objects of length 2. */ -int -PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +static int +merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override) { PyObject *it; /* iter(seq2) */ Py_ssize_t i; /* index into seq2 of current element */ @@ -2863,14 +3073,14 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) Py_INCREF(key); Py_INCREF(value); if (override) { - if (PyDict_SetItem(d, key, value) < 0) { + if (setitem_lock_held((PyDictObject *)d, key, value) < 0) { Py_DECREF(key); Py_DECREF(value); goto Fail; } } else { - if (PyDict_SetDefault(d, key, value) == NULL) { + if (dict_setdefault_ref_lock_held(d, key, value, NULL, 0) < 0) { Py_DECREF(key); Py_DECREF(value); goto Fail; @@ -2895,6 +3105,117 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) return Py_SAFE_DOWNCAST(i, Py_ssize_t, int); } +int +PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(d); + res = merge_from_seq2_lock_held(d, seq2, override); + Py_END_CRITICAL_SECTION(); + + return res; +} + +static int +dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *other, int override) +{ + if (other == mp || other->ma_used == 0) + /* a.update(a) or a.update({}); nothing to do */ + return 0; + if (mp->ma_used == 0) { + /* Since the target dict is empty, PyDict_GetItem() + * always returns NULL. Setting override to 1 + * skips the unnecessary test. + */ + override = 1; + PyDictKeysObject *okeys = other->ma_keys; + + // If other is clean, combined, and just allocated, just clone it. + if (other->ma_values == NULL && + other->ma_used == okeys->dk_nentries && + (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE || + USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) { + uint64_t new_version = _PyDict_NotifyEvent( + interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL); + PyDictKeysObject *keys = clone_combined_dict_keys(other); + if (keys == NULL) + return -1; + + dictkeys_decref(interp, mp->ma_keys); + mp->ma_keys = keys; + if (mp->ma_values != NULL) { + free_values(mp->ma_values); + mp->ma_values = NULL; + } + + mp->ma_used = other->ma_used; + mp->ma_version_tag = new_version; + ASSERT_CONSISTENT(mp); + + if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) { + /* Maintain tracking. */ + _PyObject_GC_TRACK(mp); + } + + return 0; + } + } + /* Do one big resize at the start, rather than + * incrementally resizing as we insert new items. Expect + * that there will be no (or few) overlapping keys. + */ + if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) { + int unicode = DK_IS_UNICODE(other->ma_keys); + if (dictresize(interp, mp, + estimate_log2_keysize(mp->ma_used + other->ma_used), + unicode)) { + return -1; + } + } + + Py_ssize_t orig_size = other->ma_keys->dk_nentries; + Py_ssize_t pos = 0; + Py_hash_t hash; + PyObject *key, *value; + + while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { + int err = 0; + Py_INCREF(key); + Py_INCREF(value); + if (override == 1) { + err = insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value)); + } + else { + err = contains_known_hash_lock_held(mp, key, hash); + if (err == 0) { + err = insertdict(interp, mp, + Py_NewRef(key), hash, Py_NewRef(value)); + } + else if (err > 0) { + if (override != 0) { + _PyErr_SetKeyError(key); + Py_DECREF(value); + Py_DECREF(key); + return -1; + } + err = 0; + } + } + Py_DECREF(value); + Py_DECREF(key); + if (err != 0) + return -1; + + if (orig_size != other->ma_keys->dk_nentries) { + PyErr_SetString(PyExc_RuntimeError, + "dict mutated during update"); + return -1; + } + } + return 0; +} + static int dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) { @@ -2912,127 +3233,44 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) return -1; } mp = (PyDictObject*)a; + int res = 0; if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) { other = (PyDictObject*)b; - if (other == mp || other->ma_used == 0) - /* a.update(a) or a.update({}); nothing to do */ - return 0; - if (mp->ma_used == 0) { - /* Since the target dict is empty, PyDict_GetItem() - * always returns NULL. Setting override to 1 - * skips the unnecessary test. - */ - override = 1; - PyDictKeysObject *okeys = other->ma_keys; - - // If other is clean, combined, and just allocated, just clone it. - if (other->ma_values == NULL && - other->ma_used == okeys->dk_nentries && - (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE || - USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)) { - uint64_t new_version = _PyDict_NotifyEvent( - interp, PyDict_EVENT_CLONED, mp, b, NULL); - PyDictKeysObject *keys = clone_combined_dict_keys(other); - if (keys == NULL) { - return -1; - } - - dictkeys_decref(interp, mp->ma_keys); - mp->ma_keys = keys; - if (mp->ma_values != NULL) { - free_values(mp->ma_values); - mp->ma_values = NULL; - } - - mp->ma_used = other->ma_used; - mp->ma_version_tag = new_version; - ASSERT_CONSISTENT(mp); - - if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) { - /* Maintain tracking. */ - _PyObject_GC_TRACK(mp); - } - - return 0; - } - } - /* Do one big resize at the start, rather than - * incrementally resizing as we insert new items. Expect - * that there will be no (or few) overlapping keys. - */ - if (USABLE_FRACTION(DK_SIZE(mp->ma_keys)) < other->ma_used) { - int unicode = DK_IS_UNICODE(other->ma_keys); - if (dictresize(interp, mp, - estimate_log2_keysize(mp->ma_used + other->ma_used), - unicode)) { - return -1; - } - } - - Py_ssize_t orig_size = other->ma_keys->dk_nentries; - Py_ssize_t pos = 0; - Py_hash_t hash; - PyObject *key, *value; - - while (_PyDict_Next((PyObject*)other, &pos, &key, &value, &hash)) { - int err = 0; - Py_INCREF(key); - Py_INCREF(value); - if (override == 1) { - err = insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value)); - } - else { - err = _PyDict_Contains_KnownHash(a, key, hash); - if (err == 0) { - err = insertdict(interp, mp, - Py_NewRef(key), hash, Py_NewRef(value)); - } - else if (err > 0) { - if (override != 0) { - _PyErr_SetKeyError(key); - Py_DECREF(value); - Py_DECREF(key); - return -1; - } - err = 0; - } - } - Py_DECREF(value); - Py_DECREF(key); - if (err != 0) - return -1; - - if (orig_size != other->ma_keys->dk_nentries) { - PyErr_SetString(PyExc_RuntimeError, - "dict mutated during update"); - return -1; - } - } + int res; + Py_BEGIN_CRITICAL_SECTION2(a, b); + res = dict_dict_merge(interp, (PyDictObject *)a, other, override); + ASSERT_CONSISTENT(a); + Py_END_CRITICAL_SECTION2(); + return res; } else { /* Do it the generic, slower way */ + Py_BEGIN_CRITICAL_SECTION(a); PyObject *keys = PyMapping_Keys(b); PyObject *iter; PyObject *key, *value; int status; - if (keys == NULL) + if (keys == NULL) { /* Docstring says this is equivalent to E.keys() so * if E doesn't have a .keys() method we want * AttributeError to percolate up. Might as well * do the same for any other error. */ - return -1; + res = -1; + goto slow_exit; + } iter = PyObject_GetIter(keys); Py_DECREF(keys); - if (iter == NULL) - return -1; + if (iter == NULL) { + res = -1; + goto slow_exit; + } for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) { if (override != 1) { - status = PyDict_Contains(a, key); + status = contains_lock_held(mp, key); if (status != 0) { if (status > 0) { if (override == 0) { @@ -3043,30 +3281,39 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) } Py_DECREF(key); Py_DECREF(iter); - return -1; + res = -1; + goto slow_exit; } } value = PyObject_GetItem(b, key); if (value == NULL) { Py_DECREF(iter); Py_DECREF(key); - return -1; + res = -1; + goto slow_exit; } - status = PyDict_SetItem(a, key, value); + status = setitem_lock_held(mp, key, value); Py_DECREF(key); Py_DECREF(value); if (status < 0) { Py_DECREF(iter); + res = -1; + goto slow_exit; return -1; } } Py_DECREF(iter); - if (PyErr_Occurred()) + if (PyErr_Occurred()) { /* Iterator completed, via error */ - return -1; + res = -1; + goto slow_exit; + } + +slow_exit: + ASSERT_CONSISTENT(a); + Py_END_CRITICAL_SECTION(); + return res; } - ASSERT_CONSISTENT(a); - return 0; } int @@ -3104,17 +3351,14 @@ dict_copy_impl(PyDictObject *self) return PyDict_Copy((PyObject *)self); } -PyObject * -PyDict_Copy(PyObject *o) +static PyObject * +copy_lock_held(PyObject *o) { PyObject *copy; PyDictObject *mp; PyInterpreterState *interp = _PyInterpreterState_GET(); - if (o == NULL || !PyDict_Check(o)) { - PyErr_BadInternalCall(); - return NULL; - } + ASSERT_DICT_LOCKED(o); mp = (PyDictObject *)o; if (mp->ma_used == 0) { @@ -3197,6 +3441,23 @@ PyDict_Copy(PyObject *o) return NULL; } +PyObject * +PyDict_Copy(PyObject *o) +{ + if (o == NULL || !PyDict_Check(o)) { + PyErr_BadInternalCall(); + return NULL; + } + + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(o); + + res = copy_lock_held(o); + + Py_END_CRITICAL_SECTION(); + return res; +} + Py_ssize_t PyDict_Size(PyObject *mp) { @@ -3212,10 +3473,13 @@ PyDict_Size(PyObject *mp) * Uses only Py_EQ comparison. */ static int -dict_equal(PyDictObject *a, PyDictObject *b) +dict_equal_lock_held(PyDictObject *a, PyDictObject *b) { Py_ssize_t i; + ASSERT_DICT_LOCKED(a); + ASSERT_DICT_LOCKED(b); + if (a->ma_used != b->ma_used) /* can't be equal if # of entries differ */ return 0; @@ -3270,6 +3534,17 @@ dict_equal(PyDictObject *a, PyDictObject *b) return 1; } +static int +dict_equal(PyDictObject *a, PyDictObject *b) +{ + int res; + Py_BEGIN_CRITICAL_SECTION2(a, b); + res = dict_equal_lock_held(a, b); + Py_END_CRITICAL_SECTION2(); + + return res; +} + static PyObject * dict_richcompare(PyObject *v, PyObject *w, int op) { @@ -3293,6 +3568,7 @@ dict_richcompare(PyObject *v, PyObject *w, int op) /*[clinic input] @coexist +@critical_section dict.__contains__ key: object @@ -3302,8 +3578,8 @@ True if the dictionary has the specified key, else False. [clinic start generated code]*/ static PyObject * -dict___contains__(PyDictObject *self, PyObject *key) -/*[clinic end generated code: output=a3d03db709ed6e6b input=fe1cb42ad831e820]*/ +dict___contains___impl(PyDictObject *self, PyObject *key) +/*[clinic end generated code: output=1b314e6da7687dae input=bc76ec9c157cb81b]*/ { register PyDictObject *mp = self; Py_hash_t hash; @@ -3324,6 +3600,7 @@ dict___contains__(PyDictObject *self, PyObject *key) } /*[clinic input] +@critical_section dict.get key: object @@ -3335,7 +3612,7 @@ Return the value for key if key is in the dictionary, else default. static PyObject * dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=bba707729dee05bf input=279ddb5790b6b107]*/ +/*[clinic end generated code: output=bba707729dee05bf input=a631d3f18f584c60]*/ { PyObject *val = NULL; Py_hash_t hash; @@ -3356,7 +3633,7 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) } static int -dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, +dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result, int incref_result) { PyDictObject *mp = (PyDictObject *)d; @@ -3364,6 +3641,8 @@ dict_setdefault_ref(PyObject *d, PyObject *key, PyObject *default_value, Py_hash_t hash; PyInterpreterState *interp = _PyInterpreterState_GET(); + ASSERT_DICT_LOCKED(d); + if (!PyDict_Check(d)) { PyErr_BadInternalCall(); if (result) { @@ -3490,18 +3769,25 @@ int PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result) { - return dict_setdefault_ref(d, key, default_value, result, 1); + int res; + Py_BEGIN_CRITICAL_SECTION(d); + res = dict_setdefault_ref_lock_held(d, key, default_value, result, 1); + Py_END_CRITICAL_SECTION(); + return res; } PyObject * PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj) { PyObject *result; - dict_setdefault_ref(d, key, defaultobj, &result, 0); + Py_BEGIN_CRITICAL_SECTION(d); + dict_setdefault_ref_lock_held(d, key, defaultobj, &result, 0); + Py_END_CRITICAL_SECTION(); return result; } /*[clinic input] +@critical_section dict.setdefault key: object @@ -3516,10 +3802,10 @@ Return the value for key if key is in the dictionary, else default. static PyObject * dict_setdefault_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=f8c1101ebf69e220 input=0f063756e815fd9d]*/ +/*[clinic end generated code: output=f8c1101ebf69e220 input=9237af9a0a224302]*/ { PyObject *val; - PyDict_SetDefaultRef((PyObject *)self, key, default_value, &val); + dict_setdefault_ref_lock_held((PyObject *)self, key, default_value, &val, 1); return val; } @@ -3559,6 +3845,7 @@ dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value) } /*[clinic input] +@critical_section dict.popitem Remove and return a (key, value) pair as a 2-tuple. @@ -3569,7 +3856,7 @@ Raises KeyError if the dict is empty. static PyObject * dict_popitem_impl(PyDictObject *self) -/*[clinic end generated code: output=e65fcb04420d230d input=1c38a49f21f64941]*/ +/*[clinic end generated code: output=e65fcb04420d230d input=ef28b4da5f0f762e]*/ { Py_ssize_t i, j; PyObject *res; @@ -3695,8 +3982,8 @@ dict_tp_clear(PyObject *op) static PyObject *dictiter_new(PyDictObject *, PyTypeObject *); -Py_ssize_t -_PyDict_SizeOf(PyDictObject *mp) +static Py_ssize_t +sizeof_lock_held(PyDictObject *mp) { size_t res = _PyObject_SIZE(Py_TYPE(mp)); if (mp->ma_values) { @@ -3711,6 +3998,17 @@ _PyDict_SizeOf(PyDictObject *mp) return (Py_ssize_t)res; } +Py_ssize_t +_PyDict_SizeOf(PyDictObject *mp) +{ + Py_ssize_t res; + Py_BEGIN_CRITICAL_SECTION(mp); + res = sizeof_lock_held(mp); + Py_END_CRITICAL_SECTION(); + + return res; +} + size_t _PyDict_KeysSize(PyDictKeysObject *keys) { @@ -3794,15 +4092,29 @@ static PyMethodDef mapp_methods[] = { {NULL, NULL} /* sentinel */ }; -/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ -int -PyDict_Contains(PyObject *op, PyObject *key) +static int +contains_known_hash_lock_held(PyDictObject *mp, PyObject *key, Py_ssize_t hash) +{ + Py_ssize_t ix; + PyObject *value; + + ASSERT_DICT_LOCKED(mp); + + ix = _Py_dict_lookup(mp, key, hash, &value); + if (ix == DKIX_ERROR) + return -1; + return (ix != DKIX_EMPTY && value != NULL); +} + +static int +contains_lock_held(PyDictObject *mp, PyObject *key) { Py_hash_t hash; Py_ssize_t ix; - PyDictObject *mp = (PyDictObject *)op; PyObject *value; + ASSERT_DICT_LOCKED(mp); + if (!PyUnicode_CheckExact(key) || (hash = unicode_get_hash(key)) == -1) { hash = PyObject_Hash(key); if (hash == -1) @@ -3814,6 +4126,17 @@ PyDict_Contains(PyObject *op, PyObject *key) return (ix != DKIX_EMPTY && value != NULL); } +/* Return 1 if `key` is in dict `op`, 0 if not, and -1 on error. */ +int +PyDict_Contains(PyObject *op, PyObject *key) +{ + int res; + Py_BEGIN_CRITICAL_SECTION(op); + res = contains_lock_held((PyDictObject *)op, key); + Py_END_CRITICAL_SECTION(); + return res; +} + int PyDict_ContainsString(PyObject *op, const char *key) { @@ -4180,17 +4503,15 @@ static PyMethodDef dictiter_methods[] = { }; static PyObject* -dictiter_iternextkey(PyObject *self) +dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *key; Py_ssize_t i; PyDictKeysObject *k; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4248,6 +4569,23 @@ dictiter_iternextkey(PyObject *self) return NULL; } +static PyObject* +dictiter_iternextkey(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictiter_iternextkey_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictIterKey_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_keyiterator", /* tp_name */ @@ -4282,16 +4620,14 @@ PyTypeObject PyDictIterKey_Type = { }; static PyObject * -dictiter_iternextvalue(PyObject *self) +dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *value; Py_ssize_t i; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4348,6 +4684,23 @@ dictiter_iternextvalue(PyObject *self) return NULL; } +static PyObject * +dictiter_iternextvalue(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictiter_iternextvalue_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictIterValue_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_valueiterator", /* tp_name */ @@ -4382,15 +4735,12 @@ PyTypeObject PyDictIterValue_Type = { }; static PyObject * -dictiter_iternextitem(PyObject *self) +dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; PyObject *key, *value, *result; Py_ssize_t i; - PyDictObject *d = di->di_dict; - if (d == NULL) - return NULL; assert (PyDict_Check(d)); if (di->di_used != d->ma_used) { @@ -4473,6 +4823,22 @@ dictiter_iternextitem(PyObject *self) return NULL; } +static PyObject * +dictiter_iternextitem(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *item; + Py_BEGIN_CRITICAL_SECTION(d); + item = dictiter_iternextitem_lock_held(d, self); + Py_END_CRITICAL_SECTION(); + return item; +} + PyTypeObject PyDictIterItem_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_itemiterator", /* tp_name */ @@ -4510,15 +4876,12 @@ PyTypeObject PyDictIterItem_Type = { /* dictreviter */ static PyObject * -dictreviter_iternext(PyObject *self) +dictreviter_iter_PyDict_Next(PyDictObject *d, PyObject *self) { dictiterobject *di = (dictiterobject *)self; - PyDictObject *d = di->di_dict; - if (d == NULL) { - return NULL; - } assert (PyDict_Check(d)); + ASSERT_DICT_LOCKED(d); if (di->di_used != d->ma_used) { PyErr_SetString(PyExc_RuntimeError, @@ -4609,6 +4972,23 @@ dictreviter_iternext(PyObject *self) return NULL; } +static PyObject * +dictreviter_iternext(PyObject *self) +{ + dictiterobject *di = (dictiterobject *)self; + PyDictObject *d = di->di_dict; + + if (d == NULL) + return NULL; + + PyObject *value; + Py_BEGIN_CRITICAL_SECTION(d); + value = dictreviter_iter_PyDict_Next(d, self); + Py_END_CRITICAL_SECTION(); + + return value; +} + PyTypeObject PyDictRevIterKey_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_reversekeyiterator", @@ -5037,14 +5417,12 @@ dictviews_or(PyObject* self, PyObject *other) } static PyObject * -dictitems_xor(PyObject *self, PyObject *other) +dictitems_xor_lock_held(PyObject *d1, PyObject *d2) { - assert(PyDictItems_Check(self)); - assert(PyDictItems_Check(other)); - PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict; - PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict; + ASSERT_DICT_LOCKED(d1); + ASSERT_DICT_LOCKED(d2); - PyObject *temp_dict = PyDict_Copy(d1); + PyObject *temp_dict = copy_lock_held(d1); if (temp_dict == NULL) { return NULL; } @@ -5122,6 +5500,22 @@ dictitems_xor(PyObject *self, PyObject *other) return NULL; } +static PyObject * +dictitems_xor(PyObject *self, PyObject *other) +{ + assert(PyDictItems_Check(self)); + assert(PyDictItems_Check(other)); + PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict; + PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict; + + PyObject *res; + Py_BEGIN_CRITICAL_SECTION2(d1, d2); + res = dictitems_xor_lock_held(d1, d2); + Py_END_CRITICAL_SECTION2(); + + return res; +} + static PyObject* dictviews_xor(PyObject* self, PyObject *other) { diff --git a/Objects/odictobject.c b/Objects/odictobject.c index b5280c39e1be542..421bc52992d7354 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -465,12 +465,13 @@ Potential Optimizations */ #include "Python.h" -#include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_dict.h" // _Py_dict_lookup() -#include "pycore_object.h" // _PyObject_GC_UNTRACK() -#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() -#include <stddef.h> // offsetof() +#include "pycore_call.h" // _PyObject_CallNoArgs() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_critical_section.h" //_Py_BEGIN_CRITICAL_SECTION +#include "pycore_dict.h" // _Py_dict_lookup() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() +#include <stddef.h> // offsetof() #include "clinic/odictobject.c.h" @@ -1039,6 +1040,8 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, { PyObject *value = NULL; + Py_BEGIN_CRITICAL_SECTION(od); + _ODictNode *node = _odict_find_node_hash((PyODictObject *)od, key, hash); if (node != NULL) { /* Pop the node first to avoid a possible dict resize (due to @@ -1046,7 +1049,7 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, resolution. */ int res = _odict_clear_node((PyODictObject *)od, node, key, hash); if (res < 0) { - return NULL; + goto done; } /* Now delete the value from the dict. */ if (_PyDict_Pop_KnownHash((PyDictObject *)od, key, hash, @@ -1063,6 +1066,8 @@ _odict_popkey_hash(PyObject *od, PyObject *key, PyObject *failobj, PyErr_SetObject(PyExc_KeyError, key); } } + Py_END_CRITICAL_SECTION(); +done: return value; } diff --git a/Objects/setobject.c b/Objects/setobject.c index 93de8e84f2ddf99..3acf2a7a74890b1 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -32,13 +32,14 @@ */ #include "Python.h" -#include "pycore_ceval.h" // _PyEval_GetBuiltin() -#include "pycore_dict.h" // _PyDict_Contains_KnownHash() -#include "pycore_modsupport.h" // _PyArg_NoKwnames() -#include "pycore_object.h" // _PyObject_GC_UNTRACK() -#include "pycore_pyerrors.h" // _PyErr_SetKeyError() -#include "pycore_setobject.h" // _PySet_NextEntry() definition -#include <stddef.h> // offsetof() +#include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION +#include "pycore_dict.h" // _PyDict_Contains_KnownHash() +#include "pycore_modsupport.h" // _PyArg_NoKwnames() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyerrors.h" // _PyErr_SetKeyError() +#include "pycore_setobject.h" // _PySet_NextEntry() definition +#include <stddef.h> // offsetof() /* Object used as dummy key to fill deleted entries */ static PyObject _dummy_struct; @@ -903,11 +904,17 @@ set_update_internal(PySetObject *so, PyObject *other) if (set_table_resize(so, (so->used + dictsize)*2) != 0) return -1; } + int err = 0; + Py_BEGIN_CRITICAL_SECTION(other); while (_PyDict_Next(other, &pos, &key, &value, &hash)) { - if (set_add_entry(so, key, hash)) - return -1; + if (set_add_entry(so, key, hash)) { + err = -1; + goto exit; + } } - return 0; +exit: + Py_END_CRITICAL_SECTION(); + return err; } it = PyObject_GetIter(other); @@ -1620,6 +1627,33 @@ set_isub(PySetObject *so, PyObject *other) return Py_NewRef(so); } +static PyObject * +set_symmetric_difference_update_dict(PySetObject *so, PyObject *other) +{ + PyObject *key; + Py_ssize_t pos = 0; + Py_hash_t hash; + PyObject *value; + int rv; + + while (_PyDict_Next(other, &pos, &key, &value, &hash)) { + Py_INCREF(key); + rv = set_discard_entry(so, key, hash); + if (rv < 0) { + Py_DECREF(key); + return NULL; + } + if (rv == DISCARD_NOTFOUND) { + if (set_add_entry(so, key, hash)) { + Py_DECREF(key); + return NULL; + } + } + Py_DECREF(key); + } + Py_RETURN_NONE; +} + static PyObject * set_symmetric_difference_update(PySetObject *so, PyObject *other) { @@ -1634,23 +1668,13 @@ set_symmetric_difference_update(PySetObject *so, PyObject *other) return set_clear(so, NULL); if (PyDict_CheckExact(other)) { - PyObject *value; - while (_PyDict_Next(other, &pos, &key, &value, &hash)) { - Py_INCREF(key); - rv = set_discard_entry(so, key, hash); - if (rv < 0) { - Py_DECREF(key); - return NULL; - } - if (rv == DISCARD_NOTFOUND) { - if (set_add_entry(so, key, hash)) { - Py_DECREF(key); - return NULL; - } - } - Py_DECREF(key); - } - Py_RETURN_NONE; + PyObject *res; + + Py_BEGIN_CRITICAL_SECTION(other); + res = set_symmetric_difference_update_dict(so, other); + Py_END_CRITICAL_SECTION(); + + return res; } if (PyAnySet_Check(other)) { From 11ac6f5354ec7a4da2a7e052d27d636b5a41c714 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Tue, 6 Feb 2024 23:44:14 +0100 Subject: [PATCH 232/263] gh-115009: Update Windows installer to use SQLite 3.45.1 (#115065) --- .../next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst | 1 + PCbuild/get_externals.bat | 2 +- PCbuild/python.props | 2 +- PCbuild/readme.txt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst diff --git a/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst b/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst new file mode 100644 index 000000000000000..5bdb6963a243118 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-02-06-09-05-13.gh-issue-115009.ShMjZs.rst @@ -0,0 +1 @@ +Update Windows installer to use SQLite 3.45.1. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 0989bd46a580f77..60ce12b725e2331 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -54,7 +54,7 @@ set libraries= set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.0.13 -set libraries=%libraries% sqlite-3.44.2.0 +set libraries=%libraries% sqlite-3.45.1.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.1 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.1 set libraries=%libraries% xz-5.2.5 diff --git a/PCbuild/python.props b/PCbuild/python.props index 54553db40572888..e21f1f60464bc8c 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -68,7 +68,7 @@ <Import Project="$(ExternalProps)" Condition="$(ExternalProps) != '' and Exists('$(ExternalProps)')" /> <PropertyGroup> - <sqlite3Dir Condition="$(sqlite3Dir) == ''">$(ExternalsDir)sqlite-3.44.2.0\</sqlite3Dir> + <sqlite3Dir Condition="$(sqlite3Dir) == ''">$(ExternalsDir)sqlite-3.45.1.0\</sqlite3Dir> <bz2Dir Condition="$(bz2Dir) == ''">$(ExternalsDir)bzip2-1.0.8\</bz2Dir> <lzmaDir Condition="$(lzmaDir) == ''">$(ExternalsDir)xz-5.2.5\</lzmaDir> <libffiDir Condition="$(libffiDir) == ''">$(ExternalsDir)libffi-3.4.4\</libffiDir> diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index b9d76515c383f77..387565515fa0b02 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -189,7 +189,7 @@ _ssl again when building. _sqlite3 - Wraps SQLite 3.44.2, which is itself built by sqlite3.vcxproj + Wraps SQLite 3.45.1, which is itself built by sqlite3.vcxproj Homepage: https://www.sqlite.org/ _tkinter From 3f71c416c085cfaed49ef325f70eb374a4966256 Mon Sep 17 00:00:00 2001 From: Finite State Machine <38001514+finite-state-machine@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:28:01 -0500 Subject: [PATCH 233/263] gh-115106 docs: 'enum.Flag.__iter__()' did not exist prior to Python 3.11 (GH-115107) change versionchanged to versionadded --- Doc/library/enum.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index f31e6ea848f3b2e..534939943d33267 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -534,9 +534,7 @@ Data Types >>> list(purple) [<Color.RED: 1>, <Color.BLUE: 4>] - .. versionchanged:: 3.11 - - Aliases are no longer returned during iteration. + .. versionadded:: 3.11 .. method:: __len__(self): From 60375a38092b4d4dec9a826818a20adc5d4ff2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:22:47 -0600 Subject: [PATCH 234/263] gh-115114: Add missing slash to file URI prefix `file:/` (#115115) Add missing slash to file URI prefix `file:/` --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e034d34c5fb5abc..c75d44065313940 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -391,7 +391,7 @@ pathlib (Contributed by Barney Gale in :gh:`89812`.) * Add :meth:`pathlib.Path.from_uri`, a new constructor to create a :class:`pathlib.Path` - object from a 'file' URI (``file:/``). + object from a 'file' URI (``file://``). (Contributed by Barney Gale in :gh:`107465`.) * Add :meth:`pathlib.PurePath.full_match` for matching paths with From 2afc7182e66635b3ec7efb59d2a6c18a7ad1f215 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 7 Feb 2024 02:50:24 -0600 Subject: [PATCH 235/263] gh-114505: Add missing header file dependencies (#114513) Also move PYTHON_HEADERS up and make _testembed.o depend on it. --- Makefile.pre.in | 500 +++++++++++++++++++++++++----------------------- 1 file changed, 259 insertions(+), 241 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index aad637876ead809..07b2ec7adde78a5 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -936,6 +936,261 @@ python.html: $(srcdir)/Tools/wasm/python.html python.worker.js python.worker.js: $(srcdir)/Tools/wasm/python.worker.js @cp $(srcdir)/Tools/wasm/python.worker.js $@ +############################################################################ +# Header files + +PYTHON_HEADERS= \ + $(srcdir)/Include/Python.h \ + $(srcdir)/Include/abstract.h \ + $(srcdir)/Include/bltinmodule.h \ + $(srcdir)/Include/boolobject.h \ + $(srcdir)/Include/bytearrayobject.h \ + $(srcdir)/Include/bytesobject.h \ + $(srcdir)/Include/ceval.h \ + $(srcdir)/Include/codecs.h \ + $(srcdir)/Include/compile.h \ + $(srcdir)/Include/complexobject.h \ + $(srcdir)/Include/descrobject.h \ + $(srcdir)/Include/dictobject.h \ + $(srcdir)/Include/dynamic_annotations.h \ + $(srcdir)/Include/enumobject.h \ + $(srcdir)/Include/errcode.h \ + $(srcdir)/Include/exports.h \ + $(srcdir)/Include/fileobject.h \ + $(srcdir)/Include/fileutils.h \ + $(srcdir)/Include/floatobject.h \ + $(srcdir)/Include/frameobject.h \ + $(srcdir)/Include/genericaliasobject.h \ + $(srcdir)/Include/import.h \ + $(srcdir)/Include/interpreteridobject.h \ + $(srcdir)/Include/intrcheck.h \ + $(srcdir)/Include/iterobject.h \ + $(srcdir)/Include/listobject.h \ + $(srcdir)/Include/longobject.h \ + $(srcdir)/Include/marshal.h \ + $(srcdir)/Include/memoryobject.h \ + $(srcdir)/Include/methodobject.h \ + $(srcdir)/Include/modsupport.h \ + $(srcdir)/Include/moduleobject.h \ + $(srcdir)/Include/object.h \ + $(srcdir)/Include/objimpl.h \ + $(srcdir)/Include/opcode.h \ + $(srcdir)/Include/opcode_ids.h \ + $(srcdir)/Include/osdefs.h \ + $(srcdir)/Include/osmodule.h \ + $(srcdir)/Include/patchlevel.h \ + $(srcdir)/Include/pyatomic.h \ + $(srcdir)/Include/pybuffer.h \ + $(srcdir)/Include/pycapsule.h \ + $(srcdir)/Include/pydtrace.h \ + $(srcdir)/Include/pyerrors.h \ + $(srcdir)/Include/pyexpat.h \ + $(srcdir)/Include/pyframe.h \ + $(srcdir)/Include/pyhash.h \ + $(srcdir)/Include/pylifecycle.h \ + $(srcdir)/Include/pymacconfig.h \ + $(srcdir)/Include/pymacro.h \ + $(srcdir)/Include/pymath.h \ + $(srcdir)/Include/pymem.h \ + $(srcdir)/Include/pyport.h \ + $(srcdir)/Include/pystate.h \ + $(srcdir)/Include/pystats.h \ + $(srcdir)/Include/pystrcmp.h \ + $(srcdir)/Include/pystrtod.h \ + $(srcdir)/Include/pythonrun.h \ + $(srcdir)/Include/pythread.h \ + $(srcdir)/Include/pytypedefs.h \ + $(srcdir)/Include/rangeobject.h \ + $(srcdir)/Include/setobject.h \ + $(srcdir)/Include/sliceobject.h \ + $(srcdir)/Include/structmember.h \ + $(srcdir)/Include/structseq.h \ + $(srcdir)/Include/sysmodule.h \ + $(srcdir)/Include/traceback.h \ + $(srcdir)/Include/tupleobject.h \ + $(srcdir)/Include/typeslots.h \ + $(srcdir)/Include/unicodeobject.h \ + $(srcdir)/Include/warnings.h \ + $(srcdir)/Include/weakrefobject.h \ + \ + pyconfig.h \ + $(PARSER_HEADERS) \ + \ + $(srcdir)/Include/cpython/abstract.h \ + $(srcdir)/Include/cpython/bytearrayobject.h \ + $(srcdir)/Include/cpython/bytesobject.h \ + $(srcdir)/Include/cpython/cellobject.h \ + $(srcdir)/Include/cpython/ceval.h \ + $(srcdir)/Include/cpython/classobject.h \ + $(srcdir)/Include/cpython/code.h \ + $(srcdir)/Include/cpython/compile.h \ + $(srcdir)/Include/cpython/complexobject.h \ + $(srcdir)/Include/cpython/context.h \ + $(srcdir)/Include/cpython/descrobject.h \ + $(srcdir)/Include/cpython/dictobject.h \ + $(srcdir)/Include/cpython/fileobject.h \ + $(srcdir)/Include/cpython/fileutils.h \ + $(srcdir)/Include/cpython/floatobject.h \ + $(srcdir)/Include/cpython/frameobject.h \ + $(srcdir)/Include/cpython/funcobject.h \ + $(srcdir)/Include/cpython/genobject.h \ + $(srcdir)/Include/cpython/import.h \ + $(srcdir)/Include/cpython/initconfig.h \ + $(srcdir)/Include/cpython/interpreteridobject.h \ + $(srcdir)/Include/cpython/listobject.h \ + $(srcdir)/Include/cpython/longintrepr.h \ + $(srcdir)/Include/cpython/longobject.h \ + $(srcdir)/Include/cpython/memoryobject.h \ + $(srcdir)/Include/cpython/methodobject.h \ + $(srcdir)/Include/cpython/object.h \ + $(srcdir)/Include/cpython/objimpl.h \ + $(srcdir)/Include/cpython/odictobject.h \ + $(srcdir)/Include/cpython/optimizer.h \ + $(srcdir)/Include/cpython/picklebufobject.h \ + $(srcdir)/Include/cpython/pthread_stubs.h \ + $(srcdir)/Include/cpython/pyatomic.h \ + $(srcdir)/Include/cpython/pyatomic_gcc.h \ + $(srcdir)/Include/cpython/pyatomic_std.h \ + $(srcdir)/Include/cpython/pyctype.h \ + $(srcdir)/Include/cpython/pydebug.h \ + $(srcdir)/Include/cpython/pyerrors.h \ + $(srcdir)/Include/cpython/pyfpe.h \ + $(srcdir)/Include/cpython/pyframe.h \ + $(srcdir)/Include/cpython/pyhash.h \ + $(srcdir)/Include/cpython/pylifecycle.h \ + $(srcdir)/Include/cpython/pymem.h \ + $(srcdir)/Include/cpython/pystate.h \ + $(srcdir)/Include/cpython/pystats.h \ + $(srcdir)/Include/cpython/pythonrun.h \ + $(srcdir)/Include/cpython/pythread.h \ + $(srcdir)/Include/cpython/setobject.h \ + $(srcdir)/Include/cpython/sysmodule.h \ + $(srcdir)/Include/cpython/traceback.h \ + $(srcdir)/Include/cpython/tracemalloc.h \ + $(srcdir)/Include/cpython/tupleobject.h \ + $(srcdir)/Include/cpython/unicodeobject.h \ + $(srcdir)/Include/cpython/warnings.h \ + $(srcdir)/Include/cpython/weakrefobject.h \ + \ + $(MIMALLOC_HEADERS) \ + \ + $(srcdir)/Include/internal/pycore_abstract.h \ + $(srcdir)/Include/internal/pycore_asdl.h \ + $(srcdir)/Include/internal/pycore_ast.h \ + $(srcdir)/Include/internal/pycore_ast_state.h \ + $(srcdir)/Include/internal/pycore_atexit.h \ + $(srcdir)/Include/internal/pycore_bitutils.h \ + $(srcdir)/Include/internal/pycore_blocks_output_buffer.h \ + $(srcdir)/Include/internal/pycore_bytes_methods.h \ + $(srcdir)/Include/internal/pycore_bytesobject.h \ + $(srcdir)/Include/internal/pycore_call.h \ + $(srcdir)/Include/internal/pycore_capsule.h \ + $(srcdir)/Include/internal/pycore_ceval.h \ + $(srcdir)/Include/internal/pycore_ceval_state.h \ + $(srcdir)/Include/internal/pycore_code.h \ + $(srcdir)/Include/internal/pycore_codecs.h \ + $(srcdir)/Include/internal/pycore_compile.h \ + $(srcdir)/Include/internal/pycore_complexobject.h \ + $(srcdir)/Include/internal/pycore_condvar.h \ + $(srcdir)/Include/internal/pycore_context.h \ + $(srcdir)/Include/internal/pycore_critical_section.h \ + $(srcdir)/Include/internal/pycore_crossinterp.h \ + $(srcdir)/Include/internal/pycore_descrobject.h \ + $(srcdir)/Include/internal/pycore_dict.h \ + $(srcdir)/Include/internal/pycore_dict_state.h \ + $(srcdir)/Include/internal/pycore_dtoa.h \ + $(srcdir)/Include/internal/pycore_exceptions.h \ + $(srcdir)/Include/internal/pycore_faulthandler.h \ + $(srcdir)/Include/internal/pycore_fileutils.h \ + $(srcdir)/Include/internal/pycore_floatobject.h \ + $(srcdir)/Include/internal/pycore_flowgraph.h \ + $(srcdir)/Include/internal/pycore_format.h \ + $(srcdir)/Include/internal/pycore_frame.h \ + $(srcdir)/Include/internal/pycore_freelist.h \ + $(srcdir)/Include/internal/pycore_function.h \ + $(srcdir)/Include/internal/pycore_gc.h \ + $(srcdir)/Include/internal/pycore_genobject.h \ + $(srcdir)/Include/internal/pycore_getopt.h \ + $(srcdir)/Include/internal/pycore_gil.h \ + $(srcdir)/Include/internal/pycore_global_objects.h \ + $(srcdir)/Include/internal/pycore_global_objects_fini_generated.h \ + $(srcdir)/Include/internal/pycore_global_strings.h \ + $(srcdir)/Include/internal/pycore_hamt.h \ + $(srcdir)/Include/internal/pycore_hashtable.h \ + $(srcdir)/Include/internal/pycore_identifier.h \ + $(srcdir)/Include/internal/pycore_import.h \ + $(srcdir)/Include/internal/pycore_importdl.h \ + $(srcdir)/Include/internal/pycore_initconfig.h \ + $(srcdir)/Include/internal/pycore_instruments.h \ + $(srcdir)/Include/internal/pycore_interp.h \ + $(srcdir)/Include/internal/pycore_intrinsics.h \ + $(srcdir)/Include/internal/pycore_jit.h \ + $(srcdir)/Include/internal/pycore_list.h \ + $(srcdir)/Include/internal/pycore_llist.h \ + $(srcdir)/Include/internal/pycore_lock.h \ + $(srcdir)/Include/internal/pycore_long.h \ + $(srcdir)/Include/internal/pycore_memoryobject.h \ + $(srcdir)/Include/internal/pycore_mimalloc.h \ + $(srcdir)/Include/internal/pycore_modsupport.h \ + $(srcdir)/Include/internal/pycore_moduleobject.h \ + $(srcdir)/Include/internal/pycore_namespace.h \ + $(srcdir)/Include/internal/pycore_object.h \ + $(srcdir)/Include/internal/pycore_object_alloc.h \ + $(srcdir)/Include/internal/pycore_object_stack.h \ + $(srcdir)/Include/internal/pycore_object_state.h \ + $(srcdir)/Include/internal/pycore_obmalloc.h \ + $(srcdir)/Include/internal/pycore_obmalloc_init.h \ + $(srcdir)/Include/internal/pycore_opcode_metadata.h \ + $(srcdir)/Include/internal/pycore_opcode_utils.h \ + $(srcdir)/Include/internal/pycore_optimizer.h \ + $(srcdir)/Include/internal/pycore_parking_lot.h \ + $(srcdir)/Include/internal/pycore_parser.h \ + $(srcdir)/Include/internal/pycore_pathconfig.h \ + $(srcdir)/Include/internal/pycore_pyarena.h \ + $(srcdir)/Include/internal/pycore_pybuffer.h \ + $(srcdir)/Include/internal/pycore_pyerrors.h \ + $(srcdir)/Include/internal/pycore_pyhash.h \ + $(srcdir)/Include/internal/pycore_pylifecycle.h \ + $(srcdir)/Include/internal/pycore_pymath.h \ + $(srcdir)/Include/internal/pycore_pymem.h \ + $(srcdir)/Include/internal/pycore_pymem_init.h \ + $(srcdir)/Include/internal/pycore_pystate.h \ + $(srcdir)/Include/internal/pycore_pystats.h \ + $(srcdir)/Include/internal/pycore_pythonrun.h \ + $(srcdir)/Include/internal/pycore_pythread.h \ + $(srcdir)/Include/internal/pycore_range.h \ + $(srcdir)/Include/internal/pycore_runtime.h \ + $(srcdir)/Include/internal/pycore_runtime_init.h \ + $(srcdir)/Include/internal/pycore_runtime_init_generated.h \ + $(srcdir)/Include/internal/pycore_semaphore.h \ + $(srcdir)/Include/internal/pycore_setobject.h \ + $(srcdir)/Include/internal/pycore_signal.h \ + $(srcdir)/Include/internal/pycore_sliceobject.h \ + $(srcdir)/Include/internal/pycore_strhex.h \ + $(srcdir)/Include/internal/pycore_structseq.h \ + $(srcdir)/Include/internal/pycore_symtable.h \ + $(srcdir)/Include/internal/pycore_sysmodule.h \ + $(srcdir)/Include/internal/pycore_time.h \ + $(srcdir)/Include/internal/pycore_token.h \ + $(srcdir)/Include/internal/pycore_traceback.h \ + $(srcdir)/Include/internal/pycore_tracemalloc.h \ + $(srcdir)/Include/internal/pycore_tstate.h \ + $(srcdir)/Include/internal/pycore_tuple.h \ + $(srcdir)/Include/internal/pycore_typeobject.h \ + $(srcdir)/Include/internal/pycore_typevarobject.h \ + $(srcdir)/Include/internal/pycore_ucnhash.h \ + $(srcdir)/Include/internal/pycore_unicodeobject.h \ + $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ + $(srcdir)/Include/internal/pycore_unionobject.h \ + $(srcdir)/Include/internal/pycore_uop_ids.h \ + $(srcdir)/Include/internal/pycore_uop_metadata.h \ + $(srcdir)/Include/internal/pycore_warnings.h \ + $(srcdir)/Include/internal/pycore_weakref.h \ + $(DTRACE_HEADERS) \ + @PLATFORM_HEADERS@ \ + \ + $(srcdir)/Python/stdlib_module_names.h + ########################################################################## # Build static libmpdec.a LIBMPDEC_CFLAGS=@LIBMPDEC_CFLAGS@ $(PY_STDMODULE_CFLAGS) $(CCSHARED) @@ -1400,7 +1655,7 @@ Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h M Programs/python.o: $(srcdir)/Programs/python.c $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c -Programs/_testembed.o: $(srcdir)/Programs/_testembed.c Programs/test_frozenmain.h +Programs/_testembed.o: $(srcdir)/Programs/_testembed.c Programs/test_frozenmain.h $(PYTHON_HEADERS) $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/_testembed.c Modules/_sre/sre.o: $(srcdir)/Modules/_sre/sre.c $(srcdir)/Modules/_sre/sre.h $(srcdir)/Modules/_sre/sre_constants.h $(srcdir)/Modules/_sre/sre_lib.h @@ -1669,246 +1924,6 @@ regen-typeslots: $(srcdir)/Objects/typeslots.inc.new $(UPDATE_FILE) $(srcdir)/Objects/typeslots.inc $(srcdir)/Objects/typeslots.inc.new -############################################################################ -# Header files - -PYTHON_HEADERS= \ - $(srcdir)/Include/Python.h \ - $(srcdir)/Include/abstract.h \ - $(srcdir)/Include/bltinmodule.h \ - $(srcdir)/Include/boolobject.h \ - $(srcdir)/Include/bytearrayobject.h \ - $(srcdir)/Include/bytesobject.h \ - $(srcdir)/Include/ceval.h \ - $(srcdir)/Include/codecs.h \ - $(srcdir)/Include/compile.h \ - $(srcdir)/Include/complexobject.h \ - $(srcdir)/Include/descrobject.h \ - $(srcdir)/Include/dictobject.h \ - $(srcdir)/Include/dynamic_annotations.h \ - $(srcdir)/Include/enumobject.h \ - $(srcdir)/Include/errcode.h \ - $(srcdir)/Include/fileobject.h \ - $(srcdir)/Include/fileutils.h \ - $(srcdir)/Include/floatobject.h \ - $(srcdir)/Include/frameobject.h \ - $(srcdir)/Include/import.h \ - $(srcdir)/Include/interpreteridobject.h \ - $(srcdir)/Include/intrcheck.h \ - $(srcdir)/Include/iterobject.h \ - $(srcdir)/Include/listobject.h \ - $(srcdir)/Include/longobject.h \ - $(srcdir)/Include/marshal.h \ - $(srcdir)/Include/memoryobject.h \ - $(srcdir)/Include/methodobject.h \ - $(srcdir)/Include/modsupport.h \ - $(srcdir)/Include/moduleobject.h \ - $(srcdir)/Include/object.h \ - $(srcdir)/Include/objimpl.h \ - $(srcdir)/Include/opcode.h \ - $(srcdir)/Include/opcode_ids.h \ - $(srcdir)/Include/osdefs.h \ - $(srcdir)/Include/osmodule.h \ - $(srcdir)/Include/patchlevel.h \ - $(srcdir)/Include/pybuffer.h \ - $(srcdir)/Include/pycapsule.h \ - $(srcdir)/Include/pydtrace.h \ - $(srcdir)/Include/pyerrors.h \ - $(srcdir)/Include/pyframe.h \ - $(srcdir)/Include/pyhash.h \ - $(srcdir)/Include/pylifecycle.h \ - $(srcdir)/Include/pymacconfig.h \ - $(srcdir)/Include/pymacro.h \ - $(srcdir)/Include/pymath.h \ - $(srcdir)/Include/pymem.h \ - $(srcdir)/Include/pyport.h \ - $(srcdir)/Include/pystate.h \ - $(srcdir)/Include/pystats.h \ - $(srcdir)/Include/pystrcmp.h \ - $(srcdir)/Include/pystrtod.h \ - $(srcdir)/Include/pythonrun.h \ - $(srcdir)/Include/pythread.h \ - $(srcdir)/Include/pytypedefs.h \ - $(srcdir)/Include/rangeobject.h \ - $(srcdir)/Include/setobject.h \ - $(srcdir)/Include/sliceobject.h \ - $(srcdir)/Include/structmember.h \ - $(srcdir)/Include/structseq.h \ - $(srcdir)/Include/sysmodule.h \ - $(srcdir)/Include/traceback.h \ - $(srcdir)/Include/tupleobject.h \ - $(srcdir)/Include/unicodeobject.h \ - $(srcdir)/Include/warnings.h \ - $(srcdir)/Include/weakrefobject.h \ - \ - pyconfig.h \ - $(PARSER_HEADERS) \ - \ - $(srcdir)/Include/cpython/abstract.h \ - $(srcdir)/Include/cpython/bytearrayobject.h \ - $(srcdir)/Include/cpython/bytesobject.h \ - $(srcdir)/Include/cpython/cellobject.h \ - $(srcdir)/Include/cpython/ceval.h \ - $(srcdir)/Include/cpython/classobject.h \ - $(srcdir)/Include/cpython/code.h \ - $(srcdir)/Include/cpython/compile.h \ - $(srcdir)/Include/cpython/complexobject.h \ - $(srcdir)/Include/cpython/context.h \ - $(srcdir)/Include/cpython/descrobject.h \ - $(srcdir)/Include/cpython/dictobject.h \ - $(srcdir)/Include/cpython/fileobject.h \ - $(srcdir)/Include/cpython/fileutils.h \ - $(srcdir)/Include/cpython/floatobject.h \ - $(srcdir)/Include/cpython/frameobject.h \ - $(srcdir)/Include/cpython/funcobject.h \ - $(srcdir)/Include/cpython/genobject.h \ - $(srcdir)/Include/cpython/import.h \ - $(srcdir)/Include/cpython/initconfig.h \ - $(srcdir)/Include/cpython/interpreteridobject.h \ - $(srcdir)/Include/cpython/listobject.h \ - $(srcdir)/Include/cpython/longintrepr.h \ - $(srcdir)/Include/cpython/longobject.h \ - $(srcdir)/Include/cpython/memoryobject.h \ - $(srcdir)/Include/cpython/methodobject.h \ - $(srcdir)/Include/cpython/object.h \ - $(srcdir)/Include/cpython/objimpl.h \ - $(srcdir)/Include/cpython/odictobject.h \ - $(srcdir)/Include/cpython/optimizer.h \ - $(srcdir)/Include/cpython/picklebufobject.h \ - $(srcdir)/Include/cpython/pthread_stubs.h \ - $(srcdir)/Include/cpython/pyatomic.h \ - $(srcdir)/Include/cpython/pyatomic_gcc.h \ - $(srcdir)/Include/cpython/pyatomic_std.h \ - $(srcdir)/Include/cpython/pyctype.h \ - $(srcdir)/Include/cpython/pydebug.h \ - $(srcdir)/Include/cpython/pyerrors.h \ - $(srcdir)/Include/cpython/pyfpe.h \ - $(srcdir)/Include/cpython/pyframe.h \ - $(srcdir)/Include/cpython/pyhash.h \ - $(srcdir)/Include/cpython/pylifecycle.h \ - $(srcdir)/Include/cpython/pymem.h \ - $(srcdir)/Include/cpython/pystate.h \ - $(srcdir)/Include/cpython/pystats.h \ - $(srcdir)/Include/cpython/pythonrun.h \ - $(srcdir)/Include/cpython/pythread.h \ - $(srcdir)/Include/cpython/setobject.h \ - $(srcdir)/Include/cpython/sysmodule.h \ - $(srcdir)/Include/cpython/traceback.h \ - $(srcdir)/Include/cpython/tracemalloc.h \ - $(srcdir)/Include/cpython/tupleobject.h \ - $(srcdir)/Include/cpython/unicodeobject.h \ - $(srcdir)/Include/cpython/warnings.h \ - $(srcdir)/Include/cpython/weakrefobject.h \ - \ - $(MIMALLOC_HEADERS) \ - \ - $(srcdir)/Include/internal/pycore_abstract.h \ - $(srcdir)/Include/internal/pycore_asdl.h \ - $(srcdir)/Include/internal/pycore_ast.h \ - $(srcdir)/Include/internal/pycore_ast_state.h \ - $(srcdir)/Include/internal/pycore_atexit.h \ - $(srcdir)/Include/internal/pycore_bitutils.h \ - $(srcdir)/Include/internal/pycore_bytes_methods.h \ - $(srcdir)/Include/internal/pycore_bytesobject.h \ - $(srcdir)/Include/internal/pycore_call.h \ - $(srcdir)/Include/internal/pycore_capsule.h \ - $(srcdir)/Include/internal/pycore_ceval.h \ - $(srcdir)/Include/internal/pycore_ceval_state.h \ - $(srcdir)/Include/internal/pycore_code.h \ - $(srcdir)/Include/internal/pycore_codecs.h \ - $(srcdir)/Include/internal/pycore_compile.h \ - $(srcdir)/Include/internal/pycore_complexobject.h \ - $(srcdir)/Include/internal/pycore_condvar.h \ - $(srcdir)/Include/internal/pycore_context.h \ - $(srcdir)/Include/internal/pycore_critical_section.h \ - $(srcdir)/Include/internal/pycore_crossinterp.h \ - $(srcdir)/Include/internal/pycore_dict.h \ - $(srcdir)/Include/internal/pycore_dict_state.h \ - $(srcdir)/Include/internal/pycore_descrobject.h \ - $(srcdir)/Include/internal/pycore_dtoa.h \ - $(srcdir)/Include/internal/pycore_exceptions.h \ - $(srcdir)/Include/internal/pycore_faulthandler.h \ - $(srcdir)/Include/internal/pycore_fileutils.h \ - $(srcdir)/Include/internal/pycore_floatobject.h \ - $(srcdir)/Include/internal/pycore_format.h \ - $(srcdir)/Include/internal/pycore_frame.h \ - $(srcdir)/Include/internal/pycore_freelist.h \ - $(srcdir)/Include/internal/pycore_function.h \ - $(srcdir)/Include/internal/pycore_gc.h \ - $(srcdir)/Include/internal/pycore_genobject.h \ - $(srcdir)/Include/internal/pycore_getopt.h \ - $(srcdir)/Include/internal/pycore_gil.h \ - $(srcdir)/Include/internal/pycore_global_objects.h \ - $(srcdir)/Include/internal/pycore_global_objects_fini_generated.h \ - $(srcdir)/Include/internal/pycore_hamt.h \ - $(srcdir)/Include/internal/pycore_hashtable.h \ - $(srcdir)/Include/internal/pycore_identifier.h \ - $(srcdir)/Include/internal/pycore_import.h \ - $(srcdir)/Include/internal/pycore_initconfig.h \ - $(srcdir)/Include/internal/pycore_interp.h \ - $(srcdir)/Include/internal/pycore_intrinsics.h \ - $(srcdir)/Include/internal/pycore_jit.h \ - $(srcdir)/Include/internal/pycore_list.h \ - $(srcdir)/Include/internal/pycore_llist.h \ - $(srcdir)/Include/internal/pycore_lock.h \ - $(srcdir)/Include/internal/pycore_long.h \ - $(srcdir)/Include/internal/pycore_modsupport.h \ - $(srcdir)/Include/internal/pycore_moduleobject.h \ - $(srcdir)/Include/internal/pycore_namespace.h \ - $(srcdir)/Include/internal/pycore_object.h \ - $(srcdir)/Include/internal/pycore_object_alloc.h \ - $(srcdir)/Include/internal/pycore_object_stack.h \ - $(srcdir)/Include/internal/pycore_object_state.h \ - $(srcdir)/Include/internal/pycore_obmalloc.h \ - $(srcdir)/Include/internal/pycore_obmalloc_init.h \ - $(srcdir)/Include/internal/pycore_opcode_metadata.h \ - $(srcdir)/Include/internal/pycore_opcode_utils.h \ - $(srcdir)/Include/internal/pycore_optimizer.h \ - $(srcdir)/Include/internal/pycore_parking_lot.h \ - $(srcdir)/Include/internal/pycore_pathconfig.h \ - $(srcdir)/Include/internal/pycore_pyarena.h \ - $(srcdir)/Include/internal/pycore_pybuffer.h \ - $(srcdir)/Include/internal/pycore_pyerrors.h \ - $(srcdir)/Include/internal/pycore_pyhash.h \ - $(srcdir)/Include/internal/pycore_pylifecycle.h \ - $(srcdir)/Include/internal/pycore_pymem.h \ - $(srcdir)/Include/internal/pycore_pymem_init.h \ - $(srcdir)/Include/internal/pycore_pystate.h \ - $(srcdir)/Include/internal/pycore_pystats.h \ - $(srcdir)/Include/internal/pycore_pythonrun.h \ - $(srcdir)/Include/internal/pycore_pythread.h \ - $(srcdir)/Include/internal/pycore_range.h \ - $(srcdir)/Include/internal/pycore_runtime.h \ - $(srcdir)/Include/internal/pycore_runtime_init_generated.h \ - $(srcdir)/Include/internal/pycore_runtime_init.h \ - $(srcdir)/Include/internal/pycore_semaphore.h \ - $(srcdir)/Include/internal/pycore_setobject.h \ - $(srcdir)/Include/internal/pycore_signal.h \ - $(srcdir)/Include/internal/pycore_sliceobject.h \ - $(srcdir)/Include/internal/pycore_strhex.h \ - $(srcdir)/Include/internal/pycore_structseq.h \ - $(srcdir)/Include/internal/pycore_symtable.h \ - $(srcdir)/Include/internal/pycore_sysmodule.h \ - $(srcdir)/Include/internal/pycore_time.h \ - $(srcdir)/Include/internal/pycore_token.h \ - $(srcdir)/Include/internal/pycore_traceback.h \ - $(srcdir)/Include/internal/pycore_tracemalloc.h \ - $(srcdir)/Include/internal/pycore_tstate.h \ - $(srcdir)/Include/internal/pycore_tuple.h \ - $(srcdir)/Include/internal/pycore_typeobject.h \ - $(srcdir)/Include/internal/pycore_typevarobject.h \ - $(srcdir)/Include/internal/pycore_ucnhash.h \ - $(srcdir)/Include/internal/pycore_unionobject.h \ - $(srcdir)/Include/internal/pycore_unicodeobject.h \ - $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ - $(srcdir)/Include/internal/pycore_uop_metadata.h \ - $(srcdir)/Include/internal/pycore_warnings.h \ - $(srcdir)/Include/internal/pycore_weakref.h \ - $(DTRACE_HEADERS) \ - @PLATFORM_HEADERS@ \ - \ - $(srcdir)/Python/stdlib_module_names.h - $(LIBRARY_OBJS) $(MODOBJS) Programs/python.o: $(PYTHON_HEADERS) @@ -2877,6 +2892,9 @@ Python/thread.o: @THREADHEADERS@ $(srcdir)/Python/condvar.h MODULE_DEPS_STATIC=Modules/config.c MODULE_DEPS_SHARED=$(MODULE_DEPS_STATIC) $(EXPORTSYMS) +MODULE__CURSES_DEPS=$(srcdir)/Include/py_curses.h +MODULE__CURSES_PANEL_DEPS=$(srcdir)/Include/py_curses.h +MODULE__DATETIME_DEPS=$(srcdir)/Include/datetime.h MODULE_CMATH_DEPS=$(srcdir)/Modules/_math.h MODULE_MATH_DEPS=$(srcdir)/Modules/_math.h MODULE_PYEXPAT_DEPS=@LIBEXPAT_INTERNAL@ From d0322fdf2c1a7292a43959fe5a572d783b88a1c4 Mon Sep 17 00:00:00 2001 From: Skip Montanaro <skip.montanaro@gmail.com> Date: Wed, 7 Feb 2024 04:48:42 -0600 Subject: [PATCH 236/263] gh-101100: Fix Py_DEBUG dangling Sphinx references (#115003) --- Doc/c-api/intro.rst | 11 ++++++----- Doc/library/test.rst | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 4dbca92b18b5cd2..dcda1071a58f35b 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -148,7 +148,7 @@ complete listing. worse performances (due to increased code size for example). The compiler is usually smarter than the developer for the cost/benefit analysis. - If Python is :ref:`built in debug mode <debug-build>` (if the ``Py_DEBUG`` + If Python is :ref:`built in debug mode <debug-build>` (if the :c:macro:`Py_DEBUG` macro is defined), the :c:macro:`Py_ALWAYS_INLINE` macro does nothing. It must be specified before the function return type. Usage:: @@ -812,12 +812,14 @@ available that support tracing of reference counts, debugging the memory allocator, or low-level profiling of the main interpreter loop. Only the most frequently used builds will be described in the remainder of this section. -Compiling the interpreter with the :c:macro:`Py_DEBUG` macro defined produces +.. c:macro:: Py_DEBUG + +Compiling the interpreter with the :c:macro:`!Py_DEBUG` macro defined produces what is generally meant by :ref:`a debug build of Python <debug-build>`. -:c:macro:`Py_DEBUG` is enabled in the Unix build by adding +:c:macro:`!Py_DEBUG` is enabled in the Unix build by adding :option:`--with-pydebug` to the :file:`./configure` command. It is also implied by the presence of the -not-Python-specific :c:macro:`_DEBUG` macro. When :c:macro:`Py_DEBUG` is enabled +not-Python-specific :c:macro:`!_DEBUG` macro. When :c:macro:`!Py_DEBUG` is enabled in the Unix build, compiler optimization is disabled. In addition to the reference count debugging described below, extra checks are @@ -832,4 +834,3 @@ after every statement run by the interpreter.) Please refer to :file:`Misc/SpecialBuilds.txt` in the Python source distribution for more detailed information. - diff --git a/Doc/library/test.rst b/Doc/library/test.rst index cad1023021a5129..7d28f6253457263 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -324,9 +324,9 @@ The :mod:`test.support` module defines the following constants: .. data:: Py_DEBUG - True if Python is built with the :c:macro:`Py_DEBUG` macro defined: if - Python is :ref:`built in debug mode <debug-build>` - (:option:`./configure --with-pydebug <--with-pydebug>`). + True if Python was built with the :c:macro:`Py_DEBUG` macro + defined, that is, if + Python was :ref:`built in debug mode <debug-build>`. .. versionadded:: 3.12 From 8a3c499ffe7e15297dd4c0b446a0b97b4d32108a Mon Sep 17 00:00:00 2001 From: Mark Shannon <mark@hotpy.org> Date: Wed, 7 Feb 2024 12:38:34 +0000 Subject: [PATCH 237/263] GH-108362: Revert "GH-108362: Incremental GC implementation (GH-108038)" (#115132) Revert "GH-108362: Incremental GC implementation (GH-108038)" This reverts commit 36518e69d74607e5f094ce55286188e4545a947d. --- Doc/whatsnew/3.13.rst | 34 - Include/internal/pycore_gc.h | 42 +- Include/internal/pycore_object.h | 17 +- Include/internal/pycore_runtime_init.h | 8 +- Lib/test/test_gc.py | 22 +- ...-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst | 13 - Modules/gcmodule.c | 23 +- Objects/object.c | 15 - Objects/structseq.c | 5 +- Python/gc.c | 824 +++++++----------- Python/gc_free_threading.c | 27 +- Python/import.c | 2 +- Tools/gdb/libpython.py | 7 +- 13 files changed, 392 insertions(+), 647 deletions(-) delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c75d44065313940..2ac5afa8ce601cb 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -92,10 +92,6 @@ Interpreter improvements: New Features ============ -* The cyclic garbage collector is now incremental. - This means that maximum pause times are reduced, - by an order of magnitude or more for larger heaps. - Improved Error Messages ----------------------- @@ -105,13 +101,6 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) -Incremental Garbage Collection ------------------------------- - -* The cycle garbage collector is now incremental. - This means that maximum pause times are reduced - by an order of magnitude or more for larger heaps. - Other Language Changes ====================== @@ -257,29 +246,6 @@ fractions sign handling, minimum width and grouping. (Contributed by Mark Dickinson in :gh:`111320`.) -gc --- -* The cyclic garbage collector is now incremental, which changes the meanings - of the results of :meth:`gc.get_threshold` and :meth:`gc.get_threshold` as - well as :meth:`gc.get_count` and :meth:`gc.get_stats`. -* :meth:`gc.get_threshold` returns a three-tuple for backwards compatibility, - the first value is the threshold for young collections, as before, the second - value determines the rate at which the old collection is scanned; the - default is 10 and higher values mean that the old collection is scanned more slowly. - The third value is meangless and is always zero. -* :meth:`gc.set_threshold` ignores any items after the second. -* :meth:`gc.get_count` and :meth:`gc.get_stats`. - These functions return the same format of results as before. - The only difference is that instead of the results refering to - the young, aging and old generations, the results refer to the - young generation and the aging and collecting spaces of the old generation. - -In summary, code that attempted to manipulate the behavior of the cycle GC may -not work as well as intended, but it is very unlikely to harmful. -All other code will work just fine. -Uses should avoid calling :meth:`gc.collect` unless their workload is episodic, -but that has always been the case to some extent. - glob ---- diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index aeb07238fc83455..8d0bc2a218e48de 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -88,15 +88,11 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) { /* Bit flags for _gc_prev */ /* Bit 0 is set when tp_finalize is called */ -#define _PyGC_PREV_MASK_FINALIZED 1 +#define _PyGC_PREV_MASK_FINALIZED (1) /* Bit 1 is set when the object is in generation which is GCed currently. */ -#define _PyGC_PREV_MASK_COLLECTING 2 - -/* Bit 0 is set if the object belongs to old space 1 */ -#define _PyGC_NEXT_MASK_OLD_SPACE_1 1 - +#define _PyGC_PREV_MASK_COLLECTING (2) /* The (N-2) most significant bits contain the real address. */ -#define _PyGC_PREV_SHIFT 2 +#define _PyGC_PREV_SHIFT (2) #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) /* set for debugging information */ @@ -122,13 +118,11 @@ typedef enum { // Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) { - uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK; + uintptr_t next = gc->_gc_next; return (PyGC_Head*)next; } static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) { - uintptr_t unext = (uintptr_t)next; - assert((unext & ~_PyGC_PREV_MASK) == 0); - gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext; + gc->_gc_next = (uintptr_t)next; } // Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags. @@ -136,7 +130,6 @@ static inline PyGC_Head* _PyGCHead_PREV(PyGC_Head *gc) { uintptr_t prev = (gc->_gc_prev & _PyGC_PREV_MASK); return (PyGC_Head*)prev; } - static inline void _PyGCHead_SET_PREV(PyGC_Head *gc, PyGC_Head *prev) { uintptr_t uprev = (uintptr_t)prev; assert((uprev & ~_PyGC_PREV_MASK) == 0); @@ -222,13 +215,6 @@ struct gc_generation { generations */ }; -struct gc_collection_stats { - /* number of collected objects */ - Py_ssize_t collected; - /* total number of uncollectable objects (put into gc.garbage) */ - Py_ssize_t uncollectable; -}; - /* Running stats per generation */ struct gc_generation_stats { /* total number of collections */ @@ -250,8 +236,8 @@ struct _gc_runtime_state { int enabled; int debug; /* linked lists of container objects */ - struct gc_generation young; - struct gc_generation old[2]; + struct gc_generation generations[NUM_GENERATIONS]; + PyGC_Head *generation0; /* a permanent generation which won't be collected */ struct gc_generation permanent_generation; struct gc_generation_stats generation_stats[NUM_GENERATIONS]; @@ -264,20 +250,22 @@ struct _gc_runtime_state { /* This is the number of objects that survived the last full collection. It approximates the number of long lived objects tracked by the GC. + (by "full collection", we mean a collection of the oldest generation). */ Py_ssize_t long_lived_total; - - Py_ssize_t work_to_do; - /* Which of the old spaces is the visited space */ - int visited_space; + /* This is the number of objects that survived all "non-full" + collections, and are awaiting to undergo a full collection for + the first time. */ + Py_ssize_t long_lived_pending; }; extern void _PyGC_InitState(struct _gc_runtime_state *); -extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason); -extern void _PyGC_CollectNoFail(PyThreadState *tstate); +extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, + _PyGC_Reason reason); +extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate); /* Freeze objects tracked by the GC and ignore them in future collections. */ extern void _PyGC_Freeze(PyInterpreterState *interp); diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index efa712c4a0b4588..34a83ea228e8b10 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -125,7 +125,19 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) } #define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) -extern void _Py_SetImmortal(PyObject *op); +static inline void _Py_SetImmortal(PyObject *op) +{ + if (op) { +#ifdef Py_GIL_DISABLED + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; +#else + op->ob_refcnt = _Py_IMMORTAL_REFCNT; +#endif + } +} +#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) // Makes an immortal object mortal again with the specified refcnt. Should only // be used during runtime finalization. @@ -313,12 +325,11 @@ static inline void _PyObject_GC_TRACK( filename, lineno, __func__); PyInterpreterState *interp = _PyInterpreterState_GET(); - PyGC_Head *generation0 = &interp->gc.young.head; + PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); - assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0); generation0->_gc_prev = (uintptr_t)gc; #endif } diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 2ad1347ad48a596..571a7d612c94e25 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -162,12 +162,12 @@ extern PyTypeObject _PyExc_MemoryError; }, \ .gc = { \ .enabled = 1, \ - .young = { .threshold = 2000, }, \ - .old = { \ + .generations = { \ + /* .head is set in _PyGC_InitState(). */ \ + { .threshold = 700, }, \ + { .threshold = 10, }, \ { .threshold = 10, }, \ - { .threshold = 0, }, \ }, \ - .work_to_do = -5000, \ }, \ .object_state = _py_object_state_INIT(INTERP), \ .dtoa = _dtoa_state_INIT(&(INTERP)), \ diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 0002852fce96438..b01f344cb14a1a7 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -383,11 +383,19 @@ def test_collect_generations(self): # each call to collect(N) x = [] gc.collect(0) - # x is now in the old gen + # x is now in gen 1 a, b, c = gc.get_count() - # We don't check a since its exact values depends on + gc.collect(1) + # x is now in gen 2 + d, e, f = gc.get_count() + gc.collect(2) + # x is now in gen 3 + g, h, i = gc.get_count() + # We don't check a, d, g since their exact values depends on # internal implementation details of the interpreter. self.assertEqual((b, c), (1, 0)) + self.assertEqual((e, f), (0, 1)) + self.assertEqual((h, i), (0, 0)) def test_trashcan(self): class Ouch: @@ -838,6 +846,16 @@ def test_get_objects_generations(self): self.assertFalse( any(l is element for element in gc.get_objects(generation=2)) ) + gc.collect(generation=1) + self.assertFalse( + any(l is element for element in gc.get_objects(generation=0)) + ) + self.assertFalse( + any(l is element for element in gc.get_objects(generation=1)) + ) + self.assertTrue( + any(l is element for element in gc.get_objects(generation=2)) + ) gc.collect(generation=2) self.assertFalse( any(l is element for element in gc.get_objects(generation=0)) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst deleted file mode 100644 index 1fe4e0f41e12951..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-07-04-22-51.gh-issue-108362.oB9Gcf.rst +++ /dev/null @@ -1,13 +0,0 @@ -Implements an incremental cyclic garbage collector. By collecting the old -generation in increments, there is no need for a full heap scan. This can -hugely reduce maximum pause time for programs with large heaps. - -Reduces the number of generations from three to two. The old generation is -split into two spaces, "aging" and "collecting". - -Collection happens in two steps:: * First, the young generation is scanned -and the survivors moved to the end of the aging space. * Then objects are -taken from the collecting space, at such a rate that all cycles are -collected eventually. Those objects are then scanned and the survivors -moved to the end of the aging space. When the collecting space becomes -empty, the two spaces are swapped. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 3a42654b41b2acb..a2b66b9b78c169d 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -158,12 +158,17 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1, { GCState *gcstate = get_gc_state(); - gcstate->young.threshold = threshold0; + gcstate->generations[0].threshold = threshold0; if (group_right_1) { - gcstate->old[0].threshold = threshold1; + gcstate->generations[1].threshold = threshold1; } if (group_right_2) { - gcstate->old[1].threshold = threshold2; + gcstate->generations[2].threshold = threshold2; + + /* generations higher than 2 get the same threshold */ + for (int i = 3; i < NUM_GENERATIONS; i++) { + gcstate->generations[i].threshold = gcstate->generations[2].threshold; + } } Py_RETURN_NONE; } @@ -180,9 +185,9 @@ gc_get_threshold_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->young.threshold, - gcstate->old[0].threshold, - 0); + gcstate->generations[0].threshold, + gcstate->generations[1].threshold, + gcstate->generations[2].threshold); } /*[clinic input] @@ -197,9 +202,9 @@ gc_get_count_impl(PyObject *module) { GCState *gcstate = get_gc_state(); return Py_BuildValue("(iii)", - gcstate->young.count, - gcstate->old[gcstate->visited_space].count, - gcstate->old[gcstate->visited_space^1].count); + gcstate->generations[0].count, + gcstate->generations[1].count, + gcstate->generations[2].count); } /*[clinic input] diff --git a/Objects/object.c b/Objects/object.c index 7247eb21df6b6eb..bbf7f98ae3daf92 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2387,21 +2387,6 @@ _Py_NewReferenceNoTotal(PyObject *op) new_reference(op); } -void -_Py_SetImmortal(PyObject *op) -{ - if (PyObject_IS_GC(op) && _PyObject_GC_IS_TRACKED(op)) { - _PyObject_GC_UNTRACK(op); - } -#ifdef Py_GIL_DISABLED - op->ob_tid = _Py_UNOWNED_TID; - op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; - op->ob_ref_shared = 0; -#else - op->ob_refcnt = _Py_IMMORTAL_REFCNT; -#endif -} - void _Py_ResurrectReference(PyObject *op) { diff --git a/Objects/structseq.c b/Objects/structseq.c index 661d96a968fb809..581d6ad240885a0 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -603,9 +603,6 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, PyStructSequence_Desc *desc, unsigned long tp_flags) { - if (Py_TYPE(type) == NULL) { - Py_SET_TYPE(type, &PyType_Type); - } Py_ssize_t n_unnamed_members; Py_ssize_t n_members = count_members(desc, &n_unnamed_members); PyMemberDef *members = NULL; @@ -621,7 +618,7 @@ _PyStructSequence_InitBuiltinWithFlags(PyInterpreterState *interp, } initialize_static_fields(type, desc, members, tp_flags); - _Py_SetImmortal((PyObject *)type); + _Py_SetImmortal(type); } #ifndef NDEBUG else { diff --git a/Python/gc.c b/Python/gc.c index cda12ff7fbc982f..466467602915264 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -45,7 +45,7 @@ typedef struct _gc_runtime_state GCState; // move_legacy_finalizers() removes this flag instead. // Between them, unreachable list is not normal list and we can not use // most gc_list_* functions for it. -#define NEXT_MASK_UNREACHABLE 2 +#define NEXT_MASK_UNREACHABLE (1) #define AS_GC(op) _Py_AS_GC(op) #define FROM_GC(gc) _Py_FROM_GC(gc) @@ -95,48 +95,9 @@ gc_decref(PyGC_Head *g) g->_gc_prev -= 1 << _PyGC_PREV_SHIFT; } -static inline int -gc_old_space(PyGC_Head *g) -{ - return g->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1; -} -static inline int -flip_old_space(int space) -{ - assert(space == 0 || space == 1); - return space ^ _PyGC_NEXT_MASK_OLD_SPACE_1; -} +#define GEN_HEAD(gcstate, n) (&(gcstate)->generations[n].head) -static inline void -gc_flip_old_space(PyGC_Head *g) -{ - g->_gc_next ^= _PyGC_NEXT_MASK_OLD_SPACE_1; -} - -static inline void -gc_set_old_space(PyGC_Head *g, int space) -{ - assert(space == 0 || space == _PyGC_NEXT_MASK_OLD_SPACE_1); - g->_gc_next &= ~_PyGC_NEXT_MASK_OLD_SPACE_1; - g->_gc_next |= space; -} - -static PyGC_Head * -GEN_HEAD(GCState *gcstate, int n) -{ - assert((gcstate->visited_space & (~1)) == 0); - switch(n) { - case 0: - return &gcstate->young.head; - case 1: - return &gcstate->old[gcstate->visited_space].head; - case 2: - return &gcstate->old[gcstate->visited_space^1].head; - default: - Py_UNREACHABLE(); - } -} static GCState * get_gc_state(void) @@ -155,12 +116,11 @@ _PyGC_InitState(GCState *gcstate) GEN.head._gc_prev = (uintptr_t)&GEN.head; \ } while (0) - assert(gcstate->young.count == 0); - assert(gcstate->old[0].count == 0); - assert(gcstate->old[1].count == 0); - INIT_HEAD(gcstate->young); - INIT_HEAD(gcstate->old[0]); - INIT_HEAD(gcstate->old[1]); + for (int i = 0; i < NUM_GENERATIONS; i++) { + assert(gcstate->generations[i].count == 0); + INIT_HEAD(gcstate->generations[i]); + }; + gcstate->generation0 = GEN_HEAD(gcstate, 0); INIT_HEAD(gcstate->permanent_generation); #undef INIT_HEAD @@ -258,7 +218,6 @@ gc_list_is_empty(PyGC_Head *list) static inline void gc_list_append(PyGC_Head *node, PyGC_Head *list) { - assert((list->_gc_prev & ~_PyGC_PREV_MASK) == 0); PyGC_Head *last = (PyGC_Head *)list->_gc_prev; // last <-> node @@ -316,8 +275,6 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to) PyGC_Head *from_tail = GC_PREV(from); assert(from_head != from); assert(from_tail != from); - assert(gc_list_is_empty(to) || - gc_old_space(to_tail) == gc_old_space(from_tail)); _PyGCHead_SET_NEXT(to_tail, from_head); _PyGCHead_SET_PREV(from_head, to_tail); @@ -386,8 +343,8 @@ enum flagstates {collecting_clear_unreachable_clear, static void validate_list(PyGC_Head *head, enum flagstates flags) { - assert((head->_gc_prev & ~_PyGC_PREV_MASK) == 0); - assert((head->_gc_next & ~_PyGC_PREV_MASK) == 0); + assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0); + assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0); uintptr_t prev_value = 0, next_value = 0; switch (flags) { case collecting_clear_unreachable_clear: @@ -409,7 +366,7 @@ validate_list(PyGC_Head *head, enum flagstates flags) PyGC_Head *gc = GC_NEXT(head); while (gc != head) { PyGC_Head *trueprev = GC_PREV(gc); - PyGC_Head *truenext = GC_NEXT(gc); + PyGC_Head *truenext = (PyGC_Head *)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); assert(truenext != NULL); assert(trueprev == prev); assert((gc->_gc_prev & PREV_MASK_COLLECTING) == prev_value); @@ -419,44 +376,8 @@ validate_list(PyGC_Head *head, enum flagstates flags) } assert(prev == GC_PREV(head)); } - -static void -validate_old(GCState *gcstate) -{ - for (int space = 0; space < 2; space++) { - PyGC_Head *head = &gcstate->old[space].head; - PyGC_Head *gc = GC_NEXT(head); - while (gc != head) { - PyGC_Head *next = GC_NEXT(gc); - assert(gc_old_space(gc) == space); - gc = next; - } - } -} - -static void -validate_consistent_old_space(PyGC_Head *head) -{ - PyGC_Head *prev = head; - PyGC_Head *gc = GC_NEXT(head); - if (gc == head) { - return; - } - int old_space = gc_old_space(gc); - while (gc != head) { - PyGC_Head *truenext = GC_NEXT(gc); - assert(truenext != NULL); - assert(gc_old_space(gc) == old_space); - prev = gc; - gc = truenext; - } - assert(prev == GC_PREV(head)); -} - #else #define validate_list(x, y) do{}while(0) -#define validate_old(g) do{}while(0) -#define validate_consistent_old_space(l) do{}while(0) #endif /*** end of list stuff ***/ @@ -473,7 +394,15 @@ update_refs(PyGC_Head *containers) while (gc != containers) { next = GC_NEXT(gc); - assert(!_Py_IsImmortal(FROM_GC(gc))); + /* Move any object that might have become immortal to the + * permanent generation as the reference count is not accurately + * reflecting the actual number of live references to this object + */ + if (_Py_IsImmortal(FROM_GC(gc))) { + gc_list_move(gc, &get_gc_state()->permanent_generation.head); + gc = next; + continue; + } gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc))); /* Python's cyclic gc should never see an incoming refcount * of 0: if something decref'ed to 0, it should have been @@ -571,13 +500,12 @@ visit_reachable(PyObject *op, void *arg) // Manually unlink gc from unreachable list because the list functions // don't work right in the presence of NEXT_MASK_UNREACHABLE flags. PyGC_Head *prev = GC_PREV(gc); - PyGC_Head *next = GC_NEXT(gc); + PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(prev), prev->_gc_next & NEXT_MASK_UNREACHABLE); _PyObject_ASSERT(FROM_GC(next), next->_gc_next & NEXT_MASK_UNREACHABLE); - prev->_gc_next = gc->_gc_next; // copy flag bits - gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE _PyGCHead_SET_PREV(next, prev); gc_list_append(gc, reachable); @@ -629,9 +557,6 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) * or to the right have been scanned yet. */ - validate_consistent_old_space(young); - /* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */ - uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1); while (gc != young) { if (gc_get_refs(gc)) { /* gc is definitely reachable from outside the @@ -677,18 +602,17 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) // But this may pollute the unreachable list head's 'next' pointer // too. That's semantically senseless but expedient here - the // damage is repaired when this function ends. - last->_gc_next = flags | (uintptr_t)gc; + last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc); _PyGCHead_SET_PREV(gc, last); - gc->_gc_next = flags | (uintptr_t)unreachable; + gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable); unreachable->_gc_prev = (uintptr_t)gc; } - gc = _PyGCHead_NEXT(prev); + gc = (PyGC_Head*)prev->_gc_next; } // young->_gc_prev must be last element remained in the list. young->_gc_prev = (uintptr_t)prev; - young->_gc_next &= _PyGC_PREV_MASK; // don't let the pollution of the list head's next pointer leak - unreachable->_gc_next &= _PyGC_PREV_MASK; + unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE; } static void @@ -745,8 +669,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers) PyObject *op = FROM_GC(gc); _PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE); - next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + next = (PyGC_Head*)gc->_gc_next; if (has_legacy_finalizer(op)) { gc_clear_collecting(gc); @@ -765,8 +689,8 @@ clear_unreachable_mask(PyGC_Head *unreachable) assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0); for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) { _PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE); - next = GC_NEXT(gc); gc->_gc_next &= ~NEXT_MASK_UNREACHABLE; + next = (PyGC_Head*)gc->_gc_next; } validate_list(unreachable, collecting_set_unreachable_clear); } @@ -1099,6 +1023,25 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, } +// Show stats for objects in each generations +static void +show_stats_each_generations(GCState *gcstate) +{ + char buf[100]; + size_t pos = 0; + + for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { + pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, + " %zd", + gc_list_size(GEN_HEAD(gcstate, i))); + } + + PySys_FormatStderr( + "gc: objects in each generation:%s\n" + "gc: objects in permanent generation: %zd\n", + buf, gc_list_size(&gcstate->permanent_generation.head)); +} + /* Deduce which objects among "base" are unreachable from outside the list and move them to 'unreachable'. The process consist in the following steps: @@ -1172,6 +1115,7 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) { * the reachable objects instead. But this is a one-time cost, probably not * worth complicating the code to speed just a little. */ + gc_list_init(unreachable); move_unreachable(base, unreachable); // gc_prev is pointer again validate_list(base, collecting_clear_unreachable_clear); validate_list(unreachable, collecting_set_unreachable_set); @@ -1210,272 +1154,219 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, } -#define UNTRACK_TUPLES 1 -#define UNTRACK_DICTS 2 - -static void -gc_collect_region(PyThreadState *tstate, - PyGC_Head *from, - PyGC_Head *to, - int untrack, - struct gc_collection_stats *stats); - -static inline Py_ssize_t -gc_list_set_space(PyGC_Head *list, int space) -{ - Py_ssize_t size = 0; - PyGC_Head *gc; - for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { - gc_set_old_space(gc, space); - size++; - } - return size; -} - - +/* Invoke progress callbacks to notify clients that garbage collection + * is starting or stopping + */ static void -add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats) +invoke_gc_callback(PyThreadState *tstate, const char *phase, + int generation, Py_ssize_t collected, + Py_ssize_t uncollectable) { - gcstate->generation_stats[gen].collected += stats->collected; - gcstate->generation_stats[gen].uncollectable += stats->uncollectable; - gcstate->generation_stats[gen].collections += 1; -} - - -/* Multiply by 4 so that the default incremental threshold of 10 - * scans objects at 40% the rate that the young gen tenures them. */ -#define SCAN_RATE_MULTIPLIER 4 - + assert(!_PyErr_Occurred(tstate)); -static void -gc_collect_young(PyThreadState *tstate, - struct gc_collection_stats *stats) -{ + /* we may get called very early */ GCState *gcstate = &tstate->interp->gc; - PyGC_Head *young = &gcstate->young.head; - PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; -#ifdef Py_STATS - { - Py_ssize_t count = 0; - PyGC_Head *gc; - for (gc = GC_NEXT(young); gc != young; gc = GC_NEXT(gc)) { - count++; - } + if (gcstate->callbacks == NULL) { + return; } -#endif - PyGC_Head survivors; - gc_list_init(&survivors); - gc_collect_region(tstate, young, &survivors, UNTRACK_TUPLES, stats); - Py_ssize_t survivor_count = 0; - if (gcstate->visited_space) { - /* objects in visited space have bit set, so we set it here */ - survivor_count = gc_list_set_space(&survivors, 1); - } - else { - PyGC_Head *gc; - for (gc = GC_NEXT(&survivors); gc != &survivors; gc = GC_NEXT(gc)) { -#ifdef GC_DEBUG - assert(gc_old_space(gc) == 0); -#endif - survivor_count++; + /* The local variable cannot be rebound, check it for sanity */ + assert(PyList_CheckExact(gcstate->callbacks)); + PyObject *info = NULL; + if (PyList_GET_SIZE(gcstate->callbacks) != 0) { + info = Py_BuildValue("{sisnsn}", + "generation", generation, + "collected", collected, + "uncollectable", uncollectable); + if (info == NULL) { + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; } } - gc_list_merge(&survivors, visited); - validate_old(gcstate); - gcstate->young.count = 0; - gcstate->old[gcstate->visited_space].count++; - Py_ssize_t scale_factor = gcstate->old[0].threshold; - if (scale_factor < 1) { - scale_factor = 1; - } - gcstate->work_to_do += survivor_count + survivor_count * SCAN_RATE_MULTIPLIER / scale_factor; - add_stats(gcstate, 0, stats); -} -static inline int -is_in_visited(PyGC_Head *gc, int visited_space) -{ - assert(visited_space == 0 || flip_old_space(visited_space) == 0); - return gc_old_space(gc) == visited_space; -} - -struct container_and_flag { - PyGC_Head *container; - int visited_space; -}; + PyObject *phase_obj = PyUnicode_FromString(phase); + if (phase_obj == NULL) { + Py_XDECREF(info); + PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); + return; + } -/* A traversal callback for adding to container) */ -static int -visit_add_to_container(PyObject *op, void *arg) -{ - OBJECT_STAT_INC(object_visits); - struct container_and_flag *cf = (struct container_and_flag *)arg; - int visited = cf->visited_space; - assert(visited == get_gc_state()->visited_space); - if (_PyObject_IS_GC(op)) { - PyGC_Head *gc = AS_GC(op); - if (_PyObject_GC_IS_TRACKED(op) && - gc_old_space(gc) != visited) { - assert(!_Py_IsImmortal(op)); - gc_flip_old_space(gc); - gc_list_move(gc, cf->container); + PyObject *stack[] = {phase_obj, info}; + for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { + PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); + Py_INCREF(cb); /* make sure cb doesn't go away */ + r = PyObject_Vectorcall(cb, stack, 2, NULL); + if (r == NULL) { + PyErr_WriteUnraisable(cb); } + else { + Py_DECREF(r); + } + Py_DECREF(cb); } - return 0; + Py_DECREF(phase_obj); + Py_XDECREF(info); + assert(!_PyErr_Occurred(tstate)); } -static uintptr_t -expand_region_transitively_reachable(PyGC_Head *container, PyGC_Head *gc, GCState *gcstate) -{ - validate_list(container, collecting_clear_unreachable_clear); - struct container_and_flag arg = { - .container = container, - .visited_space = gcstate->visited_space, - }; - uintptr_t size = 0; - assert(GC_NEXT(gc) == container); - while (gc != container) { - /* Survivors will be moved to visited space, so they should - * have been marked as visited */ - assert(is_in_visited(gc, gcstate->visited_space)); - PyObject *op = FROM_GC(gc); - if (_Py_IsImmortal(op)) { - PyGC_Head *next = GC_NEXT(gc); - gc_list_move(gc, &get_gc_state()->permanent_generation.head); - gc = next; - continue; + +/* Find the oldest generation (highest numbered) where the count + * exceeds the threshold. Objects in the that generation and + * generations younger than it will be collected. */ +static int +gc_select_generation(GCState *gcstate) +{ + for (int i = NUM_GENERATIONS-1; i >= 0; i--) { + if (gcstate->generations[i].count > gcstate->generations[i].threshold) { + /* Avoid quadratic performance degradation in number + of tracked objects (see also issue #4074): + + To limit the cost of garbage collection, there are two strategies; + - make each collection faster, e.g. by scanning fewer objects + - do less collections + This heuristic is about the latter strategy. + + In addition to the various configurable thresholds, we only trigger a + full collection if the ratio + + long_lived_pending / long_lived_total + + is above a given value (hardwired to 25%). + + The reason is that, while "non-full" collections (i.e., collections of + the young and middle generations) will always examine roughly the same + number of objects -- determined by the aforementioned thresholds --, + the cost of a full collection is proportional to the total number of + long-lived objects, which is virtually unbounded. + + Indeed, it has been remarked that doing a full collection every + <constant number> of object creations entails a dramatic performance + degradation in workloads which consist in creating and storing lots of + long-lived objects (e.g. building a large list of GC-tracked objects would + show quadratic performance, instead of linear as expected: see issue #4074). + + Using the above ratio, instead, yields amortized linear performance in + the total number of objects (the effect of which can be summarized + thusly: "each full garbage collection is more and more costly as the + number of objects grows, but we do fewer and fewer of them"). + + This heuristic was suggested by Martin von Löwis on python-dev in + June 2008. His original analysis and proposal can be found at: + http://mail.python.org/pipermail/python-dev/2008-June/080579.html + */ + if (i == NUM_GENERATIONS - 1 + && gcstate->long_lived_pending < gcstate->long_lived_total / 4) + { + continue; + } + return i; } - traverseproc traverse = Py_TYPE(op)->tp_traverse; - (void) traverse(op, - visit_add_to_container, - &arg); - gc = GC_NEXT(gc); - size++; } - return size; + return -1; } -/* Do bookkeeping for a completed GC cycle */ -static void -completed_cycle(GCState *gcstate) -{ - assert(gc_list_is_empty(&gcstate->old[gcstate->visited_space^1].head)); - assert(gc_list_is_empty(&gcstate->young.head)); - gcstate->visited_space = flip_old_space(gcstate->visited_space); - if (gcstate->work_to_do > 0) { - gcstate->work_to_do = 0; - } -} -static void -gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats) +/* This is the main function. Read this to understand how the + * collection process works. */ +static Py_ssize_t +gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) { + int i; + Py_ssize_t m = 0; /* # objects collected */ + Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ + PyGC_Head *young; /* the generation we are examining */ + PyGC_Head *old; /* next older generation */ + PyGC_Head unreachable; /* non-problematic unreachable trash */ + PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ + PyGC_Head *gc; + _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ GCState *gcstate = &tstate->interp->gc; - if (gcstate->work_to_do <= 0) { - /* No work to do */ - return; - } - PyGC_Head *not_visited = &gcstate->old[gcstate->visited_space^1].head; - PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head; - PyGC_Head increment; - gc_list_init(&increment); - if (gc_list_is_empty(not_visited)) { - completed_cycle(gcstate); - return; + + // gc_collect_main() must not be called before _PyGC_Init + // or after _PyGC_Fini() + assert(gcstate->garbage != NULL); + assert(!_PyErr_Occurred(tstate)); + + int expected = 0; + if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { + // Don't start a garbage collection if one is already in progress. + return 0; } - Py_ssize_t region_size = 0; - while (region_size < gcstate->work_to_do) { - if (gc_list_is_empty(not_visited)) { - break; + + if (generation == GENERATION_AUTO) { + // Select the oldest generation that needs collecting. We will collect + // objects from that generation and all generations younger than it. + generation = gc_select_generation(gcstate); + if (generation < 0) { + // No generation needs to be collected. + _Py_atomic_store_int(&gcstate->collecting, 0); + return 0; } - PyGC_Head *gc = _PyGCHead_NEXT(not_visited); - gc_list_move(gc, &increment); - gc_set_old_space(gc, gcstate->visited_space); - region_size += expand_region_transitively_reachable(&increment, gc, gcstate); - } - assert(region_size == gc_list_size(&increment)); - PyGC_Head survivors; - gc_list_init(&survivors); - gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats); - gc_list_merge(&survivors, visited); - assert(gc_list_is_empty(&increment)); - gcstate->work_to_do -= region_size; - validate_old(gcstate); - add_stats(gcstate, 1, stats); - if (gc_list_is_empty(not_visited)) { - completed_cycle(gcstate); } -} + assert(generation >= 0 && generation < NUM_GENERATIONS); -static void -gc_collect_full(PyThreadState *tstate, - struct gc_collection_stats *stats) -{ - GCState *gcstate = &tstate->interp->gc; - validate_old(gcstate); - PyGC_Head *young = &gcstate->young.head; - PyGC_Head *old0 = &gcstate->old[0].head; - PyGC_Head *old1 = &gcstate->old[1].head; - /* merge all generations into old0 */ - gc_list_merge(young, old0); - gcstate->young.count = 0; - PyGC_Head *gc = GC_NEXT(old1); - while (gc != old1) { - PyGC_Head *next = GC_NEXT(gc); - gc_set_old_space(gc, 0); - gc = next; +#ifdef Py_STATS + if (_Py_stats) { + _Py_stats->object_stats.object_visits = 0; } - gc_list_merge(old1, old0); - - gc_collect_region(tstate, old0, old0, - UNTRACK_TUPLES | UNTRACK_DICTS, - stats); - gcstate->visited_space = 1; - gcstate->young.count = 0; - gcstate->old[0].count = 0; - gcstate->old[1].count = 0; +#endif + GC_STAT_ADD(generation, collections, 1); - gcstate->work_to_do = - gcstate->young.threshold * 2; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "start", generation, 0, 0); + } - _PyGC_ClearAllFreeLists(tstate->interp); - validate_old(gcstate); - add_stats(gcstate, 2, stats); -} + if (gcstate->debug & _PyGC_DEBUG_STATS) { + PySys_WriteStderr("gc: collecting generation %d...\n", generation); + show_stats_each_generations(gcstate); + t1 = _PyTime_GetPerfCounter(); + } -/* This is the main function. Read this to understand how the - * collection process works. */ -static void -gc_collect_region(PyThreadState *tstate, - PyGC_Head *from, - PyGC_Head *to, - int untrack, - struct gc_collection_stats *stats) -{ - PyGC_Head unreachable; /* non-problematic unreachable trash */ - PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ - PyGC_Head *gc; /* initialize to prevent a compiler warning */ - GCState *gcstate = &tstate->interp->gc; + if (PyDTrace_GC_START_ENABLED()) { + PyDTrace_GC_START(generation); + } - assert(gcstate->garbage != NULL); - assert(!_PyErr_Occurred(tstate)); + /* update collection and allocation counters */ + if (generation+1 < NUM_GENERATIONS) { + gcstate->generations[generation+1].count += 1; + } + for (i = 0; i <= generation; i++) { + gcstate->generations[i].count = 0; + } - gc_list_init(&unreachable); - deduce_unreachable(from, &unreachable); - validate_consistent_old_space(from); - if (untrack & UNTRACK_TUPLES) { - untrack_tuples(from); + /* merge younger generations with one we are currently collecting */ + for (i = 0; i < generation; i++) { + gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); } - if (untrack & UNTRACK_DICTS) { - untrack_dicts(from); + + /* handy references */ + young = GEN_HEAD(gcstate, generation); + if (generation < NUM_GENERATIONS-1) { + old = GEN_HEAD(gcstate, generation+1); } - validate_consistent_old_space(to); - if (from != to) { - gc_list_merge(from, to); + else { + old = young; } - validate_consistent_old_space(to); + validate_list(old, collecting_clear_unreachable_clear); + + deduce_unreachable(young, &unreachable); + + untrack_tuples(young); /* Move reachable objects to next generation. */ + if (young != old) { + if (generation == NUM_GENERATIONS - 2) { + gcstate->long_lived_pending += gc_list_size(young); + } + gc_list_merge(young, old); + } + else { + /* We only un-track dicts in full collections, to avoid quadratic + dict build-up. See issue #14775. */ + untrack_dicts(young); + gcstate->long_lived_pending = 0; + gcstate->long_lived_total = gc_list_size(young); + } /* All objects in unreachable are trash, but objects reachable from * legacy finalizers (e.g. tp_del) can't safely be deleted. @@ -1489,8 +1380,10 @@ gc_collect_region(PyThreadState *tstate, * and we move those into the finalizers list too. */ move_legacy_finalizer_reachable(&finalizers); + validate_list(&finalizers, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); + /* Print debugging information. */ if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) { for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) { @@ -1499,99 +1392,89 @@ gc_collect_region(PyThreadState *tstate, } /* Clear weakrefs and invoke callbacks as necessary. */ - stats->collected += handle_weakrefs(&unreachable, to); - validate_list(to, collecting_clear_unreachable_clear); + m += handle_weakrefs(&unreachable, old); + + validate_list(old, collecting_clear_unreachable_clear); validate_list(&unreachable, collecting_set_unreachable_clear); /* Call tp_finalize on objects which have one. */ finalize_garbage(tstate, &unreachable); + /* Handle any objects that may have resurrected after the call * to 'finalize_garbage' and continue the collection with the * objects that are still unreachable */ PyGC_Head final_unreachable; - gc_list_init(&final_unreachable); - handle_resurrected_objects(&unreachable, &final_unreachable, to); + handle_resurrected_objects(&unreachable, &final_unreachable, old); /* Call tp_clear on objects in the final_unreachable set. This will cause * the reference cycles to be broken. It may also cause some objects * in finalizers to be freed. */ - stats->collected += gc_list_size(&final_unreachable); - delete_garbage(tstate, gcstate, &final_unreachable, to); + m += gc_list_size(&final_unreachable); + delete_garbage(tstate, gcstate, &final_unreachable, old); /* Collect statistics on uncollectable objects found and print * debugging information. */ - Py_ssize_t n = 0; for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) { n++; if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) debug_cycle("uncollectable", FROM_GC(gc)); } - stats->uncollectable = n; + if (gcstate->debug & _PyGC_DEBUG_STATS) { + double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); + PySys_WriteStderr( + "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", + n+m, n, d); + } + /* Append instances in the uncollectable set to a Python * reachable list of garbage. The programmer has to deal with * this if they insist on creating this type of structure. */ - handle_legacy_finalizers(tstate, gcstate, &finalizers, to); - validate_list(to, collecting_clear_unreachable_clear); -} + handle_legacy_finalizers(tstate, gcstate, &finalizers, old); + validate_list(old, collecting_clear_unreachable_clear); -/* Invoke progress callbacks to notify clients that garbage collection - * is starting or stopping - */ -static void -do_gc_callback(GCState *gcstate, const char *phase, - int generation, struct gc_collection_stats *stats) -{ - assert(!PyErr_Occurred()); + /* Clear free list only during the collection of the highest + * generation */ + if (generation == NUM_GENERATIONS-1) { + _PyGC_ClearAllFreeLists(tstate->interp); + } - /* The local variable cannot be rebound, check it for sanity */ - assert(PyList_CheckExact(gcstate->callbacks)); - PyObject *info = NULL; - if (PyList_GET_SIZE(gcstate->callbacks) != 0) { - info = Py_BuildValue("{sisnsn}", - "generation", generation, - "collected", stats->collected, - "uncollectable", stats->uncollectable); - if (info == NULL) { - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + if (_PyErr_Occurred(tstate)) { + if (reason == _Py_GC_REASON_SHUTDOWN) { + _PyErr_Clear(tstate); + } + else { + PyErr_FormatUnraisable("Exception ignored in garbage collection"); } } - PyObject *phase_obj = PyUnicode_FromString(phase); - if (phase_obj == NULL) { - Py_XDECREF(info); - PyErr_FormatUnraisable("Exception ignored on invoking gc callbacks"); - return; + /* Update stats */ + struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; + stats->collections++; + stats->collected += m; + stats->uncollectable += n; + + GC_STAT_ADD(generation, objects_collected, m); +#ifdef Py_STATS + if (_Py_stats) { + GC_STAT_ADD(generation, object_visits, + _Py_stats->object_stats.object_visits); + _Py_stats->object_stats.object_visits = 0; } +#endif - PyObject *stack[] = {phase_obj, info}; - for (Py_ssize_t i=0; i<PyList_GET_SIZE(gcstate->callbacks); i++) { - PyObject *r, *cb = PyList_GET_ITEM(gcstate->callbacks, i); - Py_INCREF(cb); /* make sure cb doesn't go away */ - r = PyObject_Vectorcall(cb, stack, 2, NULL); - if (r == NULL) { - PyErr_WriteUnraisable(cb); - } - else { - Py_DECREF(r); - } - Py_DECREF(cb); + if (PyDTrace_GC_DONE_ENABLED()) { + PyDTrace_GC_DONE(n + m); } - Py_DECREF(phase_obj); - Py_XDECREF(info); - assert(!PyErr_Occurred()); -} -static void -invoke_gc_callback(GCState *gcstate, const char *phase, - int generation, struct gc_collection_stats *stats) -{ - if (gcstate->callbacks == NULL) { - return; + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "stop", generation, m, n); } - do_gc_callback(gcstate, phase, generation, stats); + + assert(!_PyErr_Occurred(tstate)); + _Py_atomic_store_int(&gcstate->collecting, 0); + return n + m; } static int @@ -1666,7 +1549,7 @@ _PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) } } else { - if (append_objects(result, GEN_HEAD(gcstate, (int)generation))) { + if (append_objects(result, GEN_HEAD(gcstate, generation))) { goto error; } } @@ -1681,16 +1564,10 @@ void _PyGC_Freeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; - gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head); - gcstate->young.count = 0; - PyGC_Head*old0 = &gcstate->old[0].head; - PyGC_Head*old1 = &gcstate->old[1].head; - gc_list_merge(old0, &gcstate->permanent_generation.head); - gcstate->old[0].count = 0; - gc_list_set_space(old1, 0); - gc_list_merge(old1, &gcstate->permanent_generation.head); - gcstate->old[1].count = 0; - validate_old(gcstate); + for (int i = 0; i < NUM_GENERATIONS; ++i) { + gc_list_merge(GEN_HEAD(gcstate, i), &gcstate->permanent_generation.head); + gcstate->generations[i].count = 0; + } } void @@ -1698,8 +1575,7 @@ _PyGC_Unfreeze(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; gc_list_merge(&gcstate->permanent_generation.head, - &gcstate->old[0].head); - validate_old(gcstate); + GEN_HEAD(gcstate, NUM_GENERATIONS-1)); } Py_ssize_t @@ -1735,100 +1611,32 @@ PyGC_IsEnabled(void) return gcstate->enabled; } -// Show stats for objects in each generations -static void -show_stats_each_generations(GCState *gcstate) -{ - char buf[100]; - size_t pos = 0; - - for (int i = 0; i < NUM_GENERATIONS && pos < sizeof(buf); i++) { - pos += PyOS_snprintf(buf+pos, sizeof(buf)-pos, - " %zd", - gc_list_size(GEN_HEAD(gcstate, i))); - } - - PySys_FormatStderr( - "gc: objects in each generation:%s\n" - "gc: objects in permanent generation: %zd\n", - buf, gc_list_size(&gcstate->permanent_generation.head)); -} - +/* Public API to invoke gc.collect() from C */ Py_ssize_t -_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +PyGC_Collect(void) { + PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - int expected = 0; - if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { - // Don't start a garbage collection if one is already in progress. + if (!gcstate->enabled) { return 0; } - struct gc_collection_stats stats = { 0 }; - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(gcstate, "start", generation, &stats); - } - _PyTime_t t1 = 0; /* initialize to prevent a compiler warning */ - if (gcstate->debug & _PyGC_DEBUG_STATS) { - PySys_WriteStderr("gc: collecting generation %d...\n", generation); - show_stats_each_generations(gcstate); - t1 = _PyTime_GetPerfCounter(); - } - if (PyDTrace_GC_START_ENABLED()) { - PyDTrace_GC_START(generation); - } - GC_STAT_ADD(generation, collections, 1); + Py_ssize_t n; PyObject *exc = _PyErr_GetRaisedException(tstate); - switch(generation) { - case 0: - gc_collect_young(tstate, &stats); - break; - case 1: - gc_collect_young(tstate, &stats); - gc_collect_increment(tstate, &stats); - break; - case 2: - gc_collect_full(tstate, &stats); - break; - default: - Py_UNREACHABLE(); - } - if (PyDTrace_GC_DONE_ENABLED()) { - PyDTrace_GC_DONE(stats.uncollectable + stats.collected); - } - if (reason != _Py_GC_REASON_SHUTDOWN) { - invoke_gc_callback(gcstate, "stop", generation, &stats); - } + n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); _PyErr_SetRaisedException(tstate, exc); - GC_STAT_ADD(generation, objects_collected, stats.collected); -#ifdef Py_STATS - if (_Py_stats) { - GC_STAT_ADD(generation, object_visits, - _Py_stats->object_stats.object_visits); - _Py_stats->object_stats.object_visits = 0; - } -#endif - validate_old(gcstate); - if (gcstate->debug & _PyGC_DEBUG_STATS) { - double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); - PySys_WriteStderr( - "gc: done, %zd collected, %zd uncollectable, %.4fs elapsed\n", - stats.collected, stats.uncollectable, d); - } - _Py_atomic_store_int(&gcstate->collecting, 0); - return stats.uncollectable + stats.collected; + return n; } -/* Public API to invoke gc.collect() from C */ Py_ssize_t -PyGC_Collect(void) +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) { - return _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_MANUAL); + return gc_collect_main(tstate, generation, reason); } -void +Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1837,7 +1645,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - _PyGC_Collect(_PyThreadState_GET(), 2, _Py_GC_REASON_SHUTDOWN); + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1972,10 +1780,10 @@ _PyObject_GC_Link(PyObject *op) GCState *gcstate = &tstate->interp->gc; g->_gc_next = 0; g->_gc_prev = 0; - gcstate->young.count++; /* number of allocated GC objects */ - if (gcstate->young.count > gcstate->young.threshold && + gcstate->generations[0].count++; /* number of allocated GC objects */ + if (gcstate->generations[0].count > gcstate->generations[0].threshold && gcstate->enabled && - gcstate->young.threshold && + gcstate->generations[0].threshold && !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { @@ -1986,9 +1794,7 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - if (tstate->interp->gc.enabled) { - _PyGC_Collect(tstate, 1, _Py_GC_REASON_HEAP); - } + gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); } static PyObject * @@ -2091,8 +1897,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->young.count > 0) { - gcstate->young.count--; + if (gcstate->generations[0].count > 0) { + gcstate->generations[0].count--; } PyObject_Free(((char *)op)-presize); } @@ -2115,36 +1921,26 @@ PyObject_GC_IsFinalized(PyObject *obj) return 0; } -static int -visit_generation(gcvisitobjects_t callback, void *arg, struct gc_generation *gen) -{ - PyGC_Head *gc_list, *gc; - gc_list = &gen->head; - for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { - PyObject *op = FROM_GC(gc); - Py_INCREF(op); - int res = callback(op, arg); - Py_DECREF(op); - if (!res) { - return -1; - } - } - return 0; -} - void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg) { + size_t i; GCState *gcstate = get_gc_state(); int origenstate = gcstate->enabled; gcstate->enabled = 0; - if (visit_generation(callback, arg, &gcstate->young)) { - goto done; - } - if (visit_generation(callback, arg, &gcstate->old[0])) { - goto done; + for (i = 0; i < NUM_GENERATIONS; i++) { + PyGC_Head *gc_list, *gc; + gc_list = GEN_HEAD(gcstate, i); + for (gc = GC_NEXT(gc_list); gc != gc_list; gc = GC_NEXT(gc)) { + PyObject *op = FROM_GC(gc); + Py_INCREF(op); + int res = callback(op, arg); + Py_DECREF(op); + if (!res) { + goto done; + } + } } - visit_generation(callback, arg, &gcstate->old[1]); done: gcstate->enabled = origenstate; } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 1c4da726866e4e1..8fbcdb15109b76c 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -616,7 +616,7 @@ void _PyGC_InitState(GCState *gcstate) { // TODO: move to pycore_runtime_init.h once the incremental GC lands. - gcstate->young.threshold = 2000; + gcstate->generations[0].threshold = 2000; } @@ -911,8 +911,8 @@ cleanup_worklist(struct worklist *worklist) static bool gc_should_collect(GCState *gcstate) { - int count = _Py_atomic_load_int_relaxed(&gcstate->young.count); - int threshold = gcstate->young.threshold; + int count = _Py_atomic_load_int_relaxed(&gcstate->generations[0].count); + int threshold = gcstate->generations[0].threshold; if (count <= threshold || threshold == 0 || !gcstate->enabled) { return false; } @@ -920,7 +920,7 @@ gc_should_collect(GCState *gcstate) // objects. A few tests rely on immediate scheduling of the GC so we ignore // the scaled threshold if generations[1].threshold is set to zero. return (count > gcstate->long_lived_total / 4 || - gcstate->old[0].threshold == 0); + gcstate->generations[1].threshold == 0); } static void @@ -1031,15 +1031,10 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* update collection and allocation counters */ if (generation+1 < NUM_GENERATIONS) { - gcstate->old[generation].count += 1; + gcstate->generations[generation+1].count += 1; } for (i = 0; i <= generation; i++) { - if (i == 0) { - gcstate->young.count = 0; - } - else { - gcstate->old[i-1].count = 0; - } + gcstate->generations[i].count = 0; } PyInterpreterState *interp = tstate->interp; @@ -1362,7 +1357,7 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) return gc_collect_main(tstate, generation, reason); } -void +Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate) { /* Ideally, this function is only called on interpreter shutdown, @@ -1371,7 +1366,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -1495,7 +1490,7 @@ _PyObject_GC_Link(PyObject *op) { PyThreadState *tstate = _PyThreadState_GET(); GCState *gcstate = &tstate->interp->gc; - gcstate->young.count++; + gcstate->generations[0].count++; if (gc_should_collect(gcstate) && !_Py_atomic_load_int_relaxed(&gcstate->collecting)) @@ -1610,8 +1605,8 @@ PyObject_GC_Del(void *op) #endif } GCState *gcstate = get_gc_state(); - if (gcstate->young.count > 0) { - gcstate->young.count--; + if (gcstate->generations[0].count > 0) { + gcstate->generations[0].count--; } PyObject_Free(((char *)op)-presize); } diff --git a/Python/import.c b/Python/import.c index dfc5ec1f2f29272..2fd0c08a6bb5aec 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1030,7 +1030,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) if (!already_set) { /* We assume that all module defs are statically allocated and will never be freed. Otherwise, we would incref here. */ - _Py_SetImmortal((PyObject *)def); + _Py_SetImmortal(def); } res = 0; diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 96b891481d9f462..483f28b46dfec74 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -1753,11 +1753,8 @@ def is_waiting_for_gil(self): return (name == 'take_gil') def is_gc_collect(self): - '''Is this frame a collector within the garbage-collector?''' - return self._gdbframe.name() in ( - 'collect', 'gc_collect_full', 'gc_collect_main', - 'gc_collect_young', 'gc_collect_increment' - ) + '''Is this frame gc_collect_main() within the garbage-collector?''' + return self._gdbframe.name() in ('collect', 'gc_collect_main') def get_pyop(self): try: From fedbf77191ea9d6515b39f958cc9e588d23517c9 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Wed, 7 Feb 2024 11:56:16 -0500 Subject: [PATCH 238/263] gh-114828: Fix __class__ in class-scope inlined comprehensions (#115139) --- Lib/test/test_listcomps.py | 12 ++++++++++++ ...-02-07-07-50-12.gh-issue-114828.nSXwMi.rst | 2 ++ Python/symtable.c | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst diff --git a/Lib/test/test_listcomps.py b/Lib/test/test_listcomps.py index f95a78aff0c7118..2868dd01545b95f 100644 --- a/Lib/test/test_listcomps.py +++ b/Lib/test/test_listcomps.py @@ -156,6 +156,18 @@ def method(self): self.assertEqual(C.y, [4, 4, 4, 4, 4]) self.assertIs(C().method(), C) + def test_references_super(self): + code = """ + res = [super for x in [1]] + """ + self._check_in_scopes(code, outputs={"res": [super]}) + + def test_references___class__(self): + code = """ + res = [__class__ for x in [1]] + """ + self._check_in_scopes(code, raises=NameError) + def test_inner_cell_shadows_outer(self): code = """ items = [(lambda: i) for i in range(5)] diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst new file mode 100644 index 000000000000000..b1c63e0a1518fdc --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-07-50-12.gh-issue-114828.nSXwMi.rst @@ -0,0 +1,2 @@ +Fix compilation crashes in uncommon code examples using :func:`super` inside +a comprehension in a class body. diff --git a/Python/symtable.c b/Python/symtable.c index 743029956e32faf..d69516351efba22 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -758,6 +758,8 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, { PyObject *k, *v; Py_ssize_t pos = 0; + int remove_dunder_class = 0; + while (PyDict_Next(comp->ste_symbols, &pos, &k, &v)) { // skip comprehension parameter long comp_flags = PyLong_AS_LONG(v); @@ -779,6 +781,19 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, if (!existing) { // name does not exist in scope, copy from comprehension assert(scope != FREE || PySet_Contains(comp_free, k) == 1); + if (scope == FREE && ste->ste_type == ClassBlock && + _PyUnicode_EqualToASCIIString(k, "__class__")) { + // if __class__ is unbound in the enclosing class scope and free + // in the comprehension scope, it needs special handling; just + // letting it be marked as free in class scope will break due to + // drop_class_free + scope = GLOBAL_IMPLICIT; + only_flags &= ~DEF_FREE; + if (PySet_Discard(comp_free, k) < 0) { + return 0; + } + remove_dunder_class = 1; + } PyObject *v_flags = PyLong_FromLong(only_flags); if (v_flags == NULL) { return 0; @@ -803,6 +818,10 @@ inline_comprehension(PySTEntryObject *ste, PySTEntryObject *comp, } } } + comp->ste_free = PySet_Size(comp_free) > 0; + if (remove_dunder_class && PyDict_DelItemString(comp->ste_symbols, "__class__") < 0) { + return 0; + } return 1; } From ef3ceab09d2d0959c343c662461123d5b0e0b64b Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Wed, 7 Feb 2024 13:43:18 -0500 Subject: [PATCH 239/263] gh-112066: Use `PyDict_SetDefaultRef` in place of `PyDict_SetDefault`. (#112211) This changes a number of internal usages of `PyDict_SetDefault` to use `PyDict_SetDefaultRef`. Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com> --- Modules/_json.c | 5 ++--- Modules/posixmodule.c | 2 +- Modules/pyexpat.c | 3 ++- Objects/typeobject.c | 6 +++--- Objects/unicodeobject.c | 12 +++++++----- Python/compile.c | 29 +++++++++++++++++------------ 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Modules/_json.c b/Modules/_json.c index 24b292ce70e5ebd..c55299899e77fe5 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -691,11 +691,10 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss key = scanstring_unicode(pystr, idx + 1, s->strict, &next_idx); if (key == NULL) goto bail; - memokey = PyDict_SetDefault(memo, key, key); - if (memokey == NULL) { + if (PyDict_SetDefaultRef(memo, key, key, &memokey) < 0) { goto bail; } - Py_SETREF(key, Py_NewRef(memokey)); + Py_SETREF(key, memokey); idx = next_idx; /* skip whitespace between key and : delimiter, read :, skip whitespace */ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 22891135bde0af7..e26265fc874ebb4 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1627,7 +1627,7 @@ convertenviron(void) Py_DECREF(d); return NULL; } - if (PyDict_SetDefault(d, k, v) == NULL) { + if (PyDict_SetDefaultRef(d, k, v, NULL) < 0) { Py_DECREF(v); Py_DECREF(k); Py_DECREF(d); diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 7c08eda83e66b2a..62cd262a7885e93 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -1615,7 +1615,8 @@ static int init_handler_descrs(pyexpat_state *state) if (descr == NULL) return -1; - if (PyDict_SetDefault(state->xml_parse_type->tp_dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(state->xml_parse_type->tp_dict, + PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e220d10ce563c29..c65d0ec2acae526 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6683,7 +6683,7 @@ type_add_method(PyTypeObject *type, PyMethodDef *meth) int err; PyObject *dict = lookup_tp_dict(type); if (!(meth->ml_flags & METH_COEXIST)) { - err = PyDict_SetDefault(dict, name, descr) == NULL; + err = PyDict_SetDefaultRef(dict, name, descr, NULL) < 0; } else { err = PyDict_SetItem(dict, name, descr) < 0; @@ -6731,7 +6731,7 @@ type_add_members(PyTypeObject *type) if (descr == NULL) return -1; - if (PyDict_SetDefault(dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } @@ -6756,7 +6756,7 @@ type_add_getset(PyTypeObject *type) return -1; } - if (PyDict_SetDefault(dict, PyDescr_NAME(descr), descr) == NULL) { + if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) { Py_DECREF(descr); return -1; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index b236ddba9cdc69f..0a569a950e88e29 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14894,16 +14894,18 @@ _PyUnicode_InternInPlace(PyInterpreterState *interp, PyObject **p) PyObject *interned = get_interned_dict(interp); assert(interned != NULL); - PyObject *t = PyDict_SetDefault(interned, s, s); - if (t == NULL) { + PyObject *t; + int res = PyDict_SetDefaultRef(interned, s, s, &t); + if (res < 0) { PyErr_Clear(); return; } - - if (t != s) { - Py_SETREF(*p, Py_NewRef(t)); + else if (res == 1) { + // value was already present (not inserted) + Py_SETREF(*p, t); return; } + Py_DECREF(t); if (_Py_IsImmortal(s)) { // XXX Restrict this to the main interpreter? diff --git a/Python/compile.c b/Python/compile.c index 4c1d3bb2d2b475a..15e5cf38a37b97a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -958,14 +958,15 @@ merge_consts_recursive(PyObject *const_cache, PyObject *o) return NULL; } - // t is borrowed reference - PyObject *t = PyDict_SetDefault(const_cache, key, key); - if (t != key) { - // o is registered in const_cache. Just use it. - Py_XINCREF(t); + PyObject *t; + int res = PyDict_SetDefaultRef(const_cache, key, key, &t); + if (res != 0) { + // o was not inserted into const_cache. t is either the existing value + // or NULL (on error). Py_DECREF(key); return t; } + Py_DECREF(t); // We registered o in const_cache. // When o is a tuple or frozenset, we want to merge its @@ -7527,22 +7528,26 @@ _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj) return ERROR; } - // t is borrowed reference - PyObject *t = PyDict_SetDefault(const_cache, key, key); + PyObject *t; + int res = PyDict_SetDefaultRef(const_cache, key, key, &t); Py_DECREF(key); - if (t == NULL) { + if (res < 0) { return ERROR; } - if (t == key) { // obj is new constant. + if (res == 0) { // inserted: obj is new constant. + Py_DECREF(t); return SUCCESS; } if (PyTuple_CheckExact(t)) { - // t is still borrowed reference - t = PyTuple_GET_ITEM(t, 1); + PyObject *item = PyTuple_GET_ITEM(t, 1); + Py_SETREF(*obj, Py_NewRef(item)); + Py_DECREF(t); + } + else { + Py_SETREF(*obj, t); } - Py_SETREF(*obj, Py_NewRef(t)); return SUCCESS; } From 8f0998e844c2fd8c0c94681d0a6331c34ee31562 Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Wed, 7 Feb 2024 15:19:47 -0500 Subject: [PATCH 240/263] gh-114828: parenthesize non-atomic macro definitions in pycore_symtable.h (#115143) --- Include/internal/pycore_symtable.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_symtable.h b/Include/internal/pycore_symtable.h index 1d782ca2c96e055..b44393b56446735 100644 --- a/Include/internal/pycore_symtable.h +++ b/Include/internal/pycore_symtable.h @@ -109,18 +109,18 @@ extern PyObject* _Py_Mangle(PyObject *p, PyObject *name); /* Flags for def-use information */ -#define DEF_GLOBAL 1 /* global stmt */ -#define DEF_LOCAL 2 /* assignment in code block */ -#define DEF_PARAM 2<<1 /* formal parameter */ -#define DEF_NONLOCAL 2<<2 /* nonlocal stmt */ -#define USE 2<<3 /* name is used */ -#define DEF_FREE 2<<4 /* name used but not defined in nested block */ -#define DEF_FREE_CLASS 2<<5 /* free variable from class's method */ -#define DEF_IMPORT 2<<6 /* assignment occurred via import */ -#define DEF_ANNOT 2<<7 /* this name is annotated */ -#define DEF_COMP_ITER 2<<8 /* this name is a comprehension iteration variable */ -#define DEF_TYPE_PARAM 2<<9 /* this name is a type parameter */ -#define DEF_COMP_CELL 2<<10 /* this name is a cell in an inlined comprehension */ +#define DEF_GLOBAL 1 /* global stmt */ +#define DEF_LOCAL 2 /* assignment in code block */ +#define DEF_PARAM (2<<1) /* formal parameter */ +#define DEF_NONLOCAL (2<<2) /* nonlocal stmt */ +#define USE (2<<3) /* name is used */ +#define DEF_FREE (2<<4) /* name used but not defined in nested block */ +#define DEF_FREE_CLASS (2<<5) /* free variable from class's method */ +#define DEF_IMPORT (2<<6) /* assignment occurred via import */ +#define DEF_ANNOT (2<<7) /* this name is annotated */ +#define DEF_COMP_ITER (2<<8) /* this name is a comprehension iteration variable */ +#define DEF_TYPE_PARAM (2<<9) /* this name is a type parameter */ +#define DEF_COMP_CELL (2<<10) /* this name is a cell in an inlined comprehension */ #define DEF_BOUND (DEF_LOCAL | DEF_PARAM | DEF_IMPORT) From 38b970dfcc3cdebc87a456f17ef1e0f06dde7375 Mon Sep 17 00:00:00 2001 From: Alex Gaynor <alex.gaynor@gmail.com> Date: Wed, 7 Feb 2024 17:21:33 -0500 Subject: [PATCH 241/263] When the Py_CompileStringExFlags fuzzer encounters a SystemError, abort (#115147) This allows us to catch bugs beyond memory corruption and assertions. --- Modules/_xxtestfuzz/fuzzer.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c index e133b4d3c444809..6ea9f64d6285304 100644 --- a/Modules/_xxtestfuzz/fuzzer.c +++ b/Modules/_xxtestfuzz/fuzzer.c @@ -502,7 +502,6 @@ static int fuzz_elementtree_parsewhole(const char* data, size_t size) { } #define MAX_PYCOMPILE_TEST_SIZE 16384 -static char pycompile_scratch[MAX_PYCOMPILE_TEST_SIZE]; static const int start_vals[] = {Py_eval_input, Py_single_input, Py_file_input}; const size_t NUM_START_VALS = sizeof(start_vals) / sizeof(start_vals[0]); @@ -531,6 +530,8 @@ static int fuzz_pycompile(const char* data, size_t size) { unsigned char optimize_idx = (unsigned char) data[1]; int optimize = optimize_vals[optimize_idx % NUM_OPTIMIZE_VALS]; + char pycompile_scratch[MAX_PYCOMPILE_TEST_SIZE]; + // Create a NUL-terminated C string from the remaining input memcpy(pycompile_scratch, data + 2, size - 2); // Put a NUL terminator just after the copied data. (Space was reserved already.) @@ -549,7 +550,13 @@ static int fuzz_pycompile(const char* data, size_t size) { PyObject *result = Py_CompileStringExFlags(pycompile_scratch, "<fuzz input>", start, flags, optimize); if (result == NULL) { - /* compilation failed, most likely from a syntax error */ + /* Compilation failed, most likely from a syntax error. If it was a + SystemError we abort. There's no non-bug reason to raise a + SystemError. */ + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemError)) { + PyErr_Print(); + abort(); + } PyErr_Clear(); } else { Py_DECREF(result); From 4a7f63869aa61b24a7cc2d33f8a5e5a7fd0d76a4 Mon Sep 17 00:00:00 2001 From: Justin Applegate <70449145+Legoclones@users.noreply.github.com> Date: Thu, 8 Feb 2024 01:12:58 -0700 Subject: [PATCH 242/263] gh-115146: Fix typo in pickletools.py documentation (GH-115148) --- Lib/pickletools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pickletools.py b/Lib/pickletools.py index 95a77aeb2afe2af..51ee4a7a2632ac0 100644 --- a/Lib/pickletools.py +++ b/Lib/pickletools.py @@ -1253,7 +1253,7 @@ def __init__(self, name, code, arg, stack_before=[], stack_after=[pyint], proto=2, - doc="""Long integer using found-byte length. + doc="""Long integer using four-byte length. A more efficient encoding of a Python long; the long4 encoding says it all."""), From 9e90313320a2af2d9ff7049ed3842344ed236630 Mon Sep 17 00:00:00 2001 From: Artem Chernyshev <62871052+dTenebrae@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:40:38 +0300 Subject: [PATCH 243/263] gh-115136: Fix possible NULL deref in getpath_joinpath() (GH-115137) Signed-off-by: Artem Chernyshev <artem.chernyshev@red-soft.ru> --- Modules/getpath.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/getpath.c b/Modules/getpath.c index a3c8fc269d1c3cf..abed139028244ad 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -262,6 +262,10 @@ getpath_joinpath(PyObject *Py_UNUSED(self), PyObject *args) } /* Convert all parts to wchar and accumulate max final length */ wchar_t **parts = (wchar_t **)PyMem_Malloc(n * sizeof(wchar_t *)); + if (parts == NULL) { + PyErr_NoMemory(); + return NULL; + } memset(parts, 0, n * sizeof(wchar_t *)); Py_ssize_t cchFinal = 0; Py_ssize_t first = 0; From 17689e3c41d9f61bcd1928b24d3c50c37ceaf3f2 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 8 Feb 2024 01:04:41 -0800 Subject: [PATCH 244/263] gh-107944: Improve error message for getargs with bad keyword arguments (#114792) --- Doc/whatsnew/3.13.rst | 11 +++ Lib/test/test_call.py | 32 ++++++++- Lib/test/test_capi/test_getargs.py | 26 +++---- Lib/test/test_exceptions.py | 2 +- ...-01-31-09-10-10.gh-issue-107944.XWm1B-.rst | 1 + Python/getargs.c | 70 +++++++++++++++---- 6 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 2ac5afa8ce601cb..50a2a69c75ac70b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -101,6 +101,17 @@ Improved Error Messages variables. See also :ref:`using-on-controlling-color`. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) +* When an incorrect keyword argument is passed to a function, the error message + now potentially suggests the correct keyword argument. + (Contributed by Pablo Galindo Salgado and Shantanu Jain in :gh:`107944`.) + + >>> "better error messages!".split(max_split=1) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + "better error messages!".split(max_split=1) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + TypeError: split() got an unexpected keyword argument 'max_split'. Did you mean 'maxsplit'? + Other Language Changes ====================== diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 3c8fc35e3c116d0..2a6a5d287b04ee9 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -155,7 +155,7 @@ def test_varargs16_kw(self): min, 0, default=1, key=2, foo=3) def test_varargs17_kw(self): - msg = r"'foo' is an invalid keyword argument for print\(\)$" + msg = r"print\(\) got an unexpected keyword argument 'foo'$" self.assertRaisesRegex(TypeError, msg, print, 0, sep=1, end=2, file=3, flush=4, foo=5) @@ -928,7 +928,7 @@ def check_suggestion_includes(self, message): self.assertIn(f"Did you mean '{message}'?", str(cm.exception)) @contextlib.contextmanager - def check_suggestion_not_pressent(self): + def check_suggestion_not_present(self): with self.assertRaises(TypeError) as cm: yield self.assertNotIn("Did you mean", str(cm.exception)) @@ -946,7 +946,7 @@ def foo(blech=None, /, aaa=None, *args, late1=None): for keyword, suggestion in cases: with self.subTest(keyword): - ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_pressent() + ctx = self.check_suggestion_includes(suggestion) if suggestion else self.check_suggestion_not_present() with ctx: foo(**{keyword:None}) @@ -987,6 +987,32 @@ def case_change_over_substitution(BLuch=None, Luch = None, fluch = None): with self.check_suggestion_includes(suggestion): func(bluch=None) + def test_unexpected_keyword_suggestion_via_getargs(self): + with self.check_suggestion_includes("maxsplit"): + "foo".split(maxsplt=1) + + self.assertRaisesRegex( + TypeError, r"split\(\) got an unexpected keyword argument 'blech'$", + "foo".split, blech=1 + ) + with self.check_suggestion_not_present(): + "foo".split(blech=1) + with self.check_suggestion_not_present(): + "foo".split(more_noise=1, maxsplt=1) + + # Also test the vgetargskeywords path + with self.check_suggestion_includes("name"): + ImportError(namez="oops") + + self.assertRaisesRegex( + TypeError, r"ImportError\(\) got an unexpected keyword argument 'blech'$", + ImportError, blech=1 + ) + with self.check_suggestion_not_present(): + ImportError(blech=1) + with self.check_suggestion_not_present(): + ImportError(blech=1, namez="oops") + @cpython_only class TestRecursion(unittest.TestCase): diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py index 9b6aef27625ad0e..12039803ba543eb 100644 --- a/Lib/test/test_capi/test_getargs.py +++ b/Lib/test/test_capi/test_getargs.py @@ -667,7 +667,7 @@ def test_invalid_keyword(self): try: getargs_keywords((1,2),3,arg5=10,arg666=666) except TypeError as err: - self.assertEqual(str(err), "'arg666' is an invalid keyword argument for this function") + self.assertEqual(str(err), "this function got an unexpected keyword argument 'arg666'") else: self.fail('TypeError should have been raised') @@ -675,7 +675,7 @@ def test_surrogate_keyword(self): try: getargs_keywords((1,2), 3, (4,(5,6)), (7,8,9), **{'\uDC80': 10}) except TypeError as err: - self.assertEqual(str(err), "'\udc80' is an invalid keyword argument for this function") + self.assertEqual(str(err), "this function got an unexpected keyword argument '\udc80'") else: self.fail('TypeError should have been raised') @@ -742,12 +742,12 @@ def test_too_many_args(self): def test_invalid_keyword(self): # extraneous keyword arg with self.assertRaisesRegex(TypeError, - "'monster' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument 'monster'"): getargs_keyword_only(1, 2, monster=666) def test_surrogate_keyword(self): with self.assertRaisesRegex(TypeError, - "'\udc80' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument '\udc80'"): getargs_keyword_only(1, 2, **{'\uDC80': 10}) def test_weird_str_subclass(self): @@ -761,7 +761,7 @@ def __hash__(self): "invalid keyword argument for this function"): getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3}) with self.assertRaisesRegex(TypeError, - "invalid keyword argument for this function"): + "this function got an unexpected keyword argument"): getargs_keyword_only(1, 2, **{BadStr("monster"): 666}) def test_weird_str_subclass2(self): @@ -774,7 +774,7 @@ def __hash__(self): "invalid keyword argument for this function"): getargs_keyword_only(1, 2, **{BadStr("keyword_only"): 3}) with self.assertRaisesRegex(TypeError, - "invalid keyword argument for this function"): + "this function got an unexpected keyword argument"): getargs_keyword_only(1, 2, **{BadStr("monster"): 666}) @@ -807,7 +807,7 @@ def test_required_args(self): def test_empty_keyword(self): with self.assertRaisesRegex(TypeError, - "'' is an invalid keyword argument for this function"): + "this function got an unexpected keyword argument ''"): self.getargs(1, 2, **{'': 666}) @@ -1204,7 +1204,7 @@ def test_basic(self): "function missing required argument 'a'"): parse((), {}, 'O', ['a']) with self.assertRaisesRegex(TypeError, - "'b' is an invalid keyword argument"): + "this function got an unexpected keyword argument 'b'"): parse((), {'b': 1}, '|O', ['a']) with self.assertRaisesRegex(TypeError, fr"argument for function given by name \('a'\) " @@ -1278,10 +1278,10 @@ def test_nonascii_keywords(self): fr"and position \(1\)"): parse((1,), {name: 2}, 'O|O', [name, 'b']) with self.assertRaisesRegex(TypeError, - f"'{name}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name}'"): parse((), {name: 1}, '|O', ['b']) with self.assertRaisesRegex(TypeError, - "'b' is an invalid keyword argument"): + "this function got an unexpected keyword argument 'b'"): parse((), {'b': 1}, '|O', [name]) invalid = name.encode() + (name.encode()[:-1] or b'\x80') @@ -1301,17 +1301,17 @@ def test_nonascii_keywords(self): for name2 in ('b', 'ë', 'ĉ', 'Ɐ', '𐀁'): with self.subTest(name2=name2): with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1}, '|O', [name]) name2 = name.encode().decode('latin1') if name2 != name: with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1}, '|O', [name]) name3 = name + '3' with self.assertRaisesRegex(TypeError, - f"'{name2}' is an invalid keyword argument"): + f"this function got an unexpected keyword argument '{name2}'"): parse((), {name2: 1, name3: 2}, '|OO', [name, name3]) def test_nested_tuple(self): diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c57488e44aecc64..c7e76414ff07154 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1917,7 +1917,7 @@ def test_attributes(self): self.assertEqual(exc.name, 'somename') self.assertEqual(exc.path, 'somepath') - msg = "'invalid' is an invalid keyword argument for ImportError" + msg = r"ImportError\(\) got an unexpected keyword argument 'invalid'" with self.assertRaisesRegex(TypeError, msg): ImportError('test', invalid='keyword') diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst new file mode 100644 index 000000000000000..8e3fb786c11055b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-31-09-10-10.gh-issue-107944.XWm1B-.rst @@ -0,0 +1 @@ +Improve error message for function calls with bad keyword arguments via getargs diff --git a/Python/getargs.c b/Python/getargs.c index 0c4ce282f487648..08e97ee3e627b5a 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -8,6 +8,7 @@ #include "pycore_modsupport.h" // export _PyArg_NoKeywords() #include "pycore_pylifecycle.h" // _PyArg_Fini #include "pycore_tuple.h" // _PyTuple_ITEMS() +#include "pycore_pyerrors.h" // _Py_CalculateSuggestions() /* Export Stable ABIs (abi only) */ PyAPI_FUNC(int) _PyArg_Parse_SizeT(PyObject *, const char *, ...); @@ -1424,12 +1425,31 @@ error_unexpected_keyword_arg(PyObject *kwargs, PyObject *kwnames, PyObject *kwtu int match = PySequence_Contains(kwtuple, keyword); if (match <= 0) { if (!match) { - PyErr_Format(PyExc_TypeError, - "'%S' is an invalid keyword " - "argument for %.200s%s", - keyword, - (fname == NULL) ? "this function" : fname, - (fname == NULL) ? "" : "()"); + PyObject *kwlist = PySequence_List(kwtuple); + if (!kwlist) { + return; + } + PyObject *suggestion_keyword = _Py_CalculateSuggestions(kwlist, keyword); + Py_DECREF(kwlist); + + if (suggestion_keyword) { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'." + " Did you mean '%S'?", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + keyword, + suggestion_keyword); + Py_DECREF(suggestion_keyword); + } + else { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + keyword); + } + } return; } @@ -1457,6 +1477,9 @@ PyArg_ValidateKeywordArguments(PyObject *kwargs) return 1; } +static PyObject * +new_kwtuple(const char * const *keywords, int total, int pos); + #define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':') static int @@ -1722,12 +1745,35 @@ vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format, } } if (!match) { - PyErr_Format(PyExc_TypeError, - "'%U' is an invalid keyword " - "argument for %.200s%s", - key, - (fname == NULL) ? "this function" : fname, - (fname == NULL) ? "" : "()"); + PyObject *_pykwtuple = new_kwtuple(kwlist, len, pos); + if (!_pykwtuple) { + return cleanreturn(0, &freelist); + } + PyObject *pykwlist = PySequence_List(_pykwtuple); + Py_DECREF(_pykwtuple); + if (!pykwlist) { + return cleanreturn(0, &freelist); + } + PyObject *suggestion_keyword = _Py_CalculateSuggestions(pykwlist, key); + Py_DECREF(pykwlist); + + if (suggestion_keyword) { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'." + " Did you mean '%S'?", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + key, + suggestion_keyword); + Py_DECREF(suggestion_keyword); + } + else { + PyErr_Format(PyExc_TypeError, + "%.200s%s got an unexpected keyword argument '%S'", + (fname == NULL) ? "this function" : fname, + (fname == NULL) ? "" : "()", + key); + } return cleanreturn(0, &freelist); } } From ed1a8daf10bc471f929c14c2d1e0474d44a63b00 Mon Sep 17 00:00:00 2001 From: Tomas R <tomas.roun8@gmail.com> Date: Thu, 8 Feb 2024 17:47:27 +0100 Subject: [PATCH 245/263] gh-112069: Adapt set/frozenset methods to Argument Clinic (#115112) --- ...-02-07-00-18-42.gh-issue-112069.jRDRR5.rst | 1 + Objects/clinic/setobject.c.h | 414 +++++++++++++++++ Objects/setobject.c | 416 +++++++++++------- 3 files changed, 674 insertions(+), 157 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst create mode 100644 Objects/clinic/setobject.c.h diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst new file mode 100644 index 000000000000000..51ba6bd1ddaac35 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-07-00-18-42.gh-issue-112069.jRDRR5.rst @@ -0,0 +1 @@ +Adapt :class:`set` and :class:`frozenset` methods to Argument Clinic. diff --git a/Objects/clinic/setobject.c.h b/Objects/clinic/setobject.c.h new file mode 100644 index 000000000000000..f3c96995ede60dd --- /dev/null +++ b/Objects/clinic/setobject.c.h @@ -0,0 +1,414 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(set_pop__doc__, +"pop($self, /)\n" +"--\n" +"\n" +"Remove and return an arbitrary set element.\n" +"\n" +"Raises KeyError if the set is empty."); + +#define SET_POP_METHODDEF \ + {"pop", (PyCFunction)set_pop, METH_NOARGS, set_pop__doc__}, + +static PyObject * +set_pop_impl(PySetObject *so); + +static PyObject * +set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_pop_impl(so); +} + +PyDoc_STRVAR(set_update__doc__, +"update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, adding elements from all others."); + +#define SET_UPDATE_METHODDEF \ + {"update", _PyCFunction_CAST(set_update), METH_FASTCALL, set_update__doc__}, + +static PyObject * +set_update_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_update(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_update_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a set."); + +#define SET_COPY_METHODDEF \ + {"copy", (PyCFunction)set_copy, METH_NOARGS, set_copy__doc__}, + +static PyObject * +set_copy_impl(PySetObject *so); + +static PyObject * +set_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_copy_impl(so); +} + +PyDoc_STRVAR(frozenset_copy__doc__, +"copy($self, /)\n" +"--\n" +"\n" +"Return a shallow copy of a set."); + +#define FROZENSET_COPY_METHODDEF \ + {"copy", (PyCFunction)frozenset_copy, METH_NOARGS, frozenset_copy__doc__}, + +static PyObject * +frozenset_copy_impl(PySetObject *so); + +static PyObject * +frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return frozenset_copy_impl(so); +} + +PyDoc_STRVAR(set_clear__doc__, +"clear($self, /)\n" +"--\n" +"\n" +"Remove all elements from this set."); + +#define SET_CLEAR_METHODDEF \ + {"clear", (PyCFunction)set_clear, METH_NOARGS, set_clear__doc__}, + +static PyObject * +set_clear_impl(PySetObject *so); + +static PyObject * +set_clear(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set_clear_impl(so); +} + +PyDoc_STRVAR(set_union__doc__, +"union($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements from the set and all others."); + +#define SET_UNION_METHODDEF \ + {"union", _PyCFunction_CAST(set_union), METH_FASTCALL, set_union__doc__}, + +static PyObject * +set_union_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_union(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("union", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_union_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_intersection_multi__doc__, +"intersection($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements common to the set and all others."); + +#define SET_INTERSECTION_MULTI_METHODDEF \ + {"intersection", _PyCFunction_CAST(set_intersection_multi), METH_FASTCALL, set_intersection_multi__doc__}, + +static PyObject * +set_intersection_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_intersection_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("intersection", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_intersection_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_intersection_update_multi__doc__, +"intersection_update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, keeping only elements found in it and all others."); + +#define SET_INTERSECTION_UPDATE_MULTI_METHODDEF \ + {"intersection_update", _PyCFunction_CAST(set_intersection_update_multi), METH_FASTCALL, set_intersection_update_multi__doc__}, + +static PyObject * +set_intersection_update_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_intersection_update_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("intersection_update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_intersection_update_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_isdisjoint__doc__, +"isdisjoint($self, other, /)\n" +"--\n" +"\n" +"Return True if two sets have a null intersection."); + +#define SET_ISDISJOINT_METHODDEF \ + {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, set_isdisjoint__doc__}, + +PyDoc_STRVAR(set_difference_update__doc__, +"difference_update($self, /, *others)\n" +"--\n" +"\n" +"Update the set, removing elements found in others."); + +#define SET_DIFFERENCE_UPDATE_METHODDEF \ + {"difference_update", _PyCFunction_CAST(set_difference_update), METH_FASTCALL, set_difference_update__doc__}, + +static PyObject * +set_difference_update_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_difference_update(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("difference_update", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_difference_update_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_difference_multi__doc__, +"difference($self, /, *others)\n" +"--\n" +"\n" +"Return a new set with elements in the set that are not in the others."); + +#define SET_DIFFERENCE_MULTI_METHODDEF \ + {"difference", _PyCFunction_CAST(set_difference_multi), METH_FASTCALL, set_difference_multi__doc__}, + +static PyObject * +set_difference_multi_impl(PySetObject *so, PyObject *args); + +static PyObject * +set_difference_multi(PySetObject *so, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *__clinic_args = NULL; + + if (!_PyArg_CheckPositional("difference", nargs, 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_New(nargs - 0); + if (!__clinic_args) { + goto exit; + } + for (Py_ssize_t i = 0; i < nargs - 0; ++i) { + PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[0 + i])); + } + return_value = set_difference_multi_impl(so, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +PyDoc_STRVAR(set_symmetric_difference_update__doc__, +"symmetric_difference_update($self, other, /)\n" +"--\n" +"\n" +"Update the set, keeping only elements found in either set, but not in both."); + +#define SET_SYMMETRIC_DIFFERENCE_UPDATE_METHODDEF \ + {"symmetric_difference_update", (PyCFunction)set_symmetric_difference_update, METH_O, set_symmetric_difference_update__doc__}, + +PyDoc_STRVAR(set_symmetric_difference__doc__, +"symmetric_difference($self, other, /)\n" +"--\n" +"\n" +"Return a new set with elements in either the set or other but not both."); + +#define SET_SYMMETRIC_DIFFERENCE_METHODDEF \ + {"symmetric_difference", (PyCFunction)set_symmetric_difference, METH_O, set_symmetric_difference__doc__}, + +PyDoc_STRVAR(set_issubset__doc__, +"issubset($self, other, /)\n" +"--\n" +"\n" +"Report whether another set contains this set."); + +#define SET_ISSUBSET_METHODDEF \ + {"issubset", (PyCFunction)set_issubset, METH_O, set_issubset__doc__}, + +PyDoc_STRVAR(set_issuperset__doc__, +"issuperset($self, other, /)\n" +"--\n" +"\n" +"Report whether this set contains another set."); + +#define SET_ISSUPERSET_METHODDEF \ + {"issuperset", (PyCFunction)set_issuperset, METH_O, set_issuperset__doc__}, + +PyDoc_STRVAR(set_add__doc__, +"add($self, object, /)\n" +"--\n" +"\n" +"Add an element to a set.\n" +"\n" +"This has no effect if the element is already present."); + +#define SET_ADD_METHODDEF \ + {"add", (PyCFunction)set_add, METH_O, set_add__doc__}, + +PyDoc_STRVAR(set___contains____doc__, +"__contains__($self, object, /)\n" +"--\n" +"\n" +"x.__contains__(y) <==> y in x."); + +#define SET___CONTAINS___METHODDEF \ + {"__contains__", (PyCFunction)set___contains__, METH_O|METH_COEXIST, set___contains____doc__}, + +PyDoc_STRVAR(set_remove__doc__, +"remove($self, object, /)\n" +"--\n" +"\n" +"Remove an element from a set; it must be a member.\n" +"\n" +"If the element is not a member, raise a KeyError."); + +#define SET_REMOVE_METHODDEF \ + {"remove", (PyCFunction)set_remove, METH_O, set_remove__doc__}, + +PyDoc_STRVAR(set_discard__doc__, +"discard($self, object, /)\n" +"--\n" +"\n" +"Remove an element from a set if it is a member.\n" +"\n" +"Unlike set.remove(), the discard() method does not raise\n" +"an exception when an element is missing from the set."); + +#define SET_DISCARD_METHODDEF \ + {"discard", (PyCFunction)set_discard, METH_O, set_discard__doc__}, + +PyDoc_STRVAR(set___reduce____doc__, +"__reduce__($self, /)\n" +"--\n" +"\n" +"Return state information for pickling."); + +#define SET___REDUCE___METHODDEF \ + {"__reduce__", (PyCFunction)set___reduce__, METH_NOARGS, set___reduce____doc__}, + +static PyObject * +set___reduce___impl(PySetObject *so); + +static PyObject * +set___reduce__(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set___reduce___impl(so); +} + +PyDoc_STRVAR(set___sizeof____doc__, +"__sizeof__($self, /)\n" +"--\n" +"\n" +"S.__sizeof__() -> size of S in memory, in bytes."); + +#define SET___SIZEOF___METHODDEF \ + {"__sizeof__", (PyCFunction)set___sizeof__, METH_NOARGS, set___sizeof____doc__}, + +static PyObject * +set___sizeof___impl(PySetObject *so); + +static PyObject * +set___sizeof__(PySetObject *so, PyObject *Py_UNUSED(ignored)) +{ + return set___sizeof___impl(so); +} +/*[clinic end generated code: output=34a30591148da884 input=a9049054013a1b77]*/ diff --git a/Objects/setobject.c b/Objects/setobject.c index 3acf2a7a74890b1..6a4c8c45f0836d1 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -40,6 +40,19 @@ #include "pycore_pyerrors.h" // _PyErr_SetKeyError() #include "pycore_setobject.h" // _PySet_NextEntry() definition #include <stddef.h> // offsetof() +#include "clinic/setobject.c.h" + +/*[clinic input] +class set "PySetObject *" "&PySet_Type" +class frozenset "PySetObject *" "&PyFrozenSet_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=97ad1d3e9f117079]*/ + +/*[python input] +class setobject_converter(self_converter): + type = "PySetObject *" +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=33a44506d4d57793]*/ /* Object used as dummy key to fill deleted entries */ static PyObject _dummy_struct; @@ -631,8 +644,18 @@ set_merge(PySetObject *so, PyObject *otherset) return 0; } +/*[clinic input] +set.pop + so: setobject + +Remove and return an arbitrary set element. + +Raises KeyError if the set is empty. +[clinic start generated code]*/ + static PyObject * -set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_pop_impl(PySetObject *so) +/*[clinic end generated code: output=4d65180f1271871b input=4a3f5552e660a260]*/ { /* Make sure the search finger is in bounds */ setentry *entry = so->table + (so->finger & so->mask); @@ -656,9 +679,6 @@ set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) return key; } -PyDoc_STRVAR(pop_doc, "Remove and return an arbitrary set element.\n\ -Raises KeyError if the set is empty."); - static int set_traverse(PySetObject *so, visitproc visit, void *arg) { @@ -935,8 +955,18 @@ set_update_internal(PySetObject *so, PyObject *other) return 0; } +/*[clinic input] +set.update + so: setobject + *others as args: object + / + +Update the set, adding elements from all others. +[clinic start generated code]*/ + static PyObject * -set_update(PySetObject *so, PyObject *args) +set_update_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=34f6371704974c8a input=eb47c4fbaeb3286e]*/ { Py_ssize_t i; @@ -948,12 +978,6 @@ set_update(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(update_doc, -"update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, adding elements from all others."); - /* XXX Todo: If aligned memory allocations become available, make the set object 64 byte aligned so that most of the fields @@ -1101,14 +1125,30 @@ set_swap_bodies(PySetObject *a, PySetObject *b) } } +/*[clinic input] +set.copy + so: setobject + +Return a shallow copy of a set. +[clinic start generated code]*/ + static PyObject * -set_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_copy_impl(PySetObject *so) +/*[clinic end generated code: output=c9223a1e1cc6b041 input=2b80b288d47b8cf1]*/ { return make_new_set_basetype(Py_TYPE(so), (PyObject *)so); } +/*[clinic input] +frozenset.copy + so: setobject + +Return a shallow copy of a set. +[clinic start generated code]*/ + static PyObject * -frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) +frozenset_copy_impl(PySetObject *so) +/*[clinic end generated code: output=b356263526af9e70 input=3dc65577d344eff7]*/ { if (PyFrozenSet_CheckExact(so)) { return Py_NewRef(so); @@ -1116,19 +1156,33 @@ frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) return set_copy(so, NULL); } -PyDoc_STRVAR(copy_doc, "Return a shallow copy of a set."); +/*[clinic input] +set.clear + so: setobject + +Remove all elements from this set. +[clinic start generated code]*/ static PyObject * -set_clear(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set_clear_impl(PySetObject *so) +/*[clinic end generated code: output=4e71d5a83904161a input=74ac19794da81a39]*/ { set_clear_internal(so); Py_RETURN_NONE; } -PyDoc_STRVAR(clear_doc, "Remove all elements from this set."); +/*[clinic input] +set.union + so: setobject + *others as args: object + / + +Return a new set with elements from the set and all others. +[clinic start generated code]*/ static PyObject * -set_union(PySetObject *so, PyObject *args) +set_union_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=2c83d05a446a1477 input=2e2024fa1e40ac84]*/ { PySetObject *result; PyObject *other; @@ -1150,12 +1204,6 @@ set_union(PySetObject *so, PyObject *args) return (PyObject *)result; } -PyDoc_STRVAR(union_doc, -"union($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements from the set and all others."); - static PyObject * set_or(PySetObject *so, PyObject *other) { @@ -1270,8 +1318,18 @@ set_intersection(PySetObject *so, PyObject *other) return NULL; } +/*[clinic input] +set.intersection as set_intersection_multi + so: setobject + *others as args: object + / + +Return a new set with elements common to the set and all others. +[clinic start generated code]*/ + static PyObject * -set_intersection_multi(PySetObject *so, PyObject *args) +set_intersection_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=2406ef3387adbe2f input=04108ea6d7f0532b]*/ { Py_ssize_t i; @@ -1291,12 +1349,6 @@ set_intersection_multi(PySetObject *so, PyObject *args) return result; } -PyDoc_STRVAR(intersection_doc, -"intersection($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements common to the set and all others."); - static PyObject * set_intersection_update(PySetObject *so, PyObject *other) { @@ -1310,12 +1362,22 @@ set_intersection_update(PySetObject *so, PyObject *other) Py_RETURN_NONE; } +/*[clinic input] +set.intersection_update as set_intersection_update_multi + so: setobject + *others as args: object + / + +Update the set, keeping only elements found in it and all others. +[clinic start generated code]*/ + static PyObject * -set_intersection_update_multi(PySetObject *so, PyObject *args) +set_intersection_update_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=251c1f729063609d input=ff8f119f97458d16]*/ { PyObject *tmp; - tmp = set_intersection_multi(so, args); + tmp = set_intersection_multi_impl(so, args); if (tmp == NULL) return NULL; set_swap_bodies(so, (PySetObject *)tmp); @@ -1323,12 +1385,6 @@ set_intersection_update_multi(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(intersection_update_doc, -"intersection_update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, keeping only elements found in it and all others."); - static PyObject * set_and(PySetObject *so, PyObject *other) { @@ -1351,8 +1407,18 @@ set_iand(PySetObject *so, PyObject *other) return Py_NewRef(so); } +/*[clinic input] +set.isdisjoint + so: setobject + other: object + / + +Return True if two sets have a null intersection. +[clinic start generated code]*/ + static PyObject * set_isdisjoint(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=a92bbf9a2db6a3da input=c254ddec8a2326e3]*/ { PyObject *key, *it, *tmp; int rv; @@ -1410,9 +1476,6 @@ set_isdisjoint(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(isdisjoint_doc, -"Return True if two sets have a null intersection."); - static int set_difference_update_internal(PySetObject *so, PyObject *other) { @@ -1471,8 +1534,18 @@ set_difference_update_internal(PySetObject *so, PyObject *other) return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4); } +/*[clinic input] +set.difference_update + so: setobject + *others as args: object + / + +Update the set, removing elements found in others. +[clinic start generated code]*/ + static PyObject * -set_difference_update(PySetObject *so, PyObject *args) +set_difference_update_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=28685b2fc63e41c4 input=e7abb43c9f2c5a73]*/ { Py_ssize_t i; @@ -1484,12 +1557,6 @@ set_difference_update(PySetObject *so, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(difference_update_doc, -"difference_update($self, /, *others)\n\ ---\n\ -\n\ -Update the set, removing elements found in others."); - static PyObject * set_copy_and_difference(PySetObject *so, PyObject *other) { @@ -1580,8 +1647,18 @@ set_difference(PySetObject *so, PyObject *other) return result; } +/*[clinic input] +set.difference as set_difference_multi + so: setobject + *others as args: object + / + +Return a new set with elements in the set that are not in the others. +[clinic start generated code]*/ + static PyObject * -set_difference_multi(PySetObject *so, PyObject *args) +set_difference_multi_impl(PySetObject *so, PyObject *args) +/*[clinic end generated code: output=3130c3bb3cac873d input=d8ae9bb6d518ab95]*/ { Py_ssize_t i; PyObject *result, *other; @@ -1604,11 +1681,6 @@ set_difference_multi(PySetObject *so, PyObject *args) return result; } -PyDoc_STRVAR(difference_doc, -"difference($self, /, *others)\n\ ---\n\ -\n\ -Return a new set with elements in the set that are not in the others."); static PyObject * set_sub(PySetObject *so, PyObject *other) { @@ -1654,8 +1726,18 @@ set_symmetric_difference_update_dict(PySetObject *so, PyObject *other) Py_RETURN_NONE; } +/*[clinic input] +set.symmetric_difference_update + so: setobject + other: object + / + +Update the set, keeping only elements found in either set, but not in both. +[clinic start generated code]*/ + static PyObject * set_symmetric_difference_update(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=fbb049c0806028de input=a50acf0365e1f0a5]*/ { PySetObject *otherset; PyObject *key; @@ -1708,14 +1790,18 @@ set_symmetric_difference_update(PySetObject *so, PyObject *other) Py_RETURN_NONE; } -PyDoc_STRVAR(symmetric_difference_update_doc, -"symmetric_difference_update($self, other, /)\n\ ---\n\ -\n\ -Update the set, keeping only elements found in either set, but not in both."); +/*[clinic input] +set.symmetric_difference + so: setobject + other: object + / + +Return a new set with elements in either the set or other but not both. +[clinic start generated code]*/ static PyObject * set_symmetric_difference(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=f95364211b88775a input=f18af370ad72ebac]*/ { PyObject *rv; PySetObject *otherset; @@ -1732,12 +1818,6 @@ set_symmetric_difference(PySetObject *so, PyObject *other) return (PyObject *)otherset; } -PyDoc_STRVAR(symmetric_difference_doc, -"symmetric_difference($self, other, /)\n\ ---\n\ -\n\ -Return a new set with elements in either the set or other but not both."); - static PyObject * set_xor(PySetObject *so, PyObject *other) { @@ -1760,8 +1840,18 @@ set_ixor(PySetObject *so, PyObject *other) return Py_NewRef(so); } +/*[clinic input] +set.issubset + so: setobject + other: object + / + +Report whether another set contains this set. +[clinic start generated code]*/ + static PyObject * set_issubset(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=78aef1f377aedef1 input=37fbc579b609db0c]*/ { setentry *entry; Py_ssize_t pos = 0; @@ -1794,14 +1884,18 @@ set_issubset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issubset_doc, -"issubset($self, other, /)\n\ ---\n\ -\n\ -Test whether every element in the set is in other."); +/*[clinic input] +set.issuperset + so: setobject + other: object + / + +Report whether this set contains another set. +[clinic start generated code]*/ static PyObject * set_issuperset(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=7d2b71dd714a7ec7 input=fd5dab052f2e9bb3]*/ { if (PyAnySet_Check(other)) { return set_issubset((PySetObject *)other, (PyObject *)so); @@ -1830,12 +1924,6 @@ set_issuperset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } -PyDoc_STRVAR(issuperset_doc, -"issuperset($self, other, /)\n\ ---\n\ -\n\ -Test whether every element in other is in the set."); - static PyObject * set_richcompare(PySetObject *v, PyObject *w, int op) { @@ -1879,19 +1967,26 @@ set_richcompare(PySetObject *v, PyObject *w, int op) Py_RETURN_NOTIMPLEMENTED; } +/*[clinic input] +set.add + so: setobject + object as key: object + / + +Add an element to a set. + +This has no effect if the element is already present. +[clinic start generated code]*/ + static PyObject * set_add(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=cd9c2d5c2069c2ba input=96f1efe029e47972]*/ { if (set_add_key(so, key)) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(add_doc, -"Add an element to a set.\n\ -\n\ -This has no effect if the element is already present."); - static int set_contains(PySetObject *so, PyObject *key) { @@ -1912,8 +2007,19 @@ set_contains(PySetObject *so, PyObject *key) return rv; } +/*[clinic input] +@coexist +set.__contains__ + so: setobject + object as key: object + / + +x.__contains__(y) <==> y in x. +[clinic start generated code]*/ + static PyObject * -set_direct_contains(PySetObject *so, PyObject *key) +set___contains__(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=b5948bc5c590d3ca input=cf4c72db704e4cf0]*/ { long result; @@ -1923,10 +2029,20 @@ set_direct_contains(PySetObject *so, PyObject *key) return PyBool_FromLong(result); } -PyDoc_STRVAR(contains_doc, "x.__contains__(y) <==> y in x."); +/*[clinic input] +set.remove + so: setobject + object as key: object + / + +Remove an element from a set; it must be a member. + +If the element is not a member, raise a KeyError. +[clinic start generated code]*/ static PyObject * set_remove(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=08ae496d0cd2b8c1 input=10132515dfe8ebd7]*/ { PyObject *tmpkey; int rv; @@ -1952,13 +2068,21 @@ set_remove(PySetObject *so, PyObject *key) Py_RETURN_NONE; } -PyDoc_STRVAR(remove_doc, -"Remove an element from a set; it must be a member.\n\ -\n\ -If the element is not a member, raise a KeyError."); +/*[clinic input] +set.discard + so: setobject + object as key: object + / + +Remove an element from a set if it is a member. + +Unlike set.remove(), the discard() method does not raise +an exception when an element is missing from the set. +[clinic start generated code]*/ static PyObject * set_discard(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=9181b60d7bb7d480 input=82a689eba94d5ad9]*/ { PyObject *tmpkey; int rv; @@ -1979,14 +2103,16 @@ set_discard(PySetObject *so, PyObject *key) Py_RETURN_NONE; } -PyDoc_STRVAR(discard_doc, -"Remove an element from a set if it is a member.\n\ -\n\ -Unlike set.remove(), the discard() method does not raise\n\ -an exception when an element is missing from the set."); +/*[clinic input] +set.__reduce__ + so: setobject + +Return state information for pickling. +[clinic start generated code]*/ static PyObject * -set_reduce(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set___reduce___impl(PySetObject *so) +/*[clinic end generated code: output=9af7d0e029df87ee input=531375e87a24a449]*/ { PyObject *keys=NULL, *args=NULL, *result=NULL, *state=NULL; @@ -2007,8 +2133,16 @@ set_reduce(PySetObject *so, PyObject *Py_UNUSED(ignored)) return result; } +/*[clinic input] +set.__sizeof__ + so: setobject + +S.__sizeof__() -> size of S in memory, in bytes. +[clinic start generated code]*/ + static PyObject * -set_sizeof(PySetObject *so, PyObject *Py_UNUSED(ignored)) +set___sizeof___impl(PySetObject *so) +/*[clinic end generated code: output=4bfa3df7bd38ed88 input=0f214fc2225319fc]*/ { size_t res = _PyObject_SIZE(Py_TYPE(so)); if (so->table != so->smalltable) { @@ -2017,7 +2151,6 @@ set_sizeof(PySetObject *so, PyObject *Py_UNUSED(ignored)) return PyLong_FromSize_t(res); } -PyDoc_STRVAR(sizeof_doc, "S.__sizeof__() -> size of S in memory, in bytes"); static int set_init(PySetObject *self, PyObject *args, PyObject *kwds) { @@ -2071,46 +2204,26 @@ static PySequenceMethods set_as_sequence = { /* set object ********************************************************/ static PyMethodDef set_methods[] = { - {"add", (PyCFunction)set_add, METH_O, - add_doc}, - {"clear", (PyCFunction)set_clear, METH_NOARGS, - clear_doc}, - {"__contains__",(PyCFunction)set_direct_contains, METH_O | METH_COEXIST, - contains_doc}, - {"copy", (PyCFunction)set_copy, METH_NOARGS, - copy_doc}, - {"discard", (PyCFunction)set_discard, METH_O, - discard_doc}, - {"difference", (PyCFunction)set_difference_multi, METH_VARARGS, - difference_doc}, - {"difference_update", (PyCFunction)set_difference_update, METH_VARARGS, - difference_update_doc}, - {"intersection",(PyCFunction)set_intersection_multi, METH_VARARGS, - intersection_doc}, - {"intersection_update",(PyCFunction)set_intersection_update_multi, METH_VARARGS, - intersection_update_doc}, - {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, - isdisjoint_doc}, - {"issubset", (PyCFunction)set_issubset, METH_O, - issubset_doc}, - {"issuperset", (PyCFunction)set_issuperset, METH_O, - issuperset_doc}, - {"pop", (PyCFunction)set_pop, METH_NOARGS, - pop_doc}, - {"__reduce__", (PyCFunction)set_reduce, METH_NOARGS, - reduce_doc}, - {"remove", (PyCFunction)set_remove, METH_O, - remove_doc}, - {"__sizeof__", (PyCFunction)set_sizeof, METH_NOARGS, - sizeof_doc}, - {"symmetric_difference",(PyCFunction)set_symmetric_difference, METH_O, - symmetric_difference_doc}, - {"symmetric_difference_update",(PyCFunction)set_symmetric_difference_update, METH_O, - symmetric_difference_update_doc}, - {"union", (PyCFunction)set_union, METH_VARARGS, - union_doc}, - {"update", (PyCFunction)set_update, METH_VARARGS, - update_doc}, + SET_ADD_METHODDEF + SET_CLEAR_METHODDEF + SET___CONTAINS___METHODDEF + SET_COPY_METHODDEF + SET_DISCARD_METHODDEF + SET_DIFFERENCE_MULTI_METHODDEF + SET_DIFFERENCE_UPDATE_METHODDEF + SET_INTERSECTION_MULTI_METHODDEF + SET_INTERSECTION_UPDATE_MULTI_METHODDEF + SET_ISDISJOINT_METHODDEF + SET_ISSUBSET_METHODDEF + SET_ISSUPERSET_METHODDEF + SET_POP_METHODDEF + SET___REDUCE___METHODDEF + SET_REMOVE_METHODDEF + SET___SIZEOF___METHODDEF + SET_SYMMETRIC_DIFFERENCE_METHODDEF + SET_SYMMETRIC_DIFFERENCE_UPDATE_METHODDEF + SET_UNION_METHODDEF + SET_UPDATE_METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ }; @@ -2203,28 +2316,17 @@ PyTypeObject PySet_Type = { static PyMethodDef frozenset_methods[] = { - {"__contains__",(PyCFunction)set_direct_contains, METH_O | METH_COEXIST, - contains_doc}, - {"copy", (PyCFunction)frozenset_copy, METH_NOARGS, - copy_doc}, - {"difference", (PyCFunction)set_difference_multi, METH_VARARGS, - difference_doc}, - {"intersection", (PyCFunction)set_intersection_multi, METH_VARARGS, - intersection_doc}, - {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, - isdisjoint_doc}, - {"issubset", (PyCFunction)set_issubset, METH_O, - issubset_doc}, - {"issuperset", (PyCFunction)set_issuperset, METH_O, - issuperset_doc}, - {"__reduce__", (PyCFunction)set_reduce, METH_NOARGS, - reduce_doc}, - {"__sizeof__", (PyCFunction)set_sizeof, METH_NOARGS, - sizeof_doc}, - {"symmetric_difference",(PyCFunction)set_symmetric_difference, METH_O, - symmetric_difference_doc}, - {"union", (PyCFunction)set_union, METH_VARARGS, - union_doc}, + SET___CONTAINS___METHODDEF + FROZENSET_COPY_METHODDEF + SET_DIFFERENCE_MULTI_METHODDEF + SET_INTERSECTION_MULTI_METHODDEF + SET_ISDISJOINT_METHODDEF + SET_ISSUBSET_METHODDEF + SET_ISSUPERSET_METHODDEF + SET___REDUCE___METHODDEF + SET___SIZEOF___METHODDEF + SET_SYMMETRIC_DIFFERENCE_METHODDEF + SET_UNION_METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} /* sentinel */ }; From 5914a211ef5542edd1f792c2684e373a42647b04 Mon Sep 17 00:00:00 2001 From: adang1345 <adang1345@gmail.com> Date: Thu, 8 Feb 2024 16:42:45 -0500 Subject: [PATCH 246/263] gh-115167: Exclude vcruntime140_threads.dll from Windows build output (GH-115176) --- .../next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst | 1 + PCbuild/pyproject.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst diff --git a/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst b/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst new file mode 100644 index 000000000000000..c60c4a93fe8906c --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-08-19-36-20.gh-issue-115167.LB9nDK.rst @@ -0,0 +1 @@ +Avoid vendoring ``vcruntime140_threads.dll`` when building with Visual Studio 2022 version 17.8. diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props index fd5fbc9e910eee4..9c85e5efa4af4ad 100644 --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -250,7 +250,7 @@ public override bool Execute() { <VCRuntimeDLL Include="$(VCRuntimeDLL)" /> </ItemGroup> <ItemGroup Condition="$(VCInstallDir) != '' and $(VCRuntimeDLL) == ''"> - <VCRuntimeDLL Include="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*.dll" /> + <VCRuntimeDLL Include="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*.dll" Exclude="$(VCRedistDir)\Microsoft.VC*.CRT\vcruntime*_threads.dll" /> </ItemGroup> <Warning Text="vcruntime*.dll not found under $(VCRedistDir)." Condition="@(VCRuntimeDLL) == ''" /> From 553c90ccc2f5b15be76a2bb6e38d23e58d739e2f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Fri, 9 Feb 2024 09:40:28 +0300 Subject: [PATCH 247/263] gh-101100: Fix sphinx warnings in `library/enum.rst` (#114696) Co-authored-by: Ethan Furman <ethan@stoneleaf.us> --- Doc/library/enum.rst | 17 +++++++++++++++-- Doc/tools/.nitignore | 1 - 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 534939943d33267..30d80ce8d488ccb 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -286,6 +286,19 @@ Data Types appropriate value will be chosen for you. See :class:`auto` for the details. + .. attribute:: Enum._name_ + + Name of the member. + + .. attribute:: Enum._value_ + + Value of the member, can be set in :meth:`~object.__new__`. + + .. attribute:: Enum._order_ + + No longer used, kept for backward compatibility. + (class attribute, removed during class creation). + .. attribute:: Enum._ignore_ ``_ignore_`` is only used during creation and is removed from the @@ -823,8 +836,8 @@ Supported ``_sunder_`` names - :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a :class:`str`, that will not be transformed into members, and will be removed from the final class -- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is - consistent (class attribute, removed during class creation) +- :attr:`~Enum._order_` -- no longer used, kept for backward + compatibility (class attribute, removed during class creation) - :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for an enum member; may be overridden diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index f96478b45e44c00..9db02c5c3c73c90 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -31,7 +31,6 @@ Doc/library/email.compat32-message.rst Doc/library/email.errors.rst Doc/library/email.parser.rst Doc/library/email.policy.rst -Doc/library/enum.rst Doc/library/exceptions.rst Doc/library/faulthandler.rst Doc/library/fcntl.rst From c968dc7ff3041137bb702436ff944692dede1ad1 Mon Sep 17 00:00:00 2001 From: Brett Cannon <brett@python.org> Date: Fri, 9 Feb 2024 00:21:49 -0800 Subject: [PATCH 248/263] GH-113632: update configure.ac for WebAssembly support tiers (#115192) Move WASI to tier 2 and drop Emscripten. --- Doc/whatsnew/3.13.rst | 6 ++++++ .../Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst | 2 ++ configure | 6 ++---- configure.ac | 3 +-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 50a2a69c75ac70b..b05e4badc9e58bf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1339,6 +1339,12 @@ Build Changes :ref:`limited C API <limited-c-api>`. (Contributed by Victor Stinner in :gh:`85283`.) +* ``wasm32-wasi`` is now a tier 2 platform. + (Contributed by Brett Cannon in :gh:`115192`.) + +* ``wasm32-emscripten`` is no longer a supported platform. + (Contributed by Brett Cannon in :gh:`115192`.) + C API Changes ============= diff --git a/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst b/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst new file mode 100644 index 000000000000000..8b02b1b2cd08c90 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-02-08-17-38-56.gh-issue-113632.y9KIGb.rst @@ -0,0 +1,2 @@ +Promote WASI to a tier 2 platform and drop Emscripten from tier 3 in +configure.ac. diff --git a/configure b/configure index 0375565c2945528..705a778cafced35 100755 --- a/configure +++ b/configure @@ -6805,6 +6805,8 @@ case $host/$ac_cv_cc_name in #( aarch64-*-linux-gnu/clang) : PY_SUPPORT_TIER=2 ;; #( powerpc64le-*-linux-gnu/gcc) : + PY_SUPPORT_TIER=2 ;; #( + wasm32-unknown-wasi/clang) : PY_SUPPORT_TIER=2 ;; #( x86_64-*-linux-gnu/clang) : PY_SUPPORT_TIER=2 ;; #( @@ -6817,10 +6819,6 @@ case $host/$ac_cv_cc_name in #( PY_SUPPORT_TIER=3 ;; #( s390x-*-linux-gnu/gcc) : PY_SUPPORT_TIER=3 ;; #( - wasm32-unknown-emscripten/clang) : - PY_SUPPORT_TIER=3 ;; #( - wasm32-unknown-wasi/clang) : - PY_SUPPORT_TIER=3 ;; #( x86_64-*-freebsd*/clang) : PY_SUPPORT_TIER=3 ;; #( *) : diff --git a/configure.ac b/configure.ac index e121e893a1d0d9e..dee7ed552b370f0 100644 --- a/configure.ac +++ b/configure.ac @@ -973,14 +973,13 @@ AS_CASE([$host/$ac_cv_cc_name], [aarch64-*-linux-gnu/gcc], [PY_SUPPORT_TIER=2], dnl Linux ARM64, glibc, gcc+clang [aarch64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], [powerpc64le-*-linux-gnu/gcc], [PY_SUPPORT_TIER=2], dnl Linux on PPC64 little endian, glibc, gcc + [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=2], dnl WebAssembly System Interface, clang [x86_64-*-linux-gnu/clang], [PY_SUPPORT_TIER=2], dnl Linux on AMD64, any vendor, glibc, clang [aarch64-pc-windows-msvc/msvc], [PY_SUPPORT_TIER=3], dnl Windows ARM64, MSVC [armv7l-*-linux-gnueabihf/gcc], [PY_SUPPORT_TIER=3], dnl ARMv7 LE with hardware floats, any vendor, glibc, gcc [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc - [wasm32-unknown-emscripten/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly Emscripten - [wasm32-unknown-wasi/clang], [PY_SUPPORT_TIER=3], dnl WebAssembly System Interface [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 [PY_SUPPORT_TIER=0] ) From 846fd721d518dda88a7d427ec3d2c03c45d9fa90 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Fri, 9 Feb 2024 12:36:12 +0200 Subject: [PATCH 249/263] gh-115059: Flush the underlying write buffer in io.BufferedRandom.read1() (GH-115163) --- Lib/test/test_io.py | 52 +++++++++++++++++++ ...-02-08-13-26-14.gh-issue-115059.DqP9dr.rst | 1 + Modules/_io/bufferedio.c | 10 ++++ 3 files changed, 63 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 73669ecc7927763..a24579dcc878cfb 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2497,6 +2497,28 @@ def test_interleaved_read_write(self): f.flush() self.assertEqual(raw.getvalue(), b'a2c') + def test_read1_after_write(self): + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + self.assertEqual(f.read1(1), b'b') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + self.assertEqual(f.read1(), b'bcd') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + with self.BytesIO(b'abcdef') as raw: + with self.tp(raw, 3) as f: + f.write(b"1") + # XXX: read(100) returns different numbers of bytes + # in Python and C implementations. + self.assertEqual(f.read1(100)[:3], b'bcd') + f.flush() + self.assertEqual(raw.getvalue(), b'1bcdef') + def test_interleaved_readline_write(self): with self.BytesIO(b'ab\ncdef\ng\n') as raw: with self.tp(raw) as f: @@ -2509,6 +2531,36 @@ def test_interleaved_readline_write(self): f.flush() self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') + def test_xxx(self): + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read(), b'defgh') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123defgh456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read(3), b'def') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123def456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read1(), b'defgh') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123defgh456') + with self.BytesIO(b'abcdefgh') as raw: + with self.tp(raw) as f: + f.write(b'123') + self.assertEqual(f.read1(3), b'def') + f.write(b'456') + f.flush() + self.assertEqual(raw.getvalue(), b'123def456') + # You can't construct a BufferedRandom over a non-seekable stream. test_unseekable = None diff --git a/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst b/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst new file mode 100644 index 000000000000000..331baedd3b24c5c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-08-13-26-14.gh-issue-115059.DqP9dr.rst @@ -0,0 +1 @@ +:meth:`io.BufferedRandom.read1` now flushes the underlying write buffer. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index f02207ace9f3d26..8ebe9ec7095586f 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1050,6 +1050,16 @@ _io__Buffered_read1_impl(buffered *self, Py_ssize_t n) Py_DECREF(res); return NULL; } + /* Flush the write buffer if necessary */ + if (self->writable) { + PyObject *r = buffered_flush_and_rewind_unlocked(self); + if (r == NULL) { + LEAVE_BUFFERED(self) + Py_DECREF(res); + return NULL; + } + Py_DECREF(r); + } _bufferedreader_reset_buf(self); r = _bufferedreader_raw_read(self, PyBytes_AS_STRING(res), n); LEAVE_BUFFERED(self) From 769d4448260aaec687d9306950225316f9faefce Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" <erlend@python.org> Date: Fri, 9 Feb 2024 15:11:36 +0100 Subject: [PATCH 250/263] Docs: correctly link to code objects (#115214) --- Doc/c-api/code.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 5082b0cb6ad3f3e..11c12e685fcace4 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -22,12 +22,13 @@ bound into a function. .. c:var:: PyTypeObject PyCode_Type This is an instance of :c:type:`PyTypeObject` representing the Python - :class:`code` type. + :ref:`code object <code-objects>`. .. c:function:: int PyCode_Check(PyObject *co) - Return true if *co* is a :class:`code` object. This function always succeeds. + Return true if *co* is a :ref:`code object <code-objects>`. + This function always succeeds. .. c:function:: int PyCode_GetNumFree(PyCodeObject *co) From 31633f4473966b3bcd470440bab7f348711be48f Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 9 Feb 2024 09:23:12 -0500 Subject: [PATCH 251/263] gh-115184: Fix refleak tracking issues in free-threaded build (#115188) Fixes a few issues related to refleak tracking in the free-threaded build: - Count blocks in abandoned segments - Call `_mi_page_free_collect` earlier during heap traversal in order to get an accurate count of blocks in use. - Add missing refcount tracking in `_Py_DecRefSharedDebug` and `_Py_ExplicitMergeRefcount`. - Pause threads in `get_num_global_allocated_blocks` to ensure that traversing the mimalloc heaps is safe. --- Objects/mimalloc/heap.c | 2 +- Objects/object.c | 11 +++++++---- Objects/obmalloc.c | 9 ++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Objects/mimalloc/heap.c b/Objects/mimalloc/heap.c index 164b28f0fab2402..154dad0b1284805 100644 --- a/Objects/mimalloc/heap.c +++ b/Objects/mimalloc/heap.c @@ -538,7 +538,6 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_ mi_assert(page != NULL); if (page == NULL) return true; - _mi_page_free_collect(page,true); mi_assert_internal(page->local_free == NULL); if (page->used == 0) return true; @@ -635,6 +634,7 @@ bool _mi_heap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t *page, mi_ typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg); void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) { + _mi_page_free_collect(page,true); const size_t bsize = mi_page_block_size(page); const size_t ubsize = mi_page_usable_block_size(page); area->reserved = page->reserved * bsize; diff --git a/Objects/object.c b/Objects/object.c index bbf7f98ae3daf92..37a4b7a417e35fa 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -346,6 +346,9 @@ _Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno) if (should_queue) { // TODO: the inter-thread queue is not yet implemented. For now, // we just merge the refcount here. +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif Py_ssize_t refcount = _Py_ExplicitMergeRefcount(o, -1); if (refcount == 0) { _Py_Dealloc(o); @@ -399,10 +402,6 @@ _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra) Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared); do { refcnt = Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT); - if (_Py_REF_IS_MERGED(shared)) { - return refcnt; - } - refcnt += (Py_ssize_t)op->ob_ref_local; refcnt += extra; @@ -410,6 +409,10 @@ _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra) } while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared, &shared, new_shared)); +#ifdef Py_REF_DEBUG + _Py_AddRefTotal(_PyInterpreterState_GET(), extra); +#endif + _Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0); _Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0); return refcnt; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index bea4ea85332bdda..6a12c3dca38b36d 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1073,7 +1073,12 @@ get_mimalloc_allocated_blocks(PyInterpreterState *interp) mi_heap_visit_blocks(heap, false, &count_blocks, &allocated_blocks); } } - // TODO(sgross): count blocks in abandoned segments. + + mi_abandoned_pool_t *pool = &interp->mimalloc.abandoned_pool; + for (uint8_t tag = 0; tag < _Py_MIMALLOC_HEAP_COUNT; tag++) { + _mi_abandoned_pool_visit_blocks(pool, tag, false, &count_blocks, + &allocated_blocks); + } #else // TODO(sgross): this only counts the current thread's blocks. mi_heap_t *heap = mi_heap_get_default(); @@ -1189,6 +1194,7 @@ get_num_global_allocated_blocks(_PyRuntimeState *runtime) } } else { + _PyEval_StopTheWorldAll(&_PyRuntime); HEAD_LOCK(runtime); PyInterpreterState *interp = PyInterpreterState_Head(); assert(interp != NULL); @@ -1208,6 +1214,7 @@ get_num_global_allocated_blocks(_PyRuntimeState *runtime) } } HEAD_UNLOCK(runtime); + _PyEval_StartTheWorldAll(&_PyRuntime); #ifdef Py_DEBUG assert(got_main); #endif From f8931adc597aa696a0f60439e8f9a9047d51ef1c Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Fri, 9 Feb 2024 19:59:41 +0300 Subject: [PATCH 252/263] gh-115142: Skip test_optimizer if _testinternalcapi module is not available (GH-115175) --- Lib/test/test_optimizer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index b56bf3cfd9560ea..c8554c40df4b2de 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -1,6 +1,9 @@ -import _testinternalcapi import unittest import types +from test.support import import_helper + + +_testinternalcapi = import_helper.import_module("_testinternalcapi") class TestRareEventCounters(unittest.TestCase): From 5a173efa693a053bf4a059c82c1c06c82a9fa8fb Mon Sep 17 00:00:00 2001 From: Peter Lazorchak <lazorchakp@gmail.com> Date: Fri, 9 Feb 2024 09:06:14 -0800 Subject: [PATCH 253/263] Add Peter L to ACKS (GH-115222) --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 466023f390a421d..8a80e02ecba26a8 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1051,6 +1051,7 @@ Mark Lawrence Chris Laws Michael Layzell Michael Lazar +Peter Lazorchak Brian Leair Mathieu Leduc-Hamel Amandine Lee From a225520af941fb125a4ede77a617501dfb8b46da Mon Sep 17 00:00:00 2001 From: Carl Meyer <carl@oddbird.net> Date: Fri, 9 Feb 2024 12:19:09 -0700 Subject: [PATCH 254/263] gh-112903: Handle non-types in _BaseGenericAlias.__mro_entries__() (#115191) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> --- Lib/test/test_typing.py | 69 +++++++++++++++++++ Lib/typing.py | 22 +++++- ...-02-08-17-04-58.gh-issue-112903.SN_vUs.rst | 2 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b684af4f33ed71d..58566c4bfc821c4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4920,6 +4920,75 @@ class B(Generic[S]): ... class C(List[int], B): ... self.assertEqual(C.__mro__, (C, list, B, Generic, object)) + def test_multiple_inheritance_non_type_with___mro_entries__(self): + class GoodEntries: + def __mro_entries__(self, bases): + return (object,) + + class A(List[int], GoodEntries()): ... + + self.assertEqual(A.__mro__, (A, list, Generic, object)) + + def test_multiple_inheritance_non_type_without___mro_entries__(self): + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex(TypeError, r"^bases must be types"): + class A(List[int], object()): ... + + def test_multiple_inheritance_non_type_bad___mro_entries__(self): + class BadEntries: + def __mro_entries__(self, bases): + return None + + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex( + TypeError, + r"^__mro_entries__ must return a tuple", + ): + class A(List[int], BadEntries()): ... + + def test_multiple_inheritance___mro_entries___returns_non_type(self): + class BadEntries: + def __mro_entries__(self, bases): + return (object(),) + + # Error should be from the type machinery, not from typing.py + with self.assertRaisesRegex( + TypeError, + r"^bases must be types", + ): + class A(List[int], BadEntries()): ... + + def test_multiple_inheritance_with_genericalias(self): + class A(typing.Sized, list[int]): ... + + self.assertEqual( + A.__mro__, + (A, collections.abc.Sized, Generic, list, object), + ) + + def test_multiple_inheritance_with_genericalias_2(self): + T = TypeVar("T") + + class BaseSeq(typing.Sequence[T]): ... + class MySeq(List[T], BaseSeq[T]): ... + + self.assertEqual( + MySeq.__mro__, + ( + MySeq, + list, + BaseSeq, + collections.abc.Sequence, + collections.abc.Reversible, + collections.abc.Collection, + collections.abc.Sized, + collections.abc.Iterable, + collections.abc.Container, + Generic, + object, + ), + ) + def test_init_subclass_super_called(self): class FinalException(Exception): pass diff --git a/Lib/typing.py b/Lib/typing.py index d278b4effc7ebaf..347373f00956c77 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1135,9 +1135,29 @@ def __mro_entries__(self, bases): res = [] if self.__origin__ not in bases: res.append(self.__origin__) + + # Check if any base that occurs after us in `bases` is either itself a + # subclass of Generic, or something which will add a subclass of Generic + # to `__bases__` via its `__mro_entries__`. If not, add Generic + # ourselves. The goal is to ensure that Generic (or a subclass) will + # appear exactly once in the final bases tuple. If we let it appear + # multiple times, we risk "can't form a consistent MRO" errors. i = bases.index(self) for b in bases[i+1:]: - if isinstance(b, _BaseGenericAlias) or issubclass(b, Generic): + if isinstance(b, _BaseGenericAlias): + break + if not isinstance(b, type): + meth = getattr(b, "__mro_entries__", None) + new_bases = meth(bases) if meth else None + if ( + isinstance(new_bases, tuple) and + any( + isinstance(b2, type) and issubclass(b2, Generic) + for b2 in new_bases + ) + ): + break + elif issubclass(b, Generic): break else: res.append(Generic) diff --git a/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst new file mode 100644 index 000000000000000..e27f5832553c136 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-08-17-04-58.gh-issue-112903.SN_vUs.rst @@ -0,0 +1,2 @@ +Fix "issubclass() arg 1 must be a class" errors in certain cases of multiple +inheritance with generic aliases (regression in early 3.13 alpha releases). From a3af3cb4f424034b56404704fdf8f18e8c0a9982 Mon Sep 17 00:00:00 2001 From: Sam Gross <colesbury@gmail.com> Date: Fri, 9 Feb 2024 17:08:32 -0500 Subject: [PATCH 255/263] gh-110481: Implement inter-thread queue for biased reference counting (#114824) Biased reference counting maintains two refcount fields in each object: `ob_ref_local` and `ob_ref_shared`. The true refcount is the sum of these two fields. In some cases, when refcounting operations are split across threads, the ob_ref_shared field can be negative (although the total refcount must be at least zero). In this case, the thread that decremented the refcount requests that the owning thread give up ownership and merge the refcount fields. --- Include/internal/pycore_brc.h | 74 +++++++ Include/internal/pycore_ceval.h | 1 + Include/internal/pycore_interp.h | 1 + Include/internal/pycore_object_stack.h | 6 + Include/internal/pycore_tstate.h | 2 + Lib/test/test_code.py | 1 + Lib/test/test_concurrent_futures/executor.py | 17 +- .../test_process_pool.py | 1 + Makefile.pre.in | 2 + Modules/posixmodule.c | 4 + Objects/dictobject.c | 16 +- Objects/object.c | 8 +- PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 2 + PCbuild/pythoncore.vcxproj.filters | 6 + Python/brc.c | 198 ++++++++++++++++++ Python/ceval_gil.c | 8 + Python/gc_free_threading.c | 46 +++- Python/object_stack.c | 21 ++ Python/pystate.c | 11 + 21 files changed, 418 insertions(+), 11 deletions(-) create mode 100644 Include/internal/pycore_brc.h create mode 100644 Python/brc.c diff --git a/Include/internal/pycore_brc.h b/Include/internal/pycore_brc.h new file mode 100644 index 000000000000000..3453d83b57ca97e --- /dev/null +++ b/Include/internal/pycore_brc.h @@ -0,0 +1,74 @@ +#ifndef Py_INTERNAL_BRC_H +#define Py_INTERNAL_BRC_H + +#include <stdint.h> +#include "pycore_llist.h" // struct llist_node +#include "pycore_lock.h" // PyMutex +#include "pycore_object_stack.h" // _PyObjectStack + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +#ifdef Py_GIL_DISABLED + +// Prime number to avoid correlations with memory addresses. +#define _Py_BRC_NUM_BUCKETS 257 + +// Hash table bucket +struct _brc_bucket { + // Mutex protects both the bucket and thread state queues in this bucket. + PyMutex mutex; + + // Linked list of _PyThreadStateImpl objects hashed to this bucket. + struct llist_node root; +}; + +// Per-interpreter biased reference counting state +struct _brc_state { + // Hash table of thread states by thread-id. Thread states within a bucket + // are chained using a doubly-linked list. + struct _brc_bucket table[_Py_BRC_NUM_BUCKETS]; +}; + +// Per-thread biased reference counting state +struct _brc_thread_state { + // Linked-list of thread states per hash bucket + struct llist_node bucket_node; + + // Thread-id as determined by _PyThread_Id() + uintptr_t tid; + + // Objects with refcounts to be merged (protected by bucket mutex) + _PyObjectStack objects_to_merge; + + // Local stack of objects to be merged (not accessed by other threads) + _PyObjectStack local_objects_to_merge; +}; + +// Initialize/finalize the per-thread biased reference counting state +void _Py_brc_init_thread(PyThreadState *tstate); +void _Py_brc_remove_thread(PyThreadState *tstate); + +// Initialize per-interpreter state +void _Py_brc_init_state(PyInterpreterState *interp); + +void _Py_brc_after_fork(PyInterpreterState *interp); + +// Enqueues an object to be merged by it's owning thread (tid). This +// steals a reference to the object. +void _Py_brc_queue_object(PyObject *ob); + +// Merge the refcounts of queued objects for the current thread. +void _Py_brc_merge_refcounts(PyThreadState *tstate); + +#endif /* Py_GIL_DISABLED */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_BRC_H */ diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index a66af1389541dd7..b158fc9ff5ebc1b 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -206,6 +206,7 @@ void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame) #define _PY_ASYNC_EXCEPTION_BIT 3 #define _PY_GC_SCHEDULED_BIT 4 #define _PY_EVAL_PLEASE_STOP_BIT 5 +#define _PY_EVAL_EXPLICIT_MERGE_BIT 6 /* Reserve a few bits for future use */ #define _PY_EVAL_EVENTS_BITS 8 diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index f7c332ed747cfac..31d88071e19d0cf 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -201,6 +201,7 @@ struct _is { #if defined(Py_GIL_DISABLED) struct _mimalloc_interp_state mimalloc; + struct _brc_state brc; // biased reference counting state #endif // Per-interpreter state for the obmalloc allocator. For the main diff --git a/Include/internal/pycore_object_stack.h b/Include/internal/pycore_object_stack.h index 1dc1c1591525ded..d042be2a98090a0 100644 --- a/Include/internal/pycore_object_stack.h +++ b/Include/internal/pycore_object_stack.h @@ -1,6 +1,8 @@ #ifndef Py_INTERNAL_OBJECT_STACK_H #define Py_INTERNAL_OBJECT_STACK_H +#include "pycore_freelist.h" // _PyFreeListState + #ifdef __cplusplus extern "C" { #endif @@ -74,6 +76,10 @@ _PyObjectStack_Pop(_PyObjectStack *stack) return obj; } +// Merge src into dst, leaving src empty +extern void +_PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src); + // Remove all items from the stack extern void _PyObjectStack_Clear(_PyObjectStack *stack); diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 472fa08154e8f92..77a1dc59163d212 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -10,6 +10,7 @@ extern "C" { #include "pycore_freelist.h" // struct _Py_freelist_state #include "pycore_mimalloc.h" // struct _mimalloc_thread_state +#include "pycore_brc.h" // struct _brc_thread_state // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The @@ -22,6 +23,7 @@ typedef struct _PyThreadStateImpl { #ifdef Py_GIL_DISABLED struct _mimalloc_thread_state mimalloc; struct _Py_freelist_state freelist_state; + struct _brc_thread_state brc; #endif } _PyThreadStateImpl; diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index d8fb826edeb6816..46bebfc7af675b1 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -865,6 +865,7 @@ def __init__(self, f, test): self.test = test def run(self): del self.f + gc_collect() self.test.assertEqual(LAST_FREED, 500) SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500)) diff --git a/Lib/test/test_concurrent_futures/executor.py b/Lib/test/test_concurrent_futures/executor.py index 1e7d4344740943d..6a79fe69ec37cf4 100644 --- a/Lib/test/test_concurrent_futures/executor.py +++ b/Lib/test/test_concurrent_futures/executor.py @@ -1,8 +1,10 @@ import threading import time +import unittest import weakref from concurrent import futures from test import support +from test.support import Py_GIL_DISABLED def mul(x, y): @@ -83,10 +85,21 @@ def test_no_stale_references(self): my_object_collected = threading.Event() my_object_callback = weakref.ref( my_object, lambda obj: my_object_collected.set()) - # Deliberately discarding the future. - self.executor.submit(my_object.my_method) + fut = self.executor.submit(my_object.my_method) del my_object + if Py_GIL_DISABLED: + # Due to biased reference counting, my_object might only be + # deallocated while the thread that created it runs -- if the + # thread is paused waiting on an event, it may not merge the + # refcount of the queued object. For that reason, we wait for the + # task to finish (so that it's no longer referenced) and force a + # GC to ensure that it is collected. + fut.result() # Wait for the task to finish. + support.gc_collect() + else: + del fut # Deliberately discard the future. + collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT) self.assertTrue(collected, "Stale reference not collected within timeout.") diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 3e61b0c9387c6fa..7fc59a05f3deac0 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -98,6 +98,7 @@ def test_ressources_gced_in_workers(self): # explicitly destroy the object to ensure that EventfulGCObj.__del__() # is called while manager is still running. + support.gc_collect() obj = None support.gc_collect() diff --git a/Makefile.pre.in b/Makefile.pre.in index 07b2ec7adde78a5..4dabe328ce0362f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -405,6 +405,7 @@ PYTHON_OBJS= \ Python/ast_opt.o \ Python/ast_unparse.o \ Python/bltinmodule.o \ + Python/brc.o \ Python/ceval.o \ Python/codecs.o \ Python/compile.o \ @@ -1081,6 +1082,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_atexit.h \ $(srcdir)/Include/internal/pycore_bitutils.h \ $(srcdir)/Include/internal/pycore_blocks_output_buffer.h \ + $(srcdir)/Include/internal/pycore_brc.h \ $(srcdir)/Include/internal/pycore_bytes_methods.h \ $(srcdir)/Include/internal/pycore_bytesobject.h \ $(srcdir)/Include/internal/pycore_call.h \ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e26265fc874ebb4..230c961a2ac3c04 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -637,6 +637,10 @@ PyOS_AfterFork_Child(void) tstate->native_thread_id = PyThread_get_thread_native_id(); #endif +#ifdef Py_GIL_DISABLED + _Py_brc_after_fork(tstate->interp); +#endif + status = _PyEval_ReInitThreads(tstate); if (_PyStatus_EXCEPTION(status)) { goto fatal_error; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 2df95e977a180fa..9b1defa5cbc609f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5989,6 +5989,18 @@ _PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values) return make_dict_from_instance_attributes(interp, keys, values); } +static bool +has_unique_reference(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + return (_Py_IsOwnedByCurrentThread(op) && + op->ob_ref_local == 1 && + _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared) == 0); +#else + return Py_REFCNT(op) == 1; +#endif +} + // Return true if the dict was dematerialized, false otherwise. bool _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) @@ -6005,7 +6017,9 @@ _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) return false; } assert(_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_HEAPTYPE)); - if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || Py_REFCNT(dict) != 1) { + if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || + !has_unique_reference((PyObject *)dict)) + { return false; } assert(dict->ma_values); diff --git a/Objects/object.c b/Objects/object.c index 37a4b7a417e35fa..61e6131c6e99bbb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2,6 +2,7 @@ /* Generic object operations; and implementation of None */ #include "Python.h" +#include "pycore_brc.h" // _Py_brc_queue_object() #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate() #include "pycore_context.h" // _PyContextTokenMissing_Type @@ -344,15 +345,10 @@ _Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno) &shared, new_shared)); if (should_queue) { - // TODO: the inter-thread queue is not yet implemented. For now, - // we just merge the refcount here. #ifdef Py_REF_DEBUG _Py_IncRefTotal(_PyInterpreterState_GET()); #endif - Py_ssize_t refcount = _Py_ExplicitMergeRefcount(o, -1); - if (refcount == 0) { - _Py_Dealloc(o); - } + _Py_brc_queue_object(o); } else if (new_shared == _Py_REF_MERGED) { // refcount is zero AND merged diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 35788ec4503e8f6..49f529ebbc2f9b0 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -191,6 +191,7 @@ <ClCompile Include="..\Python\ast_opt.c" /> <ClCompile Include="..\Python\ast_unparse.c" /> <ClCompile Include="..\Python\bltinmodule.c" /> + <ClCompile Include="..\Python\brc.c" /> <ClCompile Include="..\Python\bootstrap_hash.c" /> <ClCompile Include="..\Python\ceval.c" /> <ClCompile Include="..\Python\codecs.c" /> diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 7a44179e3561059..5b1bd7552b4cd98 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -46,6 +46,9 @@ <ClCompile Include="..\Python\bltinmodule.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\Python\brc.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Objects\boolobject.c"> <Filter>Source Files</Filter> </ClCompile> diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index e1ff97659659eea..4cc0ca4b9af8deb 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -206,6 +206,7 @@ <ClInclude Include="..\Include\internal\pycore_ast_state.h" /> <ClInclude Include="..\Include\internal\pycore_atexit.h" /> <ClInclude Include="..\Include\internal\pycore_bitutils.h" /> + <ClInclude Include="..\Include\internal\pycore_brc.h" /> <ClInclude Include="..\Include\internal\pycore_bytes_methods.h" /> <ClInclude Include="..\Include\internal\pycore_bytesobject.h" /> <ClInclude Include="..\Include\internal\pycore_call.h" /> @@ -553,6 +554,7 @@ <ClCompile Include="..\Python\ast_unparse.c" /> <ClCompile Include="..\Python\bltinmodule.c" /> <ClCompile Include="..\Python\bootstrap_hash.c" /> + <ClCompile Include="..\Python\brc.c" /> <ClCompile Include="..\Python\ceval.c" /> <ClCompile Include="..\Python\codecs.c" /> <ClCompile Include="..\Python\compile.c" /> diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 4c55f23006b2f0c..ceaa21217267cf1 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -546,6 +546,9 @@ <ClInclude Include="..\Include\internal\pycore_bitutils.h"> <Filter>Include\internal</Filter> </ClInclude> + <ClInclude Include="..\Include\internal\pycore_brc.h"> + <Filter>Include\internal</Filter> + </ClInclude> <ClInclude Include="..\Include\internal\pycore_bytes_methods.h"> <Filter>Include\internal</Filter> </ClInclude> @@ -1253,6 +1256,9 @@ <ClCompile Include="..\Python\bltinmodule.c"> <Filter>Python</Filter> </ClCompile> + <ClCompile Include="..\Python\brc.c"> + <Filter>Python</Filter> + </ClCompile> <ClCompile Include="..\Python\ceval.c"> <Filter>Python</Filter> </ClCompile> diff --git a/Python/brc.c b/Python/brc.c new file mode 100644 index 000000000000000..f1fd57a2964cf55 --- /dev/null +++ b/Python/brc.c @@ -0,0 +1,198 @@ +// Implementation of biased reference counting inter-thread queue. +// +// Biased reference counting maintains two refcount fields in each object: +// ob_ref_local and ob_ref_shared. The true refcount is the sum of these two +// fields. In some cases, when refcounting operations are split across threads, +// the ob_ref_shared field can be negative (although the total refcount must +// be at least zero). In this case, the thread that decremented the refcount +// requests that the owning thread give up ownership and merge the refcount +// fields. This file implements the mechanism for doing so. +// +// Each thread state maintains a queue of objects whose refcounts it should +// merge. The thread states are stored in a per-interpreter hash table by +// thread id. The hash table has a fixed size and uses a linked list to store +// thread states within each bucket. +// +// The queueing thread uses the eval breaker mechanism to notify the owning +// thread that it has objects to merge. Additionaly, all queued objects are +// merged during GC. +#include "Python.h" +#include "pycore_object.h" // _Py_ExplicitMergeRefcount +#include "pycore_brc.h" // struct _brc_thread_state +#include "pycore_ceval.h" // _Py_set_eval_breaker_bit +#include "pycore_llist.h" // struct llist_node +#include "pycore_pystate.h" // _PyThreadStateImpl + +#ifdef Py_GIL_DISABLED + +// Get the hashtable bucket for a given thread id. +static struct _brc_bucket * +get_bucket(PyInterpreterState *interp, uintptr_t tid) +{ + return &interp->brc.table[tid % _Py_BRC_NUM_BUCKETS]; +} + +// Find the thread state in a hash table bucket by thread id. +static _PyThreadStateImpl * +find_thread_state(struct _brc_bucket *bucket, uintptr_t thread_id) +{ + struct llist_node *node; + llist_for_each(node, &bucket->root) { + // Get the containing _PyThreadStateImpl from the linked-list node. + _PyThreadStateImpl *ts = llist_data(node, _PyThreadStateImpl, + brc.bucket_node); + if (ts->brc.tid == thread_id) { + return ts; + } + } + return NULL; +} + +// Enqueue an object to be merged by the owning thread. This steals a +// reference to the object. +void +_Py_brc_queue_object(PyObject *ob) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + + uintptr_t ob_tid = _Py_atomic_load_uintptr(&ob->ob_tid); + if (ob_tid == 0) { + // The owning thread may have concurrently decided to merge the + // refcount fields. + Py_DECREF(ob); + return; + } + + struct _brc_bucket *bucket = get_bucket(interp, ob_tid); + PyMutex_Lock(&bucket->mutex); + _PyThreadStateImpl *tstate = find_thread_state(bucket, ob_tid); + if (tstate == NULL) { + // If we didn't find the owning thread then it must have already exited. + // It's safe (and necessary) to merge the refcount. Subtract one when + // merging because we've stolen a reference. + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + PyMutex_Unlock(&bucket->mutex); + if (refcount == 0) { + _Py_Dealloc(ob); + } + return; + } + + if (_PyObjectStack_Push(&tstate->brc.objects_to_merge, ob) < 0) { + PyMutex_Unlock(&bucket->mutex); + + // Fall back to stopping all threads and manually merging the refcount + // if we can't enqueue the object to be merged. + _PyEval_StopTheWorld(interp); + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + _PyEval_StartTheWorld(interp); + + if (refcount == 0) { + _Py_Dealloc(ob); + } + return; + } + + // Notify owning thread + _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 1); + + PyMutex_Unlock(&bucket->mutex); +} + +static void +merge_queued_objects(_PyObjectStack *to_merge) +{ + PyObject *ob; + while ((ob = _PyObjectStack_Pop(to_merge)) != NULL) { + // Subtract one when merging because the queue had a reference. + Py_ssize_t refcount = _Py_ExplicitMergeRefcount(ob, -1); + if (refcount == 0) { + _Py_Dealloc(ob); + } + } +} + +// Process this thread's queue of objects to merge. +void +_Py_brc_merge_refcounts(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + + // Append all objects into a local stack. We don't want to hold the lock + // while calling destructors. + PyMutex_Lock(&bucket->mutex); + _PyObjectStack_Merge(&brc->local_objects_to_merge, &brc->objects_to_merge); + PyMutex_Unlock(&bucket->mutex); + + // Process the local stack until it's empty + merge_queued_objects(&brc->local_objects_to_merge); +} + +void +_Py_brc_init_state(PyInterpreterState *interp) +{ + struct _brc_state *brc = &interp->brc; + for (Py_ssize_t i = 0; i < _Py_BRC_NUM_BUCKETS; i++) { + llist_init(&brc->table[i].root); + } +} + +void +_Py_brc_init_thread(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + brc->tid = _Py_ThreadId(); + + // Add ourself to the hashtable + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + PyMutex_Lock(&bucket->mutex); + llist_insert_tail(&bucket->root, &brc->bucket_node); + PyMutex_Unlock(&bucket->mutex); +} + +void +_Py_brc_remove_thread(PyThreadState *tstate) +{ + struct _brc_thread_state *brc = &((_PyThreadStateImpl *)tstate)->brc; + struct _brc_bucket *bucket = get_bucket(tstate->interp, brc->tid); + + // We need to fully process any objects to merge before removing ourself + // from the hashtable. It is not safe to perform any refcount operations + // after we are removed. After that point, other threads treat our objects + // as abandoned and may merge the objects' refcounts directly. + bool empty = false; + while (!empty) { + // Process the local stack until it's empty + merge_queued_objects(&brc->local_objects_to_merge); + + PyMutex_Lock(&bucket->mutex); + empty = (brc->objects_to_merge.head == NULL); + if (empty) { + llist_remove(&brc->bucket_node); + } + else { + _PyObjectStack_Merge(&brc->local_objects_to_merge, + &brc->objects_to_merge); + } + PyMutex_Unlock(&bucket->mutex); + } + + assert(brc->local_objects_to_merge.head == NULL); + assert(brc->objects_to_merge.head == NULL); +} + +void +_Py_brc_after_fork(PyInterpreterState *interp) +{ + // Unlock all bucket mutexes. Some of the buckets may be locked because + // locks can be handed off to a parked thread (see lock.c). We don't have + // to worry about consistency here, becuase no thread can be actively + // modifying a bucket, but it might be paused (not yet woken up) on a + // PyMutex_Lock while holding that lock. + for (Py_ssize_t i = 0; i < _Py_BRC_NUM_BUCKETS; i++) { + _PyMutex_at_fork_reinit(&interp->brc.table[i].mutex); + } +} + +#endif /* Py_GIL_DISABLED */ diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index ad90359318761a5..deb9741291fca73 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -980,6 +980,14 @@ _Py_HandlePending(PyThreadState *tstate) } } +#ifdef Py_GIL_DISABLED + /* Objects with refcounts to merge */ + if (_Py_eval_breaker_bit_is_set(interp, _PY_EVAL_EXPLICIT_MERGE_BIT)) { + _Py_set_eval_breaker_bit(interp, _PY_EVAL_EXPLICIT_MERGE_BIT, 0); + _Py_brc_merge_refcounts(tstate); + } +#endif + /* GC scheduled to run */ if (_Py_eval_breaker_bit_is_set(interp, _PY_GC_SCHEDULED_BIT)) { _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 0); diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 8fbcdb15109b76c..5d3b097dee93e87 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1,5 +1,6 @@ // Cyclic garbage collector implementation for free-threaded build. #include "Python.h" +#include "pycore_brc.h" // struct _brc_thread_state #include "pycore_ceval.h" // _Py_set_eval_breaker_bit() #include "pycore_context.h" #include "pycore_dict.h" // _PyDict_MaybeUntrack() @@ -152,8 +153,7 @@ gc_decref(PyObject *op) op->ob_tid -= 1; } -// Merge refcounts while the world is stopped. -static void +static Py_ssize_t merge_refcount(PyObject *op, Py_ssize_t extra) { assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); @@ -169,6 +169,7 @@ merge_refcount(PyObject *op, Py_ssize_t extra) op->ob_tid = 0; op->ob_ref_local = 0; op->ob_ref_shared = _Py_REF_SHARED(refcount, _Py_REF_MERGED); + return refcount; } static void @@ -282,6 +283,41 @@ gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor, return err; } +static void +merge_queued_objects(_PyThreadStateImpl *tstate, struct collection_state *state) +{ + struct _brc_thread_state *brc = &tstate->brc; + _PyObjectStack_Merge(&brc->local_objects_to_merge, &brc->objects_to_merge); + + PyObject *op; + while ((op = _PyObjectStack_Pop(&brc->local_objects_to_merge)) != NULL) { + // Subtract one when merging because the queue had a reference. + Py_ssize_t refcount = merge_refcount(op, -1); + + if (!_PyObject_GC_IS_TRACKED(op) && refcount == 0) { + // GC objects with zero refcount are handled subsequently by the + // GC as if they were cyclic trash, but we have to handle dead + // non-GC objects here. Add one to the refcount so that we can + // decref and deallocate the object once we start the world again. + op->ob_ref_shared += (1 << _Py_REF_SHARED_SHIFT); +#ifdef Py_REF_DEBUG + _Py_IncRefTotal(_PyInterpreterState_GET()); +#endif + worklist_push(&state->objs_to_decref, op); + } + } +} + +static void +merge_all_queued_objects(PyInterpreterState *interp, struct collection_state *state) +{ + HEAD_LOCK(&_PyRuntime); + for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { + merge_queued_objects((_PyThreadStateImpl *)p, state); + } + HEAD_UNLOCK(&_PyRuntime); +} + // Subtract an incoming reference from the computed "gc_refs" refcount. static int visit_decref(PyObject *op, void *arg) @@ -927,6 +963,9 @@ static void gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) { _PyEval_StopTheWorld(interp); + // merge refcounts for all queued objects + merge_all_queued_objects(interp, state); + // Find unreachable objects int err = deduce_unreachable_heap(interp, state); if (err < 0) { @@ -946,6 +985,9 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) clear_weakrefs(state); _PyEval_StartTheWorld(interp); + // Deallocate any object from the refcount merge step + cleanup_worklist(&state->objs_to_decref); + // Call weakref callbacks and finalizers after unpausing other threads to // avoid potential deadlocks. call_weakref_callbacks(state); diff --git a/Python/object_stack.c b/Python/object_stack.c index 8544892eb71dcb6..ced4460da00f442 100644 --- a/Python/object_stack.c +++ b/Python/object_stack.c @@ -67,6 +67,27 @@ _PyObjectStack_Clear(_PyObjectStack *queue) } } +void +_PyObjectStack_Merge(_PyObjectStack *dst, _PyObjectStack *src) +{ + if (src->head == NULL) { + return; + } + + if (dst->head != NULL) { + // First, append dst to the bottom of src + _PyObjectStackChunk *last = src->head; + while (last->prev != NULL) { + last = last->prev; + } + last->prev = dst->head; + } + + // Now that src has all the chunks, set dst to src + dst->head = src->head; + src->head = NULL; +} + void _PyObjectStackChunk_ClearFreeList(_PyFreeListState *free_lists, int is_finalization) { diff --git a/Python/pystate.c b/Python/pystate.c index e77e5bfa7e2df86..6cd034743ddf4c3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -611,6 +611,9 @@ init_interpreter(PyInterpreterState *interp, _PyGC_InitState(&interp->gc); PyConfig_InitPythonConfig(&interp->config); _PyType_InitCache(interp); +#ifdef Py_GIL_DISABLED + _Py_brc_init_state(interp); +#endif for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; } @@ -1336,6 +1339,11 @@ init_threadstate(_PyThreadStateImpl *_tstate, tstate->datastack_limit = NULL; tstate->what_event = -1; +#ifdef Py_GIL_DISABLED + // Initialize biased reference counting inter-thread queue + _Py_brc_init_thread(tstate); +#endif + if (interp->stoptheworld.requested || _PyRuntime.stoptheworld.requested) { // Start in the suspended state if there is an ongoing stop-the-world. tstate->state = _Py_THREAD_SUSPENDED; @@ -1561,6 +1569,9 @@ PyThreadState_Clear(PyThreadState *tstate) _PyFreeListState *freelist_state = &((_PyThreadStateImpl*)tstate)->freelist_state; _Py_ClearFreeLists(freelist_state, 1); _PySlice_ClearCache(freelist_state); + + // Remove ourself from the biased reference counting table of threads. + _Py_brc_remove_thread(tstate); #endif _PyThreadState_ClearMimallocHeaps(tstate); From 564385612cdf72c2fa8e629a68225fb2cd3b3d99 Mon Sep 17 00:00:00 2001 From: dave-shawley <daveshawley@gmail.com> Date: Fri, 9 Feb 2024 17:11:37 -0500 Subject: [PATCH 256/263] gh-115165: Fix `typing.Annotated` for immutable types (#115213) The return value from an annotated callable can raise any exception from __setattr__ for the `__orig_class__` property. --- Lib/test/test_typing.py | 21 +++++++++++++++++++ Lib/typing.py | 4 +++- ...-02-09-07-20-16.gh-issue-115165.yfJLXA.rst | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 58566c4bfc821c4..c3a092f3af30097 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -4323,6 +4323,16 @@ class C(B[int]): c.bar = 'abc' self.assertEqual(c.__dict__, {'bar': 'abc'}) + def test_setattr_exceptions(self): + class Immutable[T]: + def __setattr__(self, key, value): + raise RuntimeError("immutable") + + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `Immutable` instance + # returned by the `Immutable[int]()` call + self.assertIsInstance(Immutable[int](), Immutable) + def test_subscripted_generics_as_proxies(self): T = TypeVar('T') class C(Generic[T]): @@ -8561,6 +8571,17 @@ def test_instantiate_generic(self): self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1}) self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1}) + def test_instantiate_immutable(self): + class C: + def __setattr__(self, key, value): + raise Exception("should be ignored") + + A = Annotated[C, "a decoration"] + # gh-115165: This used to cause RuntimeError to be raised + # when we tried to set `__orig_class__` on the `C` instance + # returned by the `A()` call + self.assertIsInstance(A(), C) + def test_cannot_instantiate_forward(self): A = Annotated["int", (5, 6)] with self.assertRaises(TypeError): diff --git a/Lib/typing.py b/Lib/typing.py index 347373f00956c77..914ddeaf504cd0e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1127,7 +1127,9 @@ def __call__(self, *args, **kwargs): result = self.__origin__(*args, **kwargs) try: result.__orig_class__ = self - except AttributeError: + # Some objects raise TypeError (or something even more exotic) + # if you try to set attributes on them; we guard against that here + except Exception: pass return result diff --git a/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst new file mode 100644 index 000000000000000..73d3d001f07f3f4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-09-07-20-16.gh-issue-115165.yfJLXA.rst @@ -0,0 +1,4 @@ +Most exceptions are now ignored when attempting to set the ``__orig_class__`` +attribute on objects returned when calling :mod:`typing` generic aliases +(including generic aliases created using :data:`typing.Annotated`). +Previously only :exc:`AttributeError`` was ignored. Patch by Dave Shawley. From d4d5bae1471788b345155e8e93a2fe4ab92d09dc Mon Sep 17 00:00:00 2001 From: Donghee Na <donghee.na@python.org> Date: Sat, 10 Feb 2024 09:57:04 +0900 Subject: [PATCH 257/263] gh-111968: Refactor _PyXXX_Fini to integrate with _PyObject_ClearFreeLists (gh-114899) --- Include/internal/pycore_context.h | 1 - Include/internal/pycore_floatobject.h | 1 - Include/internal/pycore_freelist.h | 10 ++++++++++ Include/internal/pycore_gc.h | 8 -------- Include/internal/pycore_genobject.h | 4 ---- Include/internal/pycore_list.h | 6 ------ Include/internal/pycore_object_stack.h | 3 --- Include/internal/pycore_sliceobject.h | 2 -- Include/internal/pycore_tuple.h | 1 - Objects/floatobject.c | 10 ---------- Objects/genobject.c | 11 ----------- Objects/listobject.c | 10 ---------- Objects/object.c | 15 +++++++++++++++ Objects/sliceobject.c | 12 ++++-------- Objects/tupleobject.c | 5 ----- Python/context.c | 11 ----------- Python/gc_free_threading.c | 2 +- Python/gc_gil.c | 2 +- Python/pylifecycle.c | 12 +++++------- Python/pystate.c | 19 ++----------------- 20 files changed, 38 insertions(+), 107 deletions(-) diff --git a/Include/internal/pycore_context.h b/Include/internal/pycore_context.h index 3284efba2b6f4cb..ae5c47f195eb7f0 100644 --- a/Include/internal/pycore_context.h +++ b/Include/internal/pycore_context.h @@ -14,7 +14,6 @@ extern PyTypeObject _PyContextTokenMissing_Type; /* runtime lifecycle */ PyStatus _PyContext_Init(PyInterpreterState *); -void _PyContext_Fini(_PyFreeListState *); /* other API */ diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 038578e1f9680a6..3767df5506d43fb 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -15,7 +15,6 @@ extern "C" { extern void _PyFloat_InitState(PyInterpreterState *); extern PyStatus _PyFloat_InitTypes(PyInterpreterState *); -extern void _PyFloat_Fini(_PyFreeListState *); extern void _PyFloat_FiniType(PyInterpreterState *); diff --git a/Include/internal/pycore_freelist.h b/Include/internal/pycore_freelist.h index 82a42300991eccd..1bc551914794f0c 100644 --- a/Include/internal/pycore_freelist.h +++ b/Include/internal/pycore_freelist.h @@ -125,6 +125,16 @@ typedef struct _Py_freelist_state { struct _Py_object_stack_state object_stacks; } _PyFreeListState; +extern void _PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization); +extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); +extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); +extern void _PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 8d0bc2a218e48de..582a16bf5218cef 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -279,14 +279,6 @@ extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs); // Functions to clear types free lists extern void _PyGC_ClearAllFreeLists(PyInterpreterState *interp); -extern void _Py_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyFloat_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyList_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PySlice_ClearCache(_PyFreeListState *state); -extern void _PyDict_ClearFreeList(_PyFreeListState *state, int is_finalization); -extern void _PyAsyncGen_ClearFreeLists(_PyFreeListState *state, int is_finalization); -extern void _PyContext_ClearFreeList(_PyFreeListState *state, int is_finalization); extern void _Py_ScheduleGC(PyInterpreterState *interp); extern void _Py_RunGC(PyThreadState *tstate); diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index 5ad63658051e86d..b2aa017598409f7 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -26,10 +26,6 @@ extern PyTypeObject _PyCoroWrapper_Type; extern PyTypeObject _PyAsyncGenWrappedValue_Type; extern PyTypeObject _PyAsyncGenAThrow_Type; -/* runtime lifecycle */ - -extern void _PyAsyncGen_Fini(_PyFreeListState *); - #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 4536f90e4144939..50dc13c4da4487c 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -13,12 +13,6 @@ extern "C" { extern PyObject* _PyList_Extend(PyListObject *, PyObject *); extern void _PyList_DebugMallocStats(FILE *out); - -/* runtime lifecycle */ - -extern void _PyList_Fini(_PyFreeListState *); - - #define _PyList_ITEMS(op) _Py_RVALUE(_PyList_CAST(op)->ob_item) extern int diff --git a/Include/internal/pycore_object_stack.h b/Include/internal/pycore_object_stack.h index d042be2a98090a0..fc130b1e9920b41 100644 --- a/Include/internal/pycore_object_stack.h +++ b/Include/internal/pycore_object_stack.h @@ -34,9 +34,6 @@ _PyObjectStackChunk_New(void); extern void _PyObjectStackChunk_Free(_PyObjectStackChunk *); -extern void -_PyObjectStackChunk_ClearFreeList(_PyFreeListState *state, int is_finalization); - // Push an item onto the stack. Return -1 on allocation failure, 0 on success. static inline int _PyObjectStack_Push(_PyObjectStack *stack, PyObject *obj) diff --git a/Include/internal/pycore_sliceobject.h b/Include/internal/pycore_sliceobject.h index 0c72d3ee6225c51..89086f67683a2f5 100644 --- a/Include/internal/pycore_sliceobject.h +++ b/Include/internal/pycore_sliceobject.h @@ -11,8 +11,6 @@ extern "C" { /* runtime lifecycle */ -extern void _PySlice_Fini(_PyFreeListState *); - extern PyObject * _PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop); diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index b348339a505b0f7..4605f355ccbc38f 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -14,7 +14,6 @@ extern void _PyTuple_DebugMallocStats(FILE *out); /* runtime lifecycle */ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); -extern void _PyTuple_Fini(_PyFreeListState *); /* other API */ diff --git a/Objects/floatobject.c b/Objects/floatobject.c index c440e0dab0e79fa..9b322c52d4daea6 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2010,16 +2010,6 @@ _PyFloat_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) #endif } -void -_PyFloat_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyFloat_ClearFreeList(state, 1); -#endif -} - void _PyFloat_FiniType(PyInterpreterState *interp) { diff --git a/Objects/genobject.c b/Objects/genobject.c index ab523e46cceaa31..59ab7abf6180bd1 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1682,17 +1682,6 @@ _PyAsyncGen_ClearFreeLists(_PyFreeListState *freelist_state, int is_finalization #endif } -void -_PyAsyncGen_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyAsyncGen_ClearFreeLists(state, 1); -#endif -} - - static PyObject * async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result) { diff --git a/Objects/listobject.c b/Objects/listobject.c index 307b8f1bd76cac8..7fdb91eab890b5f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -135,16 +135,6 @@ _PyList_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) #endif } -void -_PyList_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyList_ClearFreeList(state, 1); -#endif -} - /* Print summary info about the state of the optimized allocator */ void _PyList_DebugMallocStats(FILE *out) diff --git a/Objects/object.c b/Objects/object.c index 61e6131c6e99bbb..275aa6713c8c217 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -793,6 +793,21 @@ PyObject_Bytes(PyObject *v) return PyBytes_FromObject(v); } +void +_PyObject_ClearFreeLists(_PyFreeListState *state, int is_finalization) +{ + // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() + // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() + _PyFloat_ClearFreeList(state, is_finalization); + _PyTuple_ClearFreeList(state, is_finalization); + _PyList_ClearFreeList(state, is_finalization); + _PyDict_ClearFreeList(state, is_finalization); + _PyContext_ClearFreeList(state, is_finalization); + _PyAsyncGen_ClearFreeLists(state, is_finalization); + // Only be cleared if is_finalization is true. + _PyObjectStackChunk_ClearFreeList(state, is_finalization); + _PySlice_ClearFreeList(state, is_finalization); +} /* def _PyObject_FunctionStr(x): diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 8b9d6bbfd858b75..9880c123c80f95e 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -103,8 +103,11 @@ PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type); /* Slice object implementation */ -void _PySlice_ClearCache(_PyFreeListState *state) +void _PySlice_ClearFreeList(_PyFreeListState *state, int is_finalization) { + if (!is_finalization) { + return; + } #ifdef WITH_FREELISTS PySliceObject *obj = state->slices.slice_cache; if (obj != NULL) { @@ -114,13 +117,6 @@ void _PySlice_ClearCache(_PyFreeListState *state) #endif } -void _PySlice_Fini(_PyFreeListState *state) -{ -#ifdef WITH_FREELISTS - _PySlice_ClearCache(state); -#endif -} - /* start, stop, and step are python objects with None indicating no index is present. */ diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index b9bf6cd48f61292..7d73c3fb0f7f2cc 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -964,11 +964,6 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) static void maybe_freelist_clear(_PyFreeListState *, int); -void -_PyTuple_Fini(_PyFreeListState *state) -{ - maybe_freelist_clear(state, 1); -} void _PyTuple_ClearFreeList(_PyFreeListState *state, int is_finalization) diff --git a/Python/context.c b/Python/context.c index 793dfa2b72c7e3f..e44fef705c36e0e 100644 --- a/Python/context.c +++ b/Python/context.c @@ -1284,17 +1284,6 @@ _PyContext_ClearFreeList(_PyFreeListState *freelist_state, int is_finalization) } -void -_PyContext_Fini(_PyFreeListState *state) -{ - // With Py_GIL_DISABLED: - // the freelists for the current thread state have already been cleared. -#ifndef Py_GIL_DISABLED - _PyContext_ClearFreeList(state, 1); -#endif -} - - PyStatus _PyContext_Init(PyInterpreterState *interp) { diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 5d3b097dee93e87..93e1168002b6f7d 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -1721,7 +1721,7 @@ _PyGC_ClearAllFreeLists(PyInterpreterState *interp) HEAD_LOCK(&_PyRuntime); _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)interp->threads.head; while (tstate != NULL) { - _Py_ClearFreeLists(&tstate->freelist_state, 0); + _PyObject_ClearFreeLists(&tstate->freelist_state, 0); tstate = (_PyThreadStateImpl *)tstate->base.next; } HEAD_UNLOCK(&_PyRuntime); diff --git a/Python/gc_gil.c b/Python/gc_gil.c index 4e2aa8f7af746c2..5f1365f509deb0a 100644 --- a/Python/gc_gil.c +++ b/Python/gc_gil.c @@ -11,7 +11,7 @@ void _PyGC_ClearAllFreeLists(PyInterpreterState *interp) { - _Py_ClearFreeLists(&interp->freelist_state, 0); + _PyObject_ClearFreeLists(&interp->freelist_state, 0); } #endif diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0cac7109340129c..61c9d4f9ea95754 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1790,16 +1790,14 @@ finalize_interp_types(PyInterpreterState *interp) // a dict internally. _PyUnicode_ClearInterned(interp); - _PyDict_Fini(interp); _PyUnicode_Fini(interp); +#ifndef Py_GIL_DISABLED + // With Py_GIL_DISABLED: + // the freelists for the current thread state have already been cleared. _PyFreeListState *state = _PyFreeListState_GET(); - _PyTuple_Fini(state); - _PyList_Fini(state); - _PyFloat_Fini(state); - _PySlice_Fini(state); - _PyContext_Fini(state); - _PyAsyncGen_Fini(state); + _PyObject_ClearFreeLists(state, 1); +#endif #ifdef Py_DEBUG _PyStaticObjects_CheckRefcnt(interp); diff --git a/Python/pystate.c b/Python/pystate.c index 6cd034743ddf4c3..937c43033b068d4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1468,20 +1468,6 @@ clear_datastack(PyThreadState *tstate) } } -void -_Py_ClearFreeLists(_PyFreeListState *state, int is_finalization) -{ - // In the free-threaded build, freelists are per-PyThreadState and cleared in PyThreadState_Clear() - // In the default build, freelists are per-interpreter and cleared in finalize_interp_types() - _PyFloat_ClearFreeList(state, is_finalization); - _PyTuple_ClearFreeList(state, is_finalization); - _PyList_ClearFreeList(state, is_finalization); - _PyDict_ClearFreeList(state, is_finalization); - _PyContext_ClearFreeList(state, is_finalization); - _PyAsyncGen_ClearFreeLists(state, is_finalization); - _PyObjectStackChunk_ClearFreeList(state, is_finalization); -} - void PyThreadState_Clear(PyThreadState *tstate) { @@ -1566,9 +1552,8 @@ PyThreadState_Clear(PyThreadState *tstate) } #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds. - _PyFreeListState *freelist_state = &((_PyThreadStateImpl*)tstate)->freelist_state; - _Py_ClearFreeLists(freelist_state, 1); - _PySlice_ClearCache(freelist_state); + _PyFreeListState *freelist_state = _PyFreeListState_GET(); + _PyObject_ClearFreeLists(freelist_state, 1); // Remove ourself from the biased reference counting table of threads. _Py_brc_remove_thread(tstate); From b2d9d134dcb5633deebebf2b0118cd4f7ca598a2 Mon Sep 17 00:00:00 2001 From: Laurie O <laurie_opperman@hotmail.com> Date: Sat, 10 Feb 2024 14:58:30 +1000 Subject: [PATCH 258/263] gh-96471: Add shutdown() method to queue.Queue (#104750) Co-authored-by: Duprat <yduprat@gmail.com> --- Doc/library/queue.rst | 38 ++ Doc/whatsnew/3.13.rst | 7 + Lib/queue.py | 50 +++ Lib/test/test_queue.py | 378 ++++++++++++++++++ ...3-05-06-04-57-10.gh-issue-96471.C9wAU7.rst | 1 + 5 files changed, 474 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst diff --git a/Doc/library/queue.rst b/Doc/library/queue.rst index b2b787c5a8260cf..1421fc2e552f0e3 100644 --- a/Doc/library/queue.rst +++ b/Doc/library/queue.rst @@ -93,6 +93,14 @@ The :mod:`queue` module defines the following classes and exceptions: on a :class:`Queue` object which is full. +.. exception:: ShutDown + + Exception raised when :meth:`~Queue.put` or :meth:`~Queue.get` is called on + a :class:`Queue` object which has been shut down. + + .. versionadded:: 3.13 + + .. _queueobjects: Queue Objects @@ -135,6 +143,8 @@ provide the public methods described below. immediately available, else raise the :exc:`Full` exception (*timeout* is ignored in that case). + Raises :exc:`ShutDown` if the queue has been shut down. + .. method:: Queue.put_nowait(item) @@ -155,6 +165,9 @@ provide the public methods described below. an uninterruptible wait on an underlying lock. This means that no exceptions can occur, and in particular a SIGINT will not trigger a :exc:`KeyboardInterrupt`. + Raises :exc:`ShutDown` if the queue has been shut down and is empty, or if + the queue has been shut down immediately. + .. method:: Queue.get_nowait() @@ -177,6 +190,8 @@ fully processed by daemon consumer threads. Raises a :exc:`ValueError` if called more times than there were items placed in the queue. + Raises :exc:`ShutDown` if the queue has been shut down immediately. + .. method:: Queue.join() @@ -187,6 +202,8 @@ fully processed by daemon consumer threads. indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, :meth:`join` unblocks. + Raises :exc:`ShutDown` if the queue has been shut down immediately. + Example of how to wait for enqueued tasks to be completed:: @@ -214,6 +231,27 @@ Example of how to wait for enqueued tasks to be completed:: print('All work completed') +Terminating queues +^^^^^^^^^^^^^^^^^^ + +:class:`Queue` objects can be made to prevent further interaction by shutting +them down. + +.. method:: Queue.shutdown(immediate=False) + + Shut down the queue, making :meth:`~Queue.get` and :meth:`~Queue.put` raise + :exc:`ShutDown`. + + By default, :meth:`~Queue.get` on a shut down queue will only raise once the + queue is empty. Set *immediate* to true to make :meth:`~Queue.get` raise + immediately instead. + + All blocked callers of :meth:`~Queue.put` will be unblocked. If *immediate* + is true, also unblock callers of :meth:`~Queue.get` and :meth:`~Queue.join`. + + .. versionadded:: 3.13 + + SimpleQueue Objects ------------------- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b05e4badc9e58bf..de79bd979aff80e 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -403,6 +403,13 @@ pdb command line option or :envvar:`PYTHONSAFEPATH` environment variable). (Contributed by Tian Gao and Christian Walther in :gh:`111762`.) +queue +----- + +* Add :meth:`queue.Queue.shutdown` (along with :exc:`queue.ShutDown`) for queue + termination. + (Contributed by Laurie Opperman and Yves Duprat in :gh:`104750`.) + re -- * Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity. diff --git a/Lib/queue.py b/Lib/queue.py index 55f50088460f9e5..467ff4fcecb1340 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -25,6 +25,10 @@ class Full(Exception): pass +class ShutDown(Exception): + '''Raised when put/get with shut-down queue.''' + + class Queue: '''Create a queue object with a given maximum size. @@ -54,6 +58,9 @@ def __init__(self, maxsize=0): self.all_tasks_done = threading.Condition(self.mutex) self.unfinished_tasks = 0 + # Queue shutdown state + self.is_shutdown = False + def task_done(self): '''Indicate that a formerly enqueued task is complete. @@ -67,6 +74,8 @@ def task_done(self): Raises a ValueError if called more times than there were items placed in the queue. + + Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: unfinished = self.unfinished_tasks - 1 @@ -84,6 +93,8 @@ def join(self): to indicate the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks. + + Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: while self.unfinished_tasks: @@ -129,8 +140,12 @@ def put(self, item, block=True, timeout=None): Otherwise ('block' is false), put an item on the queue if a free slot is immediately available, else raise the Full exception ('timeout' is ignored in that case). + + Raises ShutDown if the queue has been shut down. ''' with self.not_full: + if self.is_shutdown: + raise ShutDown if self.maxsize > 0: if not block: if self._qsize() >= self.maxsize: @@ -138,6 +153,8 @@ def put(self, item, block=True, timeout=None): elif timeout is None: while self._qsize() >= self.maxsize: self.not_full.wait() + if self.is_shutdown: + raise ShutDown elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: @@ -147,6 +164,8 @@ def put(self, item, block=True, timeout=None): if remaining <= 0.0: raise Full self.not_full.wait(remaining) + if self.is_shutdown: + raise ShutDown self._put(item) self.unfinished_tasks += 1 self.not_empty.notify() @@ -161,14 +180,21 @@ def get(self, block=True, timeout=None): Otherwise ('block' is false), return an item if one is immediately available, else raise the Empty exception ('timeout' is ignored in that case). + + Raises ShutDown if the queue has been shut down and is empty, + or if the queue has been shut down immediately. ''' with self.not_empty: + if self.is_shutdown and not self._qsize(): + raise ShutDown if not block: if not self._qsize(): raise Empty elif timeout is None: while not self._qsize(): self.not_empty.wait() + if self.is_shutdown and not self._qsize(): + raise ShutDown elif timeout < 0: raise ValueError("'timeout' must be a non-negative number") else: @@ -178,6 +204,8 @@ def get(self, block=True, timeout=None): if remaining <= 0.0: raise Empty self.not_empty.wait(remaining) + if self.is_shutdown and not self._qsize(): + raise ShutDown item = self._get() self.not_full.notify() return item @@ -198,6 +226,28 @@ def get_nowait(self): ''' return self.get(block=False) + def shutdown(self, immediate=False): + '''Shut-down the queue, making queue gets and puts raise. + + By default, gets will only raise once the queue is empty. Set + 'immediate' to True to make gets raise immediately instead. + + All blocked callers of put() will be unblocked, and also get() + and join() if 'immediate'. The ShutDown exception is raised. + ''' + with self.mutex: + self.is_shutdown = True + if immediate: + n_items = self._qsize() + while self._qsize(): + self._get() + if self.unfinished_tasks > 0: + self.unfinished_tasks -= 1 + self.not_empty.notify_all() + # release all blocked threads in `join()` + self.all_tasks_done.notify_all() + self.not_full.notify_all() + # Override these methods to implement other queue organizations # (e.g. stack or priority queue). # These will only be called with appropriate locks held diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py index 33113a72e6b6a9d..e3d4d566cdda48a 100644 --- a/Lib/test/test_queue.py +++ b/Lib/test/test_queue.py @@ -241,6 +241,384 @@ def test_shrinking_queue(self): with self.assertRaises(self.queue.Full): q.put_nowait(4) + def test_shutdown_empty(self): + q = self.type2test() + q.shutdown() + with self.assertRaises(self.queue.ShutDown): + q.put("data") + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_nonempty(self): + q = self.type2test() + q.put("data") + q.shutdown() + q.get() + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_immediate(self): + q = self.type2test() + q.put("data") + q.shutdown(immediate=True) + with self.assertRaises(self.queue.ShutDown): + q.get() + + def test_shutdown_allowed_transitions(self): + # allowed transitions would be from alive via shutdown to immediate + q = self.type2test() + self.assertFalse(q.is_shutdown) + + q.shutdown() + self.assertTrue(q.is_shutdown) + + q.shutdown(immediate=True) + self.assertTrue(q.is_shutdown) + + q.shutdown(immediate=False) + + def _shutdown_all_methods_in_one_thread(self, immediate): + q = self.type2test(2) + q.put("L") + q.put_nowait("O") + q.shutdown(immediate) + + with self.assertRaises(self.queue.ShutDown): + q.put("E") + with self.assertRaises(self.queue.ShutDown): + q.put_nowait("W") + if immediate: + with self.assertRaises(self.queue.ShutDown): + q.get() + with self.assertRaises(self.queue.ShutDown): + q.get_nowait() + with self.assertRaises(ValueError): + q.task_done() + q.join() + else: + self.assertIn(q.get(), "LO") + q.task_done() + self.assertIn(q.get(), "LO") + q.task_done() + q.join() + # on shutdown(immediate=False) + # when queue is empty, should raise ShutDown Exception + with self.assertRaises(self.queue.ShutDown): + q.get() # p.get(True) + with self.assertRaises(self.queue.ShutDown): + q.get_nowait() # p.get(False) + with self.assertRaises(self.queue.ShutDown): + q.get(True, 1.0) + + def test_shutdown_all_methods_in_one_thread(self): + return self._shutdown_all_methods_in_one_thread(False) + + def test_shutdown_immediate_all_methods_in_one_thread(self): + return self._shutdown_all_methods_in_one_thread(True) + + def _write_msg_thread(self, q, n, results, delay, + i_when_exec_shutdown, + event_start, event_end): + event_start.wait() + for i in range(1, n+1): + try: + q.put((i, "YDLO")) + results.append(True) + except self.queue.ShutDown: + results.append(False) + # triggers shutdown of queue + if i == i_when_exec_shutdown: + event_end.set() + time.sleep(delay) + # end of all puts + q.join() + + def _read_msg_thread(self, q, nb, results, delay, event_start): + event_start.wait() + block = True + while nb: + time.sleep(delay) + try: + # Get at least one message + q.get(block) + block = False + q.task_done() + results.append(True) + nb -= 1 + except self.queue.ShutDown: + results.append(False) + nb -= 1 + except self.queue.Empty: + pass + q.join() + + def _shutdown_thread(self, q, event_end, immediate): + event_end.wait() + q.shutdown(immediate) + q.join() + + def _join_thread(self, q, delay, event_start): + event_start.wait() + time.sleep(delay) + q.join() + + def _shutdown_all_methods_in_many_threads(self, immediate): + q = self.type2test() + ps = [] + ev_start = threading.Event() + ev_exec_shutdown = threading.Event() + res_puts = [] + res_gets = [] + delay = 1e-4 + read_process = 4 + nb_msgs = read_process * 16 + nb_msgs_r = nb_msgs // read_process + when_exec_shutdown = nb_msgs // 2 + lprocs = ( + (self._write_msg_thread, 1, (q, nb_msgs, res_puts, delay, + when_exec_shutdown, + ev_start, ev_exec_shutdown)), + (self._read_msg_thread, read_process, (q, nb_msgs_r, + res_gets, delay*2, + ev_start)), + (self._join_thread, 2, (q, delay*2, ev_start)), + (self._shutdown_thread, 1, (q, ev_exec_shutdown, immediate)), + ) + # start all threds + for func, n, args in lprocs: + for i in range(n): + ps.append(threading.Thread(target=func, args=args)) + ps[-1].start() + # set event in order to run q.shutdown() + ev_start.set() + + if not immediate: + assert(len(res_gets) == len(res_puts)) + assert(res_gets.count(True) == res_puts.count(True)) + else: + assert(len(res_gets) <= len(res_puts)) + assert(res_gets.count(True) <= res_puts.count(True)) + + for thread in ps[1:]: + thread.join() + + def test_shutdown_all_methods_in_many_threads(self): + return self._shutdown_all_methods_in_many_threads(False) + + def test_shutdown_immediate_all_methods_in_many_threads(self): + return self._shutdown_all_methods_in_many_threads(True) + + def _get(self, q, go, results, shutdown=False): + go.wait() + try: + msg = q.get() + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _get_shutdown(self, q, go, results): + return self._get(q, go, results, True) + + def _get_task_done(self, q, go, results): + go.wait() + try: + msg = q.get() + q.task_done() + results.append(True) + return msg + except self.queue.ShutDown: + results.append(False) + return False + + def _put(self, q, msg, go, results, shutdown=False): + go.wait() + try: + q.put(msg) + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _put_shutdown(self, q, msg, go, results): + return self._put(q, msg, go, results, True) + + def _join(self, q, results, shutdown=False): + try: + q.join() + results.append(not shutdown) + return not shutdown + except self.queue.ShutDown: + results.append(shutdown) + return shutdown + + def _join_shutdown(self, q, results): + return self._join(q, results, True) + + def _shutdown_get(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + # queue full + + if immediate: + thrds = ( + (self._get_shutdown, (q, go, results)), + (self._get_shutdown, (q, go, results)), + ) + else: + thrds = ( + # on shutdown(immediate=False) + # one of these threads shoud raise Shutdown + (self._get, (q, go, results)), + (self._get, (q, go, results)), + (self._get, (q, go, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + if immediate: + self.assertListEqual(results, [True, True]) + else: + self.assertListEqual(sorted(results), [False] + [True]*(len(thrds)-1)) + + def test_shutdown_get(self): + return self._shutdown_get(False) + + def test_shutdown_immediate_get(self): + return self._shutdown_get(True) + + def _shutdown_put(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + # queue fulled + + thrds = ( + (self._put_shutdown, (q, "E", go, results)), + (self._put_shutdown, (q, "W", go, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + q.shutdown() + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_put(self): + return self._shutdown_put(False) + + def test_shutdown_immediate_put(self): + return self._shutdown_put(True) + + def _shutdown_join(self, immediate): + q = self.type2test() + results = [] + q.put("Y") + go = threading.Event() + nb = q.qsize() + + thrds = ( + (self._join, (q, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + if not immediate: + res = [] + for i in range(nb): + threads.append(threading.Thread(target=self._get_task_done, args=(q, go, res))) + threads[-1].start() + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_immediate_join(self): + return self._shutdown_join(True) + + def test_shutdown_join(self): + return self._shutdown_join(False) + + def _shutdown_put_join(self, immediate): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + nb = q.qsize() + # queue not fulled + + thrds = ( + (self._put_shutdown, (q, "E", go, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + self.assertEqual(q.unfinished_tasks, nb) + for i in range(nb): + t = threading.Thread(target=q.task_done) + t.start() + threads.append(t) + q.shutdown(immediate) + go.set() + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + def test_shutdown_immediate_put_join(self): + return self._shutdown_put_join(True) + + def test_shutdown_put_join(self): + return self._shutdown_put_join(False) + + def test_shutdown_get_task_done_join(self): + q = self.type2test(2) + results = [] + go = threading.Event() + q.put("Y") + q.put("D") + self.assertEqual(q.unfinished_tasks, q.qsize()) + + thrds = ( + (self._get_task_done, (q, go, results)), + (self._get_task_done, (q, go, results)), + (self._join, (q, results)), + (self._join, (q, results)), + ) + threads = [] + for func, params in thrds: + threads.append(threading.Thread(target=func, args=params)) + threads[-1].start() + go.set() + q.shutdown(False) + for t in threads: + t.join() + + self.assertEqual(results, [True]*len(thrds)) + + class QueueTest(BaseQueueTestMixin): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst b/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst new file mode 100644 index 000000000000000..0bace8d8bd425c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-06-04-57-10.gh-issue-96471.C9wAU7.rst @@ -0,0 +1 @@ +Add :py:class:`queue.Queue` termination with :py:meth:`~queue.Queue.shutdown`. From e19103a346f0277c44a43dfaebad9a5aa468bf1e Mon Sep 17 00:00:00 2001 From: Nikita Sobolev <mail@sobolevn.me> Date: Sat, 10 Feb 2024 11:34:23 +0300 Subject: [PATCH 259/263] gh-114552: Update `__dir__` method docs: it allows returning an iterable (#114662) --- Doc/reference/datamodel.rst | 6 +++--- Lib/test/test_builtin.py | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 0a1c1d58558e94f..885ee825c122965 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1988,8 +1988,8 @@ access (use of, assignment to, or deletion of ``x.name``) for class instances. .. method:: object.__dir__(self) - Called when :func:`dir` is called on the object. A sequence must be - returned. :func:`dir` converts the returned sequence to a list and sorts it. + Called when :func:`dir` is called on the object. An iterable must be + returned. :func:`dir` converts the returned iterable to a list and sorts it. Customizing module attribute access @@ -2009,7 +2009,7 @@ not found on a module object through the normal lookup, i.e. the module ``__dict__`` before raising an :exc:`AttributeError`. If found, it is called with the attribute name and the result is returned. -The ``__dir__`` function should accept no arguments, and return a sequence of +The ``__dir__`` function should accept no arguments, and return an iterable of strings that represents the names accessible on module. If present, this function overrides the standard :func:`dir` search on a module. diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index fcddd147bac63ef..7a3ab2274a58f20 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -611,6 +611,14 @@ def __dir__(self): self.assertIsInstance(res, list) self.assertTrue(res == ["a", "b", "c"]) + # dir(obj__dir__iterable) + class Foo(object): + def __dir__(self): + return {"b", "c", "a"} + res = dir(Foo()) + self.assertIsInstance(res, list) + self.assertEqual(sorted(res), ["a", "b", "c"]) + # dir(obj__dir__not_sequence) class Foo(object): def __dir__(self): From 6e222a55b1d63de994a2ca39afd4bbf4d2fbdd34 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren <ronaldoussoren@mac.com> Date: Sat, 10 Feb 2024 11:16:45 +0100 Subject: [PATCH 260/263] GH-87804: Fix counter overflow in statvfs on macOS (#99570) On macOS the statvfs interface returns block counts as 32-bit integers, and that results in bad reporting for larger disks. Therefore reimplement statvfs in terms of statfs, which does use 64-bit integers for block counts. Tested using a sparse filesystem image of 100TB. --- ...2-11-18-10-05-35.gh-issue-87804.rhlDmD.rst | 1 + Modules/posixmodule.c | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst diff --git a/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst b/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst new file mode 100644 index 000000000000000..e6554d5c9f1e1ec --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2022-11-18-10-05-35.gh-issue-87804.rhlDmD.rst @@ -0,0 +1 @@ +On macOS the result of ``os.statvfs`` and ``os.fstatvfs`` now correctly report the size of very large disks, in previous versions the reported number of blocks was wrong for disks with at least 2**32 blocks. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 230c961a2ac3c04..d05b4ba723ce8c8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -52,6 +52,12 @@ # define EX_OK EXIT_SUCCESS #endif +#ifdef __APPLE__ + /* Needed for the implementation of os.statvfs */ +# include <sys/param.h> +# include <sys/mount.h> +#endif + /* On android API level 21, 'AT_EACCESS' is not declared although * HAVE_FACCESSAT is defined. */ #ifdef __ANDROID__ @@ -12886,6 +12892,59 @@ os_WSTOPSIG_impl(PyObject *module, int status) #endif #include <sys/statvfs.h> +#ifdef __APPLE__ +/* On macOS struct statvfs uses 32-bit integers for block counts, + * resulting in overflow when filesystems are larger tan 4TB. Therefore + * os.statvfs is implemented in terms of statfs(2). + */ + +static PyObject* +_pystatvfs_fromstructstatfs(PyObject *module, struct statfs st) { + PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType; + PyObject *v = PyStructSequence_New((PyTypeObject *)StatVFSResultType); + if (v == NULL) + return NULL; + + long flags = 0; + if (st.f_flags & MNT_RDONLY) { + flags |= ST_RDONLY; + } + if (st.f_flags & MNT_NOSUID) { + flags |= ST_NOSUID; + } + + _Static_assert(sizeof(st.f_blocks) == sizeof(long long), "assuming large file"); + + PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_iosize)); + PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_bsize)); + PyStructSequence_SET_ITEM(v, 2, + PyLong_FromLongLong((long long) st.f_blocks)); + PyStructSequence_SET_ITEM(v, 3, + PyLong_FromLongLong((long long) st.f_bfree)); + PyStructSequence_SET_ITEM(v, 4, + PyLong_FromLongLong((long long) st.f_bavail)); + PyStructSequence_SET_ITEM(v, 5, + PyLong_FromLongLong((long long) st.f_files)); + PyStructSequence_SET_ITEM(v, 6, + PyLong_FromLongLong((long long) st.f_ffree)); + PyStructSequence_SET_ITEM(v, 7, + PyLong_FromLongLong((long long) st.f_ffree)); + PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) flags)); + + PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) NAME_MAX)); + PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0])); + if (PyErr_Occurred()) { + Py_DECREF(v); + return NULL; + } + + return v; +} + +#else + + + static PyObject* _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) { PyObject *StatVFSResultType = get_posix_state(module)->StatVFSResultType; @@ -12937,6 +12996,8 @@ _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) { return v; } +#endif + /*[clinic input] os.fstatvfs @@ -12954,6 +13015,22 @@ os_fstatvfs_impl(PyObject *module, int fd) { int result; int async_err = 0; +#ifdef __APPLE__ + struct statfs st; + /* On macOS os.fstatvfs is implemented using fstatfs(2) because + * the former uses 32-bit values for block counts. + */ + do { + Py_BEGIN_ALLOW_THREADS + result = fstatfs(fd, &st); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; + + return _pystatvfs_fromstructstatfs(module, st); +#else struct statvfs st; do { @@ -12966,6 +13043,7 @@ os_fstatvfs_impl(PyObject *module, int fd) return (!async_err) ? posix_error() : NULL; return _pystatvfs_fromstructstatvfs(module, st); +#endif } #endif /* defined(HAVE_FSTATVFS) && defined(HAVE_SYS_STATVFS_H) */ @@ -12989,6 +13067,28 @@ os_statvfs_impl(PyObject *module, path_t *path) /*[clinic end generated code: output=87106dd1beb8556e input=3f5c35791c669bd9]*/ { int result; + +#ifdef __APPLE__ + /* On macOS os.statvfs is implemented using statfs(2)/fstatfs(2) because + * the former uses 32-bit values for block counts. + */ + struct statfs st; + + Py_BEGIN_ALLOW_THREADS + if (path->fd != -1) { + result = fstatfs(path->fd, &st); + } + else + result = statfs(path->narrow, &st); + Py_END_ALLOW_THREADS + + if (result) { + return path_error(path); + } + + return _pystatvfs_fromstructstatfs(module, st); + +#else struct statvfs st; Py_BEGIN_ALLOW_THREADS @@ -13006,6 +13106,7 @@ os_statvfs_impl(PyObject *module, path_t *path) } return _pystatvfs_fromstructstatvfs(module, st); +#endif } #endif /* defined(HAVE_STATVFS) && defined(HAVE_SYS_STATVFS_H) */ From e2c403892400878707a20d4b7e183de505a64ca5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 10 Feb 2024 12:21:35 +0200 Subject: [PATCH 261/263] gh-76763: Make chr() always raising ValueError for out-of-range values (GH-114882) Previously it raised OverflowError for very large or very small values. --- Lib/test/test_builtin.py | 11 +++++--- ...4-02-01-23-43-49.gh-issue-76763.o_2J6i.rst | 3 +++ Python/bltinmodule.c | 25 ++++++++++++++++--- Python/clinic/bltinmodule.c.h | 21 +--------------- 4 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 7a3ab2274a58f20..9a0bf524e3943fd 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -308,14 +308,13 @@ class C3(C2): pass self.assertTrue(callable(c3)) def test_chr(self): + self.assertEqual(chr(0), '\0') self.assertEqual(chr(32), ' ') self.assertEqual(chr(65), 'A') self.assertEqual(chr(97), 'a') self.assertEqual(chr(0xff), '\xff') - self.assertRaises(ValueError, chr, 1<<24) - self.assertEqual(chr(sys.maxunicode), - str('\\U0010ffff'.encode("ascii"), 'unicode-escape')) self.assertRaises(TypeError, chr) + self.assertRaises(TypeError, chr, 65.0) self.assertEqual(chr(0x0000FFFF), "\U0000FFFF") self.assertEqual(chr(0x00010000), "\U00010000") self.assertEqual(chr(0x00010001), "\U00010001") @@ -327,7 +326,11 @@ def test_chr(self): self.assertEqual(chr(0x0010FFFF), "\U0010FFFF") self.assertRaises(ValueError, chr, -1) self.assertRaises(ValueError, chr, 0x00110000) - self.assertRaises((OverflowError, ValueError), chr, 2**32) + self.assertRaises(ValueError, chr, 1<<24) + self.assertRaises(ValueError, chr, 2**32-1) + self.assertRaises(ValueError, chr, -2**32) + self.assertRaises(ValueError, chr, 2**1000) + self.assertRaises(ValueError, chr, -2**1000) def test_cmp(self): self.assertTrue(not hasattr(builtins, "cmp")) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst new file mode 100644 index 000000000000000..d35d3d87073dddd --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-01-23-43-49.gh-issue-76763.o_2J6i.rst @@ -0,0 +1,3 @@ +The :func:`chr` builtin function now always raises :exc:`ValueError` for +values outside the valid range. Previously it raised :exc:`OverflowError` for +very large or small values. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 31c1bf07e8fb913..b0074962b737992 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -703,17 +703,34 @@ builtin_format_impl(PyObject *module, PyObject *value, PyObject *format_spec) /*[clinic input] chr as builtin_chr - i: int + i: object / Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff. [clinic start generated code]*/ static PyObject * -builtin_chr_impl(PyObject *module, int i) -/*[clinic end generated code: output=c733afcd200afcb7 input=3f604ef45a70750d]*/ +builtin_chr(PyObject *module, PyObject *i) +/*[clinic end generated code: output=d34f25b8035a9b10 input=f919867f0ba2f496]*/ { - return PyUnicode_FromOrdinal(i); + int overflow; + long v = PyLong_AsLongAndOverflow(i, &overflow); + if (v == -1 && PyErr_Occurred()) { + return NULL; + } + if (overflow) { + v = overflow < 0 ? INT_MIN : INT_MAX; + /* Allow PyUnicode_FromOrdinal() to raise an exception */ + } +#if SIZEOF_INT < SIZEOF_LONG + else if (v < INT_MIN) { + v = INT_MIN; + } + else if (v > INT_MAX) { + v = INT_MAX; + } +#endif + return PyUnicode_FromOrdinal(v); } diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index 8d40e659b54a57e..3898f987cd61ea6 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -233,25 +233,6 @@ PyDoc_STRVAR(builtin_chr__doc__, #define BUILTIN_CHR_METHODDEF \ {"chr", (PyCFunction)builtin_chr, METH_O, builtin_chr__doc__}, -static PyObject * -builtin_chr_impl(PyObject *module, int i); - -static PyObject * -builtin_chr(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - int i; - - i = PyLong_AsInt(arg); - if (i == -1 && PyErr_Occurred()) { - goto exit; - } - return_value = builtin_chr_impl(module, i); - -exit: - return return_value; -} - PyDoc_STRVAR(builtin_compile__doc__, "compile($module, /, source, filename, mode, flags=0,\n" " dont_inherit=False, optimize=-1, *, _feature_version=-1)\n" @@ -1212,4 +1193,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=31bded5d08647a57 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=643a8d5f900e0c36 input=a9049054013a1b77]*/ From 597fad07f7bf709ac7084ac20aa3647995759b01 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 10 Feb 2024 15:17:33 +0200 Subject: [PATCH 262/263] gh-115059: Remove debugging code in test_io (GH-115240) --- Lib/test/test_io.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index a24579dcc878cfb..cc387afa3919099 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2531,36 +2531,6 @@ def test_interleaved_readline_write(self): f.flush() self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') - def test_xxx(self): - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read(), b'defgh') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123defgh456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read(3), b'def') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123def456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read1(), b'defgh') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123defgh456') - with self.BytesIO(b'abcdefgh') as raw: - with self.tp(raw) as f: - f.write(b'123') - self.assertEqual(f.read1(3), b'def') - f.write(b'456') - f.flush() - self.assertEqual(raw.getvalue(), b'123def456') - # You can't construct a BufferedRandom over a non-seekable stream. test_unseekable = None From 5319c66550a6d6c6698dea75c0a0ee005873ce61 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora <kirill.bast9@mail.ru> Date: Sat, 10 Feb 2024 17:37:19 +0300 Subject: [PATCH 263/263] gh-102840: Fix confused traceback when floordiv or mod operations happens between Fraction and complex objects (GH-102842) --- Lib/fractions.py | 13 ++++----- Lib/test/test_fractions.py | 27 +++++++++++++++++++ ...-02-10-15-24-20.gh-issue-102840.4mnDq1.rst | 3 +++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst diff --git a/Lib/fractions.py b/Lib/fractions.py index 389ab386b6a8a4d..f8c6c9c438c7379 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -579,7 +579,8 @@ def __format__(self, format_spec, /): f"for object of type {type(self).__name__!r}" ) - def _operator_fallbacks(monomorphic_operator, fallback_operator): + def _operator_fallbacks(monomorphic_operator, fallback_operator, + handle_complex=True): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -666,7 +667,7 @@ def forward(a, b): return monomorphic_operator(a, Fraction(b)) elif isinstance(b, float): return fallback_operator(float(a), b) - elif isinstance(b, complex): + elif handle_complex and isinstance(b, complex): return fallback_operator(complex(a), b) else: return NotImplemented @@ -679,7 +680,7 @@ def reverse(b, a): return monomorphic_operator(Fraction(a), b) elif isinstance(a, numbers.Real): return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): + elif handle_complex and isinstance(a, numbers.Complex): return fallback_operator(complex(a), complex(b)) else: return NotImplemented @@ -830,7 +831,7 @@ def _floordiv(a, b): """a // b""" return (a.numerator * b.denominator) // (a.denominator * b.numerator) - __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv, False) def _divmod(a, b): """(a // b, a % b)""" @@ -838,14 +839,14 @@ def _divmod(a, b): div, n_mod = divmod(a.numerator * db, da * b.numerator) return div, Fraction(n_mod, da * db) - __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod) + __divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod, False) def _mod(a, b): """a % b""" da, db = a.denominator, b.denominator return Fraction((a.numerator * db) % (b.numerator * da), da * db) - __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod) + __mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False) def __pow__(a, b): """a ** b diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index af3cb214ab0ac11..b45bd098a366845 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1314,6 +1314,33 @@ def test_float_format_testfile(self): self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) + def test_complex_handling(self): + # See issue gh-102840 for more details. + + a = F(1, 2) + b = 1j + message = "unsupported operand type(s) for %s: '%s' and '%s'" + # test forward + self.assertRaisesMessage(TypeError, + message % ("%", "Fraction", "complex"), + operator.mod, a, b) + self.assertRaisesMessage(TypeError, + message % ("//", "Fraction", "complex"), + operator.floordiv, a, b) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "Fraction", "complex"), + divmod, a, b) + # test reverse + self.assertRaisesMessage(TypeError, + message % ("%", "complex", "Fraction"), + operator.mod, b, a) + self.assertRaisesMessage(TypeError, + message % ("//", "complex", "Fraction"), + operator.floordiv, b, a) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "complex", "Fraction"), + divmod, b, a) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst new file mode 100644 index 000000000000000..52668a9424a9765 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst @@ -0,0 +1,3 @@ +Fix confused traceback when floordiv, mod, or divmod operations happens +between instances of :class:`fractions.Fraction` and :class:`complex`. +