From 22e411e1d107f79a0904d41a489a82355a39b5de Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 27 Nov 2023 16:34:44 +0000 Subject: [PATCH 001/442] gh-111874: Call `__set_name__` on objects that define the method inside a `typing.NamedTuple` class dictionary as part of the creation of that class (#111876) Co-authored-by: Jelle Zijlstra --- Lib/test/test_typing.py | 77 +++++++++++++++++++ Lib/typing.py | 21 ++++- ...-11-09-11-07-34.gh-issue-111874.dzYc3j.rst | 4 + 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-09-11-07-34.gh-issue-111874.dzYc3j.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 31d7fda2f978da..669803177315e3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7535,6 +7535,83 @@ class GenericNamedTuple(NamedTuple, Generic[T]): self.assertEqual(CallNamedTuple.__orig_bases__, (NamedTuple,)) + def test_setname_called_on_values_in_class_dictionary(self): + class Vanilla: + def __set_name__(self, owner, name): + self.name = name + + class Foo(NamedTuple): + attr = Vanilla() + + foo = Foo() + self.assertEqual(len(foo), 0) + self.assertNotIn('attr', Foo._fields) + self.assertIsInstance(foo.attr, Vanilla) + self.assertEqual(foo.attr.name, "attr") + + class Bar(NamedTuple): + attr: Vanilla = Vanilla() + + bar = Bar() + self.assertEqual(len(bar), 1) + self.assertIn('attr', Bar._fields) + self.assertIsInstance(bar.attr, Vanilla) + self.assertEqual(bar.attr.name, "attr") + + def test_setname_raises_the_same_as_on_other_classes(self): + class CustomException(BaseException): pass + + class Annoying: + def __set_name__(self, owner, name): + raise CustomException + + annoying = Annoying() + + with self.assertRaises(CustomException) as cm: + class NormalClass: + attr = annoying + normal_exception = cm.exception + + with self.assertRaises(CustomException) as cm: + class NamedTupleClass(NamedTuple): + attr = annoying + namedtuple_exception = cm.exception + + self.assertIs(type(namedtuple_exception), CustomException) + self.assertIs(type(namedtuple_exception), type(normal_exception)) + + self.assertEqual(len(namedtuple_exception.__notes__), 1) + self.assertEqual( + len(namedtuple_exception.__notes__), len(normal_exception.__notes__) + ) + + expected_note = ( + "Error calling __set_name__ on 'Annoying' instance " + "'attr' in 'NamedTupleClass'" + ) + self.assertEqual(namedtuple_exception.__notes__[0], expected_note) + self.assertEqual( + namedtuple_exception.__notes__[0], + normal_exception.__notes__[0].replace("NormalClass", "NamedTupleClass") + ) + + def test_strange_errors_when_accessing_set_name_itself(self): + class CustomException(Exception): pass + + class Meta(type): + def __getattribute__(self, attr): + if attr == "__set_name__": + raise CustomException + return object.__getattribute__(self, attr) + + class VeryAnnoying(metaclass=Meta): pass + + very_annoying = VeryAnnoying() + + with self.assertRaises(CustomException): + class Foo(NamedTuple): + attr = very_annoying + class TypedDictTests(BaseTestCase): def test_basics_functional_syntax(self): diff --git a/Lib/typing.py b/Lib/typing.py index 872aca82c4e779..216f0c141b62af 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2743,11 +2743,26 @@ def __new__(cls, typename, bases, ns): class_getitem = _generic_class_getitem nm_tpl.__class_getitem__ = classmethod(class_getitem) # update from user namespace without overriding special namedtuple attributes - for key in ns: + for key, val in ns.items(): if key in _prohibited: raise AttributeError("Cannot overwrite NamedTuple attribute " + key) - elif key not in _special and key not in nm_tpl._fields: - setattr(nm_tpl, key, ns[key]) + elif key not in _special: + if key not in nm_tpl._fields: + setattr(nm_tpl, key, val) + try: + set_name = type(val).__set_name__ + except AttributeError: + pass + else: + try: + set_name(val, nm_tpl, key) + except BaseException as e: + e.add_note( + f"Error calling __set_name__ on {type(val).__name__!r} " + f"instance {key!r} in {typename!r}" + ) + raise + if Generic in bases: nm_tpl.__init_subclass__() return nm_tpl diff --git a/Misc/NEWS.d/next/Library/2023-11-09-11-07-34.gh-issue-111874.dzYc3j.rst b/Misc/NEWS.d/next/Library/2023-11-09-11-07-34.gh-issue-111874.dzYc3j.rst new file mode 100644 index 00000000000000..50408202a7a5a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-09-11-07-34.gh-issue-111874.dzYc3j.rst @@ -0,0 +1,4 @@ +When creating a :class:`typing.NamedTuple` class, ensure +:func:`~object.__set_name__` is called on all objects that define +``__set_name__`` and exist in the values of the ``NamedTuple`` class's class +dictionary. Patch by Alex Waygood. From 812360fddda86d7aff5823f529ab720f57ddc411 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Mon, 27 Nov 2023 09:15:39 -0800 Subject: [PATCH 002/442] gh-84443: SSLSocket.recv_into() now support buffer protocol with itemsize != 1 (GH-20310) It is also no longer use __len__(). Co-authored-by: Serhiy Storchaka --- Lib/ssl.py | 12 ++++++---- Lib/test/test_ssl.py | 22 +++++++++++++++++++ .../2020-05-21-23-32-46.bpo-40262.z4fQv1.rst | 2 ++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-05-21-23-32-46.bpo-40262.z4fQv1.rst diff --git a/Lib/ssl.py b/Lib/ssl.py index 62e55857141dfc..36fca9d4aa93d1 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -1270,10 +1270,14 @@ def recv(self, buflen=1024, flags=0): def recv_into(self, buffer, nbytes=None, flags=0): self._checkClosed() - if buffer and (nbytes is None): - nbytes = len(buffer) - elif nbytes is None: - nbytes = 1024 + if nbytes is None: + if buffer is not None: + with memoryview(buffer) as view: + nbytes = view.nbytes + if not nbytes: + nbytes = 1024 + else: + nbytes = 1024 if self._sslobj is not None: if flags != 0: raise ValueError( diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 9ade595ef8ae7e..aecba89cde1495 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -10,6 +10,7 @@ from test.support import threading_helper from test.support import warnings_helper from test.support import asyncore +import array import re import socket import select @@ -3517,6 +3518,27 @@ def test_recv_zero(self): self.assertEqual(s.recv(0), b"") self.assertEqual(s.recv_into(bytearray()), 0) + def test_recv_into_buffer_protocol_len(self): + server = ThreadedEchoServer(CERTFILE) + self.enterContext(server) + s = socket.create_connection((HOST, server.port)) + self.addCleanup(s.close) + s = test_wrap_socket(s, suppress_ragged_eofs=False) + self.addCleanup(s.close) + + s.send(b"data") + buf = array.array('I', [0, 0]) + self.assertEqual(s.recv_into(buf), 4) + self.assertEqual(bytes(buf)[:4], b"data") + + class B(bytearray): + def __len__(self): + 1/0 + s.send(b"data") + buf = B(6) + self.assertEqual(s.recv_into(buf), 4) + self.assertEqual(bytes(buf), b"data\0\0") + def test_nonblocking_send(self): server = ThreadedEchoServer(CERTFILE, certreqs=ssl.CERT_NONE, diff --git a/Misc/NEWS.d/next/Library/2020-05-21-23-32-46.bpo-40262.z4fQv1.rst b/Misc/NEWS.d/next/Library/2020-05-21-23-32-46.bpo-40262.z4fQv1.rst new file mode 100644 index 00000000000000..c017a1c8df09d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-21-23-32-46.bpo-40262.z4fQv1.rst @@ -0,0 +1,2 @@ +The :meth:`ssl.SSLSocket.recv_into` method no longer requires the *buffer* +argument to implement ``__len__`` and supports buffers with arbitrary item size. From 4eea1e82369fbf7a795d1956e7a8212a1b58009f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:32:55 +0200 Subject: [PATCH 003/442] gh-112438: Fix support of format units with the "e" prefix in nested tuples in PyArg_Parse (gh-112439) --- Lib/test/test_capi/test_getargs.py | 28 +++++++++++++++++++ ...-11-27-09-44-16.gh-issue-112438.GdNZiI.rst | 2 ++ Modules/_testcapi/getargs.c | 14 ++++++---- Python/getargs.c | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-11-27-09-44-16.gh-issue-112438.GdNZiI.rst diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py index c964b1efd577ba..9b6aef27625ad0 100644 --- a/Lib/test/test_capi/test_getargs.py +++ b/Lib/test/test_capi/test_getargs.py @@ -1314,6 +1314,34 @@ def test_nonascii_keywords(self): f"'{name2}' is an invalid keyword argument"): parse((), {name2: 1, name3: 2}, '|OO', [name, name3]) + def test_nested_tuple(self): + parse = _testcapi.parse_tuple_and_keywords + + self.assertEqual(parse(((1, 2, 3),), {}, '(OOO)', ['a']), (1, 2, 3)) + self.assertEqual(parse((1, (2, 3), 4), {}, 'O(OO)O', ['a', 'b', 'c']), + (1, 2, 3, 4)) + parse(((1, 2, 3),), {}, '(iii)', ['a']) + + with self.assertRaisesRegex(TypeError, + "argument 1 must be sequence of length 2, not 3"): + parse(((1, 2, 3),), {}, '(ii)', ['a']) + with self.assertRaisesRegex(TypeError, + "argument 1 must be sequence of length 2, not 1"): + parse(((1,),), {}, '(ii)', ['a']) + with self.assertRaisesRegex(TypeError, + "argument 1 must be 2-item sequence, not int"): + parse((1,), {}, '(ii)', ['a']) + with self.assertRaisesRegex(TypeError, + "argument 1 must be 2-item sequence, not bytes"): + parse((b'ab',), {}, '(ii)', ['a']) + + for f in 'es', 'et', 'es#', 'et#': + with self.assertRaises(LookupError): # empty encoding "" + parse((('a',),), {}, '(' + f + ')', ['a']) + with self.assertRaisesRegex(TypeError, + "argument 1 must be sequence of length 1, not 0"): + parse(((),), {}, '(' + f + ')', ['a']) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C API/2023-11-27-09-44-16.gh-issue-112438.GdNZiI.rst b/Misc/NEWS.d/next/C API/2023-11-27-09-44-16.gh-issue-112438.GdNZiI.rst new file mode 100644 index 00000000000000..113119efd6aebb --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-27-09-44-16.gh-issue-112438.GdNZiI.rst @@ -0,0 +1,2 @@ +Fix support of format units "es", "et", "es#", and "et#" in nested tuples in +:c:func:`PyArg_ParseTuple`-like functions. diff --git a/Modules/_testcapi/getargs.c b/Modules/_testcapi/getargs.c index e4cd15503fd11f..33e8af7d7bbb39 100644 --- a/Modules/_testcapi/getargs.c +++ b/Modules/_testcapi/getargs.c @@ -71,18 +71,22 @@ parse_tuple_and_keywords(PyObject *self, PyObject *args) if (result) { int objects_only = 1; + int count = 0; for (const char *f = sub_format; *f; f++) { - if (Py_ISALNUM(*f) && strchr("OSUY", *f) == NULL) { - objects_only = 0; - break; + if (Py_ISALNUM(*f)) { + if (strchr("OSUY", *f) == NULL) { + objects_only = 0; + break; + } + count++; } } if (objects_only) { - return_value = PyTuple_New(size); + return_value = PyTuple_New(count); if (return_value == NULL) { goto exit; } - for (Py_ssize_t i = 0; i < size; i++) { + for (Py_ssize_t i = 0; i < count; i++) { PyObject *arg = *(PyObject **)(buffers + i); if (arg == NULL) { arg = Py_None; diff --git a/Python/getargs.c b/Python/getargs.c index c0c2eb27184e3c..5ff8f7473609f5 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -477,7 +477,7 @@ converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags, } else if (c == ':' || c == ';' || c == '\0') break; - else if (level == 0 && Py_ISALPHA(c)) + else if (level == 0 && Py_ISALPHA(c) && c != 'e') n++; } From b14e5df120ca8ce968a67df2e00e7a764dd703a0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:35:52 +0200 Subject: [PATCH 004/442] gh-111789: Use PyDict_GetItemRef() in Modules/_csv.c (gh-112073) --- Modules/_csv.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Modules/_csv.c b/Modules/_csv.c index 714fbef08d22c9..ae6b6457ffad9a 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -160,15 +160,9 @@ static PyObject * get_dialect_from_registry(PyObject *name_obj, _csvstate *module_state) { PyObject *dialect_obj; - - dialect_obj = PyDict_GetItemWithError(module_state->dialects, name_obj); - if (dialect_obj == NULL) { - if (!PyErr_Occurred()) - PyErr_Format(module_state->error_obj, "unknown dialect"); + if (PyDict_GetItemRef(module_state->dialects, name_obj, &dialect_obj) == 0) { + PyErr_SetString(module_state->error_obj, "unknown dialect"); } - else - Py_INCREF(dialect_obj); - return dialect_obj; } From 0f009033202b339375944f613aa6d0597a2841de Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:41:47 +0200 Subject: [PATCH 005/442] gh-111789: Use PyDict_GetItemRef() in Modules/_struct.c (gh-112076) --- Modules/_struct.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 24a4cb3b6413f1..bd16fa89f18945 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2257,14 +2257,13 @@ cache_struct_converter(PyObject *module, PyObject *fmt, PyStructObject **ptr) return 1; } - s_object = PyDict_GetItemWithError(state->cache, fmt); + if (PyDict_GetItemRef(state->cache, fmt, &s_object) < 0) { + return 0; + } if (s_object != NULL) { - *ptr = (PyStructObject *)Py_NewRef(s_object); + *ptr = (PyStructObject *)s_object; return Py_CLEANUP_SUPPORTED; } - else if (PyErr_Occurred()) { - return 0; - } s_object = PyObject_CallOneArg(state->PyStructType, fmt); if (s_object != NULL) { From ef9b2fc9b0378aee87328fadce73b3fefb6aca4a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:46:43 +0200 Subject: [PATCH 006/442] gh-111789: Use PyDict_GetItemRef() in Modules/_threadmodule.c (gh-112077) --- Modules/_threadmodule.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index c608789b5fbd5c..afcf646e3bc19e 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1115,12 +1115,10 @@ local_getattro(localobject *self, PyObject *name) } /* Optimization: just look in dict ourselves */ - PyObject *value = PyDict_GetItemWithError(ldict, name); - if (value != NULL) { - return Py_NewRef(value); - } - if (PyErr_Occurred()) { - return NULL; + PyObject *value; + if (PyDict_GetItemRef(ldict, name, &value) != 0) { + // found or error + return value; } /* Fall back on generic to get __class__ and __dict__ */ From d8908932fc03e064ba8df03d17d8cc7ffa5f171f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:51:31 +0200 Subject: [PATCH 007/442] gh-111789: Use PyDict_GetItemRef() in Modules/pyexpat.c (gh-112079) --- Modules/pyexpat.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 21579a80dd7f70..9d95309dbb7aa6 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -240,19 +240,12 @@ string_intern(xmlparseobject *self, const char* str) return result; if (!self->intern) return result; - value = PyDict_GetItemWithError(self->intern, result); - if (!value) { - if (!PyErr_Occurred() && - PyDict_SetItem(self->intern, result, result) == 0) - { - return result; - } - else { - Py_DECREF(result); - return NULL; - } + if (PyDict_GetItemRef(self->intern, result, &value) == 0 && + PyDict_SetItem(self->intern, result, result) == 0) + { + return result; } - Py_INCREF(value); + assert((value != NULL) == !PyErr_Occurred()); Py_DECREF(result); return value; } From 395fd9c1808fa0babc96540744d2c915178a452b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:52:54 +0200 Subject: [PATCH 008/442] gh-111789: Use PyDict_GetItemRef() in Python/bltinmodule.c (gh-112081) --- Python/bltinmodule.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index ff07c498263cd3..7a9625134761f9 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -5,7 +5,6 @@ #include "pycore_call.h" // _PyObject_CallNoArgs() #include "pycore_ceval.h" // _PyEval_Vector() #include "pycore_compile.h" // _PyAST_Compile() -#include "pycore_dict.h" // _PyDict_GetItemWithError() #include "pycore_long.h" // _PyLong_CompactValue #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _Py_AddToAllObjects() @@ -141,18 +140,16 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs, goto error; } - meta = _PyDict_GetItemWithError(mkw, &_Py_ID(metaclass)); + if (PyDict_GetItemRef(mkw, &_Py_ID(metaclass), &meta) < 0) { + goto error; + } if (meta != NULL) { - Py_INCREF(meta); if (PyDict_DelItem(mkw, &_Py_ID(metaclass)) < 0) { goto error; } /* metaclass is explicitly given, check if it's indeed a class */ isclass = PyType_Check(meta); } - else if (PyErr_Occurred()) { - goto error; - } } if (meta == NULL) { /* if there are no bases, use type: */ From aa438bdd6deed225d30d87dc3a77602ffc924213 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:53:43 +0200 Subject: [PATCH 009/442] gh-111789: Use PyDict_GetItemRef() in Python/codecs.c (gh-112082) --- Python/codecs.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Python/codecs.c b/Python/codecs.c index b79bf555f2f22a..545bf82e00dca1 100644 --- a/Python/codecs.c +++ b/Python/codecs.c @@ -146,15 +146,14 @@ PyObject *_PyCodec_Lookup(const char *encoding) PyUnicode_InternInPlace(&v); /* First, try to lookup the name in the registry dictionary */ - PyObject *result = PyDict_GetItemWithError(interp->codec_search_cache, v); + PyObject *result; + if (PyDict_GetItemRef(interp->codec_search_cache, v, &result) < 0) { + goto onError; + } if (result != NULL) { - Py_INCREF(result); Py_DECREF(v); return result; } - else if (PyErr_Occurred()) { - goto onError; - } /* Next, scan the search functions in order of registration */ const Py_ssize_t len = PyList_Size(interp->codec_search_path); From befbad3663a48a8de2e1263afe18ec9fa47dfc6d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:55:30 +0200 Subject: [PATCH 010/442] gh-111789: Use PyDict_GetItemRef() in Python/symtable.c (gh-112084) --- Python/symtable.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Python/symtable.c b/Python/symtable.c index da7fec0ee7cf0c..52d5932896b263 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -497,18 +497,14 @@ _PySymtable_Lookup(struct symtable *st, void *key) k = PyLong_FromVoidPtr(key); if (k == NULL) return NULL; - v = PyDict_GetItemWithError(st->st_blocks, k); - Py_DECREF(k); - - if (v) { - assert(PySTEntry_Check(v)); - } - else if (!PyErr_Occurred()) { + if (PyDict_GetItemRef(st->st_blocks, k, &v) == 0) { PyErr_SetString(PyExc_KeyError, "unknown symbol table entry"); } + Py_DECREF(k); - return (PySTEntryObject *)Py_XNewRef(v); + assert(v == NULL || PySTEntry_Check(v)); + return (PySTEntryObject *)v; } long From 936c503a442ee062c837e334f237796554c792ff Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 19:58:43 +0200 Subject: [PATCH 011/442] gh-111789: Use PyDict_GetItemRef() in Python/_warnings.c (gh-112080) --- Python/_warnings.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Python/_warnings.c b/Python/_warnings.c index 4b7fb888247145..d4765032824e56 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -425,15 +425,15 @@ already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key, Py_DECREF(version_obj); } else { - already_warned = PyDict_GetItemWithError(registry, key); + if (PyDict_GetItemRef(registry, key, &already_warned) < 0) { + return -1; + } if (already_warned != NULL) { int rc = PyObject_IsTrue(already_warned); + Py_DECREF(already_warned); if (rc != 0) return rc; } - else if (PyErr_Occurred()) { - return -1; - } } /* This warning wasn't found in the registry, set it. */ From 99a73c3465a45fe57cac01a917fc50e0743b5964 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 27 Nov 2023 13:05:55 -0500 Subject: [PATCH 012/442] gh-76912: Raise OSError from any failure in getpass.getuser() (#29739) * bpo-32731: Raise OSError from any failure in getpass.getuser() Previously, if the username was not set in certain environment variables, ImportError escaped on Windows systems, and it was possible for KeyError to escape on other systems if getpwuid() failed. --- Doc/library/getpass.rst | 7 +++++-- Doc/whatsnew/3.13.rst | 4 ++++ Lib/getpass.py | 13 ++++++++++--- Lib/test/test_getpass.py | 4 ++-- .../2021-11-23-22-22-49.bpo-32731.kNOASr.rst | 3 +++ 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-11-23-22-22-49.bpo-32731.kNOASr.rst diff --git a/Doc/library/getpass.rst b/Doc/library/getpass.rst index 5c79daf0f47d8e..54c84d45a59856 100644 --- a/Doc/library/getpass.rst +++ b/Doc/library/getpass.rst @@ -46,7 +46,10 @@ The :mod:`getpass` module provides two functions: :envvar:`USER`, :envvar:`!LNAME` and :envvar:`USERNAME`, in order, and returns the value of the first one which is set to a non-empty string. If none are set, the login name from the password database is returned on - systems which support the :mod:`pwd` module, otherwise, an exception is - raised. + systems which support the :mod:`pwd` module, otherwise, an :exc:`OSError` + is raised. In general, this function should be preferred over :func:`os.getlogin()`. + + .. versionchanged:: 3.13 + Previously, various exceptions beyond just :exc:`OSError` were raised. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 3fd0f5e165f018..ec09dfea4aad3c 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1032,6 +1032,10 @@ Changes in the Python API recomended in the documentation. (Contributed by Serhiy Storchaka in :gh:`106672`.) +* An :exc:`OSError` is now raised by :func:`getpass.getuser` for any failure to + retrieve a username, instead of :exc:`ImportError` on non-Unix platforms or + :exc:`KeyError` on Unix platforms where the password database is empty. + Build Changes ============= diff --git a/Lib/getpass.py b/Lib/getpass.py index 8b42c0a536b4c4..bd0097ced94c5e 100644 --- a/Lib/getpass.py +++ b/Lib/getpass.py @@ -156,7 +156,11 @@ def getuser(): First try various environment variables, then the password database. This works on Windows as long as USERNAME is set. + Any failure to find a username raises OSError. + .. versionchanged:: 3.13 + Previously, various exceptions beyond just :exc:`OSError` + were raised. """ for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'): @@ -164,9 +168,12 @@ def getuser(): if user: return user - # If this fails, the exception will "explain" why - import pwd - return pwd.getpwuid(os.getuid())[0] + try: + import pwd + return pwd.getpwuid(os.getuid())[0] + except (ImportError, KeyError) as e: + raise OSError('No username set in the environment') from e + # Bind the name getpass to the appropriate function try: diff --git a/Lib/test/test_getpass.py b/Lib/test/test_getpass.py index 98ecec94336e32..80dda2caaa3331 100644 --- a/Lib/test/test_getpass.py +++ b/Lib/test/test_getpass.py @@ -26,7 +26,7 @@ def test_username_priorities_of_env_values(self, environ): environ.get.return_value = None try: getpass.getuser() - except ImportError: # in case there's no pwd module + except OSError: # in case there's no pwd module pass except KeyError: # current user has no pwd entry @@ -47,7 +47,7 @@ def test_username_falls_back_to_pwd(self, environ): getpass.getuser()) getpw.assert_called_once_with(42) else: - self.assertRaises(ImportError, getpass.getuser) + self.assertRaises(OSError, getpass.getuser) class GetpassRawinputTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2021-11-23-22-22-49.bpo-32731.kNOASr.rst b/Misc/NEWS.d/next/Library/2021-11-23-22-22-49.bpo-32731.kNOASr.rst new file mode 100644 index 00000000000000..92f3b870c11131 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-11-23-22-22-49.bpo-32731.kNOASr.rst @@ -0,0 +1,3 @@ +:func:`getpass.getuser` now raises :exc:`OSError` for all failures rather +than :exc:`ImportError` on systems lacking the :mod:`pwd` module or +:exc:`KeyError` if the password database is empty. From 967f2a3052c2d22e31564b428a9aa8cc63dc2a9f Mon Sep 17 00:00:00 2001 From: kale-smoothie <34165060+kale-smoothie@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:09:41 +0000 Subject: [PATCH 013/442] bpo-41422: Visit the Pickler's and Unpickler's memo in tp_traverse (GH-21664) Co-authored-by: Serhiy Storchaka --- .../2020-07-28-20-48-05.bpo-41422.iMwnMu.rst | 2 ++ Modules/_pickle.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-07-28-20-48-05.bpo-41422.iMwnMu.rst diff --git a/Misc/NEWS.d/next/Library/2020-07-28-20-48-05.bpo-41422.iMwnMu.rst b/Misc/NEWS.d/next/Library/2020-07-28-20-48-05.bpo-41422.iMwnMu.rst new file mode 100644 index 00000000000000..8bde68f8f2afc8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-28-20-48-05.bpo-41422.iMwnMu.rst @@ -0,0 +1,2 @@ +Fixed memory leaks of :class:`pickle.Pickler` and :class:`pickle.Unpickler` involving cyclic references via the +internal memo mapping. diff --git a/Modules/_pickle.c b/Modules/_pickle.c index a3cf34699ba509..227e5378e42285 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -4707,6 +4707,14 @@ Pickler_traverse(PicklerObject *self, visitproc visit, void *arg) Py_VISIT(self->fast_memo); Py_VISIT(self->reducer_override); Py_VISIT(self->buffer_callback); + PyMemoTable *memo = self->memo; + if (memo && memo->mt_table) { + Py_ssize_t i = memo->mt_allocated; + while (--i >= 0) { + Py_VISIT(memo->mt_table[i].me_key); + } + } + return 0; } @@ -7175,6 +7183,13 @@ Unpickler_traverse(UnpicklerObject *self, visitproc visit, void *arg) Py_VISIT(self->stack); Py_VISIT(self->pers_func); Py_VISIT(self->buffers); + PyObject **memo = self->memo; + if (memo) { + Py_ssize_t i = self->memo_size; + while (--i >= 0) { + Py_VISIT(memo[i]); + } + } return 0; } From 2c8b19174274c183eb652932871f60570123fe99 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 27 Nov 2023 18:36:11 +0000 Subject: [PATCH 014/442] gh-112388: Fix an error that was causing the parser to try to overwrite tokenizer errors (#112410) Signed-off-by: Pablo Galindo --- Lib/test/test_syntax.py | 1 + .../2023-11-25-22-58-49.gh-issue-112388.MU3cIM.rst | 2 ++ Parser/pegen_errors.c | 4 ++++ 3 files changed, 7 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-58-49.gh-issue-112388.MU3cIM.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index f6fa6495508d2c..e80e95383b897d 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2349,6 +2349,7 @@ def test_error_string_literal(self): def test_invisible_characters(self): self._check_error('print\x17("Hello")', "invalid non-printable character") + self._check_error(b"with(0,,):\n\x01", "invalid non-printable character") def test_match_call_does_not_raise_syntax_error(self): code = """ diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-58-49.gh-issue-112388.MU3cIM.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-58-49.gh-issue-112388.MU3cIM.rst new file mode 100644 index 00000000000000..1c82be2febda4f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-58-49.gh-issue-112388.MU3cIM.rst @@ -0,0 +1,2 @@ +Fix an error that was causing the parser to try to overwrite tokenizer +errors. Patch by pablo Galindo diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c index e2bc3b91c80718..2528d4502b3c0c 100644 --- a/Parser/pegen_errors.c +++ b/Parser/pegen_errors.c @@ -219,6 +219,10 @@ _PyPegen_tokenize_full_source_to_check_for_errors(Parser *p) { void * _PyPegen_raise_error(Parser *p, PyObject *errtype, int use_mark, const char *errmsg, ...) { + // Bail out if we already have an error set. + if (p->error_indicator && PyErr_Occurred()) { + return NULL; + } if (p->fill == 0) { va_list va; va_start(va, errmsg); From 45d648597b1146431bf3d91041e60d7f040e70bf Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 27 Nov 2023 18:37:48 +0000 Subject: [PATCH 015/442] gh-112387: Fix error positions for decoded strings with backwards tokenize errors (#112409) Signed-off-by: Pablo Galindo --- Lib/test/test_syntax.py | 4 ++++ .../2023-11-25-22-39-44.gh-issue-112387.AbBq5W.rst | 2 ++ Parser/pegen_errors.c | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-39-44.gh-issue-112387.AbBq5W.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index e80e95383b897d..99433df73387d0 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2334,6 +2334,10 @@ def test_error_parenthesis(self): """ self._check_error(code, "parenthesis '\\)' does not match opening parenthesis '\\['") + # Examples with dencodings + s = b'# coding=latin\n(aaaaaaaaaaaaaaaaa\naaaaaaaaaaa\xb5' + self._check_error(s, "'\(' was never closed") + def test_error_string_literal(self): self._check_error("'blech", r"unterminated string literal \(.*\)$") diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-39-44.gh-issue-112387.AbBq5W.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-39-44.gh-issue-112387.AbBq5W.rst new file mode 100644 index 00000000000000..adac11bf4c90a1 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-25-22-39-44.gh-issue-112387.AbBq5W.rst @@ -0,0 +1,2 @@ +Fix error positions for decoded strings with backwards tokenize errors. +Patch by Pablo Galindo diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c index 2528d4502b3c0c..20232f3a26a2cc 100644 --- a/Parser/pegen_errors.c +++ b/Parser/pegen_errors.c @@ -282,6 +282,10 @@ get_error_line_from_tokenizer_buffers(Parser *p, Py_ssize_t lineno) Py_ssize_t relative_lineno = p->starting_lineno ? lineno - p->starting_lineno + 1 : lineno; const char* buf_end = p->tok->fp_interactive ? p->tok->interactive_src_end : p->tok->inp; + if (buf_end < cur_line) { + buf_end = cur_line + strlen(cur_line); + } + for (int i = 0; i < relative_lineno - 1; i++) { char *new_line = strchr(cur_line, '\n'); // The assert is here for debug builds but the conditional that From 4dcfd02bed0d7958703ef44baa79a4a98475be2e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Nov 2023 20:57:33 +0200 Subject: [PATCH 016/442] gh-68166: Add support of "vsapi" in ttk.Style.element_create() (GH-111393) --- Doc/library/tkinter.ttk.rst | 60 +++++++++++++- Doc/whatsnew/3.13.rst | 5 ++ Lib/test/test_ttk/test_style.py | 82 +++++++++++++++++++ Lib/test/test_ttk_textonly.py | 32 ++++++-- Lib/tkinter/ttk.py | 55 +++++++------ ...3-11-08-18-53-07.gh-issue-68166.1iTh4Y.rst | 2 + 6 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-08-18-53-07.gh-issue-68166.1iTh4Y.rst diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 5fab1454944989..6e01ec7b291255 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -1391,7 +1391,8 @@ option. If you don't know the class name of a widget, use the method .. method:: element_create(elementname, etype, *args, **kw) Create a new element in the current theme, of the given *etype* which is - expected to be either "image" or "from". + expected to be either "image", "from" or "vsapi". + The latter is only available in Tk 8.6 on Windows. If "image" is used, *args* should contain the default image name followed by statespec/value pairs (this is the imagespec), and *kw* may have the @@ -1439,6 +1440,63 @@ option. If you don't know the class name of a widget, use the method style = ttk.Style(root) style.element_create('plain.background', 'from', 'default') + If "vsapi" is used as the value of *etype*, :meth:`element_create` + will create a new element in the current theme whose visual appearance + is drawn using the Microsoft Visual Styles API which is responsible + for the themed styles on Windows XP and Vista. + *args* is expected to contain the Visual Styles class and part as + given in the Microsoft documentation followed by an optional sequence + of tuples of ttk states and the corresponding Visual Styles API state + value. + *kw* may have the following options: + + padding=padding + Specify the element's interior padding. + *padding* is a list of up to four integers specifying the left, + top, right and bottom padding quantities respectively. + If fewer than four elements are specified, bottom defaults to top, + right defaults to left, and top defaults to left. + In other words, a list of three numbers specify the left, vertical, + and right padding; a list of two numbers specify the horizontal + and the vertical padding; a single number specifies the same + padding all the way around the widget. + This option may not be mixed with any other options. + + margins=padding + Specifies the elements exterior padding. + *padding* is a list of up to four integers specifying the left, top, + right and bottom padding quantities respectively. + This option may not be mixed with any other options. + + width=width + Specifies the width for the element. + If this option is set then the Visual Styles API will not be queried + for the recommended size or the part. + If this option is set then *height* should also be set. + The *width* and *height* options cannot be mixed with the *padding* + or *margins* options. + + height=height + Specifies the height of the element. + See the comments for *width*. + + Example:: + + style = ttk.Style(root) + style.element_create('pin', 'vsapi', 'EXPLORERBAR', 3, [ + ('pressed', '!selected', 3), + ('active', '!selected', 2), + ('pressed', 'selected', 6), + ('active', 'selected', 5), + ('selected', 4), + ('', 1)]) + style.layout('Explorer.Pin', + [('Explorer.Pin.pin', {'sticky': 'news'})]) + pin = ttk.Checkbutton(style='Explorer.Pin') + pin.pack(expand=True, fill='both') + + .. versionchanged:: 3.13 + Added support of the "vsapi" element factory. .. method:: element_names() diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ec09dfea4aad3c..dad49f43d9090f 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -301,6 +301,11 @@ tkinter :meth:`!tk_busy_current`, and :meth:`!tk_busy_status`. (Contributed by Miguel, klappnase and Serhiy Storchaka in :gh:`72684`.) +* Add support of the "vsapi" element type in + the :meth:`~tkinter.ttk.Style.element_create` method of + :class:`tkinter.ttk.Style`. + (Contributed by Serhiy Storchaka in :gh:`68166`.) + traceback --------- diff --git a/Lib/test/test_ttk/test_style.py b/Lib/test/test_ttk/test_style.py index 52c4b7a0beabd0..9a04a95dc40d65 100644 --- a/Lib/test/test_ttk/test_style.py +++ b/Lib/test/test_ttk/test_style.py @@ -258,6 +258,55 @@ def test_element_create_image_errors(self): with self.assertRaisesRegex(TclError, 'bad option'): style.element_create('block2', 'image', image, spam=1) + def test_element_create_vsapi_1(self): + style = self.style + if 'xpnative' not in style.theme_names(): + self.skipTest("requires 'xpnative' theme") + style.element_create('smallclose', 'vsapi', 'WINDOW', 19, [ + ('disabled', 4), + ('pressed', 3), + ('active', 2), + ('', 1)]) + style.layout('CloseButton', + [('CloseButton.smallclose', {'sticky': 'news'})]) + b = ttk.Button(self.root, style='CloseButton') + b.pack(expand=True, fill='both') + self.assertEqual(b.winfo_reqwidth(), 13) + self.assertEqual(b.winfo_reqheight(), 13) + + def test_element_create_vsapi_2(self): + style = self.style + if 'xpnative' not in style.theme_names(): + self.skipTest("requires 'xpnative' theme") + style.element_create('pin', 'vsapi', 'EXPLORERBAR', 3, [ + ('pressed', '!selected', 3), + ('active', '!selected', 2), + ('pressed', 'selected', 6), + ('active', 'selected', 5), + ('selected', 4), + ('', 1)]) + style.layout('Explorer.Pin', + [('Explorer.Pin.pin', {'sticky': 'news'})]) + pin = ttk.Checkbutton(self.root, style='Explorer.Pin') + pin.pack(expand=True, fill='both') + self.assertEqual(pin.winfo_reqwidth(), 16) + self.assertEqual(pin.winfo_reqheight(), 16) + + def test_element_create_vsapi_3(self): + style = self.style + if 'xpnative' not in style.theme_names(): + self.skipTest("requires 'xpnative' theme") + style.element_create('headerclose', 'vsapi', 'EXPLORERBAR', 2, [ + ('pressed', 3), + ('active', 2), + ('', 1)]) + style.layout('Explorer.CloseButton', + [('Explorer.CloseButton.headerclose', {'sticky': 'news'})]) + b = ttk.Button(self.root, style='Explorer.CloseButton') + b.pack(expand=True, fill='both') + self.assertEqual(b.winfo_reqwidth(), 16) + self.assertEqual(b.winfo_reqheight(), 16) + def test_theme_create(self): style = self.style curr_theme = style.theme_use() @@ -358,6 +407,39 @@ def test_theme_create_image(self): style.theme_use(curr_theme) + def test_theme_create_vsapi(self): + style = self.style + if 'xpnative' not in style.theme_names(): + self.skipTest("requires 'xpnative' theme") + curr_theme = style.theme_use() + new_theme = 'testtheme5' + style.theme_create(new_theme, settings={ + 'pin' : { + 'element create': ['vsapi', 'EXPLORERBAR', 3, [ + ('pressed', '!selected', 3), + ('active', '!selected', 2), + ('pressed', 'selected', 6), + ('active', 'selected', 5), + ('selected', 4), + ('', 1)]], + }, + 'Explorer.Pin' : { + 'layout': [('Explorer.Pin.pin', {'sticky': 'news'})], + }, + }) + + style.theme_use(new_theme) + self.assertIn('pin', style.element_names()) + self.assertEqual(style.layout('Explorer.Pin'), + [('Explorer.Pin.pin', {'sticky': 'nswe'})]) + + pin = ttk.Checkbutton(self.root, style='Explorer.Pin') + pin.pack(expand=True, fill='both') + self.assertEqual(pin.winfo_reqwidth(), 16) + self.assertEqual(pin.winfo_reqheight(), 16) + + style.theme_use(curr_theme) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_ttk_textonly.py b/Lib/test/test_ttk_textonly.py index 96dc179a69ecac..e6525c4d6c6982 100644 --- a/Lib/test/test_ttk_textonly.py +++ b/Lib/test/test_ttk_textonly.py @@ -179,7 +179,7 @@ def test_format_elemcreate(self): # don't format returned values as a tcl script # minimum acceptable for image type self.assertEqual(ttk._format_elemcreate('image', False, 'test'), - ("test ", ())) + ("test", ())) # specifying a state spec self.assertEqual(ttk._format_elemcreate('image', False, 'test', ('', 'a')), ("test {} a", ())) @@ -203,17 +203,19 @@ def test_format_elemcreate(self): # don't format returned values as a tcl script # minimum acceptable for vsapi self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b'), - ("a b ", ())) + ('a', 'b', ('', 1), ())) # now with a state spec with multiple states self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b', - ('a', 'b', 'c')), ("a b {a b} c", ())) + [('a', 'b', 'c')]), ('a', 'b', ('a b', 'c'), ())) # state spec and option self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b', - ('a', 'b'), opt='x'), ("a b a b", ("-opt", "x"))) + [('a', 'b')], opt='x'), ('a', 'b', ('a', 'b'), ("-opt", "x"))) # format returned values as a tcl script # state spec with a multivalue and an option self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b', - ('a', 'b', [1, 2]), opt='x'), ("{a b {a b} {1 2}}", "-opt x")) + opt='x'), ("a b {{} 1}", "-opt x")) + self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b', + [('a', 'b', [1, 2])], opt='x'), ("a b {{a b} {1 2}}", "-opt x")) # Testing type = from # from type expects at least a type name @@ -222,9 +224,9 @@ def test_format_elemcreate(self): self.assertEqual(ttk._format_elemcreate('from', False, 'a'), ('a', ())) self.assertEqual(ttk._format_elemcreate('from', False, 'a', 'b'), - ('a', ('b', ))) + ('a', ('b',))) self.assertEqual(ttk._format_elemcreate('from', True, 'a', 'b'), - ('{a}', 'b')) + ('a', 'b')) def test_format_layoutlist(self): @@ -326,6 +328,22 @@ def test_script_from_settings(self): "ttk::style element create thing image {name {state1 state2} val} " "-opt {3 2m}") + vsapi = {'pin': {'element create': + ['vsapi', 'EXPLORERBAR', 3, [ + ('pressed', '!selected', 3), + ('active', '!selected', 2), + ('pressed', 'selected', 6), + ('active', 'selected', 5), + ('selected', 4), + ('', 1)]]}} + self.assertEqual(ttk._script_from_settings(vsapi), + "ttk::style element create pin vsapi EXPLORERBAR 3 {" + "{pressed !selected} 3 " + "{active !selected} 2 " + "{pressed selected} 6 " + "{active selected} 5 " + "selected 4 " + "{} 1} ") def test_tclobj_to_py(self): self.assertEqual( diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index efeabb7a92c627..5ca938a670831a 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -95,40 +95,47 @@ def _format_mapdict(mapdict, script=False): def _format_elemcreate(etype, script=False, *args, **kw): """Formats args and kw according to the given element factory etype.""" - spec = None + specs = () opts = () - if etype in ("image", "vsapi"): - if etype == "image": # define an element based on an image - # first arg should be the default image name - iname = args[0] - # next args, if any, are statespec/value pairs which is almost - # a mapdict, but we just need the value - imagespec = _join(_mapdict_values(args[1:])) - spec = "%s %s" % (iname, imagespec) - + if etype == "image": # define an element based on an image + # first arg should be the default image name + iname = args[0] + # next args, if any, are statespec/value pairs which is almost + # a mapdict, but we just need the value + imagespec = (iname, *_mapdict_values(args[1:])) + if script: + specs = (imagespec,) else: - # define an element whose visual appearance is drawn using the - # Microsoft Visual Styles API which is responsible for the - # themed styles on Windows XP and Vista. - # Availability: Tk 8.6, Windows XP and Vista. - class_name, part_id = args[:2] - statemap = _join(_mapdict_values(args[2:])) - spec = "%s %s %s" % (class_name, part_id, statemap) + specs = (_join(imagespec),) + opts = _format_optdict(kw, script) + if etype == "vsapi": + # define an element whose visual appearance is drawn using the + # Microsoft Visual Styles API which is responsible for the + # themed styles on Windows XP and Vista. + # Availability: Tk 8.6, Windows XP and Vista. + if len(args) < 3: + class_name, part_id = args + statemap = (((), 1),) + else: + class_name, part_id, statemap = args + specs = (class_name, part_id, tuple(_mapdict_values(statemap))) opts = _format_optdict(kw, script) elif etype == "from": # clone an element # it expects a themename and optionally an element to clone from, # otherwise it will clone {} (empty element) - spec = args[0] # theme name + specs = (args[0],) # theme name if len(args) > 1: # elementfrom specified opts = (_format_optvalue(args[1], script),) if script: - spec = '{%s}' % spec + specs = _join(specs) opts = ' '.join(opts) + return specs, opts + else: + return *specs, opts - return spec, opts def _format_layoutlist(layout, indent=0, indent_size=2): """Formats a layout list so we can pass the result to ttk::style @@ -214,10 +221,10 @@ def _script_from_settings(settings): elemargs = eopts[1:argc] elemkw = eopts[argc] if argc < len(eopts) and eopts[argc] else {} - spec, opts = _format_elemcreate(etype, True, *elemargs, **elemkw) + specs, eopts = _format_elemcreate(etype, True, *elemargs, **elemkw) script.append("ttk::style element create %s %s %s %s" % ( - name, etype, spec, opts)) + name, etype, specs, eopts)) return '\n'.join(script) @@ -434,9 +441,9 @@ def layout(self, style, layoutspec=None): def element_create(self, elementname, etype, *args, **kw): """Create a new element in the current theme of given etype.""" - spec, opts = _format_elemcreate(etype, False, *args, **kw) + *specs, opts = _format_elemcreate(etype, False, *args, **kw) self.tk.call(self._name, "element", "create", elementname, etype, - spec, *opts) + *specs, *opts) def element_names(self): diff --git a/Misc/NEWS.d/next/Library/2023-11-08-18-53-07.gh-issue-68166.1iTh4Y.rst b/Misc/NEWS.d/next/Library/2023-11-08-18-53-07.gh-issue-68166.1iTh4Y.rst new file mode 100644 index 00000000000000..30379b8fa1afaf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-08-18-53-07.gh-issue-68166.1iTh4Y.rst @@ -0,0 +1,2 @@ +Add support of the "vsapi" element type in +:meth:`tkinter.ttk.Style.element_create`. From 8f71b349de1ff2b11223ff7a8241c62a5a932339 Mon Sep 17 00:00:00 2001 From: apaz Date: Mon, 27 Nov 2023 15:13:27 -0600 Subject: [PATCH 017/442] gh-112217: Add check to call result for `do_raise()` where cause is a type. (#112216) --- Lib/test/test_raise.py | 14 ++++++++++++++ .../2023-11-27-18-55-30.gh-issue-112217.SwFLMj.rst | 1 + Python/ceval.c | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-27-18-55-30.gh-issue-112217.SwFLMj.rst diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py index 5936d7535edd5f..6d26a61bee4292 100644 --- a/Lib/test/test_raise.py +++ b/Lib/test/test_raise.py @@ -185,6 +185,20 @@ def test_class_cause(self): else: self.fail("No exception raised") + def test_class_cause_nonexception_result(self): + class ConstructsNone(BaseException): + @classmethod + def __new__(*args, **kwargs): + return None + try: + raise IndexError from ConstructsNone + except TypeError as e: + self.assertIn("should have returned an instance of BaseException", str(e)) + except IndexError: + self.fail("Wrong kind of exception raised") + else: + self.fail("No exception raised") + def test_instance_cause(self): cause = KeyError() try: diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-27-18-55-30.gh-issue-112217.SwFLMj.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-27-18-55-30.gh-issue-112217.SwFLMj.rst new file mode 100644 index 00000000000000..d4efbab6b2d128 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-27-18-55-30.gh-issue-112217.SwFLMj.rst @@ -0,0 +1 @@ +Add check for the type of ``__cause__`` returned from calling the type ``T`` in ``raise from T``. diff --git a/Python/ceval.c b/Python/ceval.c index 76ab5df42f63db..def75fd114cb86 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1920,6 +1920,13 @@ do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause) fixed_cause = _PyObject_CallNoArgs(cause); if (fixed_cause == NULL) goto raise_error; + if (!PyExceptionInstance_Check(fixed_cause)) { + _PyErr_Format(tstate, PyExc_TypeError, + "calling %R should have returned an instance of " + "BaseException, not %R", + cause, Py_TYPE(fixed_cause)); + goto raise_error; + } Py_DECREF(cause); } else if (PyExceptionInstance_Check(cause)) { From b90a5cf11cdb69e60aed7be732e80113bca7bbe4 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 27 Nov 2023 14:11:40 -0900 Subject: [PATCH 018/442] gh-99367: Do not mangle sys.path[0] in pdb if safe_path is set (#111762) Co-authored-by: Christian Walther --- Doc/whatsnew/3.13.rst | 5 +++ Lib/pdb.py | 6 ++-- Lib/test/test_pdb.py | 33 ++++++++++++++++--- ...3-11-05-20-09-27.gh-issue-99367.HLaWKo.rst | 1 + 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-05-20-09-27.gh-issue-99367.HLaWKo.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index dad49f43d9090f..bf6a70f2009b8e 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -285,6 +285,11 @@ pdb identified and executed. (Contributed by Tian Gao in :gh:`108464`.) +* ``sys.path[0]`` will no longer be replaced by the directory of the script + being debugged when ``sys.flags.safe_path`` is set (via the :option:`-P` + command line option or :envvar:`PYTHONSAFEPATH` environment variable). + (Contributed by Tian Gao and Christian Walther in :gh:`111762`.) + sqlite3 ------- diff --git a/Lib/pdb.py b/Lib/pdb.py index ed78d749a47fa8..9d124189df11cf 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -142,8 +142,10 @@ def check(self): print('Error:', self.orig, 'is a directory') sys.exit(1) - # Replace pdb's dir with script's dir in front of module search path. - sys.path[0] = os.path.dirname(self) + # If safe_path(-P) is not set, sys.path[0] is the directory + # of pdb, and we should replace it with the directory of the script + if not sys.flags.safe_path: + sys.path[0] = os.path.dirname(self) @property def filename(self): diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 67a4053a2ac8bc..2a279ca869e9c7 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2520,15 +2520,21 @@ def tearDown(self): @unittest.skipIf(sys.flags.safe_path, 'PYTHONSAFEPATH changes default sys.path') - def _run_pdb(self, pdb_args, commands, expected_returncode=0): + def _run_pdb(self, pdb_args, commands, + expected_returncode=0, + extra_env=None): self.addCleanup(os_helper.rmtree, '__pycache__') cmd = [sys.executable, '-m', 'pdb'] + pdb_args + if extra_env is not None: + env = os.environ | extra_env + else: + env = os.environ with subprocess.Popen( cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, - env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'} + env = {**env, 'PYTHONIOENCODING': 'utf-8'} ) as proc: stdout, stderr = proc.communicate(str.encode(commands)) stdout = stdout and bytes.decode(stdout) @@ -2540,13 +2546,15 @@ def _run_pdb(self, pdb_args, commands, expected_returncode=0): ) return stdout, stderr - def run_pdb_script(self, script, commands, expected_returncode=0): + def run_pdb_script(self, script, commands, + expected_returncode=0, + extra_env=None): """Run 'script' lines with pdb and the pdb 'commands'.""" filename = 'main.py' with open(filename, 'w') as f: f.write(textwrap.dedent(script)) self.addCleanup(os_helper.unlink, filename) - return self._run_pdb([filename], commands, expected_returncode) + return self._run_pdb([filename], commands, expected_returncode, extra_env) def run_pdb_module(self, script, commands): """Runs the script code as part of a module""" @@ -3131,6 +3139,23 @@ def test_issue42384_symlink(self): self.assertEqual(stdout.split('\n')[2].rstrip('\r'), expected) + def test_safe_path(self): + """ With safe_path set, pdb should not mangle sys.path[0]""" + + script = textwrap.dedent(""" + import sys + import random + print('sys.path[0] is', sys.path[0]) + """) + commands = 'c\n' + + + with os_helper.temp_cwd() as cwd: + stdout, _ = self.run_pdb_script(script, commands, extra_env={'PYTHONSAFEPATH': '1'}) + + unexpected = f'sys.path[0] is {os.path.realpath(cwd)}' + self.assertNotIn(unexpected, stdout) + def test_issue42383(self): with os_helper.temp_cwd() as cwd: with open('foo.py', 'w') as f: diff --git a/Misc/NEWS.d/next/Library/2023-11-05-20-09-27.gh-issue-99367.HLaWKo.rst b/Misc/NEWS.d/next/Library/2023-11-05-20-09-27.gh-issue-99367.HLaWKo.rst new file mode 100644 index 00000000000000..0920da221e423f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-05-20-09-27.gh-issue-99367.HLaWKo.rst @@ -0,0 +1 @@ +Do not mangle ``sys.path[0]`` in :mod:`pdb` if safe_path is set From 562d7149c6944fb9e4c7be80664b2f2d5a12a3ea Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Mon, 27 Nov 2023 18:42:37 -0500 Subject: [PATCH 019/442] Correct documentation for AF_PACKET (#112339) Protocol in the address tuple should *not* be in the network-byte-order, because it is converted internally[1]. [1] https://github.com/python/cpython/blob/89ddea4886942b0c27a778a0ad3f0d5ac5f518f0/Modules/socketmodule.c#L2144 network byte order doesn't make sense for a python level int anyways. It's a fixed size C serialization concept. --- Doc/library/socket.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index e36fc17f89de24..e0a75304ef1606 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -185,7 +185,7 @@ created. Socket addresses are represented as follows: .. versionadded:: 3.7 - :const:`AF_PACKET` is a low-level interface directly to network devices. - The packets are represented by the tuple + The addresses are represented by the tuple ``(ifname, proto[, pkttype[, hatype[, addr]]])`` where: - *ifname* - String specifying the device name. @@ -193,7 +193,6 @@ created. Socket addresses are represented as follows: May be :data:`ETH_P_ALL` to capture all protocols, one of the :ref:`ETHERTYPE_* constants ` or any other Ethernet protocol number. - Value must be in network-byte-order. - *pkttype* - Optional integer specifying the packet type: - ``PACKET_HOST`` (the default) - Packet addressed to the local host. From cf2054059c08ef1c5546f24874191f341dc94eb9 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 28 Nov 2023 00:09:59 +0000 Subject: [PATCH 020/442] gh-112414: Add additional unit tests for calling `repr()` on a namespace package (#112475) Co-authored-by: Eric Snow --- .../test_importlib/import_/test___loader__.py | 4 --- Lib/test/test_module/__init__.py | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_importlib/import_/test___loader__.py b/Lib/test/test_importlib/import_/test___loader__.py index c6996a42534676..858b37effc64bd 100644 --- a/Lib/test/test_importlib/import_/test___loader__.py +++ b/Lib/test/test_importlib/import_/test___loader__.py @@ -23,10 +23,6 @@ def test___loader__(self): with util.uncache('blah'), util.import_state(meta_path=[loader]): module = self.__import__('blah') self.assertEqual(loader, module.__loader__) - expected_repr_pattern = ( - r"\)>" - ) - self.assertRegex(repr(module), expected_repr_pattern) (Frozen_SpecTests, diff --git a/Lib/test/test_module/__init__.py b/Lib/test/test_module/__init__.py index db2133a9e8d17b..d49c44df4d839d 100644 --- a/Lib/test/test_module/__init__.py +++ b/Lib/test/test_module/__init__.py @@ -1,4 +1,5 @@ # Test the module type +import importlib.machinery import unittest import weakref from test.support import gc_collect @@ -264,6 +265,35 @@ def test_module_repr_source(self): self.assertEqual(r[-len(ends_with):], ends_with, '{!r} does not end with {!r}'.format(r, ends_with)) + def test_module_repr_with_namespace_package(self): + m = ModuleType('foo') + loader = importlib.machinery.NamespaceLoader('foo', ['bar'], 'baz') + spec = importlib.machinery.ModuleSpec('foo', loader) + m.__loader__ = loader + m.__spec__ = spec + self.assertEqual(repr(m), "") + + def test_module_repr_with_namespace_package_and_custom_loader(self): + m = ModuleType('foo') + loader = BareLoader() + spec = importlib.machinery.ModuleSpec('foo', loader) + m.__loader__ = loader + m.__spec__ = spec + expected_repr_pattern = r"\)>" + self.assertRegex(repr(m), expected_repr_pattern) + self.assertNotIn('from', repr(m)) + + def test_module_repr_with_fake_namespace_package(self): + m = ModuleType('foo') + loader = BareLoader() + loader._path = ['spam'] + spec = importlib.machinery.ModuleSpec('foo', loader) + m.__loader__ = loader + m.__spec__ = spec + expected_repr_pattern = r"\)>" + self.assertRegex(repr(m), expected_repr_pattern) + self.assertNotIn('from', repr(m)) + def test_module_finalization_at_shutdown(self): # Module globals and builtins should still be available during shutdown rc, out, err = assert_python_ok("-c", "from test.test_module import final_a") From 2e632fa07d13a58be62f59be4e656ad58b378f9b Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 28 Nov 2023 00:15:23 +0000 Subject: [PATCH 021/442] Docs: fix markup for `importlib.machinery.NamespaceLoader` (#112479) --- Doc/library/importlib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index fc954724bb72fe..2402bc5cd3ee2c 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -1145,7 +1145,7 @@ find and load modules. .. versionadded:: 3.4 -.. class:: NamespaceLoader(name, path, path_finder): +.. class:: NamespaceLoader(name, path, path_finder) A concrete implementation of :class:`importlib.abc.InspectLoader` for namespace packages. This is an alias for a private class and is only made From 154f099e611cea74daa755c77df3b8003861cc76 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Tue, 28 Nov 2023 12:58:53 +1100 Subject: [PATCH 022/442] gh-112292 : Catch import error conditions with readline hooks (gh-112313) Prevents a segmentation fault in registered hooks for the readline library, but only when the readline module is loaded inside an isolated sub interpreter. The module is single-phase init so loading it fails, but not until the module init function has already run, where the readline hooks get registered. The readlinestate_global macro was error-prone to PyImport_FindModule returning NULL and crashing in about 18 places. I could reproduce 1 easily, but this PR replaces the macro with a function and adds error conditions to the other functions. --- ...-11-22-19-43-54.gh-issue-112292.5nDU87.rst | 2 + Modules/readline.c | 91 ++++++++++++++----- 2 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst diff --git a/Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst b/Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst new file mode 100644 index 00000000000000..8345e33791cde0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-22-19-43-54.gh-issue-112292.5nDU87.rst @@ -0,0 +1,2 @@ +Fix a crash in :mod:`readline` when imported from a sub interpreter. Patch +by Anthony Shaw diff --git a/Modules/readline.c b/Modules/readline.c index fde552d124bc77..209ac8bbcfbe78 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -147,8 +147,19 @@ readline_free(void *m) static PyModuleDef readlinemodule; -#define readlinestate_global ((readlinestate *)PyModule_GetState(PyState_FindModule(&readlinemodule))) - +static inline readlinestate* +get_hook_module_state(void) +{ + PyObject *mod = PyState_FindModule(&readlinemodule); + if (mod == NULL){ + PyErr_Clear(); + return NULL; + } + Py_INCREF(mod); + readlinestate *state = get_readline_state(mod); + Py_DECREF(mod); + return state; +} /* Convert to/from multibyte C strings */ @@ -438,14 +449,15 @@ readline_set_completion_display_matches_hook_impl(PyObject *module, PyObject *function) /*[clinic end generated code: output=516e5cb8db75a328 input=4f0bfd5ab0179a26]*/ { + readlinestate *state = get_readline_state(module); PyObject *result = set_hook("completion_display_matches_hook", - &readlinestate_global->completion_display_matches_hook, + &state->completion_display_matches_hook, function); #ifdef HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK /* We cannot set this hook globally, since it replaces the default completion display. */ rl_completion_display_matches_hook = - readlinestate_global->completion_display_matches_hook ? + state->completion_display_matches_hook ? #if defined(HAVE_RL_COMPDISP_FUNC_T) (rl_compdisp_func_t *)on_completion_display_matches_hook : 0; #else @@ -472,7 +484,8 @@ static PyObject * readline_set_startup_hook_impl(PyObject *module, PyObject *function) /*[clinic end generated code: output=02cd0e0c4fa082ad input=7783b4334b26d16d]*/ { - return set_hook("startup_hook", &readlinestate_global->startup_hook, + readlinestate *state = get_readline_state(module); + return set_hook("startup_hook", &state->startup_hook, function); } @@ -497,7 +510,8 @@ static PyObject * readline_set_pre_input_hook_impl(PyObject *module, PyObject *function) /*[clinic end generated code: output=fe1a96505096f464 input=4f3eaeaf7ce1fdbe]*/ { - return set_hook("pre_input_hook", &readlinestate_global->pre_input_hook, + readlinestate *state = get_readline_state(module); + return set_hook("pre_input_hook", &state->pre_input_hook, function); } #endif @@ -530,7 +544,8 @@ static PyObject * readline_get_begidx_impl(PyObject *module) /*[clinic end generated code: output=362616ee8ed1b2b1 input=e083b81c8eb4bac3]*/ { - return Py_NewRef(readlinestate_global->begidx); + readlinestate *state = get_readline_state(module); + return Py_NewRef(state->begidx); } /* Get the ending index for the scope of the tab-completion */ @@ -545,7 +560,8 @@ static PyObject * readline_get_endidx_impl(PyObject *module) /*[clinic end generated code: output=7f763350b12d7517 input=d4c7e34a625fd770]*/ { - return Py_NewRef(readlinestate_global->endidx); + readlinestate *state = get_readline_state(module); + return Py_NewRef(state->endidx); } /* Set the tab-completion word-delimiters that readline uses */ @@ -772,7 +788,8 @@ static PyObject * readline_set_completer_impl(PyObject *module, PyObject *function) /*[clinic end generated code: output=171a2a60f81d3204 input=51e81e13118eb877]*/ { - return set_hook("completer", &readlinestate_global->completer, function); + readlinestate *state = get_readline_state(module); + return set_hook("completer", &state->completer, function); } /*[clinic input] @@ -785,10 +802,11 @@ static PyObject * readline_get_completer_impl(PyObject *module) /*[clinic end generated code: output=6e6bbd8226d14475 input=6457522e56d70d13]*/ { - if (readlinestate_global->completer == NULL) { + readlinestate *state = get_readline_state(module); + if (state->completer == NULL) { Py_RETURN_NONE; } - return Py_NewRef(readlinestate_global->completer); + return Py_NewRef(state->completer); } /* Private function to get current length of history. XXX It may be @@ -1026,7 +1044,12 @@ on_startup_hook(void) { int r; PyGILState_STATE gilstate = PyGILState_Ensure(); - r = on_hook(readlinestate_global->startup_hook); + readlinestate *state = get_hook_module_state(); + if (state == NULL) { + PyGILState_Release(gilstate); + return -1; + } + r = on_hook(state->startup_hook); PyGILState_Release(gilstate); return r; } @@ -1043,7 +1066,12 @@ on_pre_input_hook(void) { int r; PyGILState_STATE gilstate = PyGILState_Ensure(); - r = on_hook(readlinestate_global->pre_input_hook); + readlinestate *state = get_hook_module_state(); + if (state == NULL) { + PyGILState_Release(gilstate); + return -1; + } + r = on_hook(state->pre_input_hook); PyGILState_Release(gilstate); return r; } @@ -1060,6 +1088,11 @@ on_completion_display_matches_hook(char **matches, int i; PyObject *sub, *m=NULL, *s=NULL, *r=NULL; PyGILState_STATE gilstate = PyGILState_Ensure(); + readlinestate *state = get_hook_module_state(); + if (state == NULL) { + PyGILState_Release(gilstate); + return; + } m = PyList_New(num_matches); if (m == NULL) goto error; @@ -1070,7 +1103,7 @@ on_completion_display_matches_hook(char **matches, PyList_SET_ITEM(m, i, s); } sub = decode(matches[0]); - r = PyObject_CallFunction(readlinestate_global->completion_display_matches_hook, + r = PyObject_CallFunction(state->completion_display_matches_hook, "NNi", sub, m, max_length); m=NULL; @@ -1118,12 +1151,17 @@ static char * on_completion(const char *text, int state) { char *result = NULL; - if (readlinestate_global->completer != NULL) { + PyGILState_STATE gilstate = PyGILState_Ensure(); + readlinestate *module_state = get_hook_module_state(); + if (module_state == NULL) { + PyGILState_Release(gilstate); + return NULL; + } + if (module_state->completer != NULL) { PyObject *r = NULL, *t; - PyGILState_STATE gilstate = PyGILState_Ensure(); rl_attempted_completion_over = 1; t = decode(text); - r = PyObject_CallFunction(readlinestate_global->completer, "Ni", t, state); + r = PyObject_CallFunction(module_state->completer, "Ni", t, state); if (r == NULL) goto error; if (r == Py_None) { @@ -1145,6 +1183,7 @@ on_completion(const char *text, int state) PyGILState_Release(gilstate); return result; } + PyGILState_Release(gilstate); return result; } @@ -1160,6 +1199,7 @@ flex_complete(const char *text, int start, int end) size_t start_size, end_size; wchar_t *s; PyGILState_STATE gilstate = PyGILState_Ensure(); + readlinestate *state = get_hook_module_state(); #ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER rl_completion_append_character ='\0'; #endif @@ -1187,10 +1227,12 @@ flex_complete(const char *text, int start, int end) end = start + (int)end_size; done: - Py_XDECREF(readlinestate_global->begidx); - Py_XDECREF(readlinestate_global->endidx); - readlinestate_global->begidx = PyLong_FromLong((long) start); - readlinestate_global->endidx = PyLong_FromLong((long) end); + if (state) { + Py_XDECREF(state->begidx); + Py_XDECREF(state->endidx); + state->begidx = PyLong_FromLong((long) start); + state->endidx = PyLong_FromLong((long) end); + } result = completion_matches((char *)text, *on_completion); PyGILState_Release(gilstate); return result; @@ -1511,12 +1553,17 @@ PyInit_readline(void) } mod_state = (readlinestate *) PyModule_GetState(m); + if (mod_state == NULL){ + goto error; + } PyOS_ReadlineFunctionPointer = call_readline; if (setup_readline(mod_state) < 0) { PyErr_NoMemory(); goto error; } - + if (PyErr_Occurred()){ + goto error; + } return m; error: From ac4b44266d61651aea5928ce7d3fae4de226f83d Mon Sep 17 00:00:00 2001 From: Radislav Chugunov <52372310+chgnrdv@users.noreply.github.com> Date: Tue, 28 Nov 2023 06:27:39 +0300 Subject: [PATCH 023/442] gh-112071: Make `_random.Random` methods thread-safe in `--disable-gil` builds (gh-112128) Co-authored-by: Donghee Na --- Modules/_randommodule.c | 18 +++++++++------ Modules/clinic/_randommodule.c.h | 38 +++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Modules/_randommodule.c b/Modules/_randommodule.c index 514bec16a347f4..4403e1d132c057 100644 --- a/Modules/_randommodule.c +++ b/Modules/_randommodule.c @@ -175,6 +175,7 @@ genrand_uint32(RandomObject *self) */ /*[clinic input] +@critical_section _random.Random.random self: self(type="RandomObject *") @@ -184,7 +185,7 @@ random() -> x in the interval [0, 1). static PyObject * _random_Random_random_impl(RandomObject *self) -/*[clinic end generated code: output=117ff99ee53d755c input=afb2a59cbbb00349]*/ +/*[clinic end generated code: output=117ff99ee53d755c input=26492e52d26e8b7b]*/ { uint32_t a=genrand_uint32(self)>>5, b=genrand_uint32(self)>>6; return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0)); @@ -368,6 +369,7 @@ random_seed(RandomObject *self, PyObject *arg) } /*[clinic input] +@critical_section _random.Random.seed self: self(type="RandomObject *") @@ -382,7 +384,7 @@ of the current time and the process identifier. static PyObject * _random_Random_seed_impl(RandomObject *self, PyObject *n) -/*[clinic end generated code: output=0fad1e16ba883681 input=78d6ef0d52532a54]*/ +/*[clinic end generated code: output=0fad1e16ba883681 input=46d01d2ba938c7b1]*/ { if (random_seed(self, n) < 0) { return NULL; @@ -391,6 +393,7 @@ _random_Random_seed_impl(RandomObject *self, PyObject *n) } /*[clinic input] +@critical_section _random.Random.getstate self: self(type="RandomObject *") @@ -400,7 +403,7 @@ getstate() -> tuple containing the current state. static PyObject * _random_Random_getstate_impl(RandomObject *self) -/*[clinic end generated code: output=bf6cef0c092c7180 input=b937a487928c0e89]*/ +/*[clinic end generated code: output=bf6cef0c092c7180 input=b6621f31eb639694]*/ { PyObject *state; PyObject *element; @@ -428,6 +431,7 @@ _random_Random_getstate_impl(RandomObject *self) /*[clinic input] +@critical_section _random.Random.setstate self: self(type="RandomObject *") @@ -438,8 +442,8 @@ setstate(state) -> None. Restores generator state. [clinic start generated code]*/ static PyObject * -_random_Random_setstate(RandomObject *self, PyObject *state) -/*[clinic end generated code: output=fd1c3cd0037b6681 input=b3b4efbb1bc66af8]*/ +_random_Random_setstate_impl(RandomObject *self, PyObject *state) +/*[clinic end generated code: output=babfc2c2eac6b027 input=358e898ec07469b7]*/ { int i; unsigned long element; @@ -479,7 +483,7 @@ _random_Random_setstate(RandomObject *self, PyObject *state) } /*[clinic input] - +@critical_section _random.Random.getrandbits self: self(type="RandomObject *") @@ -491,7 +495,7 @@ getrandbits(k) -> x. Generates an int with k random bits. static PyObject * _random_Random_getrandbits_impl(RandomObject *self, int k) -/*[clinic end generated code: output=b402f82a2158887f input=8c0e6396dd176fc0]*/ +/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/ { int i, words; uint32_t r; diff --git a/Modules/clinic/_randommodule.c.h b/Modules/clinic/_randommodule.c.h index 757e49e23cacfb..6193acac67e7ac 100644 --- a/Modules/clinic/_randommodule.c.h +++ b/Modules/clinic/_randommodule.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(_random_Random_random__doc__, @@ -19,7 +20,13 @@ _random_Random_random_impl(RandomObject *self); static PyObject * _random_Random_random(RandomObject *self, PyObject *Py_UNUSED(ignored)) { - return _random_Random_random_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _random_Random_random_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(_random_Random_seed__doc__, @@ -51,7 +58,9 @@ _random_Random_seed(RandomObject *self, PyObject *const *args, Py_ssize_t nargs) } n = args[0]; skip_optional: + Py_BEGIN_CRITICAL_SECTION(self); return_value = _random_Random_seed_impl(self, n); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -72,7 +81,13 @@ _random_Random_getstate_impl(RandomObject *self); static PyObject * _random_Random_getstate(RandomObject *self, PyObject *Py_UNUSED(ignored)) { - return _random_Random_getstate_impl(self); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _random_Random_getstate_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(_random_Random_setstate__doc__, @@ -84,6 +99,21 @@ PyDoc_STRVAR(_random_Random_setstate__doc__, #define _RANDOM_RANDOM_SETSTATE_METHODDEF \ {"setstate", (PyCFunction)_random_Random_setstate, METH_O, _random_Random_setstate__doc__}, +static PyObject * +_random_Random_setstate_impl(RandomObject *self, PyObject *state); + +static PyObject * +_random_Random_setstate(RandomObject *self, PyObject *state) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _random_Random_setstate_impl(self, state); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_random_Random_getrandbits__doc__, "getrandbits($self, k, /)\n" "--\n" @@ -106,9 +136,11 @@ _random_Random_getrandbits(RandomObject *self, PyObject *arg) if (k == -1 && PyErr_Occurred()) { goto exit; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = _random_Random_getrandbits_impl(self, k); + Py_END_CRITICAL_SECTION(); exit: return return_value; } -/*[clinic end generated code: output=5c800a28c2d7b9d1 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=bf49ece1d341b1b6 input=a9049054013a1b77]*/ From 2df26d83486b8f9ac6b7df2a9a4669508aa61983 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Mon, 27 Nov 2023 21:23:23 -0900 Subject: [PATCH 024/442] gh-112105: Make completer delims work on libedit (gh-112106) --- Lib/test/test_readline.py | 20 +++++++++++++++++++ ...-11-15-04-53-37.gh-issue-112105.I3RcVN.rst | 1 + Modules/readline.c | 16 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-11-15-04-53-37.gh-issue-112105.I3RcVN.rst diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index 835280f2281cde..6c2726d3209ecf 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -5,6 +5,7 @@ import os import sys import tempfile +import textwrap import unittest from test.support import verbose from test.support.import_helper import import_module @@ -163,6 +164,25 @@ def test_auto_history_disabled(self): # end, so don't expect it in the output. self.assertIn(b"History length: 0", output) + def test_set_complete_delims(self): + script = textwrap.dedent(""" + import readline + def complete(text, state): + if state == 0 and text == "$": + return "$complete" + return None + if "libedit" in getattr(readline, "__doc__", ""): + readline.parse_and_bind(r'bind "\\t" rl_complete') + else: + readline.parse_and_bind(r'"\\t": complete') + readline.set_completer_delims(" \\t\\n") + readline.set_completer(complete) + print(input()) + """) + + output = run_pty(script, input=b"$\t\n") + self.assertIn(b"$complete", output) + def test_nonascii(self): loc = locale.setlocale(locale.LC_CTYPE, None) if loc in ('C', 'POSIX'): diff --git a/Misc/NEWS.d/next/Library/2023-11-15-04-53-37.gh-issue-112105.I3RcVN.rst b/Misc/NEWS.d/next/Library/2023-11-15-04-53-37.gh-issue-112105.I3RcVN.rst new file mode 100644 index 00000000000000..4243dcb190434f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-15-04-53-37.gh-issue-112105.I3RcVN.rst @@ -0,0 +1 @@ +Make :func:`readline.set_completer_delims` work with libedit diff --git a/Modules/readline.c b/Modules/readline.c index 209ac8bbcfbe78..eb9a3d4693ee90 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -592,6 +592,13 @@ readline_set_completer_delims(PyObject *module, PyObject *string) if (break_chars) { free(completer_word_break_characters); completer_word_break_characters = break_chars; +#ifdef WITH_EDITLINE + rl_basic_word_break_characters = break_chars; +#else + if (using_libedit_emulation) { + rl_basic_word_break_characters = break_chars; + } +#endif rl_completer_word_break_characters = break_chars; Py_RETURN_NONE; } @@ -1309,6 +1316,15 @@ setup_readline(readlinestate *mod_state) completer_word_break_characters = strdup(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?"); /* All nonalphanums except '.' */ +#ifdef WITH_EDITLINE + // libedit uses rl_basic_word_break_characters instead of + // rl_completer_word_break_characters as complete delimiter + rl_basic_word_break_characters = completer_word_break_characters; +#else + if (using_libedit_emulation) { + rl_basic_word_break_characters = completer_word_break_characters; + } +#endif rl_completer_word_break_characters = completer_word_break_characters; mod_state->begidx = PyLong_FromLong(0L); From 2c68011780bd68463f5183601ea9c10af368dff6 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:03:25 +0000 Subject: [PATCH 025/442] gh-112332: Deprecate TracebackException.exc_type, add exc_type_str. (#112333) --- Doc/library/traceback.rst | 8 +++ Doc/whatsnew/3.13.rst | 11 ++++ Lib/test/test_traceback.py | 51 ++++++++++++++++--- Lib/traceback.py | 50 +++++++++++++----- ...-11-23-10-41-21.gh-issue-112332.rhTBaa.rst | 2 + 5 files changed, 102 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-23-10-41-21.gh-issue-112332.rhTBaa.rst diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 408da7fc5f0645..80dda5ec520d7a 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -287,6 +287,14 @@ capture data for later printing in a lightweight fashion. The class of the original traceback. + .. deprecated:: 3.13 + + .. attribute:: exc_type_str + + String display of the class of the original exception. + + .. versionadded:: 3.13 + .. attribute:: filename For syntax errors - the file name where the error occurred. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index bf6a70f2009b8e..198ea3a4b57bde 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -318,6 +318,12 @@ traceback to format the nested exceptions of a :exc:`BaseExceptionGroup` instance, recursively. (Contributed by Irit Katriel in :gh:`105292`.) +* Add the field *exc_type_str* to :class:`~traceback.TracebackException`, which + holds a string display of the *exc_type*. Deprecate the field *exc_type* + which holds the type object itself. Add parameter *save_exc_type* (default + ``True``) to indicate whether ``exc_type`` should be saved. + (Contributed by Irit Katriel in :gh:`112332`.) + typing ------ @@ -377,6 +383,11 @@ Deprecated security and functionality bugs. This includes removal of the ``--cgi`` flag to the ``python -m http.server`` command line in 3.15. +* :mod:`traceback`: + + * The field *exc_type* of :class:`traceback.TracebackException` is + deprecated. Use *exc_type_str* instead. + * :mod:`typing`: * Creating a :class:`typing.NamedTuple` class using keyword arguments to denote diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index b43dca6f640b9a..c58d979bdd0115 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -2715,9 +2715,9 @@ def __repr__(self) -> str: class TestTracebackException(unittest.TestCase): - def test_smoke(self): + def do_test_smoke(self, exc, expected_type_str): try: - 1/0 + raise exc except Exception as e: exc_obj = e exc = traceback.TracebackException.from_exception(e) @@ -2727,9 +2727,23 @@ def test_smoke(self): self.assertEqual(None, exc.__context__) self.assertEqual(False, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(expected_type_str, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) + def test_smoke_builtin(self): + self.do_test_smoke(ValueError(42), 'ValueError') + + def test_smoke_user_exception(self): + class MyException(Exception): + pass + + self.do_test_smoke( + MyException('bad things happened'), + ('test.test_traceback.TestTracebackException.' + 'test_smoke_user_exception..MyException')) + def test_from_exception(self): # Check all the parameters are accepted. def foo(): @@ -2750,7 +2764,9 @@ def foo(): self.assertEqual(None, exc.__context__) self.assertEqual(False, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) def test_cause(self): @@ -2772,7 +2788,9 @@ def test_cause(self): self.assertEqual(exc_context, exc.__context__) self.assertEqual(True, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) def test_context(self): @@ -2792,7 +2810,9 @@ def test_context(self): self.assertEqual(exc_context, exc.__context__) self.assertEqual(False, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) def test_long_context_chain(self): @@ -2837,7 +2857,9 @@ def test_compact_with_cause(self): self.assertEqual(None, exc.__context__) self.assertEqual(True, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) def test_compact_no_cause(self): @@ -2857,9 +2879,22 @@ def test_compact_no_cause(self): self.assertEqual(exc_context, exc.__context__) self.assertEqual(False, exc.__suppress_context__) self.assertEqual(expected_stack, exc.stack) - self.assertEqual(type(exc_obj), exc.exc_type) + with self.assertWarns(DeprecationWarning): + self.assertEqual(type(exc_obj), exc.exc_type) + self.assertEqual(type(exc_obj).__name__, exc.exc_type_str) self.assertEqual(str(exc_obj), str(exc)) + def test_no_save_exc_type(self): + try: + 1/0 + except Exception as e: + exc = e + + te = traceback.TracebackException.from_exception( + exc, save_exc_type=False) + with self.assertWarns(DeprecationWarning): + self.assertIsNone(te.exc_type) + def test_no_refs_to_exception_and_traceback_objects(self): try: 1/0 diff --git a/Lib/traceback.py b/Lib/traceback.py index b25a7291f6be51..5d83f85ac3edb0 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -5,6 +5,7 @@ import linecache import sys import textwrap +import warnings from contextlib import suppress __all__ = ['extract_stack', 'extract_tb', 'format_exception', @@ -719,7 +720,8 @@ class TracebackException: - :attr:`__suppress_context__` The *__suppress_context__* value from the original exception. - :attr:`stack` A `StackSummary` representing the traceback. - - :attr:`exc_type` The class of the original traceback. + - :attr:`exc_type` (deprecated) The class of the original traceback. + - :attr:`exc_type_str` String display of exc_type - :attr:`filename` For syntax errors - the filename where the error occurred. - :attr:`lineno` For syntax errors - the linenumber where the error @@ -737,7 +739,7 @@ class TracebackException: def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False, - max_group_width=15, max_group_depth=10, _seen=None): + max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None): # NB: we need to accept exc_traceback, exc_value, exc_traceback to # permit backwards compat with the existing API, otherwise we # need stub thunk objects just to glue it together. @@ -754,12 +756,23 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, _walk_tb_with_full_positions(exc_traceback), limit=limit, lookup_lines=lookup_lines, capture_locals=capture_locals) - self.exc_type = exc_type + + self._exc_type = exc_type if save_exc_type else None + # Capture now to permit freeing resources: only complication is in the # unofficial API _format_final_exc_line self._str = _safe_string(exc_value, 'exception') self.__notes__ = getattr(exc_value, '__notes__', None) + self._is_syntax_error = False + self._have_exc_type = exc_type is not None + if exc_type is not None: + self.exc_type_qualname = exc_type.__qualname__ + self.exc_type_module = exc_type.__module__ + else: + self.exc_type_qualname = None + self.exc_type_module = None + if exc_type and issubclass(exc_type, SyntaxError): # Handle SyntaxError's specially self.filename = exc_value.filename @@ -771,6 +784,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, self.offset = exc_value.offset self.end_offset = exc_value.end_offset self.msg = exc_value.msg + self._is_syntax_error = True elif exc_type and issubclass(exc_type, ImportError) and \ getattr(exc_value, "name_from", None) is not None: wrong_name = getattr(exc_value, "name_from", None) @@ -869,6 +883,24 @@ def from_exception(cls, exc, *args, **kwargs): """Create a TracebackException from an exception.""" return cls(type(exc), exc, exc.__traceback__, *args, **kwargs) + @property + def exc_type(self): + warnings.warn('Deprecated in 3.13. Use exc_type_str instead.', + DeprecationWarning, stacklevel=2) + return self._exc_type + + @property + def exc_type_str(self): + if not self._have_exc_type: + return None + stype = self.exc_type_qualname + smod = self.exc_type_module + if smod not in ("__main__", "builtins"): + if not isinstance(smod, str): + smod = "" + stype = smod + '.' + stype + return stype + def _load_lines(self): """Private API. force all lines in the stack to be loaded.""" for frame in self.stack: @@ -901,18 +933,12 @@ def format_exception_only(self, *, show_group=False, _depth=0): """ indent = 3 * _depth * ' ' - if self.exc_type is None: + if not self._have_exc_type: yield indent + _format_final_exc_line(None, self._str) return - stype = self.exc_type.__qualname__ - smod = self.exc_type.__module__ - if smod not in ("__main__", "builtins"): - if not isinstance(smod, str): - smod = "" - stype = smod + '.' + stype - - if not issubclass(self.exc_type, SyntaxError): + stype = self.exc_type_str + if not self._is_syntax_error: if _depth > 0: # Nested exceptions needs correct handling of multiline messages. formatted = _format_final_exc_line( diff --git a/Misc/NEWS.d/next/Library/2023-11-23-10-41-21.gh-issue-112332.rhTBaa.rst b/Misc/NEWS.d/next/Library/2023-11-23-10-41-21.gh-issue-112332.rhTBaa.rst new file mode 100644 index 00000000000000..bd686ad052e5b2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-23-10-41-21.gh-issue-112332.rhTBaa.rst @@ -0,0 +1,2 @@ +Deprecate the ``exc_type`` field of :class:`traceback.TracebackException`. +Add ``exc_type_str`` to replace it. From f14d741daa1b9e5b9c9fc1edba93d0fa92b5ba8d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 28 Nov 2023 11:18:33 +0300 Subject: [PATCH 026/442] gh-109802: Increase test coverage for complexobject.c (GH-112452) --- Lib/test/test_capi/test_complex.py | 87 ++++++++++++++++++++++++++++++ Lib/test/test_complex.py | 47 ++++++++++++++++ Modules/_testcapi/complex.c | 59 ++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 9f51efb091dea5..d6fc1f077c40aa 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -1,3 +1,5 @@ +from math import isnan +import errno import unittest import warnings @@ -10,6 +12,10 @@ _testcapi = import_helper.import_module('_testcapi') NULL = None +INF = float("inf") +NAN = float("nan") +DBL_MAX = _testcapi.DBL_MAX + class BadComplex3: def __complex__(self): @@ -141,6 +147,87 @@ def test_asccomplex(self): # CRASHES asccomplex(NULL) + def test_py_c_sum(self): + # Test _Py_c_sum() + _py_c_sum = _testcapi._py_c_sum + + self.assertEqual(_py_c_sum(1, 1j), (1+1j, 0)) + + def test_py_c_diff(self): + # Test _Py_c_diff() + _py_c_diff = _testcapi._py_c_diff + + self.assertEqual(_py_c_diff(1, 1j), (1-1j, 0)) + + def test_py_c_neg(self): + # Test _Py_c_neg() + _py_c_neg = _testcapi._py_c_neg + + self.assertEqual(_py_c_neg(1+1j), -1-1j) + + def test_py_c_prod(self): + # Test _Py_c_prod() + _py_c_prod = _testcapi._py_c_prod + + self.assertEqual(_py_c_prod(2, 1j), (2j, 0)) + + def test_py_c_quot(self): + # Test _Py_c_quot() + _py_c_quot = _testcapi._py_c_quot + + self.assertEqual(_py_c_quot(1, 1j), (-1j, 0)) + self.assertEqual(_py_c_quot(1, -1j), (1j, 0)) + self.assertEqual(_py_c_quot(1j, 2), (0.5j, 0)) + self.assertEqual(_py_c_quot(1j, -2), (-0.5j, 0)) + self.assertEqual(_py_c_quot(1, 2j), (-0.5j, 0)) + + z, e = _py_c_quot(NAN, 1j) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) + self.assertEqual(e, 0) + + z, e = _py_c_quot(1j, NAN) + self.assertTrue(isnan(z.real)) + self.assertTrue(isnan(z.imag)) + self.assertEqual(e, 0) + + self.assertEqual(_py_c_quot(1, 0j)[1], errno.EDOM) + + def test_py_c_pow(self): + # Test _Py_c_pow() + _py_c_pow = _testcapi._py_c_pow + + self.assertEqual(_py_c_pow(1j, 0j), (1+0j, 0)) + self.assertEqual(_py_c_pow(1, 1j), (1+0j, 0)) + self.assertEqual(_py_c_pow(0j, 1), (0j, 0)) + self.assertAlmostEqual(_py_c_pow(1j, 2)[0], -1.0+0j) + + r, e = _py_c_pow(1+1j, -1) + self.assertAlmostEqual(r, 0.5-0.5j) + self.assertEqual(e, 0) + + self.assertEqual(_py_c_pow(0j, -1)[1], errno.EDOM) + self.assertEqual(_py_c_pow(0j, 1j)[1], errno.EDOM) + self.assertEqual(_py_c_pow(*[DBL_MAX+1j]*2)[0], complex(*[INF]*2)) + + + def test_py_c_abs(self): + # Test _Py_c_abs() + _py_c_abs = _testcapi._py_c_abs + + self.assertEqual(_py_c_abs(-1), (1.0, 0)) + self.assertEqual(_py_c_abs(1j), (1.0, 0)) + + self.assertEqual(_py_c_abs(complex('+inf+1j')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('-inf+1j')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('1.25+infj')), (INF, 0)) + self.assertEqual(_py_c_abs(complex('1.25-infj')), (INF, 0)) + + self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0])) + self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0])) + + self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 9180cca62b28b8..b057121f285dc7 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -109,6 +109,8 @@ def test_truediv(self): complex(random(), random())) self.assertAlmostEqual(complex.__truediv__(2+0j, 1+1j), 1-1j) + self.assertRaises(TypeError, operator.truediv, 1j, None) + self.assertRaises(TypeError, operator.truediv, None, 1j) for denom_real, denom_imag in [(0, NAN), (NAN, 0), (NAN, NAN)]: z = complex(0, 0) / complex(denom_real, denom_imag) @@ -140,6 +142,7 @@ def test_floordiv_zero_division(self): def test_richcompare(self): self.assertIs(complex.__eq__(1+1j, 1<<10000), False) self.assertIs(complex.__lt__(1+1j, None), NotImplemented) + self.assertIs(complex.__eq__(1+1j, None), NotImplemented) self.assertIs(complex.__eq__(1+1j, 1+1j), True) self.assertIs(complex.__eq__(1+1j, 2+2j), False) self.assertIs(complex.__ne__(1+1j, 1+1j), False) @@ -162,6 +165,7 @@ def test_richcompare(self): self.assertIs(operator.eq(1+1j, 2+2j), False) self.assertIs(operator.ne(1+1j, 1+1j), False) self.assertIs(operator.ne(1+1j, 2+2j), True) + self.assertIs(operator.eq(1+1j, 2.0), False) def test_richcompare_boundaries(self): def check(n, deltas, is_equal, imag = 0.0): @@ -180,6 +184,27 @@ def check(n, deltas, is_equal, imag = 0.0): check(2 ** pow, range(1, 101), lambda delta: False, float(i)) check(2 ** 53, range(-100, 0), lambda delta: True) + def test_add(self): + self.assertEqual(1j + int(+1), complex(+1, 1)) + self.assertEqual(1j + int(-1), complex(-1, 1)) + self.assertRaises(OverflowError, operator.add, 1j, 10**1000) + self.assertRaises(TypeError, operator.add, 1j, None) + self.assertRaises(TypeError, operator.add, None, 1j) + + def test_sub(self): + self.assertEqual(1j - int(+1), complex(-1, 1)) + self.assertEqual(1j - int(-1), complex(1, 1)) + self.assertRaises(OverflowError, operator.sub, 1j, 10**1000) + self.assertRaises(TypeError, operator.sub, 1j, None) + self.assertRaises(TypeError, operator.sub, None, 1j) + + def test_mul(self): + self.assertEqual(1j * int(20), complex(0, 20)) + self.assertEqual(1j * int(-1), complex(0, -1)) + self.assertRaises(OverflowError, operator.mul, 1j, 10**1000) + self.assertRaises(TypeError, operator.mul, 1j, None) + self.assertRaises(TypeError, operator.mul, None, 1j) + def test_mod(self): # % is no longer supported on complex numbers with self.assertRaises(TypeError): @@ -212,11 +237,18 @@ def test_divmod_zero_division(self): def test_pow(self): self.assertAlmostEqual(pow(1+1j, 0+0j), 1.0) self.assertAlmostEqual(pow(0+0j, 2+0j), 0.0) + self.assertEqual(pow(0+0j, 2000+0j), 0.0) + self.assertEqual(pow(0, 0+0j), 1.0) + self.assertEqual(pow(-1, 0+0j), 1.0) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) + self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) self.assertRaises(OverflowError, pow, 1e200+1j, 1e200+1j) + self.assertRaises(TypeError, pow, 1j, None) + self.assertRaises(TypeError, pow, None, 1j) + self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j) a = 3.33+4.43j self.assertEqual(a ** 0j, 1) @@ -301,6 +333,7 @@ def test_boolcontext(self): for i in range(100): self.assertTrue(complex(random() + 1e-6, random() + 1e-6)) self.assertTrue(not complex(0.0, 0.0)) + self.assertTrue(1j) def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) @@ -314,6 +347,8 @@ def __complex__(self): return self.value self.assertRaises(TypeError, complex, {}) self.assertRaises(TypeError, complex, NS(1.5)) self.assertRaises(TypeError, complex, NS(1)) + self.assertRaises(TypeError, complex, object()) + self.assertRaises(TypeError, complex, NS(4.25+0.5j), object()) self.assertAlmostEqual(complex("1+10j"), 1+10j) self.assertAlmostEqual(complex(10), 10+0j) @@ -359,6 +394,8 @@ def __complex__(self): return self.value self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) + self.assertEqual(complex('1-1j'), 1.0 - 1j) + self.assertEqual(complex('1J'), 1j) class complex2(complex): pass self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) @@ -553,6 +590,8 @@ def test_hash(self): x /= 3.0 # now check against floating point self.assertEqual(hash(x), hash(complex(x, 0.))) + self.assertNotEqual(hash(2000005 - 1j), -1) + def test_abs(self): nums = [complex(x/3., y/7.) for x in range(-9,9) for y in range(-9,9)] for num in nums: @@ -602,6 +641,14 @@ def test(v, expected, test_fn=self.assertEqual): test(complex(-0., 0.), "(-0+0j)") test(complex(-0., -0.), "(-0-0j)") + def test_pos(self): + class ComplexSubclass(complex): + pass + + self.assertEqual(+(1+6j), 1+6j) + self.assertEqual(+ComplexSubclass(1, 6), 1+6j) + self.assertIs(type(+ComplexSubclass(1, 6)), complex) + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index 400f4054c613ee..4a70217eb90d62 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -85,6 +85,58 @@ complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj) return PyComplex_FromCComplex(complex); } +static PyObject* +_py_c_neg(PyObject *Py_UNUSED(module), PyObject *num) +{ + Py_complex complex; + + complex = PyComplex_AsCComplex(num); + if (complex.real == -1. && PyErr_Occurred()) { + return NULL; + } + + return PyComplex_FromCComplex(_Py_c_neg(complex)); +} + +#define _PY_C_FUNC2(suffix) \ + static PyObject * \ + _py_c_##suffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + Py_complex num, exp, res; \ + \ + if (!PyArg_ParseTuple(args, "DD", &num, &exp)) { \ + return NULL; \ + } \ + \ + errno = 0; \ + res = _Py_c_##suffix(num, exp); \ + return Py_BuildValue("Di", &res, errno); \ + }; + +_PY_C_FUNC2(sum) +_PY_C_FUNC2(diff) +_PY_C_FUNC2(prod) +_PY_C_FUNC2(quot) +_PY_C_FUNC2(pow) + +static PyObject* +_py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) +{ + Py_complex complex; + double res; + + NULLABLE(obj); + complex = PyComplex_AsCComplex(obj); + + if (complex.real == -1. && PyErr_Occurred()) { + return NULL; + } + + errno = 0; + res = _Py_c_abs(complex); + return Py_BuildValue("di", res, errno); +} + static PyMethodDef test_methods[] = { {"complex_check", complex_check, METH_O}, @@ -94,6 +146,13 @@ static PyMethodDef test_methods[] = { {"complex_realasdouble", complex_realasdouble, METH_O}, {"complex_imagasdouble", complex_imagasdouble, METH_O}, {"complex_asccomplex", complex_asccomplex, METH_O}, + {"_py_c_sum", _py_c_sum, METH_VARARGS}, + {"_py_c_diff", _py_c_diff, METH_VARARGS}, + {"_py_c_neg", _py_c_neg, METH_O}, + {"_py_c_prod", _py_c_prod, METH_VARARGS}, + {"_py_c_quot", _py_c_quot, METH_VARARGS}, + {"_py_c_pow", _py_c_pow, METH_VARARGS}, + {"_py_c_abs", _py_c_abs, METH_O}, {NULL}, }; From a194938f33a71e727e53490815bae874eece1460 Mon Sep 17 00:00:00 2001 From: James Morris <6653392+J-M0@users.noreply.github.com> Date: Tue, 28 Nov 2023 04:24:59 -0500 Subject: [PATCH 027/442] gh-112431: Unconditionally call `hash -r` (GH-112432) The `activate` script calls `hash -r` in two places to make sure the shell picks up the environment changes the script makes. Before that, it checks to see if the shell running the script is bash or zsh. `hash -r` is specified by POSIX and is not exclusive to bash and zsh. This guard prevents the script from calling `hash -r` in other `#!/bin/sh`-compatible shells like dash. --- Lib/venv/scripts/common/activate | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate index 8398981ce53b9c..6fdf423a1c516a 100644 --- a/Lib/venv/scripts/common/activate +++ b/Lib/venv/scripts/common/activate @@ -14,12 +14,9 @@ deactivate () { unset _OLD_VIRTUAL_PYTHONHOME fi - # This should detect bash and zsh, which have a hash command that must - # be called to get it to forget past commands. Without forgetting + # Call hash to forget past commands. Without forgetting # past commands the $PATH changes we made may not be respected - if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null - fi + hash -r 2> /dev/null if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then PS1="${_OLD_VIRTUAL_PS1:-}" @@ -69,9 +66,6 @@ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then export PS1 fi -# This should detect bash and zsh, which have a hash command that must -# be called to get it to forget past commands. Without forgetting +# Call hash to forget past commands. Without forgetting # past commands the $PATH changes we made may not be respected -if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then - hash -r 2> /dev/null -fi +hash -r 2> /dev/null From 3fdf7ae3d1a08894e53c263945fba67fe62ac05b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 28 Nov 2023 16:46:00 +0200 Subject: [PATCH 028/442] gh-110930: Correct book title by Alan D. Moore (#112490) --- Doc/library/tkinter.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index a8c67b02a23e4d..e084d8554c7c09 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -58,8 +58,8 @@ details that are unchanged. * `Modern Tkinter for Busy Python Developers `_ By Mark Roseman. (ISBN 978-1999149567) - * `Python and Tkinter Programming `_ - By Alan Moore. (ISBN 978-1788835886) + * `Python GUI programming with Tkinter `_ + By Alan D. Moore. (ISBN 978-1788835886) * `Programming Python `_ By Mark Lutz; has excellent coverage of Tkinter. (ISBN 978-0596158101) From 48dfd74a9db9d4aa9c6f23b4a67b461e5d977173 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Tue, 28 Nov 2023 07:45:03 -0800 Subject: [PATCH 029/442] GH-112245: Promote free threaded CI (#112246) --- .github/workflows/build.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4210194b978749..cfb36c8c32e18d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,7 +190,7 @@ jobs: build_windows_free_threaded: name: 'Windows (free-threaded)' needs: check_source - if: needs.check_source.outputs.run_tests == 'true' && contains(github.event.pull_request.labels.*.name, 'topic-free-threaded') + if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-windows.yml with: free-threaded: true @@ -206,7 +206,7 @@ jobs: build_macos_free_threaded: name: 'macOS (free-threaded)' needs: check_source - if: needs.check_source.outputs.run_tests == 'true' && contains(github.event.pull_request.labels.*.name, 'topic-free-threaded') + if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-macos.yml with: config_hash: ${{ needs.check_source.outputs.config_hash }} @@ -228,7 +228,7 @@ jobs: build_ubuntu_free_threaded: name: 'Ubuntu (free-threaded)' needs: check_source - if: needs.check_source.outputs.run_tests == 'true' && contains(github.event.pull_request.labels.*.name, 'topic-free-threaded') + if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-ubuntu.yml with: config_hash: ${{ needs.check_source.outputs.config_hash }} @@ -521,10 +521,7 @@ jobs: uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe with: allowed-failures: >- - build_macos_free_threaded, - build_ubuntu_free_threaded, build_ubuntu_ssltests, - build_windows_free_threaded, cifuzz, test_hypothesis, allowed-skips: >- From e413daf5f6b983bdb4e1965d76b5313cb93b266e Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Wed, 29 Nov 2023 13:15:39 +1300 Subject: [PATCH 030/442] gh-112454: Disable TLS-PSK if OpenSSL was built without PSK support (#112491) If OpenSSL was built without PSK support, the python TLS-PSK methods will raise "NotImplementedError" if called. Add a constant "ssl.HAS_PSK" to check if TLS-PSK is supported --- Doc/library/ssl.rst | 12 ++++++++++++ Lib/ssl.py | 2 +- Lib/test/test_ssl.py | 2 ++ Modules/_ssl.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 206294528e0016..0db233e2dde33c 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -908,6 +908,12 @@ Constants .. versionadded:: 3.7 +.. data:: HAS_PSK + + Whether the OpenSSL library has built-in support for TLS-PSK. + + .. versionadded:: 3.13 + .. data:: CHANNEL_BINDING_TYPES List of supported TLS channel binding types. Strings in this list @@ -2050,6 +2056,9 @@ to speed up repeated connections from the same clients. return 'ClientId_1', psk_table.get(hint, b'') context.set_psk_client_callback(callback) + This method will raise :exc:`NotImplementedError` if :data:`HAS_PSK` is + ``False``. + .. versionadded:: 3.13 .. method:: SSLContext.set_psk_server_callback(callback, identity_hint=None) @@ -2092,6 +2101,9 @@ to speed up repeated connections from the same clients. return psk_table.get(identity, b'') context.set_psk_server_callback(callback, 'ServerId_1') + This method will raise :exc:`NotImplementedError` if :data:`HAS_PSK` is + ``False``. + .. versionadded:: 3.13 .. index:: single: certificates diff --git a/Lib/ssl.py b/Lib/ssl.py index 36fca9d4aa93d1..d01484964b6895 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -116,7 +116,7 @@ from _ssl import ( HAS_SNI, HAS_ECDH, HAS_NPN, HAS_ALPN, HAS_SSLv2, HAS_SSLv3, HAS_TLSv1, - HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3 + HAS_TLSv1_1, HAS_TLSv1_2, HAS_TLSv1_3, HAS_PSK ) from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index aecba89cde1495..3fdfa2960503b8 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -4259,6 +4259,7 @@ def test_session_handling(self): 'Session refers to a different SSLContext.') @requires_tls_version('TLSv1_2') + @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') def test_psk(self): psk = bytes.fromhex('deadbeef') @@ -4326,6 +4327,7 @@ def server_callback(identity): s.connect((HOST, server.port)) @requires_tls_version('TLSv1_3') + @unittest.skipUnless(ssl.HAS_PSK, 'TLS-PSK disabled on this OpenSSL build') def test_psk_tls1_3(self): psk = bytes.fromhex('deadbeef') identity_hint = 'identity-hint' diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 707e7ad9543acb..90b600f4b776a3 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -301,8 +301,10 @@ typedef struct { BIO *keylog_bio; /* Cached module state, also used in SSLSocket and SSLSession code. */ _sslmodulestate *state; +#ifndef OPENSSL_NO_PSK PyObject *psk_client_callback; PyObject *psk_server_callback; +#endif } PySSLContext; typedef struct { @@ -3125,8 +3127,10 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) self->alpn_protocols = NULL; self->set_sni_cb = NULL; self->state = get_ssl_state(module); +#ifndef OPENSSL_NO_PSK self->psk_client_callback = NULL; self->psk_server_callback = NULL; +#endif /* Don't check host name by default */ if (proto_version == PY_SSL_VERSION_TLS_CLIENT) { @@ -3239,8 +3243,10 @@ context_clear(PySSLContext *self) Py_CLEAR(self->set_sni_cb); Py_CLEAR(self->msg_cb); Py_CLEAR(self->keylog_filename); +#ifndef OPENSSL_NO_PSK Py_CLEAR(self->psk_client_callback); Py_CLEAR(self->psk_server_callback); +#endif if (self->keylog_bio != NULL) { PySSL_BEGIN_ALLOW_THREADS BIO_free_all(self->keylog_bio); @@ -4668,6 +4674,7 @@ _ssl__SSLContext_get_ca_certs_impl(PySSLContext *self, int binary_form) return NULL; } +#ifndef OPENSSL_NO_PSK static unsigned int psk_client_callback(SSL *s, const char *hint, char *identity, @@ -4735,6 +4742,7 @@ static unsigned int psk_client_callback(SSL *s, PyGILState_Release(gstate); return 0; } +#endif /*[clinic input] _ssl._SSLContext.set_psk_client_callback @@ -4747,6 +4755,7 @@ _ssl__SSLContext_set_psk_client_callback_impl(PySSLContext *self, PyObject *callback) /*[clinic end generated code: output=0aba86f6ed75119e input=7627bae0e5ee7635]*/ { +#ifndef OPENSSL_NO_PSK if (self->protocol == PY_SSL_VERSION_TLS_SERVER) { _setSSLError(get_state_ctx(self), "Cannot add PSK client callback to a " @@ -4774,8 +4783,14 @@ _ssl__SSLContext_set_psk_client_callback_impl(PySSLContext *self, SSL_CTX_set_psk_client_callback(self->ctx, ssl_callback); Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, + "TLS-PSK is not supported by your OpenSSL version."); + return NULL; +#endif } +#ifndef OPENSSL_NO_PSK static unsigned int psk_server_callback(SSL *s, const char *identity, unsigned char *psk, @@ -4835,6 +4850,7 @@ static unsigned int psk_server_callback(SSL *s, PyGILState_Release(gstate); return 0; } +#endif /*[clinic input] _ssl._SSLContext.set_psk_server_callback @@ -4849,6 +4865,7 @@ _ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, const char *identity_hint) /*[clinic end generated code: output=1f4d6a4e09a92b03 input=65d4b6022aa85ea3]*/ { +#ifndef OPENSSL_NO_PSK if (self->protocol == PY_SSL_VERSION_TLS_CLIENT) { _setSSLError(get_state_ctx(self), "Cannot add PSK server callback to a " @@ -4882,6 +4899,11 @@ _ssl__SSLContext_set_psk_server_callback_impl(PySSLContext *self, SSL_CTX_set_psk_server_callback(self->ctx, ssl_callback); Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, + "TLS-PSK is not supported by your OpenSSL version."); + return NULL; +#endif } @@ -6243,6 +6265,12 @@ sslmodule_init_constants(PyObject *m) addbool(m, "HAS_TLSv1_3", 0); #endif +#ifdef OPENSSL_NO_PSK + addbool(m, "HAS_PSK", 0); +#else + addbool(m, "HAS_PSK", 1); +#endif + #undef addbool #undef ADD_INT_CONST From e723700190ba497d1601cb423ee48d5d222a9d26 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 29 Nov 2023 10:10:11 +0900 Subject: [PATCH 031/442] Rename ...Uop... to ...UOp... (uppercase O) for consistency (#112327) * Rename _PyUopExecute to _PyUOpExecute (uppercase O) for consistency * Also rename _PyUopName and _PyUOp_Replacements, and some output strings --- Include/internal/pycore_uops.h | 2 +- Python/bytecodes.c | 2 +- Python/ceval.c | 12 ++++++------ Python/generated_cases.c.h | 2 +- Python/optimizer.c | 28 ++++++++++++++-------------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Include/internal/pycore_uops.h b/Include/internal/pycore_uops.h index e2b94894681f44..ea8f90bf8c1d8f 100644 --- a/Include/internal/pycore_uops.h +++ b/Include/internal/pycore_uops.h @@ -24,7 +24,7 @@ typedef struct { _PyUOpInstruction trace[1]; } _PyUOpExecutorObject; -_PyInterpreterFrame *_PyUopExecute( +_PyInterpreterFrame *_PyUOpExecute( _PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index a1ca66e457e47d..2e5f6c8d0d10e2 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2357,7 +2357,7 @@ dummy_func( JUMPBY(1-original_oparg); frame->instr_ptr = next_instr; Py_INCREF(executor); - if (executor->execute == _PyUopExecute) { + if (executor->execute == _PyUOpExecute) { current_executor = (_PyUOpExecutorObject *)executor; GOTO_TIER_TWO(); } diff --git a/Python/ceval.c b/Python/ceval.c index def75fd114cb86..1806ceb7fa9681 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -647,7 +647,7 @@ static const _Py_CODEUNIT _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS[] = { extern const struct _PyCode_DEF(8) _Py_InitCleanup; -extern const char *_PyUopName(int index); +extern const char *_PyUOpName(int index); /* Disable unused label warnings. They are handy for debugging, even if computed gotos aren't used. */ @@ -1002,7 +1002,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int DPRINTF(3, "%4d: uop %s, oparg %d, operand %" PRIu64 ", target %d, stack_level %d\n", (int)(next_uop - current_executor->trace), - _PyUopName(uopcode), + _PyUOpName(uopcode), next_uop->oparg, next_uop->operand, next_uop->target, @@ -1051,8 +1051,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int pop_1_error_tier_two: STACK_SHRINK(1); error_tier_two: - DPRINTF(2, "Error: [Uop %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", - uopcode, _PyUopName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, + DPRINTF(2, "Error: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", + uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, (int)(next_uop - current_executor->trace - 1)); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); frame->return_offset = 0; // Don't leave this random @@ -1064,8 +1064,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int deoptimize: // On DEOPT_IF we just repeat the last instruction. // This presumes nothing was popped from the stack (nor pushed). - DPRINTF(2, "DEOPT: [Uop %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", - uopcode, _PyUopName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, + DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", + uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, (int)(next_uop - current_executor->trace - 1)); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index dedd793111b7ff..0ac99e759deb12 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2331,7 +2331,7 @@ JUMPBY(1-original_oparg); frame->instr_ptr = next_instr; Py_INCREF(executor); - if (executor->execute == _PyUopExecute) { + if (executor->execute == _PyUOpExecute) { current_executor = (_PyUOpExecutorObject *)executor; GOTO_TIER_TWO(); } diff --git a/Python/optimizer.c b/Python/optimizer.c index ec59fea26fbc70..dd24fbebbfd2a9 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -325,7 +325,7 @@ uop_dealloc(_PyUOpExecutorObject *self) { } const char * -_PyUopName(int index) +_PyUOpName(int index) { if (index <= MAX_REAL_OPCODE) { return _PyOpcode_OpName[index]; @@ -347,7 +347,7 @@ uop_item(_PyUOpExecutorObject *self, Py_ssize_t index) PyErr_SetNone(PyExc_IndexError); return NULL; } - const char *name = _PyUopName(self->trace[index].opcode); + const char *name = _PyUOpName(self->trace[index].opcode); if (name == NULL) { name = ""; } @@ -388,7 +388,7 @@ PyTypeObject _PyUOpExecutor_Type = { /* TO DO -- Generate these tables */ static const uint16_t -_PyUop_Replacements[OPCODE_METADATA_SIZE] = { +_PyUOp_Replacements[OPCODE_METADATA_SIZE] = { [_ITER_JUMP_RANGE] = _GUARD_NOT_EXHAUSTED_RANGE, [_ITER_JUMP_LIST] = _GUARD_NOT_EXHAUSTED_LIST, [_ITER_JUMP_TUPLE] = _GUARD_NOT_EXHAUSTED_TUPLE, @@ -451,7 +451,7 @@ translate_bytecode_to_trace( #define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ DPRINTF(2, \ " ADD_TO_TRACE(%s, %d, %" PRIu64 ")\n", \ - _PyUopName(OPCODE), \ + _PyUOpName(OPCODE), \ (OPARG), \ (uint64_t)(OPERAND)); \ assert(trace_length < max_length); \ @@ -474,7 +474,7 @@ translate_bytecode_to_trace( } // Reserve space for N uops, plus 3 for _SET_IP, _CHECK_VALIDITY and _EXIT_TRACE -#define RESERVE(needed) RESERVE_RAW((needed) + 3, _PyUopName(opcode)) +#define RESERVE(needed) RESERVE_RAW((needed) + 3, _PyUOpName(opcode)) // Trace stack operations (used by _PUSH_FRAME, _POP_FRAME) #define TRACE_STACK_PUSH() \ @@ -546,8 +546,8 @@ translate_bytecode_to_trace( uint32_t uopcode = BRANCH_TO_GUARD[opcode - POP_JUMP_IF_FALSE][jump_likely]; _Py_CODEUNIT *next_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; DPRINTF(4, "%s(%d): counter=%x, bitcount=%d, likely=%d, uopcode=%s\n", - _PyUopName(opcode), oparg, - counter, bitcount, jump_likely, _PyUopName(uopcode)); + _PyUOpName(opcode), oparg, + counter, bitcount, jump_likely, _PyUOpName(uopcode)); ADD_TO_TRACE(uopcode, max_length, 0, target); if (jump_likely) { _Py_CODEUNIT *target_instr = next_instr + oparg; @@ -615,8 +615,8 @@ translate_bytecode_to_trace( oparg += extras; } } - if (_PyUop_Replacements[uop]) { - uop = _PyUop_Replacements[uop]; + if (_PyUOp_Replacements[uop]) { + uop = _PyUOp_Replacements[uop]; if (uop == _FOR_ITER_TIER_TWO) { target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1; assert(_PyCode_CODE(code)[target-1].op.code == END_FOR || @@ -712,7 +712,7 @@ translate_bytecode_to_trace( } break; } - DPRINTF(2, "Unsupported opcode %s\n", _PyUopName(opcode)); + DPRINTF(2, "Unsupported opcode %s\n", _PyUOpName(opcode)); OPT_UNSUPPORTED_OPCODE(opcode); goto done; // Break out of loop } // End default @@ -832,7 +832,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) dest--; } assert(dest == -1); - executor->base.execute = _PyUopExecute; + executor->base.execute = _PyUOpExecute; _Py_ExecutorInit((_PyExecutorObject *)executor, dependencies); #ifdef Py_DEBUG char *python_lltrace = Py_GETENV("PYTHON_LLTRACE"); @@ -845,7 +845,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, _PyBloomFilter *dependencies) for (int i = 0; i < length; i++) { printf("%4d %s(%d, %d, %" PRIu64 ")\n", i, - _PyUopName(executor->trace[i].opcode), + _PyUOpName(executor->trace[i].opcode), executor->trace[i].oparg, executor->trace[i].target, executor->trace[i].operand); @@ -888,11 +888,11 @@ uop_optimize( return 1; } -/* Dummy execute() function for Uop Executor. +/* Dummy execute() function for UOp Executor. * The actual implementation is inlined in ceval.c, * in _PyEval_EvalFrameDefault(). */ _PyInterpreterFrame * -_PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer) +_PyUOpExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer) { Py_FatalError("Tier 2 is now inlined into Tier 1"); } From f9e6ce03953e9ee988d55324dc715b0ef2303cfb Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 28 Nov 2023 20:40:12 -0800 Subject: [PATCH 032/442] [Enum] update class creation for RuntimeError changes (GH-111815) --- Doc/howto/enum.rst | 1 - Lib/enum.py | 14 +++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index a136c76303c8ef..ffdafb749c73a9 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -1439,7 +1439,6 @@ alias:: Traceback (most recent call last): ... ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN' - Error calling __set_name__ on '_proto_member' instance 'GRENE' in 'Color' .. note:: diff --git a/Lib/enum.py b/Lib/enum.py index 4e76e96d2c916e..648401e80be685 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -568,12 +568,16 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k try: exc = None enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) - except RuntimeError as e: - # any exceptions raised by member.__new__ will get converted to a - # RuntimeError, so get that original exception back and raise it instead - exc = e.__cause__ or e + except Exception as e: + # since 3.12 the line "Error calling __set_name__ on '_proto_member' instance ..." + # is tacked on to the error instead of raising a RuntimeError + # recreate the exception to discard + exc = type(e)(str(e)) + exc.__cause__ = e.__cause__ + exc.__context__ = e.__context__ + tb = e.__traceback__ if exc is not None: - raise exc + raise exc.with_traceback(tb) # # update classdict with any changes made by __init_subclass__ classdict.update(enum_class.__dict__) From e0449b9a7fffc0c0eed806bf4cbb8f1f65397bbb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 29 Nov 2023 17:37:05 +0200 Subject: [PATCH 033/442] Add more C API tests (GH-112522) Add tests for PyObject_Str(), PyObject_Repr(), PyObject_ASCII() and PyObject_Bytes(). --- Lib/test/test_capi/test_abstract.py | 86 +++++++++++++++++++++++++++++ Modules/_testcapi/abstract.c | 33 +++++++++++ 2 files changed, 119 insertions(+) diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py index 26152c3049848c..97ed939928c360 100644 --- a/Lib/test/test_capi/test_abstract.py +++ b/Lib/test/test_capi/test_abstract.py @@ -8,6 +8,30 @@ NULL = None +class StrSubclass(str): + pass + +class BytesSubclass(bytes): + pass + +class WithStr: + def __init__(self, value): + self.value = value + def __str__(self): + return self.value + +class WithRepr: + def __init__(self, value): + self.value = value + def __repr__(self): + return self.value + +class WithBytes: + def __init__(self, value): + self.value = value + def __bytes__(self): + return self.value + class TestObject: @property def evil(self): @@ -44,6 +68,68 @@ def gen(): class CAPITest(unittest.TestCase): + def assertTypedEqual(self, actual, expected): + self.assertIs(type(actual), type(expected)) + self.assertEqual(actual, expected) + + def test_object_str(self): + # Test PyObject_Str() + object_str = _testcapi.object_str + self.assertTypedEqual(object_str(''), '') + self.assertTypedEqual(object_str('abc'), 'abc') + self.assertTypedEqual(object_str('\U0001f40d'), '\U0001f40d') + self.assertTypedEqual(object_str(StrSubclass('abc')), 'abc') + self.assertTypedEqual(object_str(WithStr('abc')), 'abc') + self.assertTypedEqual(object_str(WithStr(StrSubclass('abc'))), StrSubclass('abc')) + self.assertTypedEqual(object_str(WithRepr('')), '') + self.assertTypedEqual(object_str(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(object_str(NULL), '') + + def test_object_repr(self): + # Test PyObject_Repr() + object_repr = _testcapi.object_repr + self.assertTypedEqual(object_repr(''), "''") + self.assertTypedEqual(object_repr('abc'), "'abc'") + self.assertTypedEqual(object_repr('\U0001f40d'), "'\U0001f40d'") + self.assertTypedEqual(object_repr(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(object_repr(WithRepr('')), '') + self.assertTypedEqual(object_repr(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(object_repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>') + self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>')) + self.assertTypedEqual(object_repr(NULL), '') + + def test_object_ascii(self): + # Test PyObject_ASCII() + object_ascii = _testcapi.object_ascii + self.assertTypedEqual(object_ascii(''), "''") + self.assertTypedEqual(object_ascii('abc'), "'abc'") + self.assertTypedEqual(object_ascii('\U0001f40d'), r"'\U0001f40d'") + self.assertTypedEqual(object_ascii(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(object_ascii(WithRepr('')), '') + self.assertTypedEqual(object_ascii(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(object_ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>') + self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>') + self.assertTypedEqual(object_ascii(NULL), '') + + def test_object_bytes(self): + # Test PyObject_Bytes() + object_bytes = _testcapi.object_bytes + self.assertTypedEqual(object_bytes(b''), b'') + self.assertTypedEqual(object_bytes(b'abc'), b'abc') + self.assertTypedEqual(object_bytes(BytesSubclass(b'abc')), b'abc') + self.assertTypedEqual(object_bytes(WithBytes(b'abc')), b'abc') + self.assertTypedEqual(object_bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc')) + self.assertTypedEqual(object_bytes(bytearray(b'abc')), b'abc') + self.assertTypedEqual(object_bytes(memoryview(b'abc')), b'abc') + self.assertTypedEqual(object_bytes([97, 98, 99]), b'abc') + self.assertTypedEqual(object_bytes((97, 98, 99)), b'abc') + self.assertTypedEqual(object_bytes(iter([97, 98, 99])), b'abc') + self.assertRaises(TypeError, object_bytes, WithBytes(bytearray(b'abc'))) + self.assertRaises(TypeError, object_bytes, WithBytes([97, 98, 99])) + self.assertRaises(TypeError, object_bytes, 3) + self.assertRaises(TypeError, object_bytes, 'abc') + self.assertRaises(TypeError, object_bytes, object()) + self.assertTypedEqual(object_bytes(NULL), b'') def test_object_getattr(self): xgetattr = _testcapi.object_getattr diff --git a/Modules/_testcapi/abstract.c b/Modules/_testcapi/abstract.c index 4a9144e66f0fcd..a8ba009eb6a54b 100644 --- a/Modules/_testcapi/abstract.c +++ b/Modules/_testcapi/abstract.c @@ -2,6 +2,34 @@ #include "util.h" +static PyObject * +object_repr(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + return PyObject_Repr(arg); +} + +static PyObject * +object_ascii(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + return PyObject_ASCII(arg); +} + +static PyObject * +object_str(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + return PyObject_Str(arg); +} + +static PyObject * +object_bytes(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + return PyObject_Bytes(arg); +} + static PyObject * object_getattr(PyObject *self, PyObject *args) { @@ -616,6 +644,11 @@ sequence_tuple(PyObject *self, PyObject *obj) static PyMethodDef test_methods[] = { + {"object_repr", object_repr, METH_O}, + {"object_ascii", object_ascii, METH_O}, + {"object_str", object_str, METH_O}, + {"object_bytes", object_bytes, METH_O}, + {"object_getattr", object_getattr, METH_VARARGS}, {"object_getattrstring", object_getattrstring, METH_VARARGS}, {"object_getoptionalattr", object_getoptionalattr, METH_VARARGS}, From 403886942376210662610627b01fea6acd77d331 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 Nov 2023 09:36:48 -0800 Subject: [PATCH 034/442] gh-112509: Fix keys being present in both required_keys and optional_keys in TypedDict (#112512) Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 40 +++++++++++++++++++ Lib/typing.py | 25 +++++++++--- ...-11-28-20-01-33.gh-issue-112509.QtoKed.rst | 3 ++ 3 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-28-20-01-33.gh-issue-112509.QtoKed.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 669803177315e3..4fbb410f26ab8d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -7769,6 +7769,46 @@ class Cat(Animal): 'voice': str, }) + def test_keys_inheritance_with_same_name(self): + class NotTotal(TypedDict, total=False): + a: int + + class Total(NotTotal): + a: int + + self.assertEqual(NotTotal.__required_keys__, frozenset()) + self.assertEqual(NotTotal.__optional_keys__, frozenset(['a'])) + self.assertEqual(Total.__required_keys__, frozenset(['a'])) + self.assertEqual(Total.__optional_keys__, frozenset()) + + class Base(TypedDict): + a: NotRequired[int] + b: Required[int] + + class Child(Base): + a: Required[int] + b: NotRequired[int] + + self.assertEqual(Base.__required_keys__, frozenset(['b'])) + self.assertEqual(Base.__optional_keys__, frozenset(['a'])) + self.assertEqual(Child.__required_keys__, frozenset(['a'])) + self.assertEqual(Child.__optional_keys__, frozenset(['b'])) + + def test_multiple_inheritance_with_same_key(self): + class Base1(TypedDict): + a: NotRequired[int] + + class Base2(TypedDict): + a: Required[str] + + class Child(Base1, Base2): + pass + + # Last base wins + self.assertEqual(Child.__annotations__, {'a': Required[str]}) + self.assertEqual(Child.__required_keys__, frozenset(['a'])) + self.assertEqual(Child.__optional_keys__, frozenset()) + def test_required_notrequired_keys(self): self.assertEqual(NontotalMovie.__required_keys__, frozenset({"title"})) diff --git a/Lib/typing.py b/Lib/typing.py index 216f0c141b62af..b3af701f8d5437 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2884,8 +2884,14 @@ def __new__(cls, name, bases, ns, total=True): for base in bases: annotations.update(base.__dict__.get('__annotations__', {})) - required_keys.update(base.__dict__.get('__required_keys__', ())) - optional_keys.update(base.__dict__.get('__optional_keys__', ())) + + base_required = base.__dict__.get('__required_keys__', set()) + required_keys |= base_required + optional_keys -= base_required + + base_optional = base.__dict__.get('__optional_keys__', set()) + required_keys -= base_optional + optional_keys |= base_optional annotations.update(own_annotations) for annotation_key, annotation_type in own_annotations.items(): @@ -2897,14 +2903,23 @@ def __new__(cls, name, bases, ns, total=True): annotation_origin = get_origin(annotation_type) if annotation_origin is Required: - required_keys.add(annotation_key) + is_required = True elif annotation_origin is NotRequired: - optional_keys.add(annotation_key) - elif total: + is_required = False + else: + is_required = total + + if is_required: required_keys.add(annotation_key) + optional_keys.discard(annotation_key) else: optional_keys.add(annotation_key) + required_keys.discard(annotation_key) + assert required_keys.isdisjoint(optional_keys), ( + f"Required keys overlap with optional keys in {name}:" + f" {required_keys=}, {optional_keys=}" + ) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) tp_dict.__optional_keys__ = frozenset(optional_keys) diff --git a/Misc/NEWS.d/next/Library/2023-11-28-20-01-33.gh-issue-112509.QtoKed.rst b/Misc/NEWS.d/next/Library/2023-11-28-20-01-33.gh-issue-112509.QtoKed.rst new file mode 100644 index 00000000000000..a16d67e7776bcb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-28-20-01-33.gh-issue-112509.QtoKed.rst @@ -0,0 +1,3 @@ +Fix edge cases that could cause a key to be present in both the +``__required_keys__`` and ``__optional_keys__`` attributes of a +:class:`typing.TypedDict`. Patch by Jelle Zijlstra. From d4a6229afe10d7e5a9a59bf9472f36d7698988db Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 29 Nov 2023 09:38:29 -0800 Subject: [PATCH 035/442] gh-104003: Implement PEP 702 (#104004) Co-authored-by: Hugo van Kemenade Co-authored-by: Alex Waygood --- Doc/library/warnings.rst | 50 ++++ Doc/whatsnew/3.13.rst | 9 + Lib/test/test_warnings/__init__.py | 282 +++++++++++++++++- Lib/warnings.py | 131 +++++++- ...-04-29-20-49-13.gh-issue-104003.-8Ruk2.rst | 3 + 5 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-29-20-49-13.gh-issue-104003.-8Ruk2.rst diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 884de08eab1b16..a9c469707e8227 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -522,6 +522,56 @@ Available Functions and calls to :func:`simplefilter`. +.. decorator:: deprecated(msg, *, category=DeprecationWarning, stacklevel=1) + + Decorator to indicate that a class, function or overload is deprecated. + + When this decorator is applied to an object, + deprecation warnings may be emitted at runtime when the object is used. + :term:`static type checkers ` + will also generate a diagnostic on usage of the deprecated object. + + Usage:: + + from warnings import deprecated + from typing import overload + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the + warning is emitted. If it is ``1`` (the default), the warning + is emitted at the direct caller of the deprecated object; if it + is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. + + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator + must be after the :func:`@overload ` decorator + for the attribute to exist on the overload as returned by + :func:`typing.get_overloads`. + + .. versionadded:: 3.13 + See :pep:`702`. + + Available Context Managers -------------------------- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 198ea3a4b57bde..372e4a45468e68 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -348,6 +348,15 @@ venv (using ``--without-scm-ignore-files``). (Contributed by Brett Cannon in :gh:`108125`.) +warnings +-------- + +* The new :func:`warnings.deprecated` decorator provides a way to communicate + deprecations to :term:`static type checkers ` and + to warn on usage of deprecated classes and functions. A runtime deprecation + 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`.) + Optimizations ============= diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 2c523230e7e97f..cd989fe36bf26b 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -5,6 +5,8 @@ import re import sys import textwrap +import types +from typing import overload, get_overloads import unittest from test import support from test.support import import_helper @@ -16,6 +18,7 @@ from test.test_warnings.data import stacklevel as warning_tests import warnings as original_warnings +from warnings import deprecated py_warnings = import_helper.import_fresh_module('warnings', @@ -90,7 +93,7 @@ def test_module_all_attribute(self): self.assertTrue(hasattr(self.module, '__all__')) target_api = ["warn", "warn_explicit", "showwarning", "formatwarning", "filterwarnings", "simplefilter", - "resetwarnings", "catch_warnings"] + "resetwarnings", "catch_warnings", "deprecated"] self.assertSetEqual(set(self.module.__all__), set(target_api)) @@ -1377,6 +1380,283 @@ def test_late_resource_warning(self): self.assertTrue(err.startswith(expected), ascii(err)) +class DeprecatedTests(unittest.TestCase): + def test_dunder_deprecated(self): + @deprecated("A will go away soon") + class A: + pass + + self.assertEqual(A.__deprecated__, "A will go away soon") + self.assertIsInstance(A, type) + + @deprecated("b will go away soon") + def b(): + pass + + self.assertEqual(b.__deprecated__, "b will go away soon") + self.assertIsInstance(b, types.FunctionType) + + @overload + @deprecated("no more ints") + def h(x: int) -> int: ... + @overload + def h(x: str) -> str: ... + def h(x): + return x + + overloads = get_overloads(h) + self.assertEqual(len(overloads), 2) + self.assertEqual(overloads[0].__deprecated__, "no more ints") + + def test_class(self): + @deprecated("A will go away soon") + class A: + pass + + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + A() + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + with self.assertRaises(TypeError): + A(42) + + def test_class_with_init(self): + @deprecated("HasInit will go away soon") + class HasInit: + def __init__(self, x): + self.x = x + + with self.assertWarnsRegex(DeprecationWarning, "HasInit will go away soon"): + instance = HasInit(42) + self.assertEqual(instance.x, 42) + + def test_class_with_new(self): + has_new_called = False + + @deprecated("HasNew will go away soon") + class HasNew: + def __new__(cls, x): + nonlocal has_new_called + has_new_called = True + return super().__new__(cls) + + def __init__(self, x) -> None: + self.x = x + + with self.assertWarnsRegex(DeprecationWarning, "HasNew will go away soon"): + instance = HasNew(42) + self.assertEqual(instance.x, 42) + self.assertTrue(has_new_called) + + def test_class_with_inherited_new(self): + new_base_called = False + + class NewBase: + def __new__(cls, x): + nonlocal new_base_called + new_base_called = True + return super().__new__(cls) + + def __init__(self, x) -> None: + self.x = x + + @deprecated("HasInheritedNew will go away soon") + class HasInheritedNew(NewBase): + pass + + with self.assertWarnsRegex(DeprecationWarning, "HasInheritedNew will go away soon"): + instance = HasInheritedNew(42) + self.assertEqual(instance.x, 42) + self.assertTrue(new_base_called) + + def test_class_with_new_but_no_init(self): + new_called = False + + @deprecated("HasNewNoInit will go away soon") + class HasNewNoInit: + def __new__(cls, x): + nonlocal new_called + new_called = True + obj = super().__new__(cls) + obj.x = x + return obj + + with self.assertWarnsRegex(DeprecationWarning, "HasNewNoInit will go away soon"): + instance = HasNewNoInit(42) + self.assertEqual(instance.x, 42) + self.assertTrue(new_called) + + def test_mixin_class(self): + @deprecated("Mixin will go away soon") + class Mixin: + pass + + class Base: + def __init__(self, a) -> None: + self.a = a + + with self.assertWarnsRegex(DeprecationWarning, "Mixin will go away soon"): + class Child(Base, Mixin): + pass + + instance = Child(42) + self.assertEqual(instance.a, 42) + + def test_existing_init_subclass(self): + @deprecated("C will go away soon") + class C: + def __init_subclass__(cls) -> None: + cls.inited = True + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + C() + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + class D(C): + pass + + self.assertTrue(D.inited) + self.assertIsInstance(D(), D) # no deprecation + + def test_existing_init_subclass_in_base(self): + class Base: + def __init_subclass__(cls, x) -> None: + cls.inited = x + + @deprecated("C will go away soon") + class C(Base, x=42): + pass + + self.assertEqual(C.inited, 42) + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + C() + + with self.assertWarnsRegex(DeprecationWarning, "C will go away soon"): + class D(C, x=3): + pass + + self.assertEqual(D.inited, 3) + + def test_init_subclass_has_correct_cls(self): + init_subclass_saw = None + + @deprecated("Base will go away soon") + class Base: + def __init_subclass__(cls) -> None: + nonlocal init_subclass_saw + init_subclass_saw = cls + + self.assertIsNone(init_subclass_saw) + + with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"): + class C(Base): + pass + + self.assertIs(init_subclass_saw, C) + + def test_init_subclass_with_explicit_classmethod(self): + init_subclass_saw = None + + @deprecated("Base will go away soon") + class Base: + @classmethod + def __init_subclass__(cls) -> None: + nonlocal init_subclass_saw + init_subclass_saw = cls + + self.assertIsNone(init_subclass_saw) + + with self.assertWarnsRegex(DeprecationWarning, "Base will go away soon"): + class C(Base): + pass + + self.assertIs(init_subclass_saw, C) + + def test_function(self): + @deprecated("b will go away soon") + def b(): + pass + + with self.assertWarnsRegex(DeprecationWarning, "b will go away soon"): + b() + + def test_method(self): + class Capybara: + @deprecated("x will go away soon") + def x(self): + pass + + instance = Capybara() + with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"): + instance.x() + + def test_property(self): + class Capybara: + @property + @deprecated("x will go away soon") + def x(self): + pass + + @property + def no_more_setting(self): + return 42 + + @no_more_setting.setter + @deprecated("no more setting") + def no_more_setting(self, value): + pass + + instance = Capybara() + with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"): + instance.x + + with py_warnings.catch_warnings(): + py_warnings.simplefilter("error") + self.assertEqual(instance.no_more_setting, 42) + + with self.assertWarnsRegex(DeprecationWarning, "no more setting"): + instance.no_more_setting = 42 + + def test_category(self): + @deprecated("c will go away soon", category=RuntimeWarning) + def c(): + pass + + with self.assertWarnsRegex(RuntimeWarning, "c will go away soon"): + c() + + def test_turn_off_warnings(self): + @deprecated("d will go away soon", category=None) + def d(): + pass + + with py_warnings.catch_warnings(): + py_warnings.simplefilter("error") + d() + + def test_only_strings_allowed(self): + with self.assertRaisesRegex( + TypeError, + "Expected an object of type str for 'message', not 'type'" + ): + @deprecated + class Foo: ... + + with self.assertRaisesRegex( + TypeError, + "Expected an object of type str for 'message', not 'function'" + ): + @deprecated + def foo(): ... + + def test_no_retained_references_to_wrapper_instance(self): + @deprecated('depr') + def d(): pass + + self.assertFalse(any( + isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ + )) + def setUpModule(): py_warnings.onceregistry.clear() c_warnings.onceregistry.clear() diff --git a/Lib/warnings.py b/Lib/warnings.py index 32e58072b9cc33..924f872172d4d1 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -5,7 +5,7 @@ __all__ = ["warn", "warn_explicit", "showwarning", "formatwarning", "filterwarnings", "simplefilter", - "resetwarnings", "catch_warnings"] + "resetwarnings", "catch_warnings", "deprecated"] def showwarning(message, category, filename, lineno, file=None, line=None): """Hook to write a warning to a file; replace if you like.""" @@ -508,6 +508,135 @@ def __exit__(self, *exc_info): self._module._showwarnmsg_impl = self._showwarnmsg_impl +class deprecated: + """Indicate that a class, function or overload is deprecated. + + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + + Usage: + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the + warning is emitted. If it is ``1`` (the default), the warning + is emitted at the direct caller of the deprecated object; if it + is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. + + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator + must be after the ``@overload`` decorator for the attribute to + exist on the overload as returned by ``get_overloads()``. + + See PEP 702 for details. + + """ + def __init__( + self, + message: str, + /, + *, + category: type[Warning] | None = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + if not isinstance(message, str): + raise TypeError( + f"Expected an object of type str for 'message', not {type(message).__name__!r}" + ) + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, arg, /): + # Make sure the inner functions created below don't + # retain a reference to self. + msg = self.message + category = self.category + stacklevel = self.stacklevel + if category is None: + arg.__deprecated__ = msg + return arg + elif isinstance(arg, type): + import functools + from types import MethodType + + original_new = arg.__new__ + + @functools.wraps(original_new) + def __new__(cls, *args, **kwargs): + if cls is arg: + warn(msg, category=category, stacklevel=stacklevel + 1) + if original_new is not object.__new__: + return original_new(cls, *args, **kwargs) + # Mirrors a similar check in object.__new__. + elif cls.__init__ is object.__init__ and (args or kwargs): + raise TypeError(f"{cls.__name__}() takes no arguments") + else: + return original_new(cls) + + arg.__new__ = staticmethod(__new__) + + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python) + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ + + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = classmethod(__init_subclass__) + # Or otherwise, which likely means it's a builtin such as + # object's implementation of __init_subclass__. + else: + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = __init_subclass__ + + arg.__deprecated__ = __new__.__deprecated__ = msg + __init_subclass__.__deprecated__ = msg + return arg + elif callable(arg): + import functools + + @functools.wraps(arg) + def wrapper(*args, **kwargs): + warn(msg, category=category, stacklevel=stacklevel + 1) + return arg(*args, **kwargs) + + arg.__deprecated__ = wrapper.__deprecated__ = msg + return wrapper + else: + raise TypeError( + "@deprecated decorator with non-None category must be applied to " + f"a class or callable, not {arg!r}" + ) + + _DEPRECATED_MSG = "{name!r} is deprecated and slated for removal in Python {remove}" def _deprecated(name, message=_DEPRECATED_MSG, *, remove, _version=sys.version_info): diff --git a/Misc/NEWS.d/next/Library/2023-04-29-20-49-13.gh-issue-104003.-8Ruk2.rst b/Misc/NEWS.d/next/Library/2023-04-29-20-49-13.gh-issue-104003.-8Ruk2.rst new file mode 100644 index 00000000000000..82d61ca8b8bc97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-29-20-49-13.gh-issue-104003.-8Ruk2.rst @@ -0,0 +1,3 @@ +Add :func:`warnings.deprecated`, a decorator to mark deprecated functions to +static type checkers and to warn on usage of deprecated classes and functions. +See :pep:`702`. Patch by Jelle Zijlstra. From 37589d76bbe97b0bf13ffafb8dd6aab361a0209a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 29 Nov 2023 16:18:25 -0800 Subject: [PATCH 036/442] GH-103065, GH-106704, GH-105253: Provide a `Tools/wasm/wasi.py` script to simplify doing a WASI build (GH-112473) --- .devcontainer/Dockerfile | 2 +- .gitignore | 1 + Makefile.pre.in | 1 + ...-11-27-13-55-47.gh-issue-103065.o72OiA.rst | 1 + Tools/wasm/README.md | 112 +++--- Tools/wasm/mypy.ini | 5 +- Tools/wasm/wasi.py | 328 ++++++++++++++++++ 7 files changed, 373 insertions(+), 77 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2023-11-27-13-55-47.gh-issue-103065.o72OiA.rst create mode 100644 Tools/wasm/wasi.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 590d7834b2b8be..9f808af38e69df 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,7 +6,7 @@ ENV WASI_SDK_VERSION=20 ENV WASI_SDK_PATH=/opt/wasi-sdk ENV WASMTIME_HOME=/opt/wasmtime -ENV WASMTIME_VERSION=9.0.1 +ENV WASMTIME_VERSION=14.0.4 ENV WASMTIME_CPU_ARCH=x86_64 RUN dnf -y --nodocs --setopt=install_weak_deps=False install /usr/bin/{blurb,clang,curl,git,ln,tar,xz} 'dnf-command(builddep)' && \ diff --git a/.gitignore b/.gitignore index 8c8273fc7a3aa3..c424a894c2a6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ Tools/unicode/data/ /config.status.lineno # hendrikmuhs/ccache-action@v1 /.ccache +/cross-build/ /platform /profile-clean-stamp /profile-run-stamp diff --git a/Makefile.pre.in b/Makefile.pre.in index 3d766425abba34..e7f8abce43d648 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2732,6 +2732,7 @@ clobber: clean -rm -rf build platform -rm -rf $(PYTHONFRAMEWORKDIR) -rm -f python-config.py python-config + -rm -rf cross-build # Make things extra clean, before making a distribution: # remove all generated files, even Makefile[.pre] diff --git a/Misc/NEWS.d/next/Build/2023-11-27-13-55-47.gh-issue-103065.o72OiA.rst b/Misc/NEWS.d/next/Build/2023-11-27-13-55-47.gh-issue-103065.o72OiA.rst new file mode 100644 index 00000000000000..e2240b7c656a2f --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-11-27-13-55-47.gh-issue-103065.o72OiA.rst @@ -0,0 +1 @@ +Introduce ``Tools/wasm/wasi.py`` to simplify doing a WASI build. diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index 8ef63c6dcd9ddc..acbece214ed9bd 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -298,100 +298,66 @@ AddType application/wasm wasm ## WASI (wasm32-wasi) -WASI builds require the [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 16.0+. -See `.devcontainer/Dockerfile` for an example of how to download and -install the WASI SDK. +**NOTE**: The instructions below assume a Unix-based OS due to cross-compilation for CPython being set up for `./configure`. -### Build +### Prerequisites + +Developing for WASI requires two things to be installed: + +1. The [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 16.0+ + (see `.devcontainer/Dockerfile` for an example of how to download and install the WASI SDK) +2. A WASI host/runtime ([wasmtime](https://wasmtime.dev) 14+ is recommended and what the instructions below assume) -The script ``wasi-env`` sets necessary compiler and linker flags as well as -``pkg-config`` overrides. The script assumes that WASI-SDK is installed in -``/opt/wasi-sdk`` or ``$WASI_SDK_PATH``. -There are two scripts you can use to do a WASI build from a source checkout. You can either use: +### Building +Building for WASI requires doing a cross-build where you have a "build" Python to help produce a WASI build of CPython (technically it's a "host x host" cross-build because the build Python is also the target Python while the host build is the WASI build; yes, it's confusing terminology). In the end you should have a build Python in `cross-build/build` and a WASI build in `cross-build/wasm32-wasi`. + +The easiest way to do a build is to use the `wasi.py` script. You can either have it perform the entire build process from start to finish in one step, or you can do it in discrete steps that mirror running `configure` and `make` for each of the two builds of Python you end up producing (which are beneficial when you only need to do a specific step after getting a complete build, e.g. editing some code and you just need to run `make` for the WASI build). + +The discrete steps are: ```shell -./Tools/wasm/wasm_build.py wasi build +python Tools/wasm/wasi.py configure-build-python +python Tools/wasm/wasi.py make-build-python +python Tools/wasm/wasi.py configure-host +python Tools/wasm/wasi.py make-host ``` -or: +To do it in a single command, run: ```shell -./Tools/wasm/build_wasi.sh +python Tools/wasm/wasi.py build ``` -The commands are equivalent to the following steps: - -- Make sure `Modules/Setup.local` exists -- Make sure the necessary build tools are installed: - - [WASI SDK](https://github.com/WebAssembly/wasi-sdk) (which ships with `clang`) - - `make` - - `pkg-config` (on Linux) -- Create the build Python - - `mkdir -p builddir/build` - - `pushd builddir/build` - - Get the build platform - - Python: `sysconfig.get_config_var("BUILD_GNU_TYPE")` - - Shell: `../../config.guess` - - `../../configure -C` - - `make all` - - ```PYTHON_VERSION=`./python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'` ``` - - `popd` -- Create the host/WASI Python - - `mkdir builddir/wasi` - - `pushd builddir/wasi` - - `../../Tools/wasm/wasi-env ../../configure -C --host=wasm32-unknown-wasi --build=$(../../config.guess) --with-build-python=../build/python` - - `CONFIG_SITE=../../Tools/wasm/config.site-wasm32-wasi` - - `HOSTRUNNER="wasmtime run --mapdir /::$(dirname $(dirname $(pwd))) --env PYTHONPATH=/builddir/wasi/build/lib.wasi-wasm32-$PYTHON_VERSION $(pwd)/python.wasm --"` - - Maps the source checkout to `/` in the WASI runtime - - Stdlib gets loaded from `/Lib` - - Gets `_sysconfigdata__wasi_wasm32-wasi.py` on to `sys.path` via `PYTHONPATH` - - Set by `wasi-env` - - `WASI_SDK_PATH` - - `WASI_SYSROOT` - - `CC` - - `CPP` - - `CXX` - - `LDSHARED` - - `AR` - - `RANLIB` - - `CFLAGS` - - `LDFLAGS` - - `PKG_CONFIG_PATH` - - `PKG_CONFIG_LIBDIR` - - `PKG_CONFIG_SYSROOT_DIR` - - `PATH` - - `make all` - +That will: -### Running - -If you followed the instructions above, you can run the interpreter via e.g., `wasmtime` from within the `Tools/wasi` directory (make sure to set/change `$PYTHON_VERSION` and do note the paths are relative to running in`builddir/wasi` for simplicity only): +1. Run `configure` for the build Python (same as `wasi.py configure-build-python`) +2. Run `make` for the build Python (`wasi.py make-build-python`) +3. Run `configure` for the WASI build (`wasi.py configure-host`) +4. Run `make` for the WASI build (`wasi.py make-host`) +See the `--help` for the various options available for each of the subcommands which controls things like the location of the WASI SDK, the command to use with the WASI host/runtime, etc. Also note that you can use `--` as a separtor for any of the `configure`-related commands -- including `build` -- to pass arguments to `configure` itself. For example, if you want a pydebug build that also caches the results from `configure`, you can do: ```shell -wasmtime run --mapdir /::../.. --env PYTHONPATH=/builddir/wasi/build/lib.wasi-wasm32-$PYTHON_VERSION python.wasm -- +python Tools/wasm/wasi.py build -- -C --with-pydebug ``` -There are also helpers provided by `Tools/wasm/wasm_build.py` as listed below. Also, if you used `Tools/wasm/build_wasi.sh`, a `run_wasi.sh` file will be created in `builddir/wasi` which will run the above command for you (it also uses absolute paths, so it can be executed from anywhere). - -#### REPL - +The `wasi.py` script is able to infer details from the build Python, and so you only technically need to specify `--with-pydebug` once for `configure-build-python` and `configure-host` will detect its use if you use the discrete steps: ```shell -./Tools/wasm/wasm_build.py wasi repl +python Tools/wasm/wasi.py configure-build-python -- -C --with-pydebug +python Tools/wasm/wasi.py make-build-python +python Tools/wasm/wasi.py configure-host -- -C +python Tools/wasm/wasi.py make-host ``` -#### Tests +### Running + +If you used `wasi.py` to do your build then there will be a `cross-build/wasm32-wasi/python.sh` file which you can use to run the `python.wasm` file (see the output from the `configure-host` subcommand): ```shell -./Tools/wasm/wasm_build.py wasi test +cross-build/wasm32-wasi/python.sh --version ``` -### Debugging +While you _can_ run `python.wasm` directly, Python will fail to start up without certain things being set (e.g. `PYTHONPATH` for `sysconfig` data). As such, the `python.sh` file records these details for you. -* ``wasmtime run -g`` generates debugging symbols for gdb and lldb. The - feature is currently broken, see - https://github.com/bytecodealliance/wasmtime/issues/4669 . -* The environment variable ``RUST_LOG=wasi_common`` enables debug and - trace logging. ## Detect WebAssembly builds @@ -402,15 +368,17 @@ import os, sys if sys.platform == "emscripten": # Python on Emscripten + ... if sys.platform == "wasi": # Python on WASI + ... if os.name == "posix": # WASM platforms identify as POSIX-like. # Windows does not provide os.uname(). machine = os.uname().machine if machine.startswith("wasm"): - # WebAssembly (wasm32, wasm64 in the future) + # WebAssembly (wasm32, wasm64 potentially in the future) ``` ```python diff --git a/Tools/wasm/mypy.ini b/Tools/wasm/mypy.ini index c62598f89eba69..4de0a30c260f5f 100644 --- a/Tools/wasm/mypy.ini +++ b/Tools/wasm/mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = Tools/wasm +files = Tools/wasm/wasm_*.py pretty = True show_traceback = True @@ -9,6 +9,3 @@ python_version = 3.8 # Be strict... strict = True enable_error_code = truthy-bool,ignore-without-code - -# except for incomplete defs, which are useful for module authors: -disallow_incomplete_defs = False diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py new file mode 100644 index 00000000000000..34c0e9375e24c8 --- /dev/null +++ b/Tools/wasm/wasi.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 + +import argparse +import contextlib +import functools +import os +try: + from os import process_cpu_count as cpu_count +except ImportError: + from os import cpu_count +import pathlib +import shutil +import subprocess +import sys +import sysconfig +import tempfile + + +CHECKOUT = pathlib.Path(__file__).parent.parent.parent +CROSS_BUILD_DIR = CHECKOUT / "cross-build" +BUILD_DIR = CROSS_BUILD_DIR / "build" +HOST_TRIPLE = "wasm32-wasi" +HOST_DIR = CROSS_BUILD_DIR / HOST_TRIPLE + + +def updated_env(updates={}): + """Create a new dict representing the environment to use. + + The changes made to the execution environment are printed out. + """ + env_defaults = {} + # https://reproducible-builds.org/docs/source-date-epoch/ + git_epoch_cmd = ["git", "log", "-1", "--pretty=%ct"] + try: + epoch = subprocess.check_output(git_epoch_cmd, encoding="utf-8").strip() + env_defaults["SOURCE_DATE_EPOCH"] = epoch + except subprocess.CalledProcessError: + pass # Might be building from a tarball. + # This layering lets SOURCE_DATE_EPOCH from os.environ takes precedence. + environment = env_defaults | os.environ | updates + + env_diff = {} + for key, value in environment.items(): + if os.environ.get(key) != value: + env_diff[key] = value + + print("🌎 Environment changes:") + for key in sorted(env_diff.keys()): + print(f" {key}={env_diff[key]}") + + return environment + + +def subdir(working_dir, *, clean_ok=False): + """Decorator to change to a working directory.""" + def decorator(func): + @functools.wraps(func) + def wrapper(context): + try: + tput_output = subprocess.check_output(["tput", "cols"], + encoding="utf-8") + terminal_width = int(tput_output.strip()) + except subprocess.CalledProcessError: + terminal_width = 80 + print("⎯" * terminal_width) + print("📁", working_dir) + if clean_ok and context.clean and working_dir.exists(): + print(f"🚮 Deleting directory (--clean)...") + shutil.rmtree(working_dir) + + working_dir.mkdir(parents=True, exist_ok=True) + + with contextlib.chdir(working_dir): + return func(context, working_dir) + + return wrapper + + return decorator + + +def call(command, *, quiet, **kwargs): + """Execute a command. + + If 'quiet' is true, then redirect stdout and stderr to a temporary file. + """ + print("❯", " ".join(map(str, command))) + if not quiet: + stdout = None + stderr = None + else: + stdout = tempfile.NamedTemporaryFile("w", encoding="utf-8", + delete=False, + prefix="cpython-wasi-", + suffix=".log") + stderr = subprocess.STDOUT + print(f"📝 Logging output to {stdout.name} (--quiet)...") + + subprocess.check_call(command, **kwargs, stdout=stdout, stderr=stderr) + + +def build_platform(): + """The name of the build/host platform.""" + # Can also be found via `config.guess`.` + return sysconfig.get_config_var("BUILD_GNU_TYPE") + + +def build_python_path(): + """The path to the build Python binary.""" + binary = BUILD_DIR / "python" + if not binary.is_file(): + binary = binary.with_suffix(".exe") + if not binary.is_file(): + raise FileNotFoundError("Unable to find `python(.exe)` in " + f"{BUILD_DIR}") + + return binary + + +@subdir(BUILD_DIR, clean_ok=True) +def configure_build_python(context, working_dir): + """Configure the build/host Python.""" + local_setup = CHECKOUT / "Modules" / "Setup.local" + if local_setup.exists(): + print(f"👍 {local_setup} exists ...") + else: + print(f"📝 Touching {local_setup} ...") + local_setup.touch() + + configure = [os.path.relpath(CHECKOUT / 'configure', working_dir)] + if context.args: + configure.extend(context.args) + + call(configure, quiet=context.quiet) + + +@subdir(BUILD_DIR) +def make_build_python(context, working_dir): + """Make/build the build Python.""" + call(["make", "--jobs", str(cpu_count()), "all"], + quiet=context.quiet) + + binary = build_python_path() + cmd = [binary, "-c", + "import sys; " + "print(f'{sys.version_info.major}.{sys.version_info.minor}')"] + version = subprocess.check_output(cmd, encoding="utf-8").strip() + + print(f"🎉 {binary} {version}") + + +def find_wasi_sdk(): + """Find the path to wasi-sdk.""" + if wasi_sdk_path := os.environ.get("WASI_SDK_PATH"): + return pathlib.Path(wasi_sdk_path) + elif (default_path := pathlib.Path("/opt/wasi-sdk")).exists(): + return default_path + + +def wasi_sdk_env(context): + """Calculate environment variables for building with wasi-sdk.""" + wasi_sdk_path = context.wasi_sdk_path + sysroot = wasi_sdk_path / "share" / "wasi-sysroot" + env = {"CC": "clang", "CPP": "clang-cpp", "CXX": "clang++", + "LDSHARED": "wasm-ld", "AR": "llvm-ar", "RANLIB": "ranlib"} + + for env_var, binary_name in list(env.items()): + env[env_var] = os.fsdecode(wasi_sdk_path / "bin" / binary_name) + + if wasi_sdk_path != pathlib.Path("/opt/wasi-sdk"): + for compiler in ["CC", "CPP", "CXX"]: + env[compiler] += f" --sysroot={sysroot}" + + env["PKG_CONFIG_PATH"] = "" + env["PKG_CONFIG_LIBDIR"] = os.pathsep.join( + map(os.fsdecode, + [sysroot / "lib" / "pkgconfig", + sysroot / "share" / "pkgconfig"])) + env["PKG_CONFIG_SYSROOT_DIR"] = os.fsdecode(sysroot) + + env["WASI_SDK_PATH"] = os.fsdecode(wasi_sdk_path) + env["WASI_SYSROOT"] = os.fsdecode(sysroot) + + env["PATH"] = os.pathsep.join([os.fsdecode(wasi_sdk_path / "bin"), + os.environ["PATH"]]) + + return env + + +@subdir(HOST_DIR, clean_ok=True) +def configure_wasi_python(context, working_dir): + """Configure the WASI/host build.""" + if not context.wasi_sdk_path or not context.wasi_sdk_path.exists(): + raise ValueError("WASI-SDK not found; " + "download from " + "https://github.com/WebAssembly/wasi-sdk and/or " + "specify via $WASI_SDK_PATH or --wasi-sdk") + + config_site = os.fsdecode(CHECKOUT / "Tools" / "wasm" / "config.site-wasm32-wasi") + + wasi_build_dir = working_dir.relative_to(CHECKOUT) + + python_build_dir = BUILD_DIR / "build" + lib_dirs = list(python_build_dir.glob("lib.*")) + assert len(lib_dirs) == 1, f"Expected a single lib.* directory in {python_build_dir}" + lib_dir = os.fsdecode(lib_dirs[0]) + pydebug = lib_dir.endswith("-pydebug") + python_version = lib_dir.removesuffix("-pydebug").rpartition("-")[-1] + sysconfig_data = f"{wasi_build_dir}/build/lib.wasi-wasm32-{python_version}" + if pydebug: + sysconfig_data += "-pydebug" + + # Use PYTHONPATH to include sysconfig data which must be anchored to the + # WASI guest's `/` directory. + host_runner = context.host_runner.format(GUEST_DIR="/", + HOST_DIR=CHECKOUT, + ENV_VAR_NAME="PYTHONPATH", + ENV_VAR_VALUE=f"/{sysconfig_data}", + PYTHON_WASM=working_dir / "python.wasm") + env_additions = {"CONFIG_SITE": config_site, "HOSTRUNNER": host_runner} + build_python = os.fsdecode(build_python_path()) + # The path to `configure` MUST be relative, else `python.wasm` is unable + # to find the stdlib due to Python not recognizing that it's being + # executed from within a checkout. + configure = [os.path.relpath(CHECKOUT / 'configure', working_dir), + f"--host={HOST_TRIPLE}", + f"--build={build_platform()}", + f"--with-build-python={build_python}"] + if pydebug: + configure.append("--with-pydebug") + if context.args: + configure.extend(context.args) + call(configure, + env=updated_env(env_additions | wasi_sdk_env(context)), + quiet=context.quiet) + + exec_script = working_dir / "python.sh" + with exec_script.open("w", encoding="utf-8") as file: + file.write(f'#!/bin/sh\nexec {host_runner} "$@"\n') + exec_script.chmod(0o755) + print(f"🏃‍♀️ Created {exec_script} ... ") + sys.stdout.flush() + + +@subdir(HOST_DIR) +def make_wasi_python(context, working_dir): + """Run `make` for the WASI/host build.""" + call(["make", "--jobs", str(cpu_count()), "all"], + env=updated_env(), + quiet=context.quiet) + + exec_script = working_dir / "python.sh" + subprocess.check_call([exec_script, "--version"]) + + +def build_all(context): + """Build everything.""" + steps = [configure_build_python, make_build_python, configure_wasi_python, + make_wasi_python] + for step in steps: + step(context) + + +def main(): + default_host_runner = (f"{shutil.which('wasmtime')} run " + # Make sure the stack size will work for a pydebug + # build. + # The 8388608 value comes from `ulimit -s` under Linux + # which equates to 8291 KiB. + "--wasm max-wasm-stack=8388608 " + # Enable thread support. + "--wasm threads=y --wasi threads=y " + # Map the checkout to / to load the stdlib from /Lib. + "--dir {HOST_DIR}::{GUEST_DIR} " + # Set PYTHONPATH to the sysconfig data. + "--env {ENV_VAR_NAME}={ENV_VAR_VALUE} " + # Path to the WASM binary. + "{PYTHON_WASM}") + + parser = argparse.ArgumentParser() + subcommands = parser.add_subparsers(dest="subcommand") + build = subcommands.add_parser("build", help="Build everything") + configure_build = subcommands.add_parser("configure-build-python", + help="Run `configure` for the " + "build Python") + make_build = subcommands.add_parser("make-build-python", + help="Run `make` for the build Python") + configure_host = subcommands.add_parser("configure-host", + help="Run `configure` for the " + "host/WASI (pydebug builds " + "are inferred from the build " + "Python)") + make_host = subcommands.add_parser("make-host", + help="Run `make` for the host/WASI") + for subcommand in build, configure_build, make_build, configure_host, make_host: + subcommand.add_argument("--quiet", action="store_true", default=False, + dest="quiet", + help="Redirect output from subprocesses to a log file") + for subcommand in build, configure_build, configure_host: + subcommand.add_argument("--clean", action="store_true", default=False, + dest="clean", + help="Delete any relevant directories before building") + for subcommand in build, configure_build, configure_host: + subcommand.add_argument("args", nargs="*", + help="Extra arguments to pass to `configure`") + for subcommand in build, configure_host: + subcommand.add_argument("--wasi-sdk", type=pathlib.Path, + dest="wasi_sdk_path", + default=find_wasi_sdk(), + help="Path to wasi-sdk; defaults to " + "$WASI_SDK_PATH or /opt/wasi-sdk") + subcommand.add_argument("--host-runner", action="store", + default=default_host_runner, dest="host_runner", + help="Command template for running the WebAssembly " + "code (default meant for wasmtime 14 or newer: " + f"`{default_host_runner}`)") + + context = parser.parse_args() + + dispatch = {"configure-build-python": configure_build_python, + "make-build-python": make_build_python, + "configure-host": configure_wasi_python, + "make-host": make_wasi_python, + "build": build_all} + dispatch[context.subcommand](context) + + +if __name__ == "__main__": + main() From 81261fa67ff82b03c255733b0d1abbbb8a228187 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 30 Nov 2023 02:08:44 -0500 Subject: [PATCH 037/442] IDLE: fix config_key htest (#112545) Change 'Dialog' to 'Window' in two places to match the name of the config_key class being tested. --- Lib/idlelib/config_key.py | 2 +- Lib/idlelib/idle_test/htest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/config_key.py b/Lib/idlelib/config_key.py index bb07231cd590b6..e5f67e8d4069ee 100644 --- a/Lib/idlelib/config_key.py +++ b/Lib/idlelib/config_key.py @@ -351,4 +351,4 @@ def cancel(self, event=None): main('idlelib.idle_test.test_config_key', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(GetKeysDialog) + run(GetKeysWindow) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index d297f8aa0094ee..595e51d3699f6e 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -152,7 +152,7 @@ def _wrapper(parent): # htest # "Best to close editor first." } -GetKeysDialog_spec = { +GetKeysWindow_spec = { 'file': 'config_key', 'kwds': {'title': 'Test keybindings', 'action': 'find-again', From 0785c685599aaa052f85d6163872bdecb9c66486 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Thu, 30 Nov 2023 13:12:49 +0300 Subject: [PATCH 038/442] gh-111972: Make Unicode name C APIcapsule initialization thread-safe (#112249) --- Include/internal/pycore_ucnhash.h | 2 ++ Objects/unicodeobject.c | 32 ++++++++++++++++++++----------- Python/codecs.c | 12 +++--------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Include/internal/pycore_ucnhash.h b/Include/internal/pycore_ucnhash.h index 187dd68e7347ff..1561dfbb3150d3 100644 --- a/Include/internal/pycore_ucnhash.h +++ b/Include/internal/pycore_ucnhash.h @@ -28,6 +28,8 @@ typedef struct { } _PyUnicode_Name_CAPI; +extern _PyUnicode_Name_CAPI* _PyUnicode_GetNameCAPI(void); + #ifdef __cplusplus } #endif diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index cffc06297a9aee..10022e23c04abf 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -5869,6 +5869,23 @@ PyUnicode_AsUTF16String(PyObject *unicode) return _PyUnicode_EncodeUTF16(unicode, NULL, 0); } +_PyUnicode_Name_CAPI * +_PyUnicode_GetNameCAPI(void) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyUnicode_Name_CAPI *ucnhash_capi; + + ucnhash_capi = _Py_atomic_load_ptr(&interp->unicode.ucnhash_capi); + if (ucnhash_capi == NULL) { + ucnhash_capi = (_PyUnicode_Name_CAPI *)PyCapsule_Import( + PyUnicodeData_CAPSULE_NAME, 1); + + // It's fine if we overwite the value here. It's always the same value. + _Py_atomic_store_ptr(&interp->unicode.ucnhash_capi, ucnhash_capi); + } + return ucnhash_capi; +} + /* --- Unicode Escape Codec ----------------------------------------------- */ PyObject * @@ -5884,7 +5901,6 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s, PyObject *errorHandler = NULL; PyObject *exc = NULL; _PyUnicode_Name_CAPI *ucnhash_capi; - PyInterpreterState *interp = _PyInterpreterState_GET(); // so we can remember if we've seen an invalid escape char or not *first_invalid_escape = NULL; @@ -6032,19 +6048,13 @@ _PyUnicode_DecodeUnicodeEscapeInternal(const char *s, /* \N{name} */ case 'N': - ucnhash_capi = interp->unicode.ucnhash_capi; + ucnhash_capi = _PyUnicode_GetNameCAPI(); if (ucnhash_capi == NULL) { - /* load the unicode data module */ - ucnhash_capi = (_PyUnicode_Name_CAPI *)PyCapsule_Import( - PyUnicodeData_CAPSULE_NAME, 1); - if (ucnhash_capi == NULL) { - PyErr_SetString( + PyErr_SetString( PyExc_UnicodeError, "\\N escapes not supported (can't load unicodedata module)" - ); - goto onError; - } - interp->unicode.ucnhash_capi = ucnhash_capi; + ); + goto onError; } message = "malformed \\N character escape"; diff --git a/Python/codecs.c b/Python/codecs.c index 545bf82e00dca1..d8fe7b22063a80 100644 --- a/Python/codecs.c +++ b/Python/codecs.c @@ -931,8 +931,6 @@ PyObject *PyCodec_BackslashReplaceErrors(PyObject *exc) return Py_BuildValue("(Nn)", res, end); } -static _PyUnicode_Name_CAPI *ucnhash_capi = NULL; - PyObject *PyCodec_NameReplaceErrors(PyObject *exc) { if (PyObject_TypeCheck(exc, (PyTypeObject *)PyExc_UnicodeEncodeError)) { @@ -953,13 +951,9 @@ PyObject *PyCodec_NameReplaceErrors(PyObject *exc) return NULL; if (!(object = PyUnicodeEncodeError_GetObject(exc))) return NULL; - if (!ucnhash_capi) { - /* load the unicode data module */ - ucnhash_capi = (_PyUnicode_Name_CAPI *)PyCapsule_Import( - PyUnicodeData_CAPSULE_NAME, 1); - if (!ucnhash_capi) { - return NULL; - } + _PyUnicode_Name_CAPI *ucnhash_capi = _PyUnicode_GetNameCAPI(); + if (ucnhash_capi == NULL) { + return NULL; } for (i = start, ressize = 0; i < end; ++i) { /* object is guaranteed to be "ready" */ From 7eeea13403882af63a71226433c9a13b80c22564 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 30 Nov 2023 10:40:53 +0000 Subject: [PATCH 039/442] gh-112205: Support @getter annotation from AC (gh-112396) --- Lib/test/clinic.test.c | 21 ++++++++ Lib/test/test_clinic.py | 26 ++++++---- Modules/_io/bufferedio.c | 81 +++++++++++++------------------ Modules/_io/clinic/bufferedio.c.h | 56 ++++++++++++++++++++- Tools/clinic/clinic.py | 53 ++++++++++++++++---- 5 files changed, 169 insertions(+), 68 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 81f88c4d1535ce..ee4a4228fd28be 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4951,6 +4951,27 @@ static PyObject * Test_meth_coexist_impl(TestObj *self) /*[clinic end generated code: output=808a293d0cd27439 input=2a1d75b5e6fec6dd]*/ +/*[clinic input] +@getter +Test.property +[clinic start generated code]*/ + +#define TEST_PROPERTY_GETTERDEF \ + {"property", (getter)Test_property_get, NULL, NULL}, + +static PyObject * +Test_property_get_impl(TestObj *self); + +static PyObject * +Test_property_get(TestObj *self, void *Py_UNUSED(context)) +{ + return Test_property_get_impl(self); +} + +static PyObject * +Test_property_get_impl(TestObj *self) +/*[clinic end generated code: output=892b6fb351ff85fd input=2d92b3449fbc7d2b]*/ + /*[clinic input] output push diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index da957fcebaa296..f53e9481083106 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -638,7 +638,7 @@ class C "void *" "" C.__init__ = C.meth [clinic start generated code]*/ """ - err = "'__init__' must be a normal method, not a class or static method" + err = "'__init__' must be a normal method; got 'FunctionKind.CLASS_METHOD'!" self.expect_failure(block, err, lineno=8) def test_validate_cloned_new(self): @@ -2180,14 +2180,22 @@ class Foo "" "" self.expect_failure(block, err, lineno=2) def test_init_must_be_a_normal_method(self): - err = "'__init__' must be a normal method, not a class or static method!" - block = """ - module foo - class Foo "" "" - @classmethod - Foo.__init__ - """ - self.expect_failure(block, err, lineno=3) + err_template = "'__init__' must be a normal method; got 'FunctionKind.{}'!" + annotations = { + "@classmethod": "CLASS_METHOD", + "@staticmethod": "STATIC_METHOD", + "@getter": "GETTER", + } + for annotation, invalid_kind in annotations.items(): + with self.subTest(annotation=annotation, invalid_kind=invalid_kind): + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.__init__ + """ + expected_error = err_template.format(invalid_kind) + self.expect_failure(block, expected_error, lineno=3) def test_duplicate_coexist(self): err = "Called @coexist twice" diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 4f3786676d131f..679626863c385c 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -10,7 +10,6 @@ #include "Python.h" #include "pycore_bytesobject.h" // _PyBytes_Join() #include "pycore_call.h" // _PyObject_CallNoArgs() -#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_pyerrors.h" // _Py_FatalErrorFormat() #include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing() @@ -518,25 +517,20 @@ buffered_closed(buffered *self) return closed; } +/*[clinic input] +@critical_section +@getter +_io._Buffered.closed +[clinic start generated code]*/ + static PyObject * -buffered_closed_get_impl(buffered *self, void *context) +_io__Buffered_closed_get_impl(buffered *self) +/*[clinic end generated code: output=f08ce57290703a1a input=18eddefdfe4a3d2f]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(closed)); } -static PyObject * -buffered_closed_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_closed_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} - /*[clinic input] @critical_section _io._Buffered.close @@ -662,44 +656,35 @@ _io__Buffered_writable_impl(buffered *self) return PyObject_CallMethodNoArgs(self->raw, &_Py_ID(writable)); } + +/*[clinic input] +@critical_section +@getter +_io._Buffered.name +[clinic start generated code]*/ + static PyObject * -buffered_name_get_impl(buffered *self, void *context) +_io__Buffered_name_get_impl(buffered *self) +/*[clinic end generated code: output=d2adf384051d3d10 input=6b84a0e6126f545e]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(name)); } -static PyObject * -buffered_name_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_name_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} +/*[clinic input] +@critical_section +@getter +_io._Buffered.mode +[clinic start generated code]*/ static PyObject * -buffered_mode_get_impl(buffered *self, void *context) +_io__Buffered_mode_get_impl(buffered *self) +/*[clinic end generated code: output=0feb205748892fa4 input=0762d5e28542fd8c]*/ { CHECK_INITIALIZED(self) return PyObject_GetAttr(self->raw, &_Py_ID(mode)); } -static PyObject * -buffered_mode_get(buffered *self, void *context) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION(self); - return_value = buffered_mode_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - - return return_value; -} - /* Lower-level APIs */ /*[clinic input] @@ -2541,9 +2526,9 @@ static PyMemberDef bufferedreader_members[] = { }; static PyGetSetDef bufferedreader_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; @@ -2601,9 +2586,9 @@ static PyMemberDef bufferedwriter_members[] = { }; static PyGetSetDef bufferedwriter_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; @@ -2719,9 +2704,9 @@ static PyMemberDef bufferedrandom_members[] = { }; static PyGetSetDef bufferedrandom_getset[] = { - {"closed", (getter)buffered_closed_get, NULL, NULL}, - {"name", (getter)buffered_name_get, NULL, NULL}, - {"mode", (getter)buffered_mode_get, NULL, NULL}, + _IO__BUFFERED_CLOSED_GETTERDEF + _IO__BUFFERED_NAME_GETTERDEF + _IO__BUFFERED_MODE_GETTERDEF {NULL} }; diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index 20833a10139681..69d28ad00c2ad5 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -327,6 +327,24 @@ _io__Buffered_simple_flush(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#define _IO__BUFFERED_CLOSED_GETTERDEF \ + {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, + +static PyObject * +_io__Buffered_closed_get_impl(buffered *self); + +static PyObject * +_io__Buffered_closed_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_closed_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_io__Buffered_close__doc__, "close($self, /)\n" "--\n" @@ -442,6 +460,42 @@ _io__Buffered_writable(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#define _IO__BUFFERED_NAME_GETTERDEF \ + {"name", (getter)_io__Buffered_name_get, NULL, NULL}, + +static PyObject * +_io__Buffered_name_get_impl(buffered *self); + +static PyObject * +_io__Buffered_name_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_name_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#define _IO__BUFFERED_MODE_GETTERDEF \ + {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, + +static PyObject * +_io__Buffered_mode_get_impl(buffered *self); + +static PyObject * +_io__Buffered_mode_get(buffered *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io__Buffered_mode_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_io__Buffered_fileno__doc__, "fileno($self, /)\n" "--\n" @@ -1164,4 +1218,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=e8ad39a45531d7f2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index c0830864175adf..54962c9e1c92f9 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -846,6 +846,10 @@ class CLanguage(Language): static PyObject * {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored)) """) + PARSER_PROTOTYPE_GETTER: Final[str] = normalize_snippet(""" + static PyObject * + {c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) + """) METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" static PyObject * {c_basename}({impl_parameters}) @@ -865,6 +869,10 @@ class CLanguage(Language): #define {methoddef_name} \ {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, """) + GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #define {getter_name} \ + {{"{name}", (getter){c_basename}, NULL, NULL}}, + """) METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" #ifndef {methoddef_name} #define {methoddef_name} @@ -1161,6 +1169,9 @@ def output_templates( methoddef_define = self.METHODDEF_PROTOTYPE_DEFINE if new_or_init and not f.docstring: docstring_prototype = docstring_definition = '' + elif f.kind is GETTER: + methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE + docstring_prototype = docstring_definition = '' else: docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR @@ -1217,7 +1228,11 @@ def parser_body( parsearg: str | None if not parameters: parser_code: list[str] | None - if not requires_defining_class: + if f.kind is GETTER: + flags = "" # This should end up unused + parser_prototype = self.PARSER_PROTOTYPE_GETTER + parser_code = [] + elif not requires_defining_class: # no parameters, METH_NOARGS flags = "METH_NOARGS" parser_prototype = self.PARSER_PROTOTYPE_NOARGS @@ -1670,6 +1685,8 @@ def parser_body( methoddef_cast_end = "" if flags in ('METH_NOARGS', 'METH_O', 'METH_VARARGS'): methoddef_cast = "(PyCFunction)" + elif f.kind is GETTER: + methoddef_cast = "" # This should end up unused elif limited_capi: methoddef_cast = "(PyCFunction)(void(*)(void))" else: @@ -1927,8 +1944,12 @@ def render_function( full_name = f.full_name template_dict = {'full_name': full_name} template_dict['name'] = f.displayname - template_dict['c_basename'] = f.c_basename - template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" + if f.kind is GETTER: + template_dict['getter_name'] = f.c_basename.upper() + "_GETTERDEF" + template_dict['c_basename'] = f.c_basename + "_get" + else: + template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" + template_dict['c_basename'] = f.c_basename template_dict['docstring'] = self.docstring_for_c_string(f) @@ -2932,6 +2953,7 @@ class FunctionKind(enum.Enum): CLASS_METHOD = enum.auto() METHOD_INIT = enum.auto() METHOD_NEW = enum.auto() + GETTER = enum.auto() @functools.cached_property def new_or_init(self) -> bool: @@ -2947,6 +2969,7 @@ def __repr__(self) -> str: CLASS_METHOD: Final = FunctionKind.CLASS_METHOD METHOD_INIT: Final = FunctionKind.METHOD_INIT METHOD_NEW: Final = FunctionKind.METHOD_NEW +GETTER: Final = FunctionKind.GETTER ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] @@ -3033,7 +3056,8 @@ def methoddef_flags(self) -> str | None: case FunctionKind.STATIC_METHOD: flags.append('METH_STATIC') case _ as kind: - assert kind is FunctionKind.CALLABLE, f"unknown kind: {kind!r}" + acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER} + assert kind in acceptable_kinds, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') return '|'.join(flags) @@ -4678,7 +4702,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st def correct_name_for_self( f: Function ) -> tuple[str, str]: - if f.kind in (CALLABLE, METHOD_INIT): + if f.kind in {CALLABLE, METHOD_INIT, GETTER}: if f.cls: return "PyObject *", "self" return "PyObject *", "module" @@ -5310,6 +5334,9 @@ def at_critical_section(self, *args: str) -> None: self.target_critical_section.extend(args) self.critical_section = True + def at_getter(self) -> None: + self.kind = GETTER + def at_staticmethod(self) -> None: if self.kind is not CALLABLE: fail("Can't set @staticmethod, function is not a normal callable") @@ -5419,14 +5446,20 @@ def update_function_kind(self, fullname: str) -> None: _, cls = self.clinic._module_and_class(fields) if name in unsupported_special_methods: fail(f"{name!r} is a special method and cannot be converted to Argument Clinic!") + if name == '__new__': - if (self.kind is not CLASS_METHOD) or (not cls): + if (self.kind is CLASS_METHOD) and cls: + self.kind = METHOD_NEW + else: fail("'__new__' must be a class method!") - self.kind = METHOD_NEW elif name == '__init__': - if (self.kind is not CALLABLE) or (not cls): - fail("'__init__' must be a normal method, not a class or static method!") - self.kind = METHOD_INIT + if (self.kind is CALLABLE) and cls: + self.kind = METHOD_INIT + else: + fail( + "'__init__' must be a normal method; " + f"got '{self.kind}'!" + ) def state_modulename_name(self, line: str) -> None: # looking for declaration, which establishes the leftmost column From 07ebd46f9e55ed2f18c5ea2a79ec5054bc26b915 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:03:30 +0000 Subject: [PATCH 040/442] gh-112519: Make it possible to specify instruction flags for pseudo instructions in bytecodes.c (#112520) --- Include/internal/pycore_opcode_metadata.h | 8 ++--- Lib/test/test_generated_cases.py | 38 +++++++++++++++++++++++ Python/bytecodes.c | 6 ++-- Python/flowgraph.c | 2 +- Tools/cases_generator/analysis.py | 9 ++++-- Tools/cases_generator/parsing.py | 25 +++++++++++++-- 6 files changed, 76 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 4e45725d393479..4ae15e71e8d318 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1661,7 +1661,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [IMPORT_FROM] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG }, [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, @@ -1703,9 +1703,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_FINALLY] = { true, 0, 0 }, - [SETUP_CLEANUP] = { true, 0, 0 }, - [SETUP_WITH] = { true, 0, 0 }, + [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, + [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, + [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, [POP_BLOCK] = { true, 0, 0 }, [PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 }, [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 98a8fff4268746..de96a8764594ba 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -466,6 +466,44 @@ def test_macro_instruction(self): """ self.run_cases_test(input, output) + def test_pseudo_instruction_no_flags(self): + input = """ + pseudo(OP) = { + OP1, + }; + + inst(OP1, (--)) { + } + """ + output = """ + TARGET(OP1) { + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(OP1); + DISPATCH(); + } + """ + self.run_cases_test(input, output) + + def test_pseudo_instruction_with_flags(self): + input = """ + pseudo(OP, (HAS_ARG, HAS_JUMP)) = { + OP1, + }; + + inst(OP1, (--)) { + } + """ + output = """ + TARGET(OP1) { + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(OP1); + DISPATCH(); + } + """ + self.run_cases_test(input, output) + def test_array_input(self): input = """ inst(OP, (below, values[oparg*2], above --)) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2e5f6c8d0d10e2..2075c195df3d38 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2831,15 +2831,15 @@ dummy_func( ERROR_IF(res == NULL, error); } - pseudo(SETUP_FINALLY) = { + pseudo(SETUP_FINALLY, (HAS_ARG)) = { NOP, }; - pseudo(SETUP_CLEANUP) = { + pseudo(SETUP_CLEANUP, (HAS_ARG)) = { NOP, }; - pseudo(SETUP_WITH) = { + pseudo(SETUP_WITH, (HAS_ARG)) = { NOP, }; diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 87401e14f97f02..fe632082d5a66c 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -97,6 +97,7 @@ static const jump_target_label NO_LABEL = {-1}; static inline int is_block_push(cfg_instr *i) { + assert(OPCODE_HAS_ARG(i->i_opcode) || !IS_BLOCK_PUSH_OPCODE(i->i_opcode)); return IS_BLOCK_PUSH_OPCODE(i->i_opcode); } @@ -2239,7 +2240,6 @@ convert_pseudo_ops(basicblock *entryblock) 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) { - assert(SAME_OPCODE_METADATA(instr->i_opcode, NOP)); INSTR_SET_OP0(instr, NOP); } else if (instr->i_opcode == LOAD_CLOSURE) { diff --git a/Tools/cases_generator/analysis.py b/Tools/cases_generator/analysis.py index 603b15596f16de..26d92c13cd82ab 100644 --- a/Tools/cases_generator/analysis.py +++ b/Tools/cases_generator/analysis.py @@ -390,9 +390,14 @@ def analyze_pseudo(self, pseudo: parsing.Pseudo) -> PseudoInstruction: else: targets.append(self.macro_instrs[target_name]) assert targets - ignored_flags = {"HAS_EVAL_BREAK_FLAG", "HAS_DEOPT_FLAG", "HAS_ERROR_FLAG", "HAS_ESCAPES_FLAG"} + ignored_flags = {"HAS_EVAL_BREAK_FLAG", "HAS_DEOPT_FLAG", "HAS_ERROR_FLAG", + "HAS_ESCAPES_FLAG"} assert len({t.instr_flags.bitmap(ignore=ignored_flags) for t in targets}) == 1 - return PseudoInstruction(pseudo.name, targets, targets[0].instr_flags) + + flags = InstructionFlags(**{f"{f}_FLAG" : True for f in pseudo.flags}) + for t in targets: + flags.add(t.instr_flags) + return PseudoInstruction(pseudo.name, targets, flags) def analyze_instruction( self, instr: Instruction, offset: int diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index d36bd52b022ea9..7800adf16794bb 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -138,7 +138,8 @@ class Family(Node): @dataclass class Pseudo(Node): name: str - targets: list[str] # opcodes this can be replaced by + flags: list[str] # instr flags to set on the pseudo instruction + targets: list[str] # opcodes this can be replaced by class Parser(PLexer): @@ -374,19 +375,39 @@ def family_def(self) -> Family | None: ) return None + def flags(self) -> list[str]: + here = self.getpos() + if self.expect(lx.LPAREN): + if tkn := self.expect(lx.IDENTIFIER): + flags = [tkn.text] + while self.expect(lx.COMMA): + if tkn := self.expect(lx.IDENTIFIER): + flags.append(tkn.text) + else: + break + if not self.expect(lx.RPAREN): + raise self.make_syntax_error("Expected comma or right paren") + return flags + self.setpos(here) + return [] + @contextual def pseudo_def(self) -> Pseudo | None: if (tkn := self.expect(lx.IDENTIFIER)) and tkn.text == "pseudo": size = None if self.expect(lx.LPAREN): if tkn := self.expect(lx.IDENTIFIER): + if self.expect(lx.COMMA): + flags = self.flags() + else: + flags = [] if self.expect(lx.RPAREN): if self.expect(lx.EQUALS): if not self.expect(lx.LBRACE): raise self.make_syntax_error("Expected {") if members := self.members(): if self.expect(lx.RBRACE) and self.expect(lx.SEMI): - return Pseudo(tkn.text, members) + return Pseudo(tkn.text, flags, members) return None def members(self) -> list[str] | None: From 1ff212debdc094c28928011cff9f4eea8de34d44 Mon Sep 17 00:00:00 2001 From: Matt Prodani Date: Thu, 30 Nov 2023 07:53:19 -0500 Subject: [PATCH 041/442] gh-111699: Move smtpd note to dedicated section in What's New Python 3.12 doc (GH-112544) Relocate smtpd deprecation notice to it's own section rather than under 'locale' in docs for What's New in Python 3.12 doc --- Doc/whatsnew/3.12.rst | 5 ++++- Misc/ACKS | 1 + .../2023-11-30-02-33-59.gh-issue-111699._O5G_y.rst | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Documentation/2023-11-30-02-33-59.gh-issue-111699._O5G_y.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index a4b3a6d12970b4..96893527cc91ed 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1640,7 +1640,10 @@ locale use :func:`locale.format_string` instead. (Contributed by Victor Stinner in :gh:`94226`.) -* ``smtpd``: The module has been removed according to the schedule in :pep:`594`, +smtpd +----- + +* The ``smtpd`` module has been removed according to the schedule in :pep:`594`, having been deprecated in Python 3.4.7 and 3.5.4. Use aiosmtpd_ PyPI module or any other :mod:`asyncio`-based server instead. diff --git a/Misc/ACKS b/Misc/ACKS index 5fe3a177a26292..1c67d96ed3a528 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1459,6 +1459,7 @@ Paul Prescod Donovan Preston Eric Price Paul Price +Matt Prodani Iuliia Proskurnia Dorian Pula Jyrki Pulliainen diff --git a/Misc/NEWS.d/next/Documentation/2023-11-30-02-33-59.gh-issue-111699._O5G_y.rst b/Misc/NEWS.d/next/Documentation/2023-11-30-02-33-59.gh-issue-111699._O5G_y.rst new file mode 100644 index 00000000000000..2d31345e6c2044 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2023-11-30-02-33-59.gh-issue-111699._O5G_y.rst @@ -0,0 +1 @@ +Relocate ``smtpd`` deprecation notice to its own section rather than under ``locale`` in What's New in Python 3.12 document From 2223899adce858a09ebeaaf82111e6cda9b42415 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 30 Nov 2023 17:22:04 +0200 Subject: [PATCH 042/442] gh-104231: Add more tests for str(), repr(), ascii(), and bytes() (GH-112551) --- Lib/test/test_bytes.py | 81 ++++++++++++++++++++++++++----------- Lib/test/test_str.py | 90 +++++++++++++++++++++++++++++------------- 2 files changed, 121 insertions(+), 50 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index afd506f07520d8..a3804a945f2e3a 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -46,6 +46,10 @@ def __index__(self): class BaseBytesTest: + def assertTypedEqual(self, actual, expected): + self.assertIs(type(actual), type(expected)) + self.assertEqual(actual, expected) + def test_basics(self): b = self.type2test() self.assertEqual(type(b), self.type2test) @@ -1023,36 +1027,63 @@ def test_buffer_is_readonly(self): self.assertRaises(TypeError, f.readinto, b"") def test_custom(self): - class A: - def __bytes__(self): - return b'abc' - self.assertEqual(bytes(A()), b'abc') - class A: pass - self.assertRaises(TypeError, bytes, A()) - class A: - def __bytes__(self): - return None - self.assertRaises(TypeError, bytes, A()) - class A: + self.assertEqual(bytes(BytesSubclass(b'abc')), b'abc') + self.assertEqual(BytesSubclass(OtherBytesSubclass(b'abc')), + BytesSubclass(b'abc')) + self.assertEqual(bytes(WithBytes(b'abc')), b'abc') + self.assertEqual(BytesSubclass(WithBytes(b'abc')), BytesSubclass(b'abc')) + + class NoBytes: pass + self.assertRaises(TypeError, bytes, NoBytes()) + self.assertRaises(TypeError, bytes, WithBytes('abc')) + self.assertRaises(TypeError, bytes, WithBytes(None)) + class IndexWithBytes: def __bytes__(self): return b'a' def __index__(self): return 42 - self.assertEqual(bytes(A()), b'a') + self.assertEqual(bytes(IndexWithBytes()), b'a') # Issue #25766 - class A(str): + class StrWithBytes(str): + def __new__(cls, value): + self = str.__new__(cls, '\u20ac') + self.value = value + return self def __bytes__(self): - return b'abc' - self.assertEqual(bytes(A('\u20ac')), b'abc') - self.assertEqual(bytes(A('\u20ac'), 'iso8859-15'), b'\xa4') + return self.value + self.assertEqual(bytes(StrWithBytes(b'abc')), b'abc') + self.assertEqual(bytes(StrWithBytes(b'abc'), 'iso8859-15'), b'\xa4') + self.assertEqual(bytes(StrWithBytes(BytesSubclass(b'abc'))), b'abc') + self.assertEqual(BytesSubclass(StrWithBytes(b'abc')), BytesSubclass(b'abc')) + self.assertEqual(BytesSubclass(StrWithBytes(b'abc'), 'iso8859-15'), + BytesSubclass(b'\xa4')) + self.assertEqual(BytesSubclass(StrWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertEqual(BytesSubclass(StrWithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) # Issue #24731 - class A: + self.assertTypedEqual(bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(WithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(WithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + + class BytesWithBytes(bytes): + def __new__(cls, value): + self = bytes.__new__(cls, b'\xa4') + self.value = value + return self def __bytes__(self): - return OtherBytesSubclass(b'abc') - self.assertEqual(bytes(A()), b'abc') - self.assertIs(type(bytes(A())), OtherBytesSubclass) - self.assertEqual(BytesSubclass(A()), b'abc') - self.assertIs(type(BytesSubclass(A())), BytesSubclass) + return self.value + self.assertTypedEqual(bytes(BytesWithBytes(b'abc')), b'abc') + self.assertTypedEqual(BytesSubclass(BytesWithBytes(b'abc')), + BytesSubclass(b'abc')) + self.assertTypedEqual(bytes(BytesWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(BytesWithBytes(BytesSubclass(b'abc'))), + BytesSubclass(b'abc')) + self.assertTypedEqual(BytesSubclass(BytesWithBytes(OtherBytesSubclass(b'abc'))), + BytesSubclass(b'abc')) # Test PyBytes_FromFormat() def test_from_format(self): @@ -2069,6 +2100,12 @@ class BytesSubclass(bytes): class OtherBytesSubclass(bytes): pass +class WithBytes: + def __init__(self, value): + self.value = value + def __bytes__(self): + return self.value + class ByteArraySubclassTest(SubclassTest, unittest.TestCase): basetype = bytearray type2test = ByteArraySubclass diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 814ef111c5bec8..b4927113db44e3 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -55,6 +55,21 @@ def duplicate_string(text): class StrSubclass(str): pass +class OtherStrSubclass(str): + pass + +class WithStr: + def __init__(self, value): + self.value = value + def __str__(self): + return self.value + +class WithRepr: + def __init__(self, value): + self.value = value + def __repr__(self): + return self.value + class StrTest(string_tests.StringLikeTest, string_tests.MixinStrUnicodeTest, unittest.TestCase): @@ -83,6 +98,10 @@ def __repr__(self): self.assertEqual(realresult, result) self.assertTrue(object is not realresult) + def assertTypedEqual(self, actual, expected): + self.assertIs(type(actual), type(expected)) + self.assertEqual(actual, expected) + def test_literals(self): self.assertEqual('\xff', '\u00ff') self.assertEqual('\uffff', '\U0000ffff') @@ -127,10 +146,13 @@ def test_ascii(self): self.assertEqual(ascii("\U00010000" * 39 + "\uffff" * 4096), ascii("\U00010000" * 39 + "\uffff" * 4096)) - class WrongRepr: - def __repr__(self): - return b'byte-repr' - self.assertRaises(TypeError, ascii, WrongRepr()) + self.assertTypedEqual(ascii('\U0001f40d'), r"'\U0001f40d'") + self.assertTypedEqual(ascii(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(ascii(WithRepr('')), '') + self.assertTypedEqual(ascii(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>') + self.assertTypedEqual(ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>') + self.assertRaises(TypeError, ascii, WithRepr(b'byte-repr')) def test_repr(self): # Test basic sanity of repr() @@ -168,10 +190,13 @@ def test_repr(self): self.assertEqual(repr("\U00010000" * 39 + "\uffff" * 4096), repr("\U00010000" * 39 + "\uffff" * 4096)) - class WrongRepr: - def __repr__(self): - return b'byte-repr' - self.assertRaises(TypeError, repr, WrongRepr()) + self.assertTypedEqual(repr('\U0001f40d'), "'\U0001f40d'") + self.assertTypedEqual(repr(StrSubclass('abc')), "'abc'") + self.assertTypedEqual(repr(WithRepr('')), '') + self.assertTypedEqual(repr(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>') + self.assertTypedEqual(repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>')) + self.assertRaises(TypeError, repr, WithRepr(b'byte-repr')) def test_iterators(self): # Make sure unicode objects have an __iter__ method @@ -2367,28 +2392,37 @@ def test_ucs4(self): def test_conversion(self): # Make sure __str__() works properly - class ObjectToStr: - def __str__(self): - return "foo" - - class StrSubclassToStr(str): - def __str__(self): - return "foo" - - class StrSubclassToStrSubclass(str): - def __new__(cls, content=""): - return str.__new__(cls, 2*content) - def __str__(self): + class StrWithStr(str): + def __new__(cls, value): + self = str.__new__(cls, "") + self.value = value return self + def __str__(self): + return self.value - self.assertEqual(str(ObjectToStr()), "foo") - self.assertEqual(str(StrSubclassToStr("bar")), "foo") - s = str(StrSubclassToStrSubclass("foo")) - self.assertEqual(s, "foofoo") - self.assertIs(type(s), StrSubclassToStrSubclass) - s = StrSubclass(StrSubclassToStrSubclass("foo")) - self.assertEqual(s, "foofoo") - self.assertIs(type(s), StrSubclass) + self.assertTypedEqual(str(WithStr('abc')), 'abc') + self.assertTypedEqual(str(WithStr(StrSubclass('abc'))), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr('abc')), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr(StrSubclass('abc'))), + StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(WithStr(OtherStrSubclass('abc'))), + StrSubclass('abc')) + + self.assertTypedEqual(str(StrWithStr('abc')), 'abc') + self.assertTypedEqual(str(StrWithStr(StrSubclass('abc'))), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr('abc')), StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr(StrSubclass('abc'))), + StrSubclass('abc')) + self.assertTypedEqual(StrSubclass(StrWithStr(OtherStrSubclass('abc'))), + StrSubclass('abc')) + + self.assertTypedEqual(str(WithRepr('')), '') + self.assertTypedEqual(str(WithRepr(StrSubclass(''))), StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr('')), StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr(StrSubclass(''))), + StrSubclass('')) + self.assertTypedEqual(StrSubclass(WithRepr(OtherStrSubclass(''))), + StrSubclass('')) def test_unicode_repr(self): class s1: From 482b0ee8f6cdecd96c246c8bcbda93292f4d08cc Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 30 Nov 2023 13:01:07 -0800 Subject: [PATCH 043/442] Clarify that WASI tool requirements are included in the devcontainer (GH-112561) --- Tools/wasm/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index acbece214ed9bd..beb857f69e40da 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -302,20 +302,20 @@ AddType application/wasm wasm ### Prerequisites -Developing for WASI requires two things to be installed: +Developing for WASI requires two additional tools to be installed beyond the typical tools required to build CPython: 1. The [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 16.0+ - (see `.devcontainer/Dockerfile` for an example of how to download and install the WASI SDK) 2. A WASI host/runtime ([wasmtime](https://wasmtime.dev) 14+ is recommended and what the instructions below assume) +All of this is provided in the [devcontainer](https://devguide.python.org/getting-started/setup-building/#contribute-using-github-codespaces) if you don't want to install these tools locally. ### Building Building for WASI requires doing a cross-build where you have a "build" Python to help produce a WASI build of CPython (technically it's a "host x host" cross-build because the build Python is also the target Python while the host build is the WASI build; yes, it's confusing terminology). In the end you should have a build Python in `cross-build/build` and a WASI build in `cross-build/wasm32-wasi`. -The easiest way to do a build is to use the `wasi.py` script. You can either have it perform the entire build process from start to finish in one step, or you can do it in discrete steps that mirror running `configure` and `make` for each of the two builds of Python you end up producing (which are beneficial when you only need to do a specific step after getting a complete build, e.g. editing some code and you just need to run `make` for the WASI build). +The easiest way to do a build is to use the `wasi.py` script. You can either have it perform the entire build process from start to finish in one step, or you can do it in discrete steps that mirror running `configure` and `make` for each of the two builds of Python you end up producing (which are beneficial when you only need to do a specific step after getting a complete build, e.g. editing some code and you just need to run `make` for the WASI build). The script is designed to self-document what actions it is performing on your behalf, both as a way to check its work but also for educaitonal purposes. -The discrete steps are: +The discrete steps for building via `wasi.py` are: ```shell python Tools/wasm/wasi.py configure-build-python python Tools/wasm/wasi.py make-build-python @@ -323,7 +323,7 @@ python Tools/wasm/wasi.py configure-host python Tools/wasm/wasi.py make-host ``` -To do it in a single command, run: +To do it all in a single command, run: ```shell python Tools/wasm/wasi.py build ``` @@ -335,12 +335,12 @@ That will: 3. Run `configure` for the WASI build (`wasi.py configure-host`) 4. Run `make` for the WASI build (`wasi.py make-host`) -See the `--help` for the various options available for each of the subcommands which controls things like the location of the WASI SDK, the command to use with the WASI host/runtime, etc. Also note that you can use `--` as a separtor for any of the `configure`-related commands -- including `build` -- to pass arguments to `configure` itself. For example, if you want a pydebug build that also caches the results from `configure`, you can do: +See the `--help` for the various options available for each of the subcommands which controls things like the location of the WASI SDK, the command to use with the WASI host/runtime, etc. Also note that you can use `--` as a separator for any of the `configure`-related commands -- including `build` itself -- to pass arguments to the underlying `configure` call. For example, if you want a pydebug build that also caches the results from `configure`, you can do: ```shell python Tools/wasm/wasi.py build -- -C --with-pydebug ``` -The `wasi.py` script is able to infer details from the build Python, and so you only technically need to specify `--with-pydebug` once for `configure-build-python` and `configure-host` will detect its use if you use the discrete steps: +The `wasi.py` script is able to infer details from the build Python, and so you only technically need to specify `--with-pydebug` once via `configure-build-python` as this will lead to `configure-host` detecting its use if you use the discrete steps: ```shell python Tools/wasm/wasi.py configure-build-python -- -C --with-pydebug python Tools/wasm/wasi.py make-build-python @@ -359,7 +359,7 @@ cross-build/wasm32-wasi/python.sh --version While you _can_ run `python.wasm` directly, Python will fail to start up without certain things being set (e.g. `PYTHONPATH` for `sysconfig` data). As such, the `python.sh` file records these details for you. -## Detect WebAssembly builds +## Detecting WebAssembly builds ### Python code From 730d450d4334978f07e3cf39e1b320f2954e7963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alperen=20Serkan=20Aks=C3=B6z?= <61659936+Sekomer@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:04:00 +0300 Subject: [PATCH 044/442] gh-112502: Docs: Improve docs for gc.collect method (#112562) * Docs: Improve docs for gc.collect method * Update gc.rst --- Doc/library/gc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index 331c071cda7692..82277aa52aee01 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -42,8 +42,8 @@ The :mod:`gc` module provides the following functions: With no arguments, run a full collection. The optional argument *generation* may be an integer specifying which generation to collect (from 0 to 2). A - :exc:`ValueError` is raised if the generation number is invalid. The number of - unreachable objects found is returned. + :exc:`ValueError` is raised if the generation number is invalid. The sum of + collected objects and uncollectable objects is returned. The free lists maintained for a number of built-in types are cleared whenever a full collection or collection of the highest generation (2) From 6d5e0dc0e330f4009e8dc3d1642e46b129788877 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 30 Nov 2023 13:38:10 -0800 Subject: [PATCH 045/442] Clarify a comment for `test.support.Py_C_RECURSION_LIMIT` to point out where a value came from but that it doesn't need to stay in sync (#112224) --- Lib/test/support/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index eec5498e633eb6..318a0599a75acd 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2380,7 +2380,8 @@ def _get_c_recursion_limit(): import _testcapi return _testcapi.Py_C_RECURSION_LIMIT except (ImportError, AttributeError): - return 1500 # (from Include/cpython/pystate.h) + # Originally taken from Include/cpython/pystate.h . + return 1500 # The default C recursion limit. Py_C_RECURSION_LIMIT = _get_c_recursion_limit() From 674c288b1c29b5d838c0cb6de0ea7a64caf294ff Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 30 Nov 2023 23:00:14 +0000 Subject: [PATCH 046/442] gh-109413: Run mypy on `libregrtest` in CI (#112558) Co-authored-by: Hugo van Kemenade --- .github/workflows/mypy.yml | 2 ++ Lib/test/libregrtest/cmdline.py | 4 ++-- Lib/test/libregrtest/main.py | 12 +++++++++--- Lib/test/libregrtest/mypy.ini | 2 +- Lib/test/libregrtest/refleak.py | 3 ++- Lib/test/libregrtest/results.py | 2 +- Lib/test/libregrtest/run_workers.py | 9 ++++++--- Lib/test/libregrtest/runtests.py | 3 ++- Lib/test/libregrtest/setup.py | 3 ++- Lib/test/libregrtest/utils.py | 9 +++++---- 10 files changed, 32 insertions(+), 17 deletions(-) diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 405511ca6820b3..72ae67aa02aa96 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -8,6 +8,7 @@ on: pull_request: paths: - ".github/workflows/mypy.yml" + - "Lib/test/libregrtest/**" - "Tools/cases_generator/**" - "Tools/clinic/**" - "Tools/peg_generator/**" @@ -32,6 +33,7 @@ jobs: strategy: matrix: target: [ + "Lib/test/libregrtest", "Tools/cases_generator", "Tools/clinic", "Tools/peg_generator", diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index a5f02d6335f58f..0053bce4292f64 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -3,7 +3,7 @@ import shlex import sys from test.support import os_helper, Py_DEBUG -from .utils import ALL_RESOURCES, RESOURCE_NAMES +from .utils import ALL_RESOURCES, RESOURCE_NAMES, TestFilter USAGE = """\ @@ -161,7 +161,7 @@ def __init__(self, **kwargs) -> None: self.forever = False self.header = False self.failfast = False - self.match_tests = [] + self.match_tests: TestFilter = [] self.pgo = False self.pgo_extended = False self.worker_json = None diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 86428945a6def2..55fc3a820d3451 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -295,7 +295,9 @@ def run_test( namespace = dict(locals()) tracer.runctx(cmd, globals=globals(), locals=namespace) result = namespace['result'] - result.covered_lines = list(tracer.counts) + # Mypy doesn't know about this attribute yet, + # but it will do soon: https://github.com/python/typeshed/pull/11091 + result.covered_lines = list(tracer.counts) # type: ignore[attr-defined] else: result = run_single_test(test_name, runtests) @@ -371,7 +373,8 @@ def finalize_tests(self, coverage: trace.CoverageResults | None) -> None: os.unlink(self.next_single_filename) if coverage is not None: - coverage.write_results(show_missing=True, summary=True, + # uses a new-in-Python 3.13 keyword argument that mypy doesn't know about yet: + coverage.write_results(show_missing=True, summary=True, # type: ignore[call-arg] coverdir=self.coverage_dir, ignore_missing_files=True) @@ -432,7 +435,10 @@ def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int: if self.num_workers < 0: # Use all CPUs + 2 extra worker processes for tests # that like to sleep - self.num_workers = (os.process_cpu_count() or 1) + 2 + # + # os.process.cpu_count() is new in Python 3.13; + # mypy doesn't know about it yet + self.num_workers = (os.process_cpu_count() or 1) + 2 # type: ignore[attr-defined] # For a partial run, we do not need to clutter the output. if (self.want_header diff --git a/Lib/test/libregrtest/mypy.ini b/Lib/test/libregrtest/mypy.ini index fefc347728a701..331fe681b9f56b 100644 --- a/Lib/test/libregrtest/mypy.ini +++ b/Lib/test/libregrtest/mypy.ini @@ -5,7 +5,7 @@ [mypy] files = Lib/test/libregrtest explicit_package_bases = True -python_version = 3.11 +python_version = 3.12 platform = linux pretty = True diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index ada1a65b867ee6..5836a8421cb42d 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -52,7 +52,8 @@ def runtest_refleak(test_name, test_func, except ImportError: zdc = None # Run unmodified on platforms without zipimport support else: - zdc = zipimport._zip_directory_cache.copy() + # private attribute that mypy doesn't know about: + zdc = zipimport._zip_directory_cache.copy() # type: ignore[attr-defined] abcs = {} for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]: if not isabstract(abc): diff --git a/Lib/test/libregrtest/results.py b/Lib/test/libregrtest/results.py index 71aaef3ae9ae61..59a566c032847e 100644 --- a/Lib/test/libregrtest/results.py +++ b/Lib/test/libregrtest/results.py @@ -34,7 +34,7 @@ def __init__(self): self.test_times: list[tuple[float, TestName]] = [] self.stats = TestStats() # used by --junit-xml - self.testsuite_xml: list[str] = [] + self.testsuite_xml: list = [] # used by -T with -j self.covered_lines: set[Location] = set() diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index 99c2cf34d206d0..35aaf90ffc4299 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -10,7 +10,7 @@ import threading import time import traceback -from typing import Literal, TextIO +from typing import Any, Literal, TextIO from test import support from test.support import os_helper, MS_WINDOWS @@ -243,7 +243,9 @@ def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextI json_fd = json_tmpfile.fileno() if MS_WINDOWS: - json_handle = msvcrt.get_osfhandle(json_fd) + # The msvcrt module is only available on Windows; + # we run mypy with `--platform=linux` in CI + json_handle: int = msvcrt.get_osfhandle(json_fd) # type: ignore[attr-defined] json_file = JsonFile(json_handle, JsonFileType.WINDOWS_HANDLE) else: @@ -259,7 +261,7 @@ def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> Ru else: match_tests = None - kwargs = {} + kwargs: dict[str, Any] = {} if match_tests: kwargs['match_tests'] = [(test, True) for test in match_tests] if self.runtests.output_on_failure: @@ -345,6 +347,7 @@ def _runtest(self, test_name: TestName) -> MultiprocessResult: json_file, json_tmpfile = self.create_json_file(stack) worker_runtests = self.create_worker_runtests(test_name, json_file) + retcode: str | int | None retcode, tmp_files = self.run_tmp_files(worker_runtests, stdout_file.fileno()) diff --git a/Lib/test/libregrtest/runtests.py b/Lib/test/libregrtest/runtests.py index ac47c07f8d4341..b765ba5b41d236 100644 --- a/Lib/test/libregrtest/runtests.py +++ b/Lib/test/libregrtest/runtests.py @@ -33,7 +33,8 @@ def configure_subprocess(self, popen_kwargs: dict) -> None: popen_kwargs['pass_fds'] = [self.file] case JsonFileType.WINDOWS_HANDLE: # Windows handle - startupinfo = subprocess.STARTUPINFO() + # We run mypy with `--platform=linux` so it complains about this: + startupinfo = subprocess.STARTUPINFO() # type: ignore[attr-defined] startupinfo.lpAttributeList = {"handle_list": [self.file]} popen_kwargs['startupinfo'] = startupinfo diff --git a/Lib/test/libregrtest/setup.py b/Lib/test/libregrtest/setup.py index 97edba9f87d7f9..9e9741493e9a5b 100644 --- a/Lib/test/libregrtest/setup.py +++ b/Lib/test/libregrtest/setup.py @@ -124,7 +124,8 @@ def setup_tests(runtests: RunTests): support.LONG_TIMEOUT = min(support.LONG_TIMEOUT, timeout) if runtests.hunt_refleak: - unittest.BaseTestSuite._cleanup = False + # private attribute that mypy doesn't know about: + unittest.BaseTestSuite._cleanup = False # type: ignore[attr-defined] if runtests.gc_threshold is not None: gc.set_threshold(runtests.gc_threshold) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index e4a28af381ee2d..d47e9388e62db2 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -12,7 +12,7 @@ import sysconfig import tempfile import textwrap -from collections.abc import Callable +from collections.abc import Callable, Iterable from test import support from test.support import os_helper @@ -547,7 +547,7 @@ def is_cross_compiled(): return ('_PYTHON_HOST_PLATFORM' in os.environ) -def format_resources(use_resources: tuple[str, ...]): +def format_resources(use_resources: Iterable[str]): use_resources = set(use_resources) all_resources = set(ALL_RESOURCES) @@ -580,9 +580,10 @@ def display_header(use_resources: tuple[str, ...], print("== Python build:", ' '.join(get_build_info())) print("== cwd:", os.getcwd()) - cpu_count = os.cpu_count() + cpu_count: object = os.cpu_count() if cpu_count: - process_cpu_count = os.process_cpu_count() + # The function is new in Python 3.13; mypy doesn't know about it yet: + process_cpu_count = os.process_cpu_count() # type: ignore[attr-defined] if process_cpu_count and process_cpu_count != cpu_count: cpu_count = f"{process_cpu_count} (process) / {cpu_count} (system)" print("== CPU count:", cpu_count) From 5b0629966f47542527400b03498d5156846f0da6 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 30 Nov 2023 23:37:30 +0000 Subject: [PATCH 047/442] gh-112205: Update stringio module to use AC for the thread-safe (gh-112549) --- Modules/_io/clinic/stringio.c.h | 56 ++++++++++++++++++++++++++++++- Modules/_io/stringio.c | 58 ++++++++++++++------------------- 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h index 8e5c687dc6a55f..ed505ae67589a8 100644 --- a/Modules/_io/clinic/stringio.c.h +++ b/Modules/_io/clinic/stringio.c.h @@ -474,4 +474,58 @@ _io_StringIO___setstate__(stringio *self, PyObject *state) return return_value; } -/*[clinic end generated code: output=5c8d67f4408a1e6e input=a9049054013a1b77]*/ + +#define _IO_STRINGIO_CLOSED_GETTERDEF \ + {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, + +static PyObject * +_io_StringIO_closed_get_impl(stringio *self); + +static PyObject * +_io_StringIO_closed_get(stringio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_StringIO_closed_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#define _IO_STRINGIO_LINE_BUFFERING_GETTERDEF \ + {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, + +static PyObject * +_io_StringIO_line_buffering_get_impl(stringio *self); + +static PyObject * +_io_StringIO_line_buffering_get(stringio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_StringIO_line_buffering_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#define _IO_STRINGIO_NEWLINES_GETTERDEF \ + {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, + +static PyObject * +_io_StringIO_newlines_get_impl(stringio *self); + +static PyObject * +_io_StringIO_newlines_get(stringio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_StringIO_newlines_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} +/*[clinic end generated code: output=3a92e8b6c322f61b input=a9049054013a1b77]*/ diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 0aa5e34cd7c8b2..74dcee23730306 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -970,44 +970,44 @@ _io_StringIO___setstate___impl(stringio *self, PyObject *state) Py_RETURN_NONE; } +/*[clinic input] +@critical_section +@getter +_io.StringIO.closed +[clinic start generated code]*/ static PyObject * -stringio_closed_impl(stringio *self, void *context) +_io_StringIO_closed_get_impl(stringio *self) +/*[clinic end generated code: output=531ddca7954331d6 input=178d2ef24395fd49]*/ { CHECK_INITIALIZED(self); return PyBool_FromLong(self->closed); } -static PyObject * -stringio_closed(stringio *self, void *context) -{ - PyObject *result; - Py_BEGIN_CRITICAL_SECTION(self); - result = stringio_closed_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.StringIO.line_buffering +[clinic start generated code]*/ static PyObject * -stringio_line_buffering_impl(stringio *self, void *context) +_io_StringIO_line_buffering_get_impl(stringio *self) +/*[clinic end generated code: output=360710e0112966ae input=6a7634e7f890745e]*/ { CHECK_INITIALIZED(self); CHECK_CLOSED(self); Py_RETURN_FALSE; } -static PyObject * -stringio_line_buffering(stringio *self, void *context) -{ - PyObject *result; - Py_BEGIN_CRITICAL_SECTION(self); - result = stringio_line_buffering_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.StringIO.newlines +[clinic start generated code]*/ static PyObject * -stringio_newlines_impl(stringio *self, void *context) +_io_StringIO_newlines_get_impl(stringio *self) +/*[clinic end generated code: output=35d7c0b66d7e0160 input=092a14586718244b]*/ { CHECK_INITIALIZED(self); CHECK_CLOSED(self); @@ -1017,16 +1017,6 @@ stringio_newlines_impl(stringio *self, void *context) return PyObject_GetAttr(self->decoder, &_Py_ID(newlines)); } -static PyObject * -stringio_newlines(stringio *self, void *context) -{ - PyObject *result; - Py_BEGIN_CRITICAL_SECTION(self); - result = stringio_newlines_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} - static struct PyMethodDef stringio_methods[] = { _IO_STRINGIO_CLOSE_METHODDEF _IO_STRINGIO_GETVALUE_METHODDEF @@ -1047,15 +1037,15 @@ static struct PyMethodDef stringio_methods[] = { }; static PyGetSetDef stringio_getset[] = { - {"closed", (getter)stringio_closed, NULL, NULL}, - {"newlines", (getter)stringio_newlines, NULL, NULL}, + _IO_STRINGIO_CLOSED_GETTERDEF + _IO_STRINGIO_NEWLINES_GETTERDEF /* (following comments straight off of the original Python wrapper:) XXX Cruft to support the TextIOWrapper API. This would only be meaningful if StringIO supported the buffer attribute. Hopefully, a better solution, than adding these pseudo-attributes, will be found. */ - {"line_buffering", (getter)stringio_line_buffering, NULL, NULL}, + _IO_STRINGIO_LINE_BUFFERING_GETTERDEF {NULL} }; From e44f1940bd6d2ba4a3f8ac4585b3cf4f9cb93e49 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 1 Dec 2023 02:02:31 -0500 Subject: [PATCH 048/442] gh-66819: More IDLE htest updates (#112574) Revise htest.py docstring and move 2 specs to alphabetical position. --- Lib/idlelib/idle_test/htest.py | 107 ++++++++++++++++----------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 595e51d3699f6e..e21ab98d8aab89 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -1,38 +1,35 @@ -'''Run human tests of Idle's window, dialog, and popup widgets. - -run(*tests) -Create a master Tk window. Within that, run each callable in tests -after finding the matching test spec in this file. If tests is empty, -run an htest for each spec dict in this file after finding the matching -callable in the module named in the spec. Close the window to skip or -end the test. - -In a tested module, let X be a global name bound to a callable (class -or function) whose .__name__ attribute is also X (the usual situation). -The first parameter of X must be 'parent'. When called, the parent -argument will be the root window. X must create a child Toplevel -window (or subclass thereof). The Toplevel may be a test widget or -dialog, in which case the callable is the corresponding class. Or the -Toplevel may contain the widget to be tested or set up a context in -which a test widget is invoked. In this latter case, the callable is a -wrapper function that sets up the Toplevel and other objects. Wrapper -function names, such as _editor_window', should start with '_'. +"""Run human tests of Idle's window, dialog, and popup widgets. + +run(*tests) Create a master Tk() htest window. Within that, run each +callable in tests after finding the matching test spec in this file. If +tests is empty, run an htest for each spec dict in this file after +finding the matching callable in the module named in the spec. Close +the master window to end testing. + +In a tested module, let X be a global name bound to a callable (class or +function) whose .__name__ attribute is also X (the usual situation). The +first parameter of X must be 'parent'. When called, the parent argument +will be the root window. X must create a child Toplevel(parent) window +(or subclass thereof). The Toplevel may be a test widget or dialog, in +which case the callable is the corresponding class. Or the Toplevel may +contain the widget to be tested or set up a context in which a test +widget is invoked. In this latter case, the callable is a wrapper +function that sets up the Toplevel and other objects. Wrapper function +names, such as _editor_window', should start with '_' and be lowercase. End the module with if __name__ == '__main__': - + from idlelib.idle_test.htest import run - run(X) + run(callable) # There could be multiple comma-separated callables. -To have wrapper functions and test invocation code ignored by coveragepy -reports, put '# htest #' on the def statement header line. - -def _wrapper(parent): # htest # - -Also make sure that the 'if __name__' line matches the above. Then have -make sure that .coveragerc includes the following. +To have wrapper functions ignored by coverage reports, tag the def +header like so: "def _wrapper(parent): # htest #". Use the same tag +for htest lines in widget code. Make sure that the 'if __name__' line +matches the above. Then have make sure that .coveragerc includes the +following: [report] exclude_lines = @@ -46,7 +43,7 @@ def _wrapper(parent): # htest # following template, with X.__name__ prepended to '_spec'. When all tests are run, the prefix is use to get X. -_spec = { +callable_spec = { 'file': '', 'kwds': {'title': ''}, 'msg': "" @@ -54,16 +51,16 @@ def _wrapper(parent): # htest # file (no .py): run() imports file.py. kwds: augmented with {'parent':root} and passed to X as **kwds. -title: an example kwd; some widgets need this, delete if not. +title: an example kwd; some widgets need this, delete line if not. msg: master window hints about testing the widget. -Modules and classes not being tested at the moment: -pyshell.PyShellEditorWindow -debugger.Debugger -autocomplete_w.AutoCompleteWindow -outwin.OutputWindow (indirectly being tested with grep test) -''' +TODO test these modules and classes: + autocomplete_w.AutoCompleteWindow + debugger.Debugger + outwin.OutputWindow (indirectly being tested with grep test) + pyshell.PyShellEditorWindow +""" import idlelib.pyshell # Set Windows DPI awareness before Tk(). from importlib import import_module @@ -91,15 +88,6 @@ def _wrapper(parent): # htest # "Force-open-calltip does not work here.\n" } -_module_browser_spec = { - 'file': 'browser', - 'kwds': {}, - 'msg': "Inspect names of module, class(with superclass if " - "applicable), methods and functions.\nToggle nested items.\n" - "Double clicking on items prints a traceback for an exception " - "that is ignored." - } - _color_delegator_spec = { 'file': 'colorizer', 'kwds': {}, @@ -109,16 +97,6 @@ def _wrapper(parent): # htest # "The default color scheme is in idlelib/config-highlight.def" } -CustomRun_spec = { - 'file': 'query', - 'kwds': {'title': 'Customize query.py Run', - '_htest': True}, - 'msg': "Enter with or [Run]. Print valid entry to Shell\n" - "Arguments are parsed into a list\n" - "Mode is currently restart True or False\n" - "Close dialog with valid entry, , [Cancel], [X]" - } - ConfigDialog_spec = { 'file': 'configdialog', 'kwds': {'title': 'ConfigDialogTest', @@ -135,6 +113,16 @@ def _wrapper(parent): # htest # "changes made have persisted." } +CustomRun_spec = { + 'file': 'query', + 'kwds': {'title': 'Customize query.py Run', + '_htest': True}, + 'msg': "Enter with or [Run]. Print valid entry to Shell\n" + "Arguments are parsed into a list\n" + "Mode is currently restart True or False\n" + "Close dialog with valid entry, , [Cancel], [X]" + } + # TODO Improve message _dyn_option_menu_spec = { 'file': 'dynoption', @@ -236,6 +224,15 @@ def _wrapper(parent): # htest # "focusing out of the window\nare sequences to be tested." } +_module_browser_spec = { + 'file': 'browser', + 'kwds': {}, + 'msg': "Inspect names of module, class(with superclass if " + "applicable), methods and functions.\nToggle nested items.\n" + "Double clicking on items prints a traceback for an exception " + "that is ignored." + } + _multistatus_bar_spec = { 'file': 'statusbar', 'kwds': {}, From f6afa426d8e84d9bac0f7caaf11ea47759b8389c Mon Sep 17 00:00:00 2001 From: William Andrea Date: Fri, 1 Dec 2023 03:41:11 -0500 Subject: [PATCH 049/442] Add links under "generator expression" in glossary (#112537) --- Doc/glossary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 6b517b95f97013..29f2f80cebd5f0 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -502,7 +502,7 @@ Glossary .. index:: single: generator expression generator expression - An expression that returns an iterator. It looks like a normal expression + An :term:`expression` that returns an :term:`iterator`. It looks like a normal expression followed by a :keyword:`!for` clause defining a loop variable, range, and an optional :keyword:`!if` clause. The combined expression generates values for an enclosing function:: From 19a86148e6dc20752283536c3a41a7f997ddd36e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:21:45 +0000 Subject: [PATCH 050/442] build(deps-dev): bump mypy from 1.7.0 to 1.7.1 in /Tools (#112581) Bumps [mypy](https://github.com/python/mypy) from 1.7.0 to 1.7.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.7.0...v1.7.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 591baac33c7e8f..4107dc823e7aaa 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -1,6 +1,6 @@ # Requirements file for external linters and checks we run on # Tools/clinic, Tools/cases_generator/, and Tools/peg_generator/ in CI -mypy==1.7.0 +mypy==1.7.1 # needed for peg_generator: types-psutil==5.9.5.17 From 467e3f94171f84221272011d40e4ee32fba47f73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:29:04 +0000 Subject: [PATCH 051/442] build(deps-dev): bump types-setuptools from 68.2.0.0 to 69.0.0.0 in /Tools (#112582) build(deps-dev): bump types-setuptools in /Tools Bumps [types-setuptools](https://github.com/python/typeshed) from 68.2.0.0 to 69.0.0.0. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-setuptools dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] 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 4107dc823e7aaa..3a2e62f70bbb60 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -4,4 +4,4 @@ mypy==1.7.1 # needed for peg_generator: types-psutil==5.9.5.17 -types-setuptools==68.2.0.0 +types-setuptools==69.0.0.0 From 707c37e373d7ea4e3f06b24c719fa45f70fbfa49 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Fri, 1 Dec 2023 17:37:40 +0800 Subject: [PATCH 052/442] Fix typos in variable names, function names, and comments (GH-101868) --- Lib/zipimport.py | 2 +- Mac/BuildScript/build-installer.py | 2 +- PC/launcher2.c | 2 +- Parser/pegen_errors.c | 2 +- Tools/build/stable_abi.py | 12 ++++++------ Tools/c-analyzer/c_analyzer/__init__.py | 2 +- Tools/clinic/clinic.py | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Lib/zipimport.py b/Lib/zipimport.py index 5b9f614f02f7af..823a82ee830465 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -352,7 +352,7 @@ def _read_directory(archive): with fp: # GH-87235: On macOS all file descriptors for /dev/fd/N share the same - # file offset, reset the file offset after scanning the zipfile diretory + # file offset, reset the file offset after scanning the zipfile directory # to not cause problems when some runs 'python3 /dev/fd/9 9 None: flush() return tuple(version) -def version_comparitor(version1: str, version2: str) -> Literal[-1, 0, 1]: +def version_comparator(version1: str, version2: str) -> Literal[-1, 0, 1]: iterator = itertools.zip_longest( version_splitter(version1), version_splitter(version2), fillvalue=0 ) @@ -5203,7 +5203,7 @@ def reset(self) -> None: def directive_version(self, required: str) -> None: global version - if version_comparitor(version, required) < 0: + if version_comparator(version, required) < 0: fail("Insufficient Clinic version!\n" f" Version: {version}\n" f" Required: {required}") From f21e2f4b12298210d7ef60be97c7d2e6b07070d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:41:09 +0100 Subject: [PATCH 053/442] build(deps): bump actions/github-script from 6 to 7 (#112584) Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/add-issue-header.yml | 2 +- .github/workflows/new-bugs-announce-notifier.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/add-issue-header.yml b/.github/workflows/add-issue-header.yml index 1ef9178b95e5f6..570b8779994a0f 100644 --- a/.github/workflows/add-issue-header.yml +++ b/.github/workflows/add-issue-header.yml @@ -19,7 +19,7 @@ jobs: permissions: issues: write steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: # language=JavaScript script: | diff --git a/.github/workflows/new-bugs-announce-notifier.yml b/.github/workflows/new-bugs-announce-notifier.yml index 4599b21ef35f05..9f1a8a824e5f19 100644 --- a/.github/workflows/new-bugs-announce-notifier.yml +++ b/.github/workflows/new-bugs-announce-notifier.yml @@ -18,7 +18,7 @@ jobs: node-version: 20 - run: npm install mailgun.js form-data - name: Send notification - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: MAILGUN_API_KEY: ${{ secrets.MAILGUN_PYTHON_ORG_MAILGUN_KEY }} with: From 847e4fe0e81f0e6e54ef52a9be63e3fb74b0779a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:17:24 +0000 Subject: [PATCH 054/442] build(deps): bump hypothesis from 6.88.1 to 6.91.0 in /Tools (#112580) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.88.1 to 6.91.0. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.88.1...hypothesis-python-6.91.0) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 0fcc72ac69073d..1bca5d2367f4b2 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.88.1 +hypothesis==6.91.0 From a65a3d4806a4087f229b5ab6ab28d3e0b0a2d840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 1 Dec 2023 12:17:47 +0100 Subject: [PATCH 055/442] bpo-39912: Raise appropriate exceptions in filterwarnings() and simplefilter() (GH-18878) --- Lib/test/test_warnings/__init__.py | 22 ++++++++++++++ Lib/warnings.py | 30 +++++++++++-------- .../2020-03-09-15-08-29.bpo-39912.xPOBBY.rst | 3 ++ 3 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-03-09-15-08-29.bpo-39912.xPOBBY.rst diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index cd989fe36bf26b..232480c46e0a00 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -375,6 +375,28 @@ def test_append_duplicate(self): "appended duplicate changed order of filters" ) + def test_argument_validation(self): + with self.assertRaises(ValueError): + self.module.filterwarnings(action='foo') + with self.assertRaises(TypeError): + self.module.filterwarnings('ignore', message=0) + with self.assertRaises(TypeError): + self.module.filterwarnings('ignore', category=0) + with self.assertRaises(TypeError): + self.module.filterwarnings('ignore', category=int) + with self.assertRaises(TypeError): + self.module.filterwarnings('ignore', module=0) + with self.assertRaises(TypeError): + self.module.filterwarnings('ignore', lineno=int) + with self.assertRaises(ValueError): + self.module.filterwarnings('ignore', lineno=-1) + with self.assertRaises(ValueError): + self.module.simplefilter(action='foo') + with self.assertRaises(TypeError): + self.module.simplefilter('ignore', lineno=int) + with self.assertRaises(ValueError): + self.module.simplefilter('ignore', lineno=-1) + def test_catchwarnings_with_simplefilter_ignore(self): with original_warnings.catch_warnings(module=self.module): self.module.resetwarnings() diff --git a/Lib/warnings.py b/Lib/warnings.py index 924f872172d4d1..b8ff078569d2ce 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -139,14 +139,18 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0, 'lineno' -- an integer line number, 0 matches all warnings 'append' -- if true, append to the list of filters """ - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(message, str), "message must be a string" - assert isinstance(category, type), "category must be a class" - assert issubclass(category, Warning), "category must be a Warning subclass" - assert isinstance(module, str), "module must be a string" - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" + if action not in {"error", "ignore", "always", "default", "module", "once"}: + raise ValueError(f"invalid action: {action!r}") + if not isinstance(message, str): + raise TypeError("message must be a string") + if not isinstance(category, type) or not issubclass(category, Warning): + raise TypeError("category must be a Warning subclass") + if not isinstance(module, str): + raise TypeError("module must be a string") + if not isinstance(lineno, int): + raise TypeError("lineno must be an int") + if lineno < 0: + raise ValueError("lineno must be an int >= 0") if message or module: import re @@ -172,10 +176,12 @@ def simplefilter(action, category=Warning, lineno=0, append=False): 'lineno' -- an integer line number, 0 matches all warnings 'append' -- if true, append to the list of filters """ - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" + if action not in {"error", "ignore", "always", "default", "module", "once"}: + raise ValueError(f"invalid action: {action!r}") + if not isinstance(lineno, int): + raise TypeError("lineno must be an int") + if lineno < 0: + raise ValueError("lineno must be an int >= 0") _add_filter(action, None, category, None, lineno, append=append) def _add_filter(*item, append): diff --git a/Misc/NEWS.d/next/Library/2020-03-09-15-08-29.bpo-39912.xPOBBY.rst b/Misc/NEWS.d/next/Library/2020-03-09-15-08-29.bpo-39912.xPOBBY.rst new file mode 100644 index 00000000000000..fb8579725a2d7d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-03-09-15-08-29.bpo-39912.xPOBBY.rst @@ -0,0 +1,3 @@ +:func:`warnings.filterwarnings()` and :func:`warnings.simplefilter()` now raise +appropriate exceptions instead of ``AssertionError``. Patch contributed by +Rémi Lapeyre. From bfb576ee23c133bec0ce7c26a8ecea76926b9d8e Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:57:31 +0000 Subject: [PATCH 056/442] gh-111058: Change coro.cr_frame/gen.gi_frame to be None for a closed coroutine/generator. (#112428) --- Lib/test/test_coroutines.py | 8 ++++++++ Lib/test/test_inspect/test_inspect.py | 8 ++++++++ .../2023-11-26-21-30-11.gh-issue-111058.q4DqDY.rst | 3 +++ Objects/genobject.c | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-26-21-30-11.gh-issue-111058.q4DqDY.rst diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 47145782c0f04f..25c981d1511bc1 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -2216,6 +2216,14 @@ async def f(): gen.cr_frame.clear() gen.close() + def test_cr_frame_after_close(self): + async def f(): + pass + gen = f() + self.assertIsNotNone(gen.cr_frame) + gen.close() + self.assertIsNone(gen.cr_frame) + def test_stack_in_coroutine_throw(self): # Regression test for https://github.com/python/cpython/issues/93592 async def a(): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index becbb0498bbb3f..e75682f881ab34 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -2384,6 +2384,10 @@ def test_closed_after_immediate_exception(self): self.generator.throw(RuntimeError) self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + def test_closed_after_close(self): + self.generator.close() + self.assertEqual(self._generatorstate(), inspect.GEN_CLOSED) + def test_running(self): # As mentioned on issue #10220, checking for the RUNNING state only # makes sense inside the generator itself. @@ -2493,6 +2497,10 @@ def test_closed_after_immediate_exception(self): self.coroutine.throw(RuntimeError) self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + def test_closed_after_close(self): + self.coroutine.close() + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + def test_easy_debugging(self): # repr() and str() of a coroutine state should contain the state name names = 'CORO_CREATED CORO_RUNNING CORO_SUSPENDED CORO_CLOSED'.split() diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-26-21-30-11.gh-issue-111058.q4DqDY.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-26-21-30-11.gh-issue-111058.q4DqDY.rst new file mode 100644 index 00000000000000..de5661f911aa82 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-26-21-30-11.gh-issue-111058.q4DqDY.rst @@ -0,0 +1,3 @@ +Change coro.cr_frame/gen.gi_frame to return ``None`` after the coroutine/generator has been closed. +This fixes a bug where :func:`~inspect.getcoroutinestate` and :func:`~inspect.getgeneratorstate` +return the wrong state for a closed coroutine/generator. diff --git a/Objects/genobject.c b/Objects/genobject.c index f98aa357cd2ce1..9614713883741c 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -732,7 +732,7 @@ _gen_getframe(PyGenObject *gen, const char *const name) if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) { return NULL; } - if (gen->gi_frame_state == FRAME_CLEARED) { + if (FRAME_STATE_FINISHED(gen->gi_frame_state)) { Py_RETURN_NONE; } return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject((_PyInterpreterFrame *)gen->gi_iframe)); From a73aa48e6bec900be7edd3431deaa5fc1d809e6f Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Fri, 1 Dec 2023 13:20:51 +0000 Subject: [PATCH 057/442] gh-112367: Only free perf trampoline arenas at shutdown (#112368) Signed-off-by: Pablo Galindo --- Include/internal/pycore_ceval.h | 1 + ...-11-24-14-10-57.gh-issue-112367.9z1IDp.rst | 2 + Python/perf_trampoline.c | 40 ++++++++++++++++--- Python/pylifecycle.c | 2 +- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-24-14-10-57.gh-issue-112367.9z1IDp.rst diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index c372b7224fb047..3f7ac922bdf451 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -101,6 +101,7 @@ extern int _PyPerfTrampoline_SetCallbacks(_PyPerf_Callbacks *); extern void _PyPerfTrampoline_GetCallbacks(_PyPerf_Callbacks *); extern int _PyPerfTrampoline_Init(int activate); extern int _PyPerfTrampoline_Fini(void); +extern void _PyPerfTrampoline_FreeArenas(void); extern int _PyIsPerfTrampolineActive(void); extern PyStatus _PyPerfTrampoline_AfterFork_Child(void); #ifdef PY_HAVE_PERF_TRAMPOLINE diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-24-14-10-57.gh-issue-112367.9z1IDp.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-24-14-10-57.gh-issue-112367.9z1IDp.rst new file mode 100644 index 00000000000000..991e45ad47fabe --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-24-14-10-57.gh-issue-112367.9z1IDp.rst @@ -0,0 +1,2 @@ +Avoid undefined behaviour when using the perf trampolines by not freeing the +code arenas until shutdown. Patch by Pablo Galindo diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 208ced6c101dce..540b650192ed34 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -216,10 +216,24 @@ perf_map_write_entry(void *state, const void *code_addr, PyMem_RawFree(perf_map_entry); } +static void* +perf_map_init_state(void) +{ + PyUnstable_PerfMapState_Init(); + return NULL; +} + +static int +perf_map_free_state(void *state) +{ + PyUnstable_PerfMapState_Fini(); + return 0; +} + _PyPerf_Callbacks _Py_perfmap_callbacks = { - NULL, + &perf_map_init_state, &perf_map_write_entry, - NULL, + &perf_map_free_state, }; static int @@ -415,7 +429,6 @@ _PyPerfTrampoline_SetCallbacks(_PyPerf_Callbacks *callbacks) trampoline_api.write_state = callbacks->write_state; trampoline_api.free_state = callbacks->free_state; trampoline_api.state = NULL; - perf_status = PERF_STATUS_OK; #endif return 0; } @@ -434,6 +447,7 @@ _PyPerfTrampoline_Init(int activate) } if (!activate) { tstate->interp->eval_frame = NULL; + perf_status = PERF_STATUS_NO_INIT; } else { tstate->interp->eval_frame = py_trampoline_evaluator; @@ -444,6 +458,9 @@ _PyPerfTrampoline_Init(int activate) if (extra_code_index == -1) { return -1; } + if (trampoline_api.state == NULL && trampoline_api.init_state != NULL) { + trampoline_api.state = trampoline_api.init_state(); + } perf_status = PERF_STATUS_OK; } #endif @@ -454,16 +471,29 @@ int _PyPerfTrampoline_Fini(void) { #ifdef PY_HAVE_PERF_TRAMPOLINE + if (perf_status != PERF_STATUS_OK) { + return 0; + } PyThreadState *tstate = _PyThreadState_GET(); if (tstate->interp->eval_frame == py_trampoline_evaluator) { tstate->interp->eval_frame = NULL; } - free_code_arenas(); + if (perf_status == PERF_STATUS_OK) { + trampoline_api.free_state(trampoline_api.state); + } extra_code_index = -1; + perf_status = PERF_STATUS_NO_INIT; #endif return 0; } +void _PyPerfTrampoline_FreeArenas(void) { +#ifdef PY_HAVE_PERF_TRAMPOLINE + free_code_arenas(); +#endif + return; +} + int PyUnstable_PerfTrampoline_SetPersistAfterFork(int enable){ #ifdef PY_HAVE_PERF_TRAMPOLINE @@ -477,8 +507,8 @@ PyStatus _PyPerfTrampoline_AfterFork_Child(void) { #ifdef PY_HAVE_PERF_TRAMPOLINE - PyUnstable_PerfMapState_Fini(); if (persist_after_fork) { + _PyPerfTrampoline_Fini(); char filename[256]; pid_t parent_pid = getppid(); snprintf(filename, sizeof(filename), "/tmp/perf-%d.map", parent_pid); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ac8d5208322882..aff67d7a835e89 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1797,6 +1797,7 @@ finalize_interp_clear(PyThreadState *tstate) _PyArg_Fini(); _Py_ClearFileSystemEncoding(); _PyPerfTrampoline_Fini(); + _PyPerfTrampoline_FreeArenas(); } finalize_interp_types(tstate->interp); @@ -1854,7 +1855,6 @@ Py_FinalizeEx(void) */ _PyAtExit_Call(tstate->interp); - PyUnstable_PerfMapState_Fini(); /* Copy the core config, PyInterpreterState_Delete() free the core config memory */ From 058444308abee79bb1b3358883adfa8c97bd043a Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Fri, 1 Dec 2023 05:36:37 -0800 Subject: [PATCH 058/442] gh-82565: Add tests for pickle and unpickle with bad files (GH-16606) --- Lib/test/pickletester.py | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index ddb180ef5ef825..fd446c8145850c 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -3514,6 +3514,84 @@ def __init__(self): pass self.assertRaises(pickle.PicklingError, BadPickler().dump, 0) self.assertRaises(pickle.UnpicklingError, BadUnpickler().load) + def test_unpickler_bad_file(self): + # bpo-38384: Crash in _pickle if the read attribute raises an error. + def raises_oserror(self, *args, **kwargs): + raise OSError + @property + def bad_property(self): + 1/0 + + # File without read and readline + class F: + pass + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) + + # File without read + class F: + readline = raises_oserror + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) + + # File without readline + class F: + read = raises_oserror + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) + + # File with bad read + class F: + read = bad_property + readline = raises_oserror + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) + + # File with bad readline + class F: + readline = bad_property + read = raises_oserror + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) + + # File with bad readline, no read + class F: + readline = bad_property + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) + + # File with bad read, no readline + class F: + read = bad_property + self.assertRaises((AttributeError, ZeroDivisionError), self.Unpickler, F()) + + # File with bad peek + class F: + peek = bad_property + read = raises_oserror + readline = raises_oserror + try: + self.Unpickler(F()) + except ZeroDivisionError: + pass + + # File with bad readinto + class F: + readinto = bad_property + read = raises_oserror + readline = raises_oserror + try: + self.Unpickler(F()) + except ZeroDivisionError: + pass + + def test_pickler_bad_file(self): + # File without write + class F: + pass + self.assertRaises(TypeError, self.Pickler, F()) + + # File with bad write + class F: + @property + def write(self): + 1/0 + self.assertRaises(ZeroDivisionError, self.Pickler, F()) + def check_dumps_loads_oob_buffers(self, dumps, loads): # No need to do the full gamut of tests here, just enough to # check that dumps() and loads() redirect their arguments From f8ff80f63536e96b004d29112452a8f1738fde37 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 14:46:50 +0100 Subject: [PATCH 059/442] gh-109413: regrtest: add WorkerRunTests class (#112588) --- Lib/test/libregrtest/main.py | 1 - Lib/test/libregrtest/run_workers.py | 12 +++++------ Lib/test/libregrtest/runtests.py | 31 +++++++++++++++++++---------- Lib/test/libregrtest/worker.py | 6 +++--- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 55fc3a820d3451..16f6974ae32465 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -423,7 +423,6 @@ def create_run_tests(self, tests: TestTuple): python_cmd=self.python_cmd, randomize=self.randomize, random_seed=self.random_seed, - json_file=None, ) def _run_tests(self, selected: TestTuple, tests: TestList | None) -> int: diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index 35aaf90ffc4299..18a0342f0611cf 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -18,7 +18,7 @@ from .logger import Logger from .result import TestResult, State from .results import TestResults -from .runtests import RunTests, JsonFile, JsonFileType +from .runtests import RunTests, WorkerRunTests, JsonFile, JsonFileType from .single import PROGRESS_MIN_TIME from .utils import ( StrPath, TestName, @@ -162,7 +162,7 @@ def stop(self) -> None: self._stopped = True self._kill() - def _run_process(self, runtests: RunTests, output_fd: int, + def _run_process(self, runtests: WorkerRunTests, output_fd: int, tmp_dir: StrPath | None = None) -> int | None: popen = create_worker_process(runtests, output_fd, tmp_dir) self._popen = popen @@ -252,9 +252,7 @@ def create_json_file(self, stack: contextlib.ExitStack) -> tuple[JsonFile, TextI json_file = JsonFile(json_fd, JsonFileType.UNIX_FD) return (json_file, json_tmpfile) - def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> RunTests: - """Create the worker RunTests.""" - + def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> WorkerRunTests: tests = (test_name,) if self.runtests.rerun: match_tests = self.runtests.get_match_tests(test_name) @@ -267,12 +265,12 @@ def create_worker_runtests(self, test_name: TestName, json_file: JsonFile) -> Ru if self.runtests.output_on_failure: kwargs['verbose'] = True kwargs['output_on_failure'] = False - return self.runtests.copy( + return self.runtests.create_worker_runtests( tests=tests, json_file=json_file, **kwargs) - def run_tmp_files(self, worker_runtests: RunTests, + def run_tmp_files(self, worker_runtests: WorkerRunTests, stdout_fd: int) -> tuple[int | None, list[StrPath]]: # gh-93353: Check for leaked temporary files in the parent process, # since the deletion of temporary files can happen late during diff --git a/Lib/test/libregrtest/runtests.py b/Lib/test/libregrtest/runtests.py index b765ba5b41d236..edd72276320e41 100644 --- a/Lib/test/libregrtest/runtests.py +++ b/Lib/test/libregrtest/runtests.py @@ -93,13 +93,17 @@ class RunTests: python_cmd: tuple[str, ...] | None randomize: bool random_seed: int | str - json_file: JsonFile | None - def copy(self, **override): + def copy(self, **override) -> 'RunTests': state = dataclasses.asdict(self) state.update(override) return RunTests(**state) + def create_worker_runtests(self, **override): + state = dataclasses.asdict(self) + state.update(override) + return WorkerRunTests(**state) + def get_match_tests(self, test_name) -> FilterTuple | None: if self.match_tests_dict is not None: return self.match_tests_dict.get(test_name, None) @@ -120,13 +124,6 @@ def iter_tests(self): else: yield from self.tests - def as_json(self) -> StrJSON: - return json.dumps(self, cls=_EncodeRunTests) - - @staticmethod - def from_json(worker_json: StrJSON) -> 'RunTests': - return json.loads(worker_json, object_hook=_decode_runtests) - def json_file_use_stdout(self) -> bool: # Use STDOUT in two cases: # @@ -141,9 +138,21 @@ def json_file_use_stdout(self) -> bool: ) +@dataclasses.dataclass(slots=True, frozen=True) +class WorkerRunTests(RunTests): + json_file: JsonFile + + def as_json(self) -> StrJSON: + return json.dumps(self, cls=_EncodeRunTests) + + @staticmethod + def from_json(worker_json: StrJSON) -> 'WorkerRunTests': + return json.loads(worker_json, object_hook=_decode_runtests) + + class _EncodeRunTests(json.JSONEncoder): def default(self, o: Any) -> dict[str, Any]: - if isinstance(o, RunTests): + if isinstance(o, WorkerRunTests): result = dataclasses.asdict(o) result["__runtests__"] = True return result @@ -158,6 +167,6 @@ def _decode_runtests(data: dict[str, Any]) -> RunTests | dict[str, Any]: data['hunt_refleak'] = HuntRefleak(**data['hunt_refleak']) if data['json_file']: data['json_file'] = JsonFile(**data['json_file']) - return RunTests(**data) + return WorkerRunTests(**data) else: return data diff --git a/Lib/test/libregrtest/worker.py b/Lib/test/libregrtest/worker.py index b3bb0b7f34a060..7a6d33d4499943 100644 --- a/Lib/test/libregrtest/worker.py +++ b/Lib/test/libregrtest/worker.py @@ -7,7 +7,7 @@ from test.support import os_helper, Py_DEBUG from .setup import setup_process, setup_test_dir -from .runtests import RunTests, JsonFile, JsonFileType +from .runtests import WorkerRunTests, JsonFile, JsonFileType from .single import run_single_test from .utils import ( StrPath, StrJSON, TestFilter, @@ -17,7 +17,7 @@ USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg")) -def create_worker_process(runtests: RunTests, output_fd: int, +def create_worker_process(runtests: WorkerRunTests, output_fd: int, tmp_dir: StrPath | None = None) -> subprocess.Popen: python_cmd = runtests.python_cmd worker_json = runtests.as_json() @@ -73,7 +73,7 @@ def create_worker_process(runtests: RunTests, output_fd: int, def worker_process(worker_json: StrJSON) -> NoReturn: - runtests = RunTests.from_json(worker_json) + runtests = WorkerRunTests.from_json(worker_json) test_name = runtests.tests[0] match_tests: TestFilter = runtests.match_tests json_file: JsonFile = runtests.json_file From c2982380f827e53057068eccf9f1a16b5a653728 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 1 Dec 2023 05:05:55 -0900 Subject: [PATCH 060/442] gh-112510: Add `readline.backend` for the backend readline uses (GH-112511) Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Donghee Na --- Doc/library/readline.rst | 15 ++++++++++----- Lib/site.py | 3 +-- Lib/test/test_pdb.py | 2 +- Lib/test/test_readline.py | 9 ++++++--- ...2023-11-29-02-26-32.gh-issue-112510.j-zXGc.rst | 1 + Modules/readline.c | 9 ++++++++- 6 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-29-02-26-32.gh-issue-112510.j-zXGc.rst diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 8fb0eca8df74d8..2e0f45ced30b9c 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -27,16 +27,15 @@ Readline library in general. .. note:: The underlying Readline library API may be implemented by - the ``libedit`` library instead of GNU readline. + the ``editline`` (``libedit``) library instead of GNU readline. On macOS the :mod:`readline` module detects which library is being used at run time. - The configuration file for ``libedit`` is different from that + The configuration file for ``editline`` is different from that of GNU readline. If you programmatically load configuration strings - you can check for the text "libedit" in :const:`readline.__doc__` - to differentiate between GNU readline and libedit. + you can use :data:`backend` to determine which library is being used. - If you use *editline*/``libedit`` readline emulation on macOS, the + If you use ``editline``/``libedit`` readline emulation on macOS, the initialization file located in your home directory is named ``.editrc``. For example, the following content in ``~/.editrc`` will turn ON *vi* keybindings and TAB completion:: @@ -44,6 +43,12 @@ Readline library in general. python:bind -v python:bind ^I rl_complete +.. data:: backend + + The name of the underlying Readline library being used, either + ``"readline"`` or ``"editline"``. + + .. versionadded:: 3.13 Init file --------- diff --git a/Lib/site.py b/Lib/site.py index 672fa7b000ad02..2517b7e5f1d22a 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -444,8 +444,7 @@ def register_readline(): # Reading the initialization (config) file may not be enough to set a # completion key, so we set one first and then read the file. - readline_doc = getattr(readline, '__doc__', '') - if readline_doc is not None and 'libedit' in readline_doc: + if readline.backend == 'editline': readline.parse_and_bind('bind ^I rl_complete') else: readline.parse_and_bind('tab: complete') diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 2a279ca869e9c7..50d8c8f52a909d 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3293,7 +3293,7 @@ def setUpClass(): # Ensure that the readline module is loaded # If this fails, the test is skipped because SkipTest will be raised readline = import_module('readline') - if readline.__doc__ and "libedit" in readline.__doc__: + if readline.backend == "editline": raise unittest.SkipTest("libedit readline is not supported for pdb") def test_basic_completion(self): diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index 6c2726d3209ecf..5e0e6f8dfac651 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -19,7 +19,7 @@ if hasattr(readline, "_READLINE_LIBRARY_VERSION"): is_editline = ("EditLine wrapper" in readline._READLINE_LIBRARY_VERSION) else: - is_editline = (readline.__doc__ and "libedit" in readline.__doc__) + is_editline = readline.backend == "editline" def setUpModule(): @@ -145,6 +145,9 @@ def test_init(self): TERM='xterm-256color') self.assertEqual(stdout, b'') + def test_backend(self): + self.assertIn(readline.backend, ("readline", "editline")) + auto_history_script = """\ import readline readline.set_auto_history({}) @@ -171,7 +174,7 @@ def complete(text, state): if state == 0 and text == "$": return "$complete" return None - if "libedit" in getattr(readline, "__doc__", ""): + if readline.backend == "editline": readline.parse_and_bind(r'bind "\\t" rl_complete') else: readline.parse_and_bind(r'"\\t": complete') @@ -198,7 +201,7 @@ def test_nonascii(self): script = r"""import readline -is_editline = readline.__doc__ and "libedit" in readline.__doc__ +is_editline = readline.backend == "editline" inserted = "[\xEFnserted]" macro = "|t\xEB[after]" set_pre_input_hook = getattr(readline, "set_pre_input_hook", None) diff --git a/Misc/NEWS.d/next/Library/2023-11-29-02-26-32.gh-issue-112510.j-zXGc.rst b/Misc/NEWS.d/next/Library/2023-11-29-02-26-32.gh-issue-112510.j-zXGc.rst new file mode 100644 index 00000000000000..02de6fa80c1b3e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-29-02-26-32.gh-issue-112510.j-zXGc.rst @@ -0,0 +1 @@ +Add :data:`readline.backend` for the backend readline uses (``editline`` or ``readline``) diff --git a/Modules/readline.c b/Modules/readline.c index eb9a3d4693ee90..afbb7f8f0ec18f 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1538,6 +1538,7 @@ static struct PyModuleDef readlinemodule = { PyMODINIT_FUNC PyInit_readline(void) { + const char *backend = "readline"; PyObject *m; readlinestate *mod_state; @@ -1545,8 +1546,10 @@ PyInit_readline(void) using_libedit_emulation = 1; } - if (using_libedit_emulation) + if (using_libedit_emulation) { readlinemodule.m_doc = doc_module_le; + backend = "editline"; + } m = PyModule_Create(&readlinemodule); @@ -1568,6 +1571,10 @@ PyInit_readline(void) goto error; } + if (PyModule_AddStringConstant(m, "backend", backend) < 0) { + goto error; + } + mod_state = (readlinestate *) PyModule_GetState(m); if (mod_state == NULL){ goto error; From 5f6ac2d88a49b8a7c764691365cd41ee6226a8d0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 15:50:16 +0100 Subject: [PATCH 061/442] gh-110481: Fix Py_SET_REFCNT() integer overflow (#112174) If Py_NOGIL is defined and Py_SET_REFCNT() is called with a reference count larger than UINT32_MAX, make the object immortal. Set _Py_IMMORTAL_REFCNT constant type to Py_ssize_t to fix the following compiler warning: Include/internal/pycore_global_objects_fini_generated.h:14:24: warning: comparison of integers of different signs: 'Py_ssize_t' (aka 'long') and 'unsigned int' [-Wsign-compare] if (Py_REFCNT(obj) < _Py_IMMORTAL_REFCNT) { ~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~ --- Doc/c-api/refcounting.rst | 3 +++ Doc/using/configure.rst | 2 ++ Include/object.h | 31 +++++++++++++++++++++---------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst index 68119a27b18ec2..75e1d46474f1e7 100644 --- a/Doc/c-api/refcounting.rst +++ b/Doc/c-api/refcounting.rst @@ -34,6 +34,9 @@ of Python objects. Set the object *o* reference counter to *refcnt*. + On :ref:`Python build with Free Threading `, if + *refcnt* is larger than ``UINT32_MAX``, the object is made :term:`immortal`. + This function has no effect on :term:`immortal` objects. .. versionadded:: 3.9 diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index b51546e072a353..56d2d6dc4ab5f1 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -287,6 +287,8 @@ General Options .. versionadded:: 3.11 +.. _free-threading-build: + .. option:: --disable-gil Enables **experimental** support for running Python without the diff --git a/Include/object.h b/Include/object.h index 6b70a494844476..86fcba21caa9c8 100644 --- a/Include/object.h +++ b/Include/object.h @@ -88,7 +88,7 @@ having all the lower 32 bits set, which will avoid the reference count to go beyond the refcount limit. Immortality checks for reference count decreases will be done by checking the bit sign flag in the lower 32 bits. */ -#define _Py_IMMORTAL_REFCNT UINT_MAX +#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX) #else /* @@ -103,7 +103,7 @@ immortality, but the execution would still be correct. Reference count increases and decreases will first go through an immortality check by comparing the reference count field to the immortality reference count. */ -#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2) +#define _Py_IMMORTAL_REFCNT _Py_CAST(Py_ssize_t, UINT_MAX >> 2) #endif // Py_GIL_DISABLED builds indicate immortal objects using `ob_ref_local`, which is @@ -317,11 +317,11 @@ static inline Py_ssize_t Py_SIZE(PyObject *ob) { static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op) { #if defined(Py_GIL_DISABLED) - return op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL; + return (op->ob_ref_local == _Py_IMMORTAL_REFCNT_LOCAL); #elif SIZEOF_VOID_P > 4 - return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0; + return (_Py_CAST(PY_INT32_T, op->ob_refcnt) < 0); #else - return op->ob_refcnt == _Py_IMMORTAL_REFCNT; + return (op->ob_refcnt == _Py_IMMORTAL_REFCNT); #endif } #define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op)) @@ -350,15 +350,23 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { if (_Py_IsImmortal(ob)) { return; } + #ifndef Py_GIL_DISABLED ob->ob_refcnt = refcnt; #else if (_Py_IsOwnedByCurrentThread(ob)) { - // Set local refcount to desired refcount and shared refcount to zero, - // but preserve the shared refcount flags. - assert(refcnt < UINT32_MAX); - ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt); - ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK; + if ((size_t)refcnt > (size_t)UINT32_MAX) { + // On overflow, make the object immortal + op->ob_tid = _Py_UNOWNED_TID; + op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + op->ob_ref_shared = 0; + } + else { + // Set local refcount to desired refcount and shared refcount + // to zero, but preserve the shared refcount flags. + ob->ob_ref_local = _Py_STATIC_CAST(uint32_t, refcnt); + ob->ob_ref_shared &= _Py_REF_SHARED_FLAG_MASK; + } } else { // Set local refcount to zero and shared refcount to desired refcount. @@ -750,6 +758,7 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op) uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local); uint32_t new_local = local + 1; if (new_local == 0) { + // local is equal to _Py_IMMORTAL_REFCNT: do nothing return; } if (_Py_IsOwnedByCurrentThread(op)) { @@ -763,6 +772,8 @@ static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op) PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN]; PY_UINT32_T new_refcnt = cur_refcnt + 1; if (new_refcnt == 0) { + // cur_refcnt is equal to _Py_IMMORTAL_REFCNT: the object is immortal, + // do nothing return; } op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt; From 70a38ffb3d712f973eb17bd1bda541f238ae70d2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 1 Dec 2023 14:54:33 +0000 Subject: [PATCH 062/442] gh-109413: libregrtest: enable mypy's `--strict-optional` check on most files (#112586) Co-authored-by: Victor Stinner --- Lib/test/libregrtest/mypy.ini | 2 +- Lib/test/libregrtest/results.py | 2 ++ Lib/test/libregrtest/single.py | 8 ++++---- Lib/test/libregrtest/utils.py | 9 +++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/test/libregrtest/mypy.ini b/Lib/test/libregrtest/mypy.ini index 331fe681b9f56b..22c7c7a9acef14 100644 --- a/Lib/test/libregrtest/mypy.ini +++ b/Lib/test/libregrtest/mypy.ini @@ -25,7 +25,7 @@ warn_return_any = False disable_error_code = return # Enable --strict-optional for these ASAP: -[mypy-Lib.test.libregrtest.main.*,Lib.test.libregrtest.run_workers.*,Lib.test.libregrtest.worker.*,Lib.test.libregrtest.single.*,Lib.test.libregrtest.results.*,Lib.test.libregrtest.utils.*] +[mypy-Lib.test.libregrtest.main.*,Lib.test.libregrtest.run_workers.*] strict_optional = False # Various internal modules that typeshed deliberately doesn't have stubs for: diff --git a/Lib/test/libregrtest/results.py b/Lib/test/libregrtest/results.py index 59a566c032847e..a41ea8aba028c3 100644 --- a/Lib/test/libregrtest/results.py +++ b/Lib/test/libregrtest/results.py @@ -117,6 +117,8 @@ def accumulate_result(self, result: TestResult, runtests: RunTests): self.worker_bug = True if result.has_meaningful_duration() and not rerun: + if result.duration is None: + raise ValueError("result.duration is None") self.test_times.append((result.duration, test_name)) if result.stats is not None: self.stats.accumulate(result.stats) diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py index 5c7bc7d40fb394..eafeb5fe26f3f3 100644 --- a/Lib/test/libregrtest/single.py +++ b/Lib/test/libregrtest/single.py @@ -237,11 +237,11 @@ def _runtest(result: TestResult, runtests: RunTests) -> None: output_on_failure = runtests.output_on_failure timeout = runtests.timeout - use_timeout = ( - timeout is not None and threading_helper.can_start_thread - ) - if use_timeout: + if timeout is not None and threading_helper.can_start_thread: + use_timeout = True faulthandler.dump_traceback_later(timeout, exit=True) + else: + use_timeout = False try: setup_tests(runtests) diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index d47e9388e62db2..d4972ce4a50d2a 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -377,10 +377,19 @@ def get_temp_dir(tmp_dir: StrPath | None = None) -> StrPath: # Python out of the source tree, especially when the # source tree is read only. tmp_dir = sysconfig.get_config_var('srcdir') + if not tmp_dir: + raise RuntimeError( + "Could not determine the correct value for tmp_dir" + ) tmp_dir = os.path.join(tmp_dir, 'build') else: # WASI platform tmp_dir = sysconfig.get_config_var('projectbase') + if not tmp_dir: + raise RuntimeError( + "sysconfig.get_config_var('projectbase') " + f"unexpectedly returned {tmp_dir!r} on WASI" + ) tmp_dir = os.path.join(tmp_dir, 'build') # When get_temp_dir() is called in a worker process, From 0daf555c6fb3feba77989382135a58215e1d70a5 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Fri, 1 Dec 2023 07:16:49 -0800 Subject: [PATCH 063/442] bpo-37013: Fix the error handling in socket.if_indextoname() (GH-13503) * Fix a crash when pass UINT_MAX. * Fix an integer overflow on 64-bit non-Windows platforms. --- Lib/test/test_socket.py | 13 +++++++++++++ ...2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst | 3 +++ Modules/socketmodule.c | 16 +++++++++++----- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 86701caf05399e..4eb5af99d6674c 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1082,7 +1082,20 @@ def testInterfaceNameIndex(self): 'socket.if_indextoname() not available.') def testInvalidInterfaceIndexToName(self): self.assertRaises(OSError, socket.if_indextoname, 0) + self.assertRaises(OverflowError, socket.if_indextoname, -1) + self.assertRaises(OverflowError, socket.if_indextoname, 2**1000) self.assertRaises(TypeError, socket.if_indextoname, '_DEADBEEF') + if hasattr(socket, 'if_nameindex'): + indices = dict(socket.if_nameindex()) + for index in indices: + index2 = index + 2**32 + if index2 not in indices: + with self.assertRaises((OverflowError, OSError)): + socket.if_indextoname(index2) + for index in 2**32-1, 2**64-1: + if index not in indices: + with self.assertRaises((OverflowError, OSError)): + socket.if_indextoname(index) @unittest.skipUnless(hasattr(socket, 'if_nametoindex'), 'socket.if_nametoindex() not available.') diff --git a/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst b/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst new file mode 100644 index 00000000000000..feb7a8643b97f6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-01-16-09-59.gh-issue-81194.FFad1c.rst @@ -0,0 +1,3 @@ +Fix a crash in :func:`socket.if_indextoname` with specific value (UINT_MAX). +Fix an integer overflow in :func:`socket.if_indextoname` on 64-bit +non-Windows platforms. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 9ac2001c0132d3..0a0e0e78656f76 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7071,17 +7071,23 @@ _socket_socket_if_nametoindex_impl(PySocketSockObject *self, PyObject *oname) static PyObject * socket_if_indextoname(PyObject *self, PyObject *arg) { + unsigned long index_long = PyLong_AsUnsignedLong(arg); + if (index_long == (unsigned long) -1 && PyErr_Occurred()) { + return NULL; + } + #ifdef MS_WINDOWS - NET_IFINDEX index; + NET_IFINDEX index = (NET_IFINDEX)index_long; #else - unsigned long index; + unsigned int index = (unsigned int)index_long; #endif - char name[IF_NAMESIZE + 1]; - index = PyLong_AsUnsignedLong(arg); - if (index == (unsigned long) -1) + if ((unsigned long)index != index_long) { + PyErr_SetString(PyExc_OverflowError, "index is too large"); return NULL; + } + char name[IF_NAMESIZE + 1]; if (if_indextoname(index, name) == NULL) { PyErr_SetFromErrno(PyExc_OSError); return NULL; From a9073564ee50bc610e1fd36e45b0a5204618883a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 16:54:40 +0100 Subject: [PATCH 064/442] gh-110481: Fix typo in Py_SET_REFCNT() (#112595) --- Include/object.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/object.h b/Include/object.h index 86fcba21caa9c8..81f777ad21f2f9 100644 --- a/Include/object.h +++ b/Include/object.h @@ -357,9 +357,9 @@ static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { if (_Py_IsOwnedByCurrentThread(ob)) { if ((size_t)refcnt > (size_t)UINT32_MAX) { // On overflow, make the object immortal - op->ob_tid = _Py_UNOWNED_TID; - op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; - op->ob_ref_shared = 0; + ob->ob_tid = _Py_UNOWNED_TID; + ob->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; + ob->ob_ref_shared = 0; } else { // Set local refcount to desired refcount and shared refcount From 05a370abd6cdfe4b54be60b3b911f3a441026bb2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 17:05:56 +0100 Subject: [PATCH 065/442] gh-112567: Add _Py_GetTicksPerSecond() function (#112587) * Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * Add process_time_times() helper function, called by py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES. --- Include/internal/pycore_fileutils.h | 4 + Include/internal/pycore_pylifecycle.h | 1 - Include/internal/pycore_runtime.h | 2 - Include/internal/pycore_time.h | 10 -- Modules/clinic/posixmodule.c.h | 10 +- Modules/posixmodule.c | 53 +++++---- Modules/timemodule.c | 158 ++++++++++++++------------ Python/fileutils.c | 24 ++++ Python/pylifecycle.c | 5 - 9 files changed, 142 insertions(+), 125 deletions(-) diff --git a/Include/internal/pycore_fileutils.h b/Include/internal/pycore_fileutils.h index 2f89da2c6ecd91..5c55282fa39e6f 100644 --- a/Include/internal/pycore_fileutils.h +++ b/Include/internal/pycore_fileutils.h @@ -320,6 +320,10 @@ PyAPI_FUNC(char*) _Py_UniversalNewlineFgetsWithSize(char *, int, FILE*, PyObject extern int _PyFile_Flush(PyObject *); +#ifndef MS_WINDOWS +extern int _Py_GetTicksPerSecond(long *ticks_per_second); +#endif + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 61e0150e89009c..daf7cb77dcc63a 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -40,7 +40,6 @@ extern void _PySys_FiniTypes(PyInterpreterState *interp); extern int _PyBuiltins_AddExceptions(PyObject * bltinmod); extern PyStatus _Py_HashRandomization_Init(const PyConfig *); -extern PyStatus _PyTime_Init(void); extern PyStatus _PyGC_Init(PyInterpreterState *interp); extern PyStatus _PyAtExit_Init(PyInterpreterState *interp); extern int _Py_Deepfreeze_Init(void); diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index e6efe8b646e86f..36743723f8afd8 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -21,7 +21,6 @@ extern "C" { #include "pycore_pymem.h" // struct _pymem_allocators #include "pycore_pythread.h" // struct _pythread_runtime_state #include "pycore_signal.h" // struct _signals_runtime_state -#include "pycore_time.h" // struct _time_runtime_state #include "pycore_tracemalloc.h" // struct _tracemalloc_runtime_state #include "pycore_typeobject.h" // struct _types_runtime_state #include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_state @@ -205,7 +204,6 @@ typedef struct pyruntimestate { struct _pymem_allocators allocators; struct _obmalloc_global_state obmalloc; struct pyhash_runtime_state pyhash_state; - struct _time_runtime_state time; struct _pythread_runtime_state threads; struct _signals_runtime_state signals; diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 46713f91d190ff..7ea3485107572e 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -52,16 +52,6 @@ extern "C" { #endif -struct _time_runtime_state { -#ifdef HAVE_TIMES - int ticks_per_second_initialized; - long ticks_per_second; -#else - int _not_used; -#endif -}; - - #ifdef __clang__ struct timeval; #endif diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 9c54935bafa617..a6c76370f241be 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -5997,8 +5997,6 @@ os_symlink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #endif /* defined(HAVE_SYMLINK) */ -#if defined(HAVE_TIMES) - PyDoc_STRVAR(os_times__doc__, "times($module, /)\n" "--\n" @@ -6021,8 +6019,6 @@ os_times(PyObject *module, PyObject *Py_UNUSED(ignored)) return os_times_impl(module); } -#endif /* defined(HAVE_TIMES) */ - #if defined(HAVE_TIMERFD_CREATE) PyDoc_STRVAR(os_timerfd_create__doc__, @@ -12116,10 +12112,6 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #define OS_SYMLINK_METHODDEF #endif /* !defined(OS_SYMLINK_METHODDEF) */ -#ifndef OS_TIMES_METHODDEF - #define OS_TIMES_METHODDEF -#endif /* !defined(OS_TIMES_METHODDEF) */ - #ifndef OS_TIMERFD_CREATE_METHODDEF #define OS_TIMERFD_CREATE_METHODDEF #endif /* !defined(OS_TIMERFD_CREATE_METHODDEF) */ @@ -12403,4 +12395,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=0f216bf44ea358f9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2900675ac5219924 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d99b5335b6989a..70d107a297f315 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1030,6 +1030,10 @@ typedef struct { PyObject *struct_rusage; #endif PyObject *st_mode; +#ifndef MS_WINDOWS + // times() clock frequency in hertz; used by os.times() + long ticks_per_second; +#endif } _posixstate; @@ -9986,8 +9990,6 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst, #endif /* HAVE_SYMLINK */ - - static PyStructSequence_Field times_result_fields[] = { {"user", "user time"}, {"system", "system time"}, @@ -10013,12 +10015,6 @@ static PyStructSequence_Desc times_result_desc = { 5 }; -#ifdef MS_WINDOWS -#define HAVE_TIMES /* mandatory, for the method table */ -#endif - -#ifdef HAVE_TIMES - static PyObject * build_times_result(PyObject *module, double user, double system, double children_user, double children_system, @@ -10064,8 +10060,8 @@ All fields are floating point numbers. static PyObject * os_times_impl(PyObject *module) /*[clinic end generated code: output=35f640503557d32a input=2bf9df3d6ab2e48b]*/ -#ifdef MS_WINDOWS { +#ifdef MS_WINDOWS FILETIME create, exit, kernel, user; HANDLE hProc; hProc = GetCurrentProcess(); @@ -10083,28 +10079,26 @@ os_times_impl(PyObject *module) (double)0, (double)0, (double)0); -} #else /* MS_WINDOWS */ -{ - struct tms t; - clock_t c; + _posixstate *state = get_posix_state(module); + long ticks_per_second = state->ticks_per_second; + + struct tms process; + clock_t elapsed; errno = 0; - c = times(&t); - if (c == (clock_t) -1) { + elapsed = times(&process); + if (elapsed == (clock_t) -1) { return posix_error(); } - assert(_PyRuntime.time.ticks_per_second_initialized); -#define ticks_per_second _PyRuntime.time.ticks_per_second + return build_times_result(module, - (double)t.tms_utime / ticks_per_second, - (double)t.tms_stime / ticks_per_second, - (double)t.tms_cutime / ticks_per_second, - (double)t.tms_cstime / ticks_per_second, - (double)c / ticks_per_second); -#undef ticks_per_second -} + (double)process.tms_utime / ticks_per_second, + (double)process.tms_stime / ticks_per_second, + (double)process.tms_cutime / ticks_per_second, + (double)process.tms_cstime / ticks_per_second, + (double)elapsed / ticks_per_second); #endif /* MS_WINDOWS */ -#endif /* HAVE_TIMES */ +} #if defined(HAVE_TIMERFD_CREATE) @@ -17279,6 +17273,15 @@ posixmodule_exec(PyObject *m) Py_DECREF(unicode); } +#ifndef MS_WINDOWS + if (_Py_GetTicksPerSecond(&state->ticks_per_second) < 0) { + PyErr_SetString(PyExc_RuntimeError, + "cannot read ticks_per_second"); + return -1; + } + assert(state->ticks_per_second >= 1); +#endif + return PyModule_Add(m, "_have_functions", list); } diff --git a/Modules/timemodule.c b/Modules/timemodule.c index bc3901e0d7a621..aa0cdc5f026e7c 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -73,51 +73,20 @@ module time static int check_ticks_per_second(long tps, const char *context) { - /* Effectively, check that _PyTime_MulDiv(t, SEC_TO_NS, ticks_per_second) + /* Effectively, check that _PyTime_MulDiv(t, SEC_TO_NS, tps) cannot overflow. */ if (tps >= 0 && (_PyTime_t)tps > _PyTime_MAX / SEC_TO_NS) { PyErr_Format(PyExc_OverflowError, "%s is too large", context); return -1; } + if (tps < 1) { + PyErr_Format(PyExc_RuntimeError, "invalid %s", context); + return -1; + } return 0; } #endif /* HAVE_TIMES || HAVE_CLOCK */ -#ifdef HAVE_TIMES - -# define ticks_per_second _PyRuntime.time.ticks_per_second - -static void -ensure_ticks_per_second(void) -{ - if (_PyRuntime.time.ticks_per_second_initialized) { - return; - } - _PyRuntime.time.ticks_per_second_initialized = 1; -# if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) - ticks_per_second = sysconf(_SC_CLK_TCK); - if (ticks_per_second < 1) { - ticks_per_second = -1; - } -# elif defined(HZ) - ticks_per_second = HZ; -# else - ticks_per_second = 60; /* magic fallback value; may be bogus */ -# endif -} - -#endif /* HAVE_TIMES */ - - -PyStatus -_PyTime_Init(void) -{ -#ifdef HAVE_TIMES - ensure_ticks_per_second(); -#endif - return PyStatus_Ok(); -} - /* Forward declarations */ static int pysleep(_PyTime_t timeout); @@ -125,6 +94,14 @@ static int pysleep(_PyTime_t timeout); typedef struct { PyTypeObject *struct_time_type; +#ifdef HAVE_TIMES + // times() clock frequency in hertz + long ticks_per_second; +#endif +#ifdef HAVE_CLOCK + // clock() frequency in hertz + long clocks_per_second; +#endif } time_module_state; static inline time_module_state* @@ -184,7 +161,7 @@ PyDoc_STRVAR(time_ns_doc, \n\ Return the current time in nanoseconds since the Epoch."); -#if defined(HAVE_CLOCK) +#ifdef HAVE_CLOCK #ifndef CLOCKS_PER_SEC # ifdef CLK_TCK @@ -195,15 +172,12 @@ Return the current time in nanoseconds since the Epoch."); #endif static int -_PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) { - if (check_ticks_per_second(CLOCKS_PER_SEC, "CLOCKS_PER_SEC") < 0) { - return -1; - } - + long clocks_per_second = state->clocks_per_second; if (info) { info->implementation = "clock()"; - info->resolution = 1.0 / (double)CLOCKS_PER_SEC; + info->resolution = 1.0 / (double)clocks_per_second; info->monotonic = 1; info->adjustable = 0; } @@ -215,7 +189,7 @@ _PyTime_GetClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) "or its value cannot be represented"); return -1; } - _PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)CLOCKS_PER_SEC); + _PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, clocks_per_second); *tp = _PyTime_FromNanoseconds(ns); return 0; } @@ -1277,8 +1251,38 @@ PyDoc_STRVAR(perf_counter_ns_doc, \n\ Performance counter for benchmarking as nanoseconds."); + +#ifdef HAVE_TIMES static int -_PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) +process_time_times(time_module_state *state, _PyTime_t *tp, + _Py_clock_info_t *info) +{ + long ticks_per_second = state->ticks_per_second; + + struct tms process; + if (times(&process) == (clock_t)-1) { + return 0; + } + + if (info) { + info->implementation = "times()"; + info->monotonic = 1; + info->adjustable = 0; + info->resolution = 1.0 / (double)ticks_per_second; + } + + _PyTime_t ns; + ns = _PyTime_MulDiv(process.tms_utime, SEC_TO_NS, ticks_per_second); + ns += _PyTime_MulDiv(process.tms_stime, SEC_TO_NS, ticks_per_second); + *tp = _PyTime_FromNanoseconds(ns); + return 1; +} +#endif + + +static int +py_process_time(time_module_state *state, _PyTime_t *tp, + _Py_clock_info_t *info) { #if defined(MS_WINDOWS) HANDLE process; @@ -1381,41 +1385,28 @@ _PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) /* times() */ #ifdef HAVE_TIMES - struct tms t; - - if (times(&t) != (clock_t)-1) { - assert(_PyRuntime.time.ticks_per_second_initialized); - if (check_ticks_per_second(ticks_per_second, "_SC_CLK_TCK") < 0) { - return -1; - } - if (ticks_per_second != -1) { - if (info) { - info->implementation = "times()"; - info->monotonic = 1; - info->adjustable = 0; - info->resolution = 1.0 / (double)ticks_per_second; - } - - _PyTime_t ns; - ns = _PyTime_MulDiv(t.tms_utime, SEC_TO_NS, ticks_per_second); - ns += _PyTime_MulDiv(t.tms_stime, SEC_TO_NS, ticks_per_second); - *tp = _PyTime_FromNanoseconds(ns); - return 0; - } + int res = process_time_times(state, tp, info); + if (res < 0) { + return -1; } + if (res == 1) { + return 0; + } + // times() failed, ignore failure #endif /* clock */ /* Currently, Python 3 requires clock() to build: see issue #22624 */ - return _PyTime_GetClockWithInfo(tp, info); + return py_clock(state, tp, info); #endif } static PyObject * -time_process_time(PyObject *self, PyObject *unused) +time_process_time(PyObject *module, PyObject *unused) { + time_module_state *state = get_time_state(module); _PyTime_t t; - if (_PyTime_GetProcessTimeWithInfo(&t, NULL) < 0) { + if (py_process_time(state, &t, NULL) < 0) { return NULL; } return _PyFloat_FromPyTime(t); @@ -1427,10 +1418,11 @@ PyDoc_STRVAR(process_time_doc, Process time for profiling: sum of the kernel and user-space CPU time."); static PyObject * -time_process_time_ns(PyObject *self, PyObject *unused) +time_process_time_ns(PyObject *module, PyObject *unused) { + time_module_state *state = get_time_state(module); _PyTime_t t; - if (_PyTime_GetProcessTimeWithInfo(&t, NULL) < 0) { + if (py_process_time(state, &t, NULL) < 0) { return NULL; } return _PyTime_AsNanosecondsObject(t); @@ -1617,7 +1609,7 @@ sum of the kernel and user-space CPU time."); static PyObject * -time_get_clock_info(PyObject *self, PyObject *args) +time_get_clock_info(PyObject *module, PyObject *args) { char *name; _Py_clock_info_t info; @@ -1656,7 +1648,8 @@ time_get_clock_info(PyObject *self, PyObject *args) } } else if (strcmp(name, "process_time") == 0) { - if (_PyTime_GetProcessTimeWithInfo(&t, &info) < 0) { + time_module_state *state = get_time_state(module); + if (py_process_time(state, &t, &info) < 0) { return NULL; } } @@ -2116,6 +2109,25 @@ time_exec(PyObject *module) } #endif +#ifdef HAVE_TIMES + if (_Py_GetTicksPerSecond(&state->ticks_per_second) < 0) { + PyErr_SetString(PyExc_RuntimeError, + "cannot read ticks_per_second"); + return -1; + } + + if (check_ticks_per_second(state->ticks_per_second, "_SC_CLK_TCK") < 0) { + return -1; + } +#endif + +#ifdef HAVE_CLOCK + state->clocks_per_second = CLOCKS_PER_SEC; + if (check_ticks_per_second(state->clocks_per_second, "CLOCKS_PER_SEC") < 0) { + return -1; + } +#endif + return 0; } diff --git a/Python/fileutils.c b/Python/fileutils.c index 649b188b5167d0..9d12bc89c95436 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2943,3 +2943,27 @@ _Py_closerange(int first, int last) #endif /* USE_FDWALK */ _Py_END_SUPPRESS_IPH } + + +#ifndef MS_WINDOWS +// Ticks per second used by clock() and times() functions. +// See os.times() and time.process_time() implementations. +int +_Py_GetTicksPerSecond(long *ticks_per_second) +{ +#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) + long value = sysconf(_SC_CLK_TCK); + if (value < 1) { + return -1; + } + *ticks_per_second = value; +#elif defined(HZ) + assert(HZ >= 1); + *ticks_per_second = HZ; +#else + // Magic fallback value; may be bogus + *ticks_per_second = 60; +#endif + return 0; +} +#endif diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index aff67d7a835e89..95a72eb47048f2 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -528,11 +528,6 @@ pycore_init_runtime(_PyRuntimeState *runtime, return status; } - status = _PyTime_Init(); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - status = _PyImport_Init(); if (_PyStatus_EXCEPTION(status)) { return status; From 5c5022b8625e34f0035ad5a23bc4c2f16649d134 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 1 Dec 2023 19:50:10 +0100 Subject: [PATCH 066/442] gh-112567: Add _PyTimeFraction C API (#112568) Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Divisor (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow. --- Include/internal/pycore_time.h | 33 ++++++-- Modules/timemodule.c | 54 +++++------- Python/pytime.c | 150 +++++++++++++++++++-------------- 3 files changed, 130 insertions(+), 107 deletions(-) diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 7ea3485107572e..dabbd7b41556cd 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -253,13 +253,6 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts); // Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2); -// Compute ticks * mul / div. -// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. -// The caller must ensure that ((div - 1) * mul) cannot overflow. -extern _PyTime_t _PyTime_MulDiv(_PyTime_t ticks, - _PyTime_t mul, - _PyTime_t div); - // Structure used by time.get_clock_info() typedef struct { const char *implementation; @@ -355,6 +348,32 @@ PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout); PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline); +// --- _PyTimeFraction ------------------------------------------------------- + +typedef struct { + _PyTime_t numer; + _PyTime_t denom; +} _PyTimeFraction; + +// Set a fraction. +// Return 0 on success. +// Return -1 if the fraction is invalid. +extern int _PyTimeFraction_Set( + _PyTimeFraction *frac, + _PyTime_t numer, + _PyTime_t denom); + +// Compute ticks * frac.numer / frac.denom. +// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow. +extern _PyTime_t _PyTimeFraction_Mul( + _PyTime_t ticks, + const _PyTimeFraction *frac); + +// Compute a clock resolution: frac.numer / frac.denom / 1e9. +extern double _PyTimeFraction_Resolution( + const _PyTimeFraction *frac); + + #ifdef __cplusplus } #endif diff --git a/Modules/timemodule.c b/Modules/timemodule.c index aa0cdc5f026e7c..b3fe175d9b184a 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -69,25 +69,6 @@ module time /*[clinic end generated code: output=da39a3ee5e6b4b0d input=a668a08771581f36]*/ -#if defined(HAVE_TIMES) || defined(HAVE_CLOCK) -static int -check_ticks_per_second(long tps, const char *context) -{ - /* Effectively, check that _PyTime_MulDiv(t, SEC_TO_NS, tps) - cannot overflow. */ - if (tps >= 0 && (_PyTime_t)tps > _PyTime_MAX / SEC_TO_NS) { - PyErr_Format(PyExc_OverflowError, "%s is too large", context); - return -1; - } - if (tps < 1) { - PyErr_Format(PyExc_RuntimeError, "invalid %s", context); - return -1; - } - return 0; -} -#endif /* HAVE_TIMES || HAVE_CLOCK */ - - /* Forward declarations */ static int pysleep(_PyTime_t timeout); @@ -96,11 +77,11 @@ typedef struct { PyTypeObject *struct_time_type; #ifdef HAVE_TIMES // times() clock frequency in hertz - long ticks_per_second; + _PyTimeFraction times_base; #endif #ifdef HAVE_CLOCK // clock() frequency in hertz - long clocks_per_second; + _PyTimeFraction clock_base; #endif } time_module_state; @@ -174,10 +155,11 @@ Return the current time in nanoseconds since the Epoch."); static int py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) { - long clocks_per_second = state->clocks_per_second; + _PyTimeFraction *base = &state->clock_base; + if (info) { info->implementation = "clock()"; - info->resolution = 1.0 / (double)clocks_per_second; + info->resolution = _PyTimeFraction_Resolution(base); info->monotonic = 1; info->adjustable = 0; } @@ -189,7 +171,7 @@ py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) "or its value cannot be represented"); return -1; } - _PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, clocks_per_second); + _PyTime_t ns = _PyTimeFraction_Mul(ticks, base); *tp = _PyTime_FromNanoseconds(ns); return 0; } @@ -1257,7 +1239,7 @@ static int process_time_times(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info) { - long ticks_per_second = state->ticks_per_second; + _PyTimeFraction *base = &state->times_base; struct tms process; if (times(&process) == (clock_t)-1) { @@ -1266,14 +1248,14 @@ process_time_times(time_module_state *state, _PyTime_t *tp, if (info) { info->implementation = "times()"; + info->resolution = _PyTimeFraction_Resolution(base); info->monotonic = 1; info->adjustable = 0; - info->resolution = 1.0 / (double)ticks_per_second; } _PyTime_t ns; - ns = _PyTime_MulDiv(process.tms_utime, SEC_TO_NS, ticks_per_second); - ns += _PyTime_MulDiv(process.tms_stime, SEC_TO_NS, ticks_per_second); + ns = _PyTimeFraction_Mul(process.tms_utime, base); + ns += _PyTimeFraction_Mul(process.tms_stime, base); *tp = _PyTime_FromNanoseconds(ns); return 1; } @@ -1395,8 +1377,7 @@ py_process_time(time_module_state *state, _PyTime_t *tp, // times() failed, ignore failure #endif - /* clock */ - /* Currently, Python 3 requires clock() to build: see issue #22624 */ + /* clock(). Python 3 requires clock() to build (see gh-66814) */ return py_clock(state, tp, info); #endif } @@ -2110,20 +2091,23 @@ time_exec(PyObject *module) #endif #ifdef HAVE_TIMES - if (_Py_GetTicksPerSecond(&state->ticks_per_second) < 0) { + long ticks_per_second; + if (_Py_GetTicksPerSecond(&ticks_per_second) < 0) { PyErr_SetString(PyExc_RuntimeError, "cannot read ticks_per_second"); return -1; } - - if (check_ticks_per_second(state->ticks_per_second, "_SC_CLK_TCK") < 0) { + if (_PyTimeFraction_Set(&state->times_base, SEC_TO_NS, + ticks_per_second) < 0) { + PyErr_Format(PyExc_OverflowError, "ticks_per_second is too large"); return -1; } #endif #ifdef HAVE_CLOCK - state->clocks_per_second = CLOCKS_PER_SEC; - if (check_ticks_per_second(state->clocks_per_second, "CLOCKS_PER_SEC") < 0) { + if (_PyTimeFraction_Set(&state->clock_base, SEC_TO_NS, + CLOCKS_PER_SEC) < 0) { + PyErr_Format(PyExc_OverflowError, "CLOCKS_PER_SEC is too large"); return -1; } #endif diff --git a/Python/pytime.c b/Python/pytime.c index e4813d4a9c2a2a..77cb95f8feb179 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -55,6 +55,43 @@ #endif +static _PyTime_t +_PyTime_GCD(_PyTime_t x, _PyTime_t y) +{ + // Euclidean algorithm + assert(x >= 1); + assert(y >= 1); + while (y != 0) { + _PyTime_t tmp = y; + y = x % y; + x = tmp; + } + assert(x >= 1); + return x; +} + + +int +_PyTimeFraction_Set(_PyTimeFraction *frac, _PyTime_t numer, _PyTime_t denom) +{ + if (numer < 1 || denom < 1) { + return -1; + } + + _PyTime_t gcd = _PyTime_GCD(numer, denom); + frac->numer = numer / gcd; + frac->denom = denom / gcd; + return 0; +} + + +double +_PyTimeFraction_Resolution(const _PyTimeFraction *frac) +{ + return (double)frac->numer / (double)frac->denom / 1e9; +} + + static void pytime_time_t_overflow(void) { @@ -152,11 +189,17 @@ _PyTime_Mul(_PyTime_t t, _PyTime_t k) } - - _PyTime_t -_PyTime_MulDiv(_PyTime_t ticks, _PyTime_t mul, _PyTime_t div) +_PyTimeFraction_Mul(_PyTime_t ticks, const _PyTimeFraction *frac) { + const _PyTime_t mul = frac->numer; + const _PyTime_t div = frac->denom; + + if (div == 1) { + // Fast-path taken by mach_absolute_time() with 1/1 time base. + return _PyTime_Mul(ticks, mul); + } + /* Compute (ticks * mul / div) in two parts to reduce the risk of integer overflow: compute the integer part, and then the remaining part. @@ -1016,51 +1059,34 @@ _PyTime_GetSystemClockWithInfo(_PyTime_t *t, _Py_clock_info_t *info) #ifdef __APPLE__ static int -py_mach_timebase_info(_PyTime_t *pnumer, _PyTime_t *pdenom, int raise) +py_mach_timebase_info(_PyTimeFraction *base, int raise) { - static mach_timebase_info_data_t timebase; - /* According to the Technical Q&A QA1398, mach_timebase_info() cannot - fail: https://developer.apple.com/library/mac/#qa/qa1398/ */ + mach_timebase_info_data_t timebase; + // According to the Technical Q&A QA1398, mach_timebase_info() cannot + // fail: https://developer.apple.com/library/mac/#qa/qa1398/ (void)mach_timebase_info(&timebase); - /* Sanity check: should never occur in practice */ - if (timebase.numer < 1 || timebase.denom < 1) { + // Check that timebase.numer and timebase.denom can be casted to + // _PyTime_t. In practice, timebase uses uint32_t, so casting cannot + // overflow. At the end, only make sure that the type is uint32_t + // (_PyTime_t is 64-bit long). + Py_BUILD_ASSERT(sizeof(timebase.numer) <= sizeof(_PyTime_t)); + Py_BUILD_ASSERT(sizeof(timebase.denom) <= sizeof(_PyTime_t)); + _PyTime_t numer = (_PyTime_t)timebase.numer; + _PyTime_t denom = (_PyTime_t)timebase.denom; + + // Known time bases: + // + // * (1, 1) on Intel: 1 ns + // * (1000000000, 33333335) on PowerPC: ~30 ns + // * (1000000000, 25000000) on PowerPC: 40 ns + if (_PyTimeFraction_Set(base, numer, denom) < 0) { if (raise) { PyErr_SetString(PyExc_RuntimeError, "invalid mach_timebase_info"); } return -1; } - - /* Check that timebase.numer and timebase.denom can be casted to - _PyTime_t. In practice, timebase uses uint32_t, so casting cannot - overflow. At the end, only make sure that the type is uint32_t - (_PyTime_t is 64-bit long). */ - static_assert(sizeof(timebase.numer) <= sizeof(_PyTime_t), - "timebase.numer is larger than _PyTime_t"); - static_assert(sizeof(timebase.denom) <= sizeof(_PyTime_t), - "timebase.denom is larger than _PyTime_t"); - - /* Make sure that _PyTime_MulDiv(ticks, timebase_numer, timebase_denom) - cannot overflow. - - Known time bases: - - * (1, 1) on Intel - * (1000000000, 33333335) or (1000000000, 25000000) on PowerPC - - None of these time bases can overflow with 64-bit _PyTime_t, but - check for overflow, just in case. */ - if ((_PyTime_t)timebase.numer > _PyTime_MAX / (_PyTime_t)timebase.denom) { - if (raise) { - PyErr_SetString(PyExc_OverflowError, - "mach_timebase_info is too large"); - } - return -1; - } - - *pnumer = (_PyTime_t)timebase.numer; - *pdenom = (_PyTime_t)timebase.denom; return 0; } #endif @@ -1109,17 +1135,16 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) } #elif defined(__APPLE__) - static _PyTime_t timebase_numer = 0; - static _PyTime_t timebase_denom = 0; - if (timebase_denom == 0) { - if (py_mach_timebase_info(&timebase_numer, &timebase_denom, raise_exc) < 0) { + static _PyTimeFraction base = {0, 0}; + if (base.denom == 0) { + if (py_mach_timebase_info(&base, raise_exc) < 0) { return -1; } } if (info) { info->implementation = "mach_absolute_time()"; - info->resolution = (double)timebase_numer / (double)timebase_denom * 1e-9; + info->resolution = _PyTimeFraction_Resolution(&base); info->monotonic = 1; info->adjustable = 0; } @@ -1129,7 +1154,7 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) assert(uticks <= (uint64_t)_PyTime_MAX); _PyTime_t ticks = (_PyTime_t)uticks; - _PyTime_t ns = _PyTime_MulDiv(ticks, timebase_numer, timebase_denom); + _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); *tp = pytime_from_nanoseconds(ns); #elif defined(__hpux) @@ -1213,7 +1238,7 @@ _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #ifdef MS_WINDOWS static int -py_win_perf_counter_frequency(LONGLONG *pfrequency, int raise) +py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) { LONGLONG frequency; @@ -1225,25 +1250,20 @@ py_win_perf_counter_frequency(LONGLONG *pfrequency, int raise) // Since Windows XP, frequency cannot be zero. assert(frequency >= 1); - /* Make also sure that (ticks * SEC_TO_NS) cannot overflow in - _PyTime_MulDiv(), with ticks < frequency. + Py_BUILD_ASSERT(sizeof(_PyTime_t) == sizeof(frequency)); + _PyTime_t denom = (_PyTime_t)frequency; - Known QueryPerformanceFrequency() values: - - * 10,000,000 (10 MHz): 100 ns resolution - * 3,579,545 Hz (3.6 MHz): 279 ns resolution - - None of these frequencies can overflow with 64-bit _PyTime_t, but - check for integer overflow just in case. */ - if (frequency > _PyTime_MAX / SEC_TO_NS) { + // Known QueryPerformanceFrequency() values: + // + // * 10,000,000 (10 MHz): 100 ns resolution + // * 3,579,545 Hz (3.6 MHz): 279 ns resolution + if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) { if (raise) { - PyErr_SetString(PyExc_OverflowError, - "QueryPerformanceFrequency is too large"); + PyErr_SetString(PyExc_RuntimeError, + "invalid QueryPerformanceFrequency"); } return -1; } - - *pfrequency = frequency; return 0; } @@ -1253,16 +1273,16 @@ py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) { assert(info == NULL || raise_exc); - static LONGLONG frequency = 0; - if (frequency == 0) { - if (py_win_perf_counter_frequency(&frequency, raise_exc) < 0) { + static _PyTimeFraction base = {0, 0}; + if (base.denom == 0) { + if (py_win_perf_counter_frequency(&base, raise_exc) < 0) { return -1; } } if (info) { info->implementation = "QueryPerformanceCounter()"; - info->resolution = 1.0 / (double)frequency; + info->resolution = _PyTimeFraction_Resolution(&base); info->monotonic = 1; info->adjustable = 0; } @@ -1278,7 +1298,7 @@ py_get_win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) "LONGLONG is larger than _PyTime_t"); ticks = (_PyTime_t)ticksll; - _PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)frequency); + _PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); *tp = pytime_from_nanoseconds(ns); return 0; } From 939fc6d6eab9b7ea8c244d513610dbdd556503a7 Mon Sep 17 00:00:00 2001 From: William Wen Date: Fri, 1 Dec 2023 14:18:16 -0800 Subject: [PATCH 067/442] gh-106922: Support multi-line error locations in traceback (attempt 2) (#112097) --- Doc/library/traceback.rst | 13 +- Lib/test/test_doctest.py | 3 + Lib/test/test_exceptions.py | 3 +- Lib/test/test_repl.py | 3 +- Lib/test/test_sys.py | 6 +- Lib/test/test_traceback.py | 435 ++++++++++++++++-- Lib/test/test_warnings/__init__.py | 4 +- Lib/traceback.py | 368 +++++++++++---- ...-11-15-01-36-04.gh-issue-106922.qslOVH.rst | 1 + 9 files changed, 709 insertions(+), 127 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-15-01-36-04.gh-issue-106922.qslOVH.rst diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 80dda5ec520d7a..2d5ea19b2cb892 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -523,27 +523,32 @@ The output for the example would look similar to this: *** print_tb: File "", line 10, in lumberjack() + ~~~~~~~~~~^^ *** print_exception: Traceback (most recent call last): File "", line 10, in lumberjack() + ~~~~~~~~~~^^ File "", line 4, in lumberjack bright_side_of_life() + ~~~~~~~~~~~~~~~~~~~^^ IndexError: tuple index out of range *** print_exc: Traceback (most recent call last): File "", line 10, in lumberjack() + ~~~~~~~~~~^^ File "", line 4, in lumberjack bright_side_of_life() + ~~~~~~~~~~~~~~~~~~~^^ IndexError: tuple index out of range *** format_exc, first and last line: Traceback (most recent call last): IndexError: tuple index out of range *** format_exception: ['Traceback (most recent call last):\n', - ' File "", line 10, in \n lumberjack()\n', - ' File "", line 4, in lumberjack\n bright_side_of_life()\n', + ' File "", line 10, in \n lumberjack()\n ~~~~~~~~~~^^\n', + ' File "", line 4, in lumberjack\n bright_side_of_life()\n ~~~~~~~~~~~~~~~~~~~^^\n', ' File "", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n', 'IndexError: tuple index out of range\n'] *** extract_tb: @@ -551,8 +556,8 @@ The output for the example would look similar to this: , line 4 in lumberjack>, , line 7 in bright_side_of_life>] *** format_tb: - [' File "", line 10, in \n lumberjack()\n', - ' File "", line 4, in lumberjack\n bright_side_of_life()\n', + [' File "", line 10, in \n lumberjack()\n ~~~~~~~~~~^^\n', + ' File "", line 4, in lumberjack\n bright_side_of_life()\n ~~~~~~~~~~~~~~~~~~~^^\n', ' File "", line 7, in bright_side_of_life\n return tuple()[0]\n ~~~~~~~^^^\n'] *** tb_lineno: 10 diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 772dbd1d021305..36328f8086c7ad 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -2922,6 +2922,9 @@ def test_unicode(): """ Traceback (most recent call last): File ... exec(compile(example.source, filename, "single", + ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + compileflags, True), test.globs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "", line 1, in raise Exception('clé') Exception: clé diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 8ccf08703e5389..c57488e44aecc6 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2080,6 +2080,7 @@ def test_multiline_not_highlighted(self): """, [ ' 1 < 2 and', + ' 3 > 4', 'AssertionError', ], ), @@ -2087,7 +2088,7 @@ def test_multiline_not_highlighted(self): for source, expected in cases: with self.subTest(source): result = self.write_source(source) - self.assertEqual(result[-2:], expected) + self.assertEqual(result[-len(expected):], expected) class SyntaxErrorTests(unittest.TestCase): diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 7533376e015e73..a28d1595f44533 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -161,10 +161,11 @@ def foo(x): output = kill_python(p) self.assertEqual(p.returncode, 0) - traceback_lines = output.splitlines()[-7:-1] + traceback_lines = output.splitlines()[-8:-1] expected_lines = [ ' File "", line 1, in ', ' foo(0)', + ' ~~~^^^', ' File "", line 2, in foo', ' 1 / x', ' ~~^~~', diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 7f49fb004272bb..0028281596fa4b 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1115,8 +1115,10 @@ def check(tracebacklimit, expected): b'Traceback (most recent call last):', b' File "", line 8, in ', b' f2()', + b' ~~^^', b' File "", line 6, in f2', b' f1()', + b' ~~^^', b' File "", line 4, in f1', b' 1 / 0', b' ~~^~~', @@ -1124,8 +1126,8 @@ def check(tracebacklimit, expected): ] check(10, traceback) check(3, traceback) - check(2, traceback[:1] + traceback[3:]) - check(1, traceback[:1] + traceback[5:]) + check(2, traceback[:1] + traceback[4:]) + check(1, traceback[:1] + traceback[7:]) check(0, [traceback[-1]]) check(-1, [traceback[-1]]) check(1<<1000, traceback) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index c58d979bdd0115..b60e06ff37f494 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -578,6 +578,7 @@ def f(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+1}, in f\n' ' if True: raise ValueError("basic caret tests")\n' ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' @@ -596,6 +597,7 @@ def f_with_unicode(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+1}, in f_with_unicode\n' ' if True: raise ValueError("Ĥellö Wörld")\n' ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' @@ -613,6 +615,7 @@ def foo(a: THIS_DOES_NOT_EXIST ) -> int: 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+1}, in f_with_type\n' ' def foo(a: THIS_DOES_NOT_EXIST ) -> int:\n' ' ^^^^^^^^^^^^^^^^^^^\n' @@ -633,9 +636,14 @@ def f_with_multiline(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+1}, in f_with_multiline\n' ' if True: raise ValueError(\n' - ' ^^^^^^^^^^^^^^^^^' + ' ^^^^^^^^^^^^^^^^^\n' + ' "error over multiple lines"\n' + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' + ' )\n' + ' ^' ) result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) @@ -664,9 +672,10 @@ def f_with_multiline(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n' ' return compile(code, "?", "exec")\n' - ' ^^^^^^^^^^^^^^^^^^^^^^^^^^\n' + ' ~~~~~~~^^^^^^^^^^^^^^^^^^^\n' ' File "?", line 7\n' ' foo(a, z\n' ' ^' @@ -689,9 +698,12 @@ def f_with_multiline(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n' ' 2 + 1 /\n' - ' ^^^' + ' ~~^\n' + ' 0\n' + ' ~' ) result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) @@ -706,6 +718,7 @@ def f_with_binary_operator(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n' ' return 10 + divisor / 0 + 30\n' ' ~~~~~~~~^~~\n' @@ -723,6 +736,7 @@ def f_with_binary_operator(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n' ' return 10 + áóí / 0 + 30\n' ' ~~~~^~~\n' @@ -740,6 +754,7 @@ def f_with_binary_operator(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n' ' return 10 + divisor // 0 + 30\n' ' ~~~~~~~~^^~~\n' @@ -751,16 +766,102 @@ def test_caret_for_binary_operators_with_spaces_and_parenthesis(self): def f_with_binary_operator(): a = 1 b = "" - return ( a ) + b + return ( a ) +b lineno_f = f_with_binary_operator.__code__.co_firstlineno expected_error = ( 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n' - ' return ( a ) + b\n' - ' ~~~~~~~~~~^~~\n' + ' return ( a ) +b\n' + ' ~~~~~~~~~~^~\n' + ) + result_lines = self.get_exception(f_with_binary_operator) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_binary_operators_multiline(self): + def f_with_binary_operator(): + b = 1 + c = "" + a = b \ + +\ + c # test + return a + + lineno_f = f_with_binary_operator.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n' + ' a = b \\\n' + ' ~~~~~~\n' + ' +\\\n' + ' ^~\n' + ' c # test\n' + ' ~\n' + ) + result_lines = self.get_exception(f_with_binary_operator) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_binary_operators_multiline_two_char(self): + def f_with_binary_operator(): + b = 1 + c = "" + a = ( + (b # test + + ) \ + # + + << (c # test + \ + ) # test + ) + return a + + lineno_f = f_with_binary_operator.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+4}, in f_with_binary_operator\n' + ' (b # test +\n' + ' ~~~~~~~~~~~~\n' + ' ) \\\n' + ' ~~~~\n' + ' # +\n' + ' ~~~\n' + ' << (c # test\n' + ' ^^~~~~~~~~~~~\n' + ' \\\n' + ' ~\n' + ' ) # test\n' + ' ~\n' + ) + result_lines = self.get_exception(f_with_binary_operator) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_binary_operators_multiline_with_unicode(self): + def f_with_binary_operator(): + b = 1 + a = ("ááá" + + "áá") + b + return a + + lineno_f = f_with_binary_operator.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+2}, in f_with_binary_operator\n' + ' a = ("ááá" +\n' + ' ~~~~~~~~\n' + ' "áá") + b\n' + ' ~~~~~~^~~\n' ) result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) @@ -775,6 +876,7 @@ def f_with_subscript(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n' " return some_dict['x']['y']['z']\n" ' ~~~~~~~~~~~~~~~~~~~^^^^^\n' @@ -792,6 +894,7 @@ def f_with_subscript(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n' " return some_dict['ó']['á']['í']['beta']\n" ' ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^\n' @@ -810,6 +913,7 @@ def f_with_binary_operator(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n' ' return b [ a ] + c\n' ' ~~~~~~^^^^^^^^^\n' @@ -817,6 +921,226 @@ def f_with_binary_operator(): result_lines = self.get_exception(f_with_binary_operator) self.assertEqual(result_lines, expected_error.splitlines()) + def test_caret_for_subscript_multiline(self): + def f_with_subscript(): + bbbbb = {} + ccc = 1 + ddd = 2 + b = bbbbb \ + [ ccc # test + + + ddd \ + + ] # test + return b + + lineno_f = f_with_subscript.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+4}, in f_with_subscript\n' + ' b = bbbbb \\\n' + ' ~~~~~~~\n' + ' [ ccc # test\n' + ' ^^^^^^^^^^^^^\n' + ' \n' + ' \n' + ' + ddd \\\n' + ' ^^^^^^^^\n' + ' \n' + ' \n' + ' ] # test\n' + ' ^\n' + ) + result_lines = self.get_exception(f_with_subscript) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_call(self): + def f_with_call(): + def f1(a): + def f2(b): + raise RuntimeError("fail") + return f2 + return f1("x")("y") + + lineno_f = f_with_call.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+5}, in f_with_call\n' + ' return f1("x")("y")\n' + ' ~~~~~~~^^^^^\n' + f' File "{__file__}", line {lineno_f+3}, in f2\n' + ' raise RuntimeError("fail")\n' + ) + result_lines = self.get_exception(f_with_call) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_call_unicode(self): + def f_with_call(): + def f1(a): + def f2(b): + raise RuntimeError("fail") + return f2 + return f1("ó")("á") + + lineno_f = f_with_call.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+5}, in f_with_call\n' + ' return f1("ó")("á")\n' + ' ~~~~~~~^^^^^\n' + f' File "{__file__}", line {lineno_f+3}, in f2\n' + ' raise RuntimeError("fail")\n' + ) + result_lines = self.get_exception(f_with_call) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_call_with_spaces_and_parenthesis(self): + def f_with_binary_operator(): + def f(a): + raise RuntimeError("fail") + return f ( "x" ) + 2 + + lineno_f = f_with_binary_operator.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n' + ' return f ( "x" ) + 2\n' + ' ~~~~~~^^^^^^^^^^^\n' + f' File "{__file__}", line {lineno_f+2}, in f\n' + ' raise RuntimeError("fail")\n' + ) + result_lines = self.get_exception(f_with_binary_operator) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_caret_for_call_multiline(self): + def f_with_call(): + class C: + def y(self, a): + def f(b): + raise RuntimeError("fail") + return f + def g(x): + return C() + a = (g(1).y)( + 2 + )(3)(4) + return a + + lineno_f = f_with_call.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+8}, in f_with_call\n' + ' a = (g(1).y)(\n' + ' ~~~~~~~~~\n' + ' 2\n' + ' ~\n' + ' )(3)(4)\n' + ' ~^^^\n' + f' File "{__file__}", line {lineno_f+4}, in f\n' + ' raise RuntimeError("fail")\n' + ) + result_lines = self.get_exception(f_with_call) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_many_lines(self): + def f(): + x = 1 + if True: x += ( + "a" + + "a" + ) # test + + lineno_f = f.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+2}, in f\n' + ' if True: x += (\n' + ' ^^^^^^\n' + ' ...<2 lines>...\n' + ' ) # test\n' + ' ^\n' + ) + result_lines = self.get_exception(f) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_many_lines_no_caret(self): + def f(): + x = 1 + x += ( + "a" + + "a" + ) + + lineno_f = f.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+2}, in f\n' + ' x += (\n' + ' ...<2 lines>...\n' + ' )\n' + ) + result_lines = self.get_exception(f) + self.assertEqual(result_lines, expected_error.splitlines()) + + def test_many_lines_binary_op(self): + def f_with_binary_operator(): + b = 1 + c = "a" + a = ( + b + + b + ) + ( + c + + c + + c + ) + return a + + lineno_f = f_with_binary_operator.__code__.co_firstlineno + expected_error = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+3}, in f_with_binary_operator\n' + ' a = (\n' + ' ~\n' + ' b +\n' + ' ~~~\n' + ' b\n' + ' ~\n' + ' ) + (\n' + ' ~~^~~\n' + ' c +\n' + ' ~~~\n' + ' ...<2 lines>...\n' + ' )\n' + ' ~\n' + ) + result_lines = self.get_exception(f_with_binary_operator) + self.assertEqual(result_lines, expected_error.splitlines()) + def test_traceback_specialization_with_syntax_error(self): bytecode = compile("1 / 0 / 1 / 2\n", TESTFN, "exec") @@ -833,6 +1157,7 @@ def test_traceback_specialization_with_syntax_error(self): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{TESTFN}", line {lineno_f}, in \n' " 1 $ 0 / 1 / 2\n" ' ^^^^^\n' @@ -855,6 +1180,7 @@ def test_traceback_very_long_line(self): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{TESTFN}", line {lineno_f}, in \n' f' {source}\n' f' {" "*len("if True: ") + "^"*256}\n' @@ -872,6 +1198,7 @@ def f_with_subscript(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_f+2}, in f_with_subscript\n' " some_dict['x']['y']['z']\n" ' ~~~~~~~~~~~~~~~~~~~^^^^^\n' @@ -891,6 +1218,7 @@ def exc(): f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | callable()\n' + f' | ~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n' f' | if True: raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n' f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' @@ -956,6 +1284,7 @@ def g(): pass 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_applydescs + 1}, in applydecs\n' ' @dec_error\n' ' ^^^^^^^^^\n' @@ -974,6 +1303,7 @@ class A: pass 'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' ' callable()\n' + ' ~~~~~~~~^^\n' f' File "{__file__}", line {lineno_applydescs_class + 1}, in applydecs_class\n' ' @dec_error\n' ' ^^^^^^^^^\n' @@ -992,6 +1322,7 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", " .method", " ^^^^^^", @@ -1008,6 +1339,7 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", " method", ] @@ -1023,6 +1355,7 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", " . method", " ^^^^^^", @@ -1038,6 +1371,7 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f", " width", ] @@ -1054,6 +1388,7 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 2}, in f", " raise ValueError(width)", ] @@ -1072,9 +1407,12 @@ def f(): "Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", " callable()", + " ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 4}, in f", f" print(1, www(", - f" ^^^^^^^", + f" ~~~~~~^", + f" th))", + f" ^^^^^", ] self.assertEqual(actual, expected) @@ -1089,6 +1427,7 @@ def f(): f"Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", f" callable()", + f" ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 3}, in f", f" return 说明说明 / şçöğıĤellö", f" ~~~~~~~~~^~~~~~~~~~~~", @@ -1105,6 +1444,7 @@ def f(): f"Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", f" callable()", + f" ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 1}, in f", f' return "✨🐍" + func_说明说明("📗🚛",', f" ^^^^^^^^^^^^^", @@ -1127,6 +1467,7 @@ def f(): f"Traceback (most recent call last):", f" File \"{__file__}\", line {self.callable_line}, in get_exception", f" callable()", + f" ~~~~~~~~^^", f" File \"{__file__}\", line {f.__code__.co_firstlineno + 8}, in f", f' return my_dct["✨🚛✨"]["说明"]["🐍"]["说明"]["🐍🐍"]', f" ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^", @@ -1141,6 +1482,7 @@ def f(): expected = ['Traceback (most recent call last):', f' File "{__file__}", line {self.callable_line}, in get_exception', ' callable()', + ' ~~~~~~~~^^', f' File "{__file__}", line {f.__code__.co_firstlineno + 1}, in f', ' raise MemoryError()'] self.assertEqual(actual, expected) @@ -1187,6 +1529,14 @@ class TracebackFormatMixin: def some_exception(self): raise KeyError('blah') + def _filter_debug_ranges(self, expected): + return [line for line in expected if not set(line.strip()) <= set("^~")] + + def _maybe_filter_debug_ranges(self, expected): + if not self.DEBUG_RANGES: + return self._filter_debug_ranges(expected) + return expected + @cpython_only def check_traceback_format(self, cleanup_func=None): from _testcapi import traceback_print @@ -1199,6 +1549,11 @@ def check_traceback_format(self, cleanup_func=None): cleanup_func(tb.tb_next) traceback_fmt = 'Traceback (most recent call last):\n' + \ ''.join(traceback.format_tb(tb)) + # clear caret lines from traceback_fmt since internal API does + # not emit them + traceback_fmt = "\n".join( + self._filter_debug_ranges(traceback_fmt.splitlines()) + ) + "\n" file_ = StringIO() traceback_print(tb, file_) python_fmt = file_.getvalue() @@ -1291,12 +1646,16 @@ def f(): 'Traceback (most recent call last):\n' f' File "{__file__}", line {lineno_f+5}, in _check_recursive_traceback_display\n' ' f()\n' + ' ~^^\n' f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' + ' ~^^\n' f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' + ' ~^^\n' f' File "{__file__}", line {lineno_f+1}, in f\n' ' f()\n' + ' ~^^\n' # XXX: The following line changes depending on whether the tests # are run through the interactive interpreter or with -m # It also varies depending on the platform (stack size) @@ -1305,7 +1664,7 @@ def f(): 'RecursionError: maximum recursion depth exceeded\n' ) - expected = result_f.splitlines() + expected = self._maybe_filter_debug_ranges(result_f.splitlines()) actual = stderr_f.getvalue().splitlines() # Check the output text matches expectations @@ -1337,13 +1696,13 @@ def g(count=10): result_g = ( f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' ' [Previous line repeated 7 more times]\n' f' File "{__file__}", line {lineno_g+3}, in g\n' ' raise ValueError\n' @@ -1353,11 +1712,10 @@ def g(count=10): 'Traceback (most recent call last):\n' f' File "{__file__}", line {lineno_g+7}, in _check_recursive_traceback_display\n' ' g()\n' + ' ~^^\n' ) - expected = (tb_line + result_g).splitlines() + expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines()) actual = stderr_g.getvalue().splitlines() - if not self.DEBUG_RANGES: - expected = [line for line in expected if not set(line.strip()) == {"^"}] self.assertEqual(actual, expected) # Check 2 different repetitive sections @@ -1379,23 +1737,23 @@ def h(count=10): 'Traceback (most recent call last):\n' f' File "{__file__}", line {lineno_h+7}, in _check_recursive_traceback_display\n' ' h()\n' + ' ~^^\n' f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_h+2}, in h\n' ' return h(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' ' [Previous line repeated 7 more times]\n' f' File "{__file__}", line {lineno_h+3}, in h\n' ' g()\n' + ' ~^^\n' ) - expected = (result_h + result_g).splitlines() + expected = self._maybe_filter_debug_ranges((result_h + result_g).splitlines()) actual = stderr_h.getvalue().splitlines() - if not self.DEBUG_RANGES: - expected = [line for line in expected if not set(line.strip()) == {"^"}] self.assertEqual(actual, expected) # Check the boundary conditions. First, test just below the cutoff. @@ -1409,26 +1767,25 @@ def h(count=10): result_g = ( f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+3}, in g\n' ' raise ValueError\n' 'ValueError\n' ) tb_line = ( 'Traceback (most recent call last):\n' - f' File "{__file__}", line {lineno_g+81}, in _check_recursive_traceback_display\n' + f' File "{__file__}", line {lineno_g+80}, in _check_recursive_traceback_display\n' ' g(traceback._RECURSIVE_CUTOFF)\n' + ' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' ) - expected = (tb_line + result_g).splitlines() + expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines()) actual = stderr_g.getvalue().splitlines() - if not self.DEBUG_RANGES: - expected = [line for line in expected if not set(line.strip()) == {"^"}] self.assertEqual(actual, expected) # Second, test just above the cutoff. @@ -1442,13 +1799,13 @@ def h(count=10): result_g = ( f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' f' File "{__file__}", line {lineno_g+2}, in g\n' ' return g(count-1)\n' - ' ^^^^^^^^^^\n' + ' ~^^^^^^^^^\n' ' [Previous line repeated 1 more time]\n' f' File "{__file__}", line {lineno_g+3}, in g\n' ' raise ValueError\n' @@ -1456,13 +1813,12 @@ def h(count=10): ) tb_line = ( 'Traceback (most recent call last):\n' - f' File "{__file__}", line {lineno_g+114}, in _check_recursive_traceback_display\n' + f' File "{__file__}", line {lineno_g+112}, in _check_recursive_traceback_display\n' ' g(traceback._RECURSIVE_CUTOFF + 1)\n' + ' ~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' ) - expected = (tb_line + result_g).splitlines() + expected = self._maybe_filter_debug_ranges((tb_line + result_g).splitlines()) actual = stderr_g.getvalue().splitlines() - if not self.DEBUG_RANGES: - expected = [line for line in expected if not set(line.strip()) == {"^"}] self.assertEqual(actual, expected) @requires_debug_ranges() @@ -1942,6 +2298,7 @@ def exc(): f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 1}, in exc\n' f' | raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])\n' f' | ExceptionGroup: eg (2 sub-exceptions)\n' @@ -1977,6 +2334,7 @@ def exc(): f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n' f' | raise EG("eg2", [ValueError(3), TypeError(4)]) from e\n' f' | ExceptionGroup: eg2 (2 sub-exceptions)\n' @@ -2028,6 +2386,7 @@ def exc(): f'Traceback (most recent call last):\n' f' File "{__file__}", line {self.callable_line}, in get_exception\n' f' exception_or_callable()\n' + f' ~~~~~~~~~~~~~~~~~~~~~^^\n' f' File "{__file__}", line {exc.__code__.co_firstlineno + 8}, in exc\n' f' raise ImportError(5)\n' f'ImportError: 5\n') @@ -2074,6 +2433,7 @@ def exc(): f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 11}, in exc\n' f' | raise EG("top", [VE(5)])\n' f' | ExceptionGroup: top (1 sub-exception)\n' @@ -2233,6 +2593,7 @@ def exc(): expected = (f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n' f' | raise ExceptionGroup("nested", excs)\n' f' | ExceptionGroup: nested (2 sub-exceptions)\n' @@ -2284,6 +2645,7 @@ def exc(): expected = (f' + Exception Group Traceback (most recent call last):\n' f' | File "{__file__}", line {self.callable_line}, in get_exception\n' f' | exception_or_callable()\n' + f' | ~~~~~~~~~~~~~~~~~~~~~^^\n' f' | File "{__file__}", line {exc.__code__.co_firstlineno + 10}, in exc\n' f' | raise ExceptionGroup("nested", excs)\n' f' | ExceptionGroup: nested (2 sub-exceptions)\n' @@ -2552,7 +2914,7 @@ def test_basics(self): def test_lazy_lines(self): linecache.clearcache() f = traceback.FrameSummary("f", 1, "dummy", lookup_line=False) - self.assertEqual(None, f._line) + self.assertEqual(None, f._lines) linecache.lazycache("f", globals()) self.assertEqual( '"""Test cases for traceback module"""', @@ -3143,6 +3505,7 @@ def test_exception_group_format(self): f' | Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+9}, in _get_exception_group', f' | f()', + f' | ~^^', f' | File "{__file__}", line {lno_f+1}, in f', f' | 1/0', f' | ~^~', @@ -3151,6 +3514,7 @@ def test_exception_group_format(self): f' | Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+13}, in _get_exception_group', f' | g(42)', + f' | ~^^^^', f' | File "{__file__}", line {lno_g+1}, in g', f' | raise ValueError(v)', f' | ValueError: 42', @@ -3159,6 +3523,7 @@ def test_exception_group_format(self): f' | Traceback (most recent call last):', f' | File "{__file__}", line {lno_g+20}, in _get_exception_group', f' | g(24)', + f' | ~^^^^', f' | File "{__file__}", line {lno_g+1}, in g', f' | raise ValueError(v)', f' | ValueError: 24', diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 232480c46e0a00..50b0f3fff04c57 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1260,8 +1260,8 @@ def test_conflicting_envvar_and_command_line(self): b" File \"\", line 1, in ", b' import sys, warnings; sys.stdout.write(str(sys.warnoptions)); warnings.w' b"arn('Message', DeprecationWarning)", - b' ^^^^^^^^^^' - b'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', + b' ~~~~~~~~~~' + b'~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^', b"DeprecationWarning: Message"]) def test_default_filter_configuration(self): diff --git a/Lib/traceback.py b/Lib/traceback.py index 5d83f85ac3edb0..a0485a7023d07d 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -274,7 +274,7 @@ class FrameSummary: """ __slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno', - 'name', '_line', 'locals') + 'name', '_lines', '_lines_dedented', 'locals') def __init__(self, filename, lineno, name, *, lookup_line=True, locals=None, line=None, @@ -290,15 +290,16 @@ def __init__(self, filename, lineno, name, *, lookup_line=True, """ self.filename = filename self.lineno = lineno + self.end_lineno = lineno if end_lineno is None else end_lineno + self.colno = colno + self.end_colno = end_colno self.name = name - self._line = line + self._lines = line + self._lines_dedented = None if lookup_line: self.line self.locals = {k: _safe_string(v, 'local', func=repr) for k, v in locals.items()} if locals else None - self.end_lineno = end_lineno - self.colno = colno - self.end_colno = end_colno def __eq__(self, other): if isinstance(other, FrameSummary): @@ -323,19 +324,39 @@ def __repr__(self): def __len__(self): return 4 + def _set_lines(self): + if ( + self._lines is None + and self.lineno is not None + and self.end_lineno is not None + ): + lines = [] + for lineno in range(self.lineno, self.end_lineno + 1): + # treat errors (empty string) and empty lines (newline) as the same + lines.append(linecache.getline(self.filename, lineno).rstrip()) + self._lines = "\n".join(lines) + "\n" + @property - def _original_line(self): + def _original_lines(self): # Returns the line as-is from the source, without modifying whitespace. - self.line - return self._line + self._set_lines() + return self._lines + + @property + def _dedented_lines(self): + # Returns _original_lines, but dedented + self._set_lines() + if self._lines_dedented is None and self._lines is not None: + self._lines_dedented = textwrap.dedent(self._lines) + return self._lines_dedented @property def line(self): - if self._line is None: - if self.lineno is None: - return None - self._line = linecache.getline(self.filename, self.lineno) - return self._line.strip() + self._set_lines() + if self._lines is None: + return None + # return only the first line, stripped + return self._lines.partition("\n")[0].strip() def walk_stack(f): @@ -487,56 +508,135 @@ def format_frame_summary(self, frame_summary): filename = "" row.append(' File "{}", line {}, in {}\n'.format( filename, frame_summary.lineno, frame_summary.name)) - if frame_summary.line: - stripped_line = frame_summary.line.strip() - row.append(' {}\n'.format(stripped_line)) - - line = frame_summary._original_line - orig_line_len = len(line) - frame_line_len = len(frame_summary.line.lstrip()) - stripped_characters = orig_line_len - frame_line_len + if frame_summary._dedented_lines and frame_summary._dedented_lines.strip(): if ( - frame_summary.colno is not None - and frame_summary.end_colno is not None + frame_summary.colno is None or + frame_summary.end_colno is None ): - start_offset = _byte_offset_to_character_offset( - line, frame_summary.colno) - end_offset = _byte_offset_to_character_offset( - line, frame_summary.end_colno) - code_segment = line[start_offset:end_offset] + # only output first line if column information is missing + row.append(textwrap.indent(frame_summary.line, ' ') + "\n") + else: + # get first and last line + all_lines_original = frame_summary._original_lines.splitlines() + first_line = all_lines_original[0] + # assume all_lines_original has enough lines (since we constructed it) + last_line = all_lines_original[frame_summary.end_lineno - frame_summary.lineno] + + # character index of the start/end of the instruction + start_offset = _byte_offset_to_character_offset(first_line, frame_summary.colno) + end_offset = _byte_offset_to_character_offset(last_line, frame_summary.end_colno) + + all_lines = frame_summary._dedented_lines.splitlines()[ + :frame_summary.end_lineno - frame_summary.lineno + 1 + ] + # adjust start/end offset based on dedent + dedent_characters = len(first_line) - len(all_lines[0]) + start_offset = max(0, start_offset - dedent_characters) + end_offset = max(0, end_offset - dedent_characters) + + # When showing this on a terminal, some of the non-ASCII characters + # might be rendered as double-width characters, so we need to take + # that into account when calculating the length of the line. + dp_start_offset = _display_width(all_lines[0], offset=start_offset) + dp_end_offset = _display_width(all_lines[-1], offset=end_offset) + + # get exact code segment corresponding to the instruction + segment = "\n".join(all_lines) + segment = segment[start_offset:len(segment) - (len(all_lines[-1]) - end_offset)] + + # attempt to parse for anchors anchors = None - if frame_summary.lineno == frame_summary.end_lineno: - with suppress(Exception): - anchors = _extract_caret_anchors_from_line_segment(code_segment) - else: - # Don't count the newline since the anchors only need to - # go up until the last character of the line. - end_offset = len(line.rstrip()) - - # show indicators if primary char doesn't span the frame line - if end_offset - start_offset < len(stripped_line) or ( - anchors and anchors.right_start_offset - anchors.left_end_offset > 0): - # When showing this on a terminal, some of the non-ASCII characters - # might be rendered as double-width characters, so we need to take - # that into account when calculating the length of the line. - dp_start_offset = _display_width(line, start_offset) + 1 - dp_end_offset = _display_width(line, end_offset) + 1 - - row.append(' ') - row.append(' ' * (dp_start_offset - stripped_characters)) - - if anchors: - dp_left_end_offset = _display_width(code_segment, anchors.left_end_offset) - dp_right_start_offset = _display_width(code_segment, anchors.right_start_offset) - row.append(anchors.primary_char * dp_left_end_offset) - row.append(anchors.secondary_char * (dp_right_start_offset - dp_left_end_offset)) - row.append(anchors.primary_char * (dp_end_offset - dp_start_offset - dp_right_start_offset)) - else: - row.append('^' * (dp_end_offset - dp_start_offset)) + with suppress(Exception): + anchors = _extract_caret_anchors_from_line_segment(segment) + + # only use carets if there are anchors or the carets do not span all lines + show_carets = False + if anchors or all_lines[0][:start_offset].lstrip() or all_lines[-1][end_offset:].rstrip(): + show_carets = True + + result = [] + + # only display first line, last line, and lines around anchor start/end + significant_lines = {0, len(all_lines) - 1} + + anchors_left_end_offset = 0 + anchors_right_start_offset = 0 + primary_char = "^" + secondary_char = "^" + if anchors: + anchors_left_end_offset = anchors.left_end_offset + anchors_right_start_offset = anchors.right_start_offset + # computed anchor positions do not take start_offset into account, + # so account for it here + if anchors.left_end_lineno == 0: + anchors_left_end_offset += start_offset + if anchors.right_start_lineno == 0: + anchors_right_start_offset += start_offset + + # account for display width + anchors_left_end_offset = _display_width( + all_lines[anchors.left_end_lineno], offset=anchors_left_end_offset + ) + anchors_right_start_offset = _display_width( + all_lines[anchors.right_start_lineno], offset=anchors_right_start_offset + ) - row.append('\n') + primary_char = anchors.primary_char + secondary_char = anchors.secondary_char + significant_lines.update( + range(anchors.left_end_lineno - 1, anchors.left_end_lineno + 2) + ) + significant_lines.update( + range(anchors.right_start_lineno - 1, anchors.right_start_lineno + 2) + ) + # remove bad line numbers + significant_lines.discard(-1) + significant_lines.discard(len(all_lines)) + + def output_line(lineno): + """output all_lines[lineno] along with carets""" + result.append(all_lines[lineno] + "\n") + if not show_carets: + return + num_spaces = len(all_lines[lineno]) - len(all_lines[lineno].lstrip()) + carets = [] + num_carets = dp_end_offset if lineno == len(all_lines) - 1 else _display_width(all_lines[lineno]) + # compute caret character for each position + for col in range(num_carets): + if col < num_spaces or (lineno == 0 and col < dp_start_offset): + # before first non-ws char of the line, or before start of instruction + carets.append(' ') + elif anchors and ( + lineno > anchors.left_end_lineno or + (lineno == anchors.left_end_lineno and col >= anchors_left_end_offset) + ) and ( + lineno < anchors.right_start_lineno or + (lineno == anchors.right_start_lineno and col < anchors_right_start_offset) + ): + # within anchors + carets.append(secondary_char) + else: + carets.append(primary_char) + result.append("".join(carets) + "\n") + + # display significant lines + sig_lines_list = sorted(significant_lines) + for i, lineno in enumerate(sig_lines_list): + if i: + linediff = lineno - sig_lines_list[i - 1] + if linediff == 2: + # 1 line in between - just output it + output_line(lineno - 1) + elif linediff > 2: + # > 1 line in between - abbreviate + result.append(f"...<{linediff - 1} lines>...\n") + output_line(lineno) + + row.append( + textwrap.indent(textwrap.dedent("".join(result)), ' ', lambda line: True) + ) if frame_summary.locals: for name, value in sorted(frame_summary.locals.items()): row.append(' {name} = {value}\n'.format(name=name, value=value)) @@ -599,7 +699,9 @@ def _byte_offset_to_character_offset(str, offset): _Anchors = collections.namedtuple( "_Anchors", [ + "left_end_lineno", "left_end_offset", + "right_start_lineno", "right_start_offset", "primary_char", "secondary_char", @@ -608,59 +710,161 @@ def _byte_offset_to_character_offset(str, offset): ) def _extract_caret_anchors_from_line_segment(segment): + """ + Given source code `segment` corresponding to a FrameSummary, determine: + - for binary ops, the location of the binary op + - for indexing and function calls, the location of the brackets. + `segment` is expected to be a valid Python expression. + """ import ast try: - tree = ast.parse(segment) + # Without parentheses, `segment` is parsed as a statement. + # Binary ops, subscripts, and calls are expressions, so + # we can wrap them with parentheses to parse them as + # (possibly multi-line) expressions. + # e.g. if we try to highlight the addition in + # x = ( + # a + + # b + # ) + # then we would ast.parse + # a + + # b + # which is not a valid statement because of the newline. + # Adding brackets makes it a valid expression. + # ( + # a + + # b + # ) + # Line locations will be different than the original, + # which is taken into account later on. + tree = ast.parse(f"(\n{segment}\n)") except SyntaxError: return None if len(tree.body) != 1: return None - normalize = lambda offset: _byte_offset_to_character_offset(segment, offset) + lines = segment.splitlines() + + def normalize(lineno, offset): + """Get character index given byte offset""" + return _byte_offset_to_character_offset(lines[lineno], offset) + + def next_valid_char(lineno, col): + """Gets the next valid character index in `lines`, if + the current location is not valid. Handles empty lines. + """ + while lineno < len(lines) and col >= len(lines[lineno]): + col = 0 + lineno += 1 + assert lineno < len(lines) and col < len(lines[lineno]) + return lineno, col + + def increment(lineno, col): + """Get the next valid character index in `lines`.""" + col += 1 + lineno, col = next_valid_char(lineno, col) + return lineno, col + + def nextline(lineno, col): + """Get the next valid character at least on the next line""" + col = 0 + lineno += 1 + lineno, col = next_valid_char(lineno, col) + return lineno, col + + def increment_until(lineno, col, stop): + """Get the next valid non-"\\#" character that satisfies the `stop` predicate""" + while True: + ch = lines[lineno][col] + if ch in "\\#": + lineno, col = nextline(lineno, col) + elif not stop(ch): + lineno, col = increment(lineno, col) + else: + break + return lineno, col + + def setup_positions(expr, force_valid=True): + """Get the lineno/col position of the end of `expr`. If `force_valid` is True, + forces the position to be a valid character (e.g. if the position is beyond the + end of the line, move to the next line) + """ + # -2 since end_lineno is 1-indexed and because we added an extra + # bracket + newline to `segment` when calling ast.parse + lineno = expr.end_lineno - 2 + col = normalize(lineno, expr.end_col_offset) + return next_valid_char(lineno, col) if force_valid else (lineno, col) + statement = tree.body[0] match statement: case ast.Expr(expr): match expr: case ast.BinOp(): - operator_start = normalize(expr.left.end_col_offset) - operator_end = normalize(expr.right.col_offset) - operator_str = segment[operator_start:operator_end] - operator_offset = len(operator_str) - len(operator_str.lstrip()) + # ast gives these locations for BinOp subexpressions + # ( left_expr ) + ( right_expr ) + # left^^^^^ right^^^^^ + lineno, col = setup_positions(expr.left) - left_anchor = expr.left.end_col_offset + operator_offset - right_anchor = left_anchor + 1 + # First operator character is the first non-space/')' character + lineno, col = increment_until(lineno, col, lambda x: not x.isspace() and x != ')') + + # binary op is 1 or 2 characters long, on the same line, + # before the right subexpression + right_col = col + 1 if ( - operator_offset + 1 < len(operator_str) - and not operator_str[operator_offset + 1].isspace() + right_col < len(lines[lineno]) + and ( + # operator char should not be in the right subexpression + expr.right.lineno - 2 > lineno or + right_col < normalize(expr.right.lineno - 2, expr.right.col_offset) + ) + and not (ch := lines[lineno][right_col]).isspace() + and ch not in "\\#" ): - right_anchor += 1 + right_col += 1 - while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch in ")#"): - left_anchor += 1 - right_anchor += 1 - return _Anchors(normalize(left_anchor), normalize(right_anchor)) + # right_col can be invalid since it is exclusive + return _Anchors(lineno, col, lineno, right_col) case ast.Subscript(): - left_anchor = normalize(expr.value.end_col_offset) - right_anchor = normalize(expr.slice.end_col_offset + 1) - while left_anchor < len(segment) and ((ch := segment[left_anchor]).isspace() or ch != "["): - left_anchor += 1 - while right_anchor < len(segment) and ((ch := segment[right_anchor]).isspace() or ch != "]"): - right_anchor += 1 - if right_anchor < len(segment): - right_anchor += 1 - return _Anchors(left_anchor, right_anchor) + # ast gives these locations for value and slice subexpressions + # ( value_expr ) [ slice_expr ] + # value^^^^^ slice^^^^^ + # subscript^^^^^^^^^^^^^^^^^^^^ + + # find left bracket + left_lineno, left_col = setup_positions(expr.value) + left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '[') + # find right bracket (final character of expression) + right_lineno, right_col = setup_positions(expr, force_valid=False) + return _Anchors(left_lineno, left_col, right_lineno, right_col) + case ast.Call(): + # ast gives these locations for function call expressions + # ( func_expr ) (args, kwargs) + # func^^^^^ + # call^^^^^^^^^^^^^^^^^^^^^^^^ + + # find left bracket + left_lineno, left_col = setup_positions(expr.func) + left_lineno, left_col = increment_until(left_lineno, left_col, lambda x: x == '(') + # find right bracket (final character of expression) + right_lineno, right_col = setup_positions(expr, force_valid=False) + return _Anchors(left_lineno, left_col, right_lineno, right_col) return None _WIDE_CHAR_SPECIFIERS = "WF" -def _display_width(line, offset): +def _display_width(line, offset=None): """Calculate the extra amount of width space the given source code segment might take if it were to be displayed on a fixed width output device. Supports wide unicode characters and emojis.""" + if offset is None: + offset = len(line) + # Fast track for ASCII-only strings if line.isascii(): return offset diff --git a/Misc/NEWS.d/next/Library/2023-11-15-01-36-04.gh-issue-106922.qslOVH.rst b/Misc/NEWS.d/next/Library/2023-11-15-01-36-04.gh-issue-106922.qslOVH.rst new file mode 100644 index 00000000000000..b68e75ab87cd0b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-15-01-36-04.gh-issue-106922.qslOVH.rst @@ -0,0 +1 @@ +Display multiple lines with ``traceback`` when errors span multiple lines. From a74daba7ca8b68f47284d82d4604721b8748bbde Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 16:13:44 +0300 Subject: [PATCH 068/442] gh-112316: Improve docs of `inspect.signature` and `Signature.from_callable` (#112317) Co-authored-by: Alex Waygood --- Doc/library/inspect.rst | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index b463c0b6d0e402..94c5d1c8979afd 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -620,7 +620,7 @@ function. .. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) - Return a :class:`Signature` object for the given ``callable``:: + Return a :class:`Signature` object for the given *callable*:: >>> from inspect import signature >>> def foo(a, *, b:int, **kwargs): @@ -646,29 +646,30 @@ function. For objects defined in modules using stringized annotations (``from __future__ import annotations``), :func:`signature` will attempt to automatically un-stringize the annotations using - :func:`inspect.get_annotations()`. The - ``global``, ``locals``, and ``eval_str`` parameters are passed - into :func:`inspect.get_annotations()` when resolving the - annotations; see the documentation for :func:`inspect.get_annotations()` + :func:`get_annotations`. The + *global*, *locals*, and *eval_str* parameters are passed + into :func:`get_annotations` when resolving the + annotations; see the documentation for :func:`get_annotations` for instructions on how to use these parameters. Raises :exc:`ValueError` if no signature can be provided, and :exc:`TypeError` if that type of object is not supported. Also, - if the annotations are stringized, and ``eval_str`` is not false, - the ``eval()`` call(s) to un-stringize the annotations could - potentially raise any kind of exception. + if the annotations are stringized, and *eval_str* is not false, + the ``eval()`` call(s) to un-stringize the annotations in :func:`get_annotations` + could potentially raise any kind of exception. A slash(/) in the signature of a function denotes that the parameters prior to it are positional-only. For more info, see :ref:`the FAQ entry on positional-only parameters `. - .. versionadded:: 3.5 - ``follow_wrapped`` parameter. Pass ``False`` to get a signature of - ``callable`` specifically (``callable.__wrapped__`` will not be used to + .. versionchanged:: 3.5 + The *follow_wrapped* parameter was added. + Pass ``False`` to get a signature of + *callable* specifically (``callable.__wrapped__`` will not be used to unwrap decorated callables.) - .. versionadded:: 3.10 - ``globals``, ``locals``, and ``eval_str`` parameters. + .. versionchanged:: 3.10 + The *globals*, *locals*, and *eval_str* parameters were added. .. note:: @@ -752,12 +753,10 @@ function. Signature objects are also supported by generic function :func:`copy.replace`. - .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globalns=None, localns=None) + .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) Return a :class:`Signature` (or its subclass) object for a given callable - ``obj``. Pass ``follow_wrapped=False`` to get a signature of ``obj`` - without unwrapping its ``__wrapped__`` chain. ``globalns`` and - ``localns`` will be used as the namespaces when resolving annotations. + *obj*. This method simplifies subclassing of :class:`Signature`:: @@ -770,8 +769,8 @@ function. .. versionadded:: 3.5 - .. versionadded:: 3.10 - ``globalns`` and ``localns`` parameters. + .. versionchanged:: 3.10 + The *globals*, *locals*, and *eval_str* parameters were added. .. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty) From a35a30509820f956d6feeaa0dbf42e9ca82c12bb Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 2 Dec 2023 19:10:19 +0300 Subject: [PATCH 069/442] gh-112618: Make `Annotated` cache typed (#112619) Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 34 +++++++++++++++++++ Lib/typing.py | 11 +++--- ...-12-02-12-55-17.gh-issue-112618.7_FT8-.rst | 2 ++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4fbb410f26ab8d..3572df7737f652 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -8675,6 +8675,40 @@ class X(Annotated[int, (1, 10)]): ... self.assertEqual(X.__mro__, (X, int, object), "Annotated should be transparent.") + def test_annotated_cached_with_types(self): + class A(str): ... + class B(str): ... + + field_a1 = Annotated[str, A("X")] + field_a2 = Annotated[str, B("X")] + a1_metadata = field_a1.__metadata__[0] + a2_metadata = field_a2.__metadata__[0] + + self.assertIs(type(a1_metadata), A) + self.assertEqual(a1_metadata, A("X")) + self.assertIs(type(a2_metadata), B) + self.assertEqual(a2_metadata, B("X")) + self.assertIsNot(type(a1_metadata), type(a2_metadata)) + + field_b1 = Annotated[str, A("Y")] + field_b2 = Annotated[str, B("Y")] + b1_metadata = field_b1.__metadata__[0] + b2_metadata = field_b2.__metadata__[0] + + self.assertIs(type(b1_metadata), A) + self.assertEqual(b1_metadata, A("Y")) + self.assertIs(type(b2_metadata), B) + self.assertEqual(b2_metadata, B("Y")) + self.assertIsNot(type(b1_metadata), type(b2_metadata)) + + field_c1 = Annotated[int, 1] + field_c2 = Annotated[int, 1.0] + field_c3 = Annotated[int, True] + + self.assertIs(type(field_c1.__metadata__[0]), int) + self.assertIs(type(field_c2.__metadata__[0]), float) + self.assertIs(type(field_c3.__metadata__[0]), bool) + class TypeAliasTests(BaseTestCase): def test_canonical_usage_with_variable_annotation(self): diff --git a/Lib/typing.py b/Lib/typing.py index b3af701f8d5437..4c19aadabe3b87 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -490,7 +490,7 @@ def __getitem__(self, parameters): return self._getitem(self, parameters) -class _LiteralSpecialForm(_SpecialForm, _root=True): +class _TypedCacheSpecialForm(_SpecialForm, _root=True): def __getitem__(self, parameters): if not isinstance(parameters, tuple): parameters = (parameters,) @@ -723,7 +723,7 @@ def Optional(self, parameters): arg = _type_check(parameters, f"{self} requires a single type.") return Union[arg, type(None)] -@_LiteralSpecialForm +@_TypedCacheSpecialForm @_tp_cache(typed=True) def Literal(self, *parameters): """Special typing form to define literal types (a.k.a. value types). @@ -2005,8 +2005,9 @@ def __mro_entries__(self, bases): return (self.__origin__,) -@_SpecialForm -def Annotated(self, params): +@_TypedCacheSpecialForm +@_tp_cache(typed=True) +def Annotated(self, *params): """Add context-specific metadata to a type. Example: Annotated[int, runtime_check.Unsigned] indicates to the @@ -2053,7 +2054,7 @@ def Annotated(self, params): where T1, T2 etc. are TypeVars, which would be invalid, because only one type should be passed to Annotated. """ - if not isinstance(params, tuple) or len(params) < 2: + if len(params) < 2: raise TypeError("Annotated[...] should be used " "with at least two arguments (a type and an " "annotation).") diff --git a/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst new file mode 100644 index 00000000000000..c732de15609c96 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-02-12-55-17.gh-issue-112618.7_FT8-.rst @@ -0,0 +1,2 @@ +Fix a caching bug relating to :data:`typing.Annotated`. +``Annotated[str, True]`` is no longer identical to ``Annotated[str, 1]``. From 0229d2a9b1d6ce6daa6a773f92e3754e7dc86d50 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 2 Dec 2023 19:41:40 +0200 Subject: [PATCH 070/442] Docs: Use sphinx-notfound-page to show a nicer 404 page (#111084) --- Doc/conf.py | 8 +++++++- Doc/requirements.txt | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index f1b411126c4e87..be2a86e12fa2e4 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -24,7 +24,13 @@ 'sphinx.ext.doctest', ] -# Skip if downstream redistributors haven't installed it +# Skip if downstream redistributors haven't installed them +try: + import notfound.extension +except ImportError: + pass +else: + extensions.append('notfound.extension') try: import sphinxext.opengraph except ImportError: diff --git a/Doc/requirements.txt b/Doc/requirements.txt index ce87be2d392824..04334fd5a464d4 100644 --- a/Doc/requirements.txt +++ b/Doc/requirements.txt @@ -13,6 +13,7 @@ blurb sphinx-autobuild sphinxext-opengraph==0.7.5 +sphinx-notfound-page==1.0.0 # The theme used by the documentation is stored separately, so we need # to install that as well. From a9574c68f04695eecd19866faaf4cdee5965bc70 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 3 Dec 2023 02:39:43 +0300 Subject: [PATCH 071/442] gh-112139: Add `inspect.Signature.format` and use it in `pydoc` (#112143) Co-authored-by: Jelle Zijlstra --- Doc/library/inspect.rst | 11 +++ Lib/inspect.py | 12 +++ Lib/pydoc.py | 5 +- Lib/test/test_inspect/test_inspect.py | 96 +++++++++++++++++-- Lib/test/test_pydoc.py | 89 +++++++++++++++++ ...-11-16-10-42-15.gh-issue-112139.WpHosf.rst | 3 + 6 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-16-10-42-15.gh-issue-112139.WpHosf.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 94c5d1c8979afd..08522510f9ab44 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -753,6 +753,17 @@ function. Signature objects are also supported by generic function :func:`copy.replace`. + .. method:: format(*, max_width=None) + + Convert signature object to string. + + If *max_width* is passed, the method will attempt to fit + the signature into lines of at most *max_width* characters. + If the signature is longer than *max_width*, + all parameters will be on separate lines. + + .. versionadded:: 3.13 + .. classmethod:: Signature.from_callable(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) Return a :class:`Signature` (or its subclass) object for a given callable diff --git a/Lib/inspect.py b/Lib/inspect.py index aaa22bef896602..079385abbc7bb2 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3316,6 +3316,16 @@ def __repr__(self): return '<{} {}>'.format(self.__class__.__name__, self) def __str__(self): + return self.format() + + def format(self, *, max_width=None): + """Convert signature object to string. + + If *max_width* integer is passed, + signature will try to fit into the *max_width*. + If signature is longer than *max_width*, + all parameters will be on separate lines. + """ result = [] render_pos_only_separator = False render_kw_only_separator = True @@ -3353,6 +3363,8 @@ def __str__(self): result.append('/') rendered = '({})'.format(', '.join(result)) + if max_width is not None and len(rendered) > max_width: + rendered = '(\n {}\n)'.format(',\n '.join(result)) if self.return_annotation is not _empty: anno = formatannotation(self.return_annotation) diff --git a/Lib/pydoc.py b/Lib/pydoc.py index be41592cc64bad..83c74a75cd1c00 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -201,7 +201,10 @@ def _getargspec(object): try: signature = inspect.signature(object) if signature: - return str(signature) + name = getattr(object, '__name__', '') + # function are always single-line and should not be formatted + max_width = (80 - len(name)) if name != '' else None + return signature.format(max_width=max_width) except (ValueError, TypeError): argspec = getattr(object, '__text_signature__', None) if argspec: diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index e75682f881ab34..09d50859970c99 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3796,26 +3796,36 @@ def foo(a:int=1, *, b, c=None, **kwargs) -> 42: pass self.assertEqual(str(inspect.signature(foo)), '(a: int = 1, *, b, c=None, **kwargs) -> 42') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) def foo(a:int=1, *args, b, c=None, **kwargs) -> 42: pass self.assertEqual(str(inspect.signature(foo)), '(a: int = 1, *args, b, c=None, **kwargs) -> 42') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) def foo(): pass self.assertEqual(str(inspect.signature(foo)), '()') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) def foo(a: list[str]) -> tuple[str, float]: pass self.assertEqual(str(inspect.signature(foo)), '(a: list[str]) -> tuple[str, float]') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) from typing import Tuple def foo(a: list[str]) -> Tuple[str, float]: pass self.assertEqual(str(inspect.signature(foo)), '(a: list[str]) -> Tuple[str, float]') + self.assertEqual(str(inspect.signature(foo)), + inspect.signature(foo).format()) def test_signature_str_positional_only(self): P = inspect.Parameter @@ -3826,19 +3836,85 @@ def test(a_po, /, *, b, **kwargs): self.assertEqual(str(inspect.signature(test)), '(a_po, /, *, b, **kwargs)') + self.assertEqual(str(inspect.signature(test)), + inspect.signature(test).format()) + + test = S(parameters=[P('foo', P.POSITIONAL_ONLY)]) + self.assertEqual(str(test), '(foo, /)') + self.assertEqual(str(test), test.format()) - self.assertEqual(str(S(parameters=[P('foo', P.POSITIONAL_ONLY)])), - '(foo, /)') + test = S(parameters=[P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_KEYWORD)]) + self.assertEqual(str(test), '(foo, /, **bar)') + self.assertEqual(str(test), test.format()) - self.assertEqual(str(S(parameters=[ - P('foo', P.POSITIONAL_ONLY), - P('bar', P.VAR_KEYWORD)])), - '(foo, /, **bar)') + test = S(parameters=[P('foo', P.POSITIONAL_ONLY), + P('bar', P.VAR_POSITIONAL)]) + self.assertEqual(str(test), '(foo, /, *bar)') + self.assertEqual(str(test), test.format()) - self.assertEqual(str(S(parameters=[ - P('foo', P.POSITIONAL_ONLY), - P('bar', P.VAR_POSITIONAL)])), - '(foo, /, *bar)') + def test_signature_format(self): + from typing import Annotated, Literal + + def func(x: Annotated[int, 'meta'], y: Literal['a', 'b'], z: 'LiteralString'): + pass + + expected_singleline = "(x: Annotated[int, 'meta'], y: Literal['a', 'b'], z: 'LiteralString')" + expected_multiline = """( + x: Annotated[int, 'meta'], + y: Literal['a', 'b'], + z: 'LiteralString' +)""" + self.assertEqual( + inspect.signature(func).format(), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=None), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=len(expected_singleline)), + expected_singleline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=len(expected_singleline) - 1), + expected_multiline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=0), + expected_multiline, + ) + self.assertEqual( + inspect.signature(func).format(max_width=-1), + expected_multiline, + ) + + def test_signature_format_all_arg_types(self): + from typing import Annotated, Literal + + def func( + x: Annotated[int, 'meta'], + /, + y: Literal['a', 'b'], + *, + z: 'LiteralString', + **kwargs: object, + ) -> None: + pass + + expected_multiline = """( + x: Annotated[int, 'meta'], + /, + y: Literal['a', 'b'], + *, + z: 'LiteralString', + **kwargs: object +) -> None""" + self.assertEqual( + inspect.signature(func).format(max_width=-1), + expected_multiline, + ) def test_signature_replace_parameters(self): def test(a, b) -> 42: diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 745717f492e07a..eb50510e12b7b6 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -870,6 +870,95 @@ class B(A) for expected_line in expected_lines: self.assertIn(expected_line, as_text) + def test_long_signatures(self): + from collections.abc import Callable + from typing import Literal, Annotated + + class A: + def __init__(self, + arg1: Callable[[int, int, int], str], + arg2: Literal['some value', 'other value'], + arg3: Annotated[int, 'some docs about this type'], + ) -> None: + ... + + doc = pydoc.render_doc(A) + # clean up the extra text formatting that pydoc performs + doc = re.sub('\b.', '', doc) + self.assertEqual(doc, '''Python Library Documentation: class A in module %s + +class A(builtins.object) + | A( + | arg1: collections.abc.Callable[[int, int, int], str], + | arg2: Literal['some value', 'other value'], + | arg3: Annotated[int, 'some docs about this type'] + | ) -> None + | + | Methods defined here: + | + | __init__( + | self, + | arg1: collections.abc.Callable[[int, int, int], str], + | arg2: Literal['some value', 'other value'], + | arg3: Annotated[int, 'some docs about this type'] + | ) -> None + | + | ---------------------------------------------------------------------- + | Data descriptors defined here: + | + | __dict__ + | dictionary for instance variables + | + | __weakref__ + | list of weak references to the object +''' % __name__) + + def func( + arg1: Callable[[Annotated[int, 'Some doc']], str], + arg2: Literal[1, 2, 3, 4, 5, 6, 7, 8], + ) -> Annotated[int, 'Some other']: + ... + + doc = pydoc.render_doc(func) + # clean up the extra text formatting that pydoc performs + doc = re.sub('\b.', '', doc) + self.assertEqual(doc, '''Python Library Documentation: function func in module %s + +func( + arg1: collections.abc.Callable[[typing.Annotated[int, 'Some doc']], str], + arg2: Literal[1, 2, 3, 4, 5, 6, 7, 8] +) -> Annotated[int, 'Some other'] +''' % __name__) + + def function_with_really_long_name_so_annotations_can_be_rather_small( + arg1: int, + arg2: str, + ): + ... + + doc = pydoc.render_doc(function_with_really_long_name_so_annotations_can_be_rather_small) + # clean up the extra text formatting that pydoc performs + doc = re.sub('\b.', '', doc) + self.assertEqual(doc, '''Python Library Documentation: function function_with_really_long_name_so_annotations_can_be_rather_small in module %s + +function_with_really_long_name_so_annotations_can_be_rather_small( + arg1: int, + arg2: str +) +''' % __name__) + + does_not_have_name = lambda \ + very_long_parameter_name_that_should_not_fit_into_a_single_line, \ + second_very_long_parameter_name: ... + + doc = pydoc.render_doc(does_not_have_name) + # clean up the extra text formatting that pydoc performs + doc = re.sub('\b.', '', doc) + self.assertEqual(doc, '''Python Library Documentation: function in module %s + + lambda very_long_parameter_name_that_should_not_fit_into_a_single_line, second_very_long_parameter_name +''' % __name__) + def test__future__imports(self): # __future__ features are excluded from module help, # except when it's the __future__ module itself diff --git a/Misc/NEWS.d/next/Library/2023-11-16-10-42-15.gh-issue-112139.WpHosf.rst b/Misc/NEWS.d/next/Library/2023-11-16-10-42-15.gh-issue-112139.WpHosf.rst new file mode 100644 index 00000000000000..090dc8847d9556 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-16-10-42-15.gh-issue-112139.WpHosf.rst @@ -0,0 +1,3 @@ +Add :meth:`Signature.format` to format signatures to string with extra options. +And use it in :mod:`pydoc` to render more readable signatures that have new +lines between parameters. From 3855b45874d5f8eb92a4957fb9de6fdce63eb760 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sun, 3 Dec 2023 04:28:37 -0500 Subject: [PATCH 072/442] gh-66819: More IDLE htest updates(2) (#112642) Examine and update spec -- callable pairs. Revise run method. --- Lib/idlelib/browser.py | 1 + Lib/idlelib/debugobj.py | 4 +- Lib/idlelib/idle_test/htest.py | 108 ++++++++++++++++----------------- Lib/idlelib/iomenu.py | 9 +-- Lib/idlelib/multicall.py | 2 + Lib/idlelib/pathbrowser.py | 6 +- Lib/idlelib/query.py | 2 +- Lib/idlelib/sidebar.py | 6 +- Lib/idlelib/statusbar.py | 1 + 9 files changed, 70 insertions(+), 69 deletions(-) diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 4fe64dced60aca..672e229ffbca94 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -254,5 +254,6 @@ class Nested_in_closure: pass if len(sys.argv) == 1: # If pass file on command line, unittest fails. from unittest import main main('idlelib.idle_test.test_browser', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_module_browser) diff --git a/Lib/idlelib/debugobj.py b/Lib/idlelib/debugobj.py index 032b686f379378..0bf2cb1d5bbfe2 100644 --- a/Lib/idlelib/debugobj.py +++ b/Lib/idlelib/debugobj.py @@ -120,7 +120,7 @@ def make_objecttreeitem(labeltext, object, setfunction=None): return c(labeltext, object, setfunction) -def _object_browser(parent): # htest # +def _debug_object_browser(parent): # htest # import sys from tkinter import Toplevel top = Toplevel(parent) @@ -140,4 +140,4 @@ def _object_browser(parent): # htest # main('idlelib.idle_test.test_debugobj', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(_object_browser) + run(_debug_object_browser) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index e21ab98d8aab89..a59b474fba47d8 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -8,14 +8,15 @@ In a tested module, let X be a global name bound to a callable (class or function) whose .__name__ attribute is also X (the usual situation). The -first parameter of X must be 'parent'. When called, the parent argument -will be the root window. X must create a child Toplevel(parent) window -(or subclass thereof). The Toplevel may be a test widget or dialog, in -which case the callable is the corresponding class. Or the Toplevel may -contain the widget to be tested or set up a context in which a test -widget is invoked. In this latter case, the callable is a wrapper -function that sets up the Toplevel and other objects. Wrapper function -names, such as _editor_window', should start with '_' and be lowercase. +first parameter of X must be 'parent' or 'master'. When called, the +first argument will be the root window. X must create a child +Toplevel(parent/master) (or subclass thereof). The Toplevel may be a +test widget or dialog, in which case the callable is the corresponding +class. Or the Toplevel may contain the widget to be tested or set up a +context in which a test widget is invoked. In this latter case, the +callable is a wrapper function that sets up the Toplevel and other +objects. Wrapper function names, such as _editor_window', should start +with '_' and be lowercase. End the module with @@ -117,12 +118,20 @@ 'file': 'query', 'kwds': {'title': 'Customize query.py Run', '_htest': True}, - 'msg': "Enter with or [Run]. Print valid entry to Shell\n" + 'msg': "Enter with or [OK]. Print valid entry to Shell\n" "Arguments are parsed into a list\n" "Mode is currently restart True or False\n" "Close dialog with valid entry, , [Cancel], [X]" } +_debug_object_browser_spec = { + 'file': 'debugobj', + 'kwds': {}, + 'msg': "Double click on items up to the lowest level.\n" + "Attributes of the objects and related information " + "will be displayed side-by-side at each level." + } + # TODO Improve message _dyn_option_menu_spec = { 'file': 'dynoption', @@ -178,7 +187,7 @@ "Any url ('www...', 'http...') is accepted.\n" "Test Browse with and without path, as cannot unittest.\n" "[Ok] or prints valid entry to shell\n" - "[Cancel] or prints None to shell" + ", [Cancel], or [X] prints None to shell" } _io_binding_spec = { @@ -199,17 +208,17 @@ 'kwds': {}, 'msg': textwrap.dedent("""\ 1. Click on the line numbers and drag down below the edge of the - window, moving the mouse a bit and then leaving it there for a while. - The text and line numbers should gradually scroll down, with the - selection updated continuously. + window, moving the mouse a bit and then leaving it there for a + while. The text and line numbers should gradually scroll down, + with the selection updated continuously. - 2. With the lines still selected, click on a line number above the - selected lines. Only the line whose number was clicked should be - selected. + 2. With the lines still selected, click on a line number above + or below the selected lines. Only the line whose number was + clicked should be selected. - 3. Repeat step #1, dragging to above the window. The text and line - numbers should gradually scroll up, with the selection updated - continuously. + 3. Repeat step #1, dragging to above the window. The text and + line numbers should gradually scroll up, with the selection + updated continuously. 4. Repeat step #2, clicking a line number below the selection."""), } @@ -217,42 +226,33 @@ _multi_call_spec = { 'file': 'multicall', 'kwds': {}, - 'msg': "The following actions should trigger a print to console or IDLE" - " Shell.\nEntering and leaving the text area, key entry, " - ",\n, , " - ", \n, and " - "focusing out of the window\nare sequences to be tested." + 'msg': "The following should trigger a print to console or IDLE Shell.\n" + "Entering and leaving the text area, key entry, ,\n" + ", , , \n" + ", and focusing elsewhere." } _module_browser_spec = { 'file': 'browser', 'kwds': {}, - 'msg': "Inspect names of module, class(with superclass if " - "applicable), methods and functions.\nToggle nested items.\n" - "Double clicking on items prints a traceback for an exception " - "that is ignored." + 'msg': textwrap.dedent(""" + "Inspect names of module, class(with superclass if applicable), + "methods and functions. Toggle nested items. Double clicking + "on items prints a traceback for an exception that is ignored.""") } _multistatus_bar_spec = { 'file': 'statusbar', 'kwds': {}, 'msg': "Ensure presence of multi-status bar below text area.\n" - "Click 'Update Status' to change the multi-status text" - } - -_object_browser_spec = { - 'file': 'debugobj', - 'kwds': {}, - 'msg': "Double click on items up to the lowest level.\n" - "Attributes of the objects and related information " - "will be displayed side-by-side at each level." + "Click 'Update Status' to change the status text" } -_path_browser_spec = { +PathBrowser_spec = { 'file': 'pathbrowser', - 'kwds': {}, + 'kwds': {'_htest': True}, 'msg': "Test for correct display of all paths in sys.path.\n" - "Toggle nested items up to the lowest level.\n" + "Toggle nested items out to the lowest level.\n" "Double clicking on an item prints a traceback\n" "for an exception that is ignored." } @@ -367,11 +367,12 @@ } def run(*tests): + "Run callables in tests." root = tk.Tk() root.title('IDLE htest') root.resizable(0, 0) - # a scrollable Label like constant width text widget. + # A scrollable Label-like constant width text widget. frameLabel = tk.Frame(root, padx=10) frameLabel.pack() text = tk.Text(frameLabel, wrap='word') @@ -381,45 +382,44 @@ def run(*tests): scrollbar.pack(side='right', fill='y', expand=False) text.pack(side='left', fill='both', expand=True) - test_list = [] # List of tuples of the form (spec, callable widget) + test_list = [] # Make list of (spec, callable) tuples. if tests: for test in tests: test_spec = globals()[test.__name__ + '_spec'] test_spec['name'] = test.__name__ test_list.append((test_spec, test)) else: - for k, d in globals().items(): - if k.endswith('_spec'): - test_name = k[:-5] - test_spec = d + for key, dic in globals().items(): + if key.endswith('_spec'): + test_name = key[:-5] + test_spec = dic test_spec['name'] = test_name mod = import_module('idlelib.' + test_spec['file']) test = getattr(mod, test_name) test_list.append((test_spec, test)) + test_list.reverse() # So can pop in proper order in next_test. test_name = tk.StringVar(root) callable_object = None test_kwds = None def next_test(): - nonlocal test_name, callable_object, test_kwds if len(test_list) == 1: next_button.pack_forget() test_spec, callable_object = test_list.pop() test_kwds = test_spec['kwds'] - test_kwds['parent'] = root test_name.set('Test ' + test_spec['name']) - text.configure(state='normal') # enable text editing - text.delete('1.0','end') - text.insert("1.0",test_spec['msg']) - text.configure(state='disabled') # preserve read-only property + text['state'] = 'normal' # Enable text replacement. + text.delete('1.0', 'end') + text.insert("1.0", test_spec['msg']) + text['state'] = 'disabled' # Restore read-only property. def run_test(_=None): - widget = callable_object(**test_kwds) + widget = callable_object(root, **test_kwds) try: - print(widget.result) + print(widget.result) # Only true for query classes(?). except AttributeError: pass diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index af8159c2b33f51..7629101635b8bb 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -396,10 +396,11 @@ def updaterecentfileslist(self,filename): def _io_binding(parent): # htest # from tkinter import Toplevel, Text - root = Toplevel(parent) - root.title("Test IOBinding") + top = Toplevel(parent) + top.title("Test IOBinding") x, y = map(int, parent.geometry().split('+')[1:]) - root.geometry("+%d+%d" % (x, y + 175)) + top.geometry("+%d+%d" % (x, y + 175)) + class MyEditWin: def __init__(self, text): self.text = text @@ -423,7 +424,7 @@ def saveas(self, event): def savecopy(self, event): self.text.event_generate("<>") - text = Text(root) + text = Text(top) text.pack() text.focus_set() editwin = MyEditWin(text) diff --git a/Lib/idlelib/multicall.py b/Lib/idlelib/multicall.py index 0200f445cc9340..2aa4a54125156f 100644 --- a/Lib/idlelib/multicall.py +++ b/Lib/idlelib/multicall.py @@ -421,6 +421,8 @@ def _multi_call(parent): # htest # top.geometry("+%d+%d" % (x, y + 175)) text = MultiCallCreator(tkinter.Text)(top) text.pack() + text.focus_set() + def bindseq(seq, n=[0]): def handler(event): print(seq) diff --git a/Lib/idlelib/pathbrowser.py b/Lib/idlelib/pathbrowser.py index 6de242d0000bed..48a77875ba5801 100644 --- a/Lib/idlelib/pathbrowser.py +++ b/Lib/idlelib/pathbrowser.py @@ -99,13 +99,9 @@ def listmodules(self, allnames): return sorted -def _path_browser(parent): # htest # - PathBrowser(parent, _htest=True) - parent.mainloop() - if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_pathbrowser', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(_path_browser) + run(PathBrowser) diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py index df02f2123ab02f..57230e2aaca66d 100644 --- a/Lib/idlelib/query.py +++ b/Lib/idlelib/query.py @@ -368,7 +368,7 @@ def create_extra(self): sticky='we') def cli_args_ok(self): - "Validity check and parsing for command line arguments." + "Return command line arg list or None if error." cli_string = self.entry.get().strip() try: cli_args = shlex.split(cli_string, posix=True) diff --git a/Lib/idlelib/sidebar.py b/Lib/idlelib/sidebar.py index 166c04342907f9..8e7eae5037c90c 100644 --- a/Lib/idlelib/sidebar.py +++ b/Lib/idlelib/sidebar.py @@ -516,13 +516,13 @@ def update_colors(self): def _linenumbers_drag_scrolling(parent): # htest # from idlelib.idle_test.test_sidebar import Dummy_editwin - toplevel = tk.Toplevel(parent) - text_frame = tk.Frame(toplevel) + top = tk.Toplevel(parent) + text_frame = tk.Frame(top) text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) text_frame.rowconfigure(1, weight=1) text_frame.columnconfigure(1, weight=1) - font = idleConf.GetFont(toplevel, 'main', 'EditorWindow') + font = idleConf.GetFont(top, 'main', 'EditorWindow') text = tk.Text(text_frame, width=80, height=24, wrap=tk.NONE, font=font) text.grid(row=1, column=1, sticky=tk.NSEW) diff --git a/Lib/idlelib/statusbar.py b/Lib/idlelib/statusbar.py index 755fafb0ac6438..7048bd64b98753 100644 --- a/Lib/idlelib/statusbar.py +++ b/Lib/idlelib/statusbar.py @@ -26,6 +26,7 @@ def _multistatus_bar(parent): # htest # x, y = map(int, parent.geometry().split('+')[1:]) top.geometry("+%d+%d" %(x, y + 175)) top.title("Test multistatus bar") + frame = Frame(top) text = Text(frame, height=5, width=40) text.pack() From fc9e24b01fb7da4160b82cef26981d72bb678c13 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 3 Dec 2023 09:37:34 +0000 Subject: [PATCH 073/442] gh-112316: improve docs for `inspect.signature` and `inspect.Signature` (#112631) --- Doc/library/inspect.rst | 91 +++++++++++++++++++++++++---------------- Lib/inspect.py | 2 +- 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 08522510f9ab44..71e7cb433cb1a8 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1,6 +1,11 @@ :mod:`inspect` --- Inspect live objects ======================================= +.. testsetup:: * + + import inspect + from inspect import * + .. module:: inspect :synopsis: Extract information and source code from live objects. @@ -614,13 +619,16 @@ Introspecting callables with the Signature object .. versionadded:: 3.3 -The Signature object represents the call signature of a callable object and its -return annotation. To retrieve a Signature object, use the :func:`signature` +The :class:`Signature` object represents the call signature of a callable object +and its return annotation. To retrieve a :class:`!Signature` object, +use the :func:`!signature` function. .. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False) - Return a :class:`Signature` object for the given *callable*:: + Return a :class:`Signature` object for the given *callable*: + + .. doctest:: >>> from inspect import signature >>> def foo(a, *, b:int, **kwargs): @@ -629,10 +637,10 @@ function. >>> sig = signature(foo) >>> str(sig) - '(a, *, b:int, **kwargs)' + '(a, *, b: int, **kwargs)' >>> str(sig.parameters['b']) - 'b:int' + 'b: int' >>> sig.parameters['b'].annotation @@ -647,7 +655,7 @@ function. (``from __future__ import annotations``), :func:`signature` will attempt to automatically un-stringize the annotations using :func:`get_annotations`. The - *global*, *locals*, and *eval_str* parameters are passed + *globals*, *locals*, and *eval_str* parameters are passed into :func:`get_annotations` when resolving the annotations; see the documentation for :func:`get_annotations` for instructions on how to use these parameters. @@ -680,7 +688,8 @@ function. .. class:: Signature(parameters=None, *, return_annotation=Signature.empty) - A Signature object represents the call signature of a function and its return + A :class:`!Signature` object represents the call signature of a function + and its return annotation. For each parameter accepted by the function it stores a :class:`Parameter` object in its :attr:`parameters` collection. @@ -690,14 +699,14 @@ function. positional-only first, then positional-or-keyword, and that parameters with defaults follow parameters without defaults. - The optional *return_annotation* argument, can be an arbitrary Python object, - is the "return" annotation of the callable. + The optional *return_annotation* argument can be an arbitrary Python object. + It represents the "return" annotation of the callable. - Signature objects are *immutable*. Use :meth:`Signature.replace` or + :class:`!Signature` objects are *immutable*. Use :meth:`Signature.replace` or :func:`copy.replace` to make a modified copy. .. versionchanged:: 3.5 - Signature objects are picklable and :term:`hashable`. + :class:`!Signature` objects are now picklable and :term:`hashable`. .. attribute:: Signature.empty @@ -734,13 +743,15 @@ function. .. method:: Signature.replace(*[, parameters][, return_annotation]) - Create a new Signature instance based on the instance :meth:`replace` was invoked - on. It is possible to pass different ``parameters`` and/or - ``return_annotation`` to override the corresponding properties of the base - signature. To remove return_annotation from the copied Signature, pass in + Create a new :class:`Signature` instance based on the instance + :meth:`replace` was invoked on. + It is possible to pass different *parameters* and/or + *return_annotation* to override the corresponding properties of the base + signature. To remove ``return_annotation`` from the copied + :class:`!Signature`, pass in :attr:`Signature.empty`. - :: + .. doctest:: >>> def test(a, b): ... pass @@ -750,12 +761,12 @@ function. >>> str(new_sig) "(a, b) -> 'new return anno'" - Signature objects are also supported by generic function + :class:`Signature` objects are also supported by the generic function :func:`copy.replace`. .. method:: format(*, max_width=None) - Convert signature object to string. + Create a string representation of the :class:`Signature` object. If *max_width* is passed, the method will attempt to fit the signature into lines of at most *max_width* characters. @@ -769,12 +780,14 @@ function. Return a :class:`Signature` (or its subclass) object for a given callable *obj*. - This method simplifies subclassing of :class:`Signature`:: + This method simplifies subclassing of :class:`Signature`: + + .. testcode:: - class MySignature(Signature): - pass - sig = MySignature.from_callable(min) - assert isinstance(sig, MySignature) + class MySignature(Signature): + pass + sig = MySignature.from_callable(sum) + assert isinstance(sig, MySignature) Its behavior is otherwise identical to that of :func:`signature`. @@ -786,11 +799,12 @@ function. .. class:: Parameter(name, kind, *, default=Parameter.empty, annotation=Parameter.empty) - Parameter objects are *immutable*. Instead of modifying a Parameter object, + :class:`!Parameter` objects are *immutable*. + Instead of modifying a :class:`!Parameter` object, you can use :meth:`Parameter.replace` or :func:`copy.replace` to create a modified copy. .. versionchanged:: 3.5 - Parameter objects are picklable and :term:`hashable`. + Parameter objects are now picklable and :term:`hashable`. .. attribute:: Parameter.empty @@ -809,7 +823,7 @@ function. expressions. .. versionchanged:: 3.6 - These parameter names are exposed by this module as names like + These parameter names are now exposed by this module as names like ``implicit0``. .. attribute:: Parameter.default @@ -859,7 +873,9 @@ function. | | definition. | +------------------------+----------------------------------------------+ - Example: print all keyword-only arguments without default values:: + Example: print all keyword-only arguments without default values: + + .. doctest:: >>> def foo(a, b, *, c, d=10): ... pass @@ -873,11 +889,13 @@ function. .. attribute:: Parameter.kind.description - Describes a enum value of Parameter.kind. + Describes a enum value of :attr:`Parameter.kind`. .. versionadded:: 3.8 - Example: print all descriptions of arguments:: + Example: print all descriptions of arguments: + + .. doctest:: >>> def foo(a, b, *, c, d=10): ... pass @@ -892,12 +910,12 @@ function. .. method:: Parameter.replace(*[, name][, kind][, default][, annotation]) - Create a new Parameter instance based on the instance replaced was invoked - on. To override a :class:`Parameter` attribute, pass the corresponding + Create a new :class:`Parameter` instance based on the instance replaced was invoked + on. To override a :class:`!Parameter` attribute, pass the corresponding argument. To remove a default value or/and an annotation from a - Parameter, pass :attr:`Parameter.empty`. + :class:`!Parameter`, pass :attr:`Parameter.empty`. - :: + .. doctest:: >>> from inspect import Parameter >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42) @@ -908,12 +926,13 @@ function. 'foo=42' >>> str(param.replace(default=Parameter.empty, annotation='spam')) - "foo:'spam'" + "foo: 'spam'" - Parameter objects are also supported by generic function :func:`copy.replace`. + :class:`Parameter` objects are also supported by the generic function + :func:`copy.replace`. .. versionchanged:: 3.4 - In Python 3.3 Parameter objects were allowed to have ``name`` set + In Python 3.3 :class:`Parameter` objects were allowed to have ``name`` set to ``None`` if their ``kind`` was set to ``POSITIONAL_ONLY``. This is no longer permitted. diff --git a/Lib/inspect.py b/Lib/inspect.py index 079385abbc7bb2..f0b72662a9a0b2 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3319,7 +3319,7 @@ def __str__(self): return self.format() def format(self, *, max_width=None): - """Convert signature object to string. + """Create a string representation of the Signature object. If *max_width* integer is passed, signature will try to fit into the *max_width*. From 29e6c7b68acac628b084a82670708008be262379 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sun, 3 Dec 2023 03:09:29 -0800 Subject: [PATCH 074/442] gh-112578: Fix RuntimeWarning when running zipfile (GH-112579) --- Lib/zipfile/__init__.py | 73 +++++++++++++++++- Lib/zipfile/__main__.py | 75 +------------------ ...-12-01-08-28-09.gh-issue-112578.bfNbfi.rst | 1 + 3 files changed, 72 insertions(+), 77 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-01-08-28-09.gh-issue-112578.bfNbfi.rst diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 2b28a079dbaa95..fe629ed1cf2fc5 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -2227,12 +2227,79 @@ def _compile(file, optimize=-1): return (fname, archivename) +def main(args=None): + import argparse + + description = 'A simple command-line interface for zipfile module.' + parser = argparse.ArgumentParser(description=description) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-l', '--list', metavar='', + help='Show listing of a zipfile') + group.add_argument('-e', '--extract', nargs=2, + metavar=('', ''), + help='Extract zipfile into target dir') + group.add_argument('-c', '--create', nargs='+', + metavar=('', ''), + help='Create zipfile from sources') + group.add_argument('-t', '--test', metavar='', + help='Test if a zipfile is valid') + parser.add_argument('--metadata-encoding', metavar='', + help='Specify encoding of member names for -l, -e and -t') + args = parser.parse_args(args) + + encoding = args.metadata_encoding + + if args.test is not None: + src = args.test + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: + badfile = zf.testzip() + if badfile: + print("The following enclosed file is corrupted: {!r}".format(badfile)) + print("Done testing") + + elif args.list is not None: + src = args.list + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: + zf.printdir() + + elif args.extract is not None: + src, curdir = args.extract + with ZipFile(src, 'r', metadata_encoding=encoding) as zf: + zf.extractall(curdir) + + elif args.create is not None: + if encoding: + print("Non-conforming encodings not supported with -c.", + file=sys.stderr) + sys.exit(1) + + zip_name = args.create.pop(0) + files = args.create + + def addToZip(zf, path, zippath): + if os.path.isfile(path): + zf.write(path, zippath, ZIP_DEFLATED) + elif os.path.isdir(path): + if zippath: + zf.write(path, zippath) + for nm in sorted(os.listdir(path)): + addToZip(zf, + os.path.join(path, nm), os.path.join(zippath, nm)) + # else: ignore + + with ZipFile(zip_name, 'w') as zf: + for path in files: + zippath = os.path.basename(path) + if not zippath: + zippath = os.path.basename(os.path.dirname(path)) + if zippath in ('', os.curdir, os.pardir): + zippath = '' + addToZip(zf, path, zippath) + + from ._path import ( # noqa: E402 Path, # used privately for tests CompleteDirs, # noqa: F401 ) - -# used privately for tests -from .__main__ import main # noqa: F401, E402 diff --git a/Lib/zipfile/__main__.py b/Lib/zipfile/__main__.py index a9e5fb1b8d72c4..868d99efc3c4a3 100644 --- a/Lib/zipfile/__main__.py +++ b/Lib/zipfile/__main__.py @@ -1,77 +1,4 @@ -import sys -import os -from . import ZipFile, ZIP_DEFLATED - - -def main(args=None): - import argparse - - description = 'A simple command-line interface for zipfile module.' - parser = argparse.ArgumentParser(description=description) - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('-l', '--list', metavar='', - help='Show listing of a zipfile') - group.add_argument('-e', '--extract', nargs=2, - metavar=('', ''), - help='Extract zipfile into target dir') - group.add_argument('-c', '--create', nargs='+', - metavar=('', ''), - help='Create zipfile from sources') - group.add_argument('-t', '--test', metavar='', - help='Test if a zipfile is valid') - parser.add_argument('--metadata-encoding', metavar='', - help='Specify encoding of member names for -l, -e and -t') - args = parser.parse_args(args) - - encoding = args.metadata_encoding - - if args.test is not None: - src = args.test - with ZipFile(src, 'r', metadata_encoding=encoding) as zf: - badfile = zf.testzip() - if badfile: - print("The following enclosed file is corrupted: {!r}".format(badfile)) - print("Done testing") - - elif args.list is not None: - src = args.list - with ZipFile(src, 'r', metadata_encoding=encoding) as zf: - zf.printdir() - - elif args.extract is not None: - src, curdir = args.extract - with ZipFile(src, 'r', metadata_encoding=encoding) as zf: - zf.extractall(curdir) - - elif args.create is not None: - if encoding: - print("Non-conforming encodings not supported with -c.", - file=sys.stderr) - sys.exit(1) - - zip_name = args.create.pop(0) - files = args.create - - def addToZip(zf, path, zippath): - if os.path.isfile(path): - zf.write(path, zippath, ZIP_DEFLATED) - elif os.path.isdir(path): - if zippath: - zf.write(path, zippath) - for nm in sorted(os.listdir(path)): - addToZip(zf, - os.path.join(path, nm), os.path.join(zippath, nm)) - # else: ignore - - with ZipFile(zip_name, 'w') as zf: - for path in files: - zippath = os.path.basename(path) - if not zippath: - zippath = os.path.basename(os.path.dirname(path)) - if zippath in ('', os.curdir, os.pardir): - zippath = '' - addToZip(zf, path, zippath) - +from . import main if __name__ == "__main__": main() diff --git a/Misc/NEWS.d/next/Library/2023-12-01-08-28-09.gh-issue-112578.bfNbfi.rst b/Misc/NEWS.d/next/Library/2023-12-01-08-28-09.gh-issue-112578.bfNbfi.rst new file mode 100644 index 00000000000000..1de5b1fe26ce6d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-01-08-28-09.gh-issue-112578.bfNbfi.rst @@ -0,0 +1 @@ +Fix a spurious :exc:`RuntimeWarning` when executing the :mod:`zipfile` module. From 1f2a676785d48ed9ac01e60cc56a82e44b725474 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 3 Dec 2023 12:16:31 +0100 Subject: [PATCH 075/442] gh-106560: Fix redundant declarations in Include/ (#112611) Don't declare PyBool_Type, PyLong_Type and PySys_Audit() twice, but only once. Compiler warnings seen by building Python with gcc -Wredundant-decls. --- Include/boolobject.h | 2 +- Include/cpython/sysmodule.h | 4 ---- Include/longobject.h | 2 +- .../next/C API/2023-12-02-02-08-11.gh-issue-106560.THvuji.rst | 2 ++ 4 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-12-02-02-08-11.gh-issue-106560.THvuji.rst diff --git a/Include/boolobject.h b/Include/boolobject.h index 976fa35201d035..19aef5b1b87c6a 100644 --- a/Include/boolobject.h +++ b/Include/boolobject.h @@ -7,7 +7,7 @@ extern "C" { #endif -PyAPI_DATA(PyTypeObject) PyBool_Type; +// PyBool_Type is declared by object.h #define PyBool_Check(x) Py_IS_TYPE((x), &PyBool_Type) diff --git a/Include/cpython/sysmodule.h b/Include/cpython/sysmodule.h index 9fd7cc0cb43931..a3ac07f538a94f 100644 --- a/Include/cpython/sysmodule.h +++ b/Include/cpython/sysmodule.h @@ -4,10 +4,6 @@ typedef int(*Py_AuditHookFunction)(const char *, PyObject *, void *); -PyAPI_FUNC(int) PySys_Audit( - const char *event, - const char *format, - ...); PyAPI_FUNC(int) PySys_AddAuditHook(Py_AuditHookFunction, void*); typedef struct { diff --git a/Include/longobject.h b/Include/longobject.h index 7393254cd24a9b..51005efff636fa 100644 --- a/Include/longobject.h +++ b/Include/longobject.h @@ -7,7 +7,7 @@ extern "C" { /* Long (arbitrary precision) integer object interface */ -PyAPI_DATA(PyTypeObject) PyLong_Type; +// PyLong_Type is declared by object.h #define PyLong_Check(op) \ PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LONG_SUBCLASS) diff --git a/Misc/NEWS.d/next/C API/2023-12-02-02-08-11.gh-issue-106560.THvuji.rst b/Misc/NEWS.d/next/C API/2023-12-02-02-08-11.gh-issue-106560.THvuji.rst new file mode 100644 index 00000000000000..59b461ec47ad64 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-12-02-02-08-11.gh-issue-106560.THvuji.rst @@ -0,0 +1,2 @@ +Fix redundant declarations in the public C API. Declare PyBool_Type, +PyLong_Type and PySys_Audit() only once. Patch by Victor Stinner. From d9e444dbb86e173ee5b8491e3facbd447b91eaed Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 3 Dec 2023 12:18:24 +0100 Subject: [PATCH 076/442] gh-106560: Fix redundant declarations in Python/frozen.c (#112612) Avoid duplicated declarations of "extern" functions in Python/frozen.c. Compiler warnings seen by building Python with gcc -Wredundant-decls. --- Python/frozen.c | 6 ------ Tools/build/freeze_modules.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Python/frozen.c b/Python/frozen.c index 0fb38a11902f35..77f51a7f750965 100644 --- a/Python/frozen.c +++ b/Python/frozen.c @@ -80,7 +80,6 @@ extern PyObject *_Py_get__sitebuiltins_toplevel(void); extern PyObject *_Py_get_genericpath_toplevel(void); extern PyObject *_Py_get_ntpath_toplevel(void); extern PyObject *_Py_get_posixpath_toplevel(void); -extern PyObject *_Py_get_posixpath_toplevel(void); extern PyObject *_Py_get_os_toplevel(void); extern PyObject *_Py_get_site_toplevel(void); extern PyObject *_Py_get_stat_toplevel(void); @@ -88,13 +87,8 @@ extern PyObject *_Py_get_importlib_util_toplevel(void); extern PyObject *_Py_get_importlib_machinery_toplevel(void); extern PyObject *_Py_get_runpy_toplevel(void); extern PyObject *_Py_get___hello___toplevel(void); -extern PyObject *_Py_get___hello___toplevel(void); -extern PyObject *_Py_get___hello___toplevel(void); -extern PyObject *_Py_get___hello___toplevel(void); -extern PyObject *_Py_get___phello___toplevel(void); extern PyObject *_Py_get___phello___toplevel(void); extern PyObject *_Py_get___phello___ham_toplevel(void); -extern PyObject *_Py_get___phello___ham_toplevel(void); extern PyObject *_Py_get___phello___ham_eggs_toplevel(void); extern PyObject *_Py_get___phello___spam_toplevel(void); extern PyObject *_Py_get_frozen_only_toplevel(void); diff --git a/Tools/build/freeze_modules.py b/Tools/build/freeze_modules.py index c5a397129201b6..6a54f45bac3a86 100644 --- a/Tools/build/freeze_modules.py +++ b/Tools/build/freeze_modules.py @@ -468,6 +468,17 @@ def replace_block(lines, start_marker, end_marker, replacements, file): return lines[:start_pos + 1] + replacements + lines[end_pos:] +class UniqueList(list): + def __init__(self): + self._seen = set() + + def append(self, item): + if item in self._seen: + return + super().append(item) + self._seen.add(item) + + def regen_frozen(modules): headerlines = [] parentdir = os.path.dirname(FROZEN_FILE) @@ -477,7 +488,7 @@ def regen_frozen(modules): header = relpath_for_posix_display(src.frozenfile, parentdir) headerlines.append(f'#include "{header}"') - externlines = [] + externlines = UniqueList() bootstraplines = [] stdliblines = [] testlines = [] From a971574b73140b6badf9442a5b954eab766a082c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 3 Dec 2023 12:21:48 +0100 Subject: [PATCH 077/442] gh-111545: Mention PEP 456 in PyHash_GetFuncDef() doc (#112647) --- Doc/c-api/hash.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst index 4dc121d7fbaa9b..3bfaf8b9f54c14 100644 --- a/Doc/c-api/hash.rst +++ b/Doc/c-api/hash.rst @@ -45,4 +45,7 @@ See also the :c:member:`PyTypeObject.tp_hash` member. Get the hash function definition. + .. seealso:: + :pep:`456` "Secure and interchangeable hash algorithm". + .. versionadded:: 3.4 From 4ed46d224401243399b41c7ceef4532bd249da27 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 3 Dec 2023 11:50:22 +0000 Subject: [PATCH 078/442] Run more `inspect.rst` code snippets in CI (#112654) --- Doc/library/inspect.rst | 66 ++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 71e7cb433cb1a8..815bd54107a987 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -392,7 +392,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Return ``True`` if the object can be used in :keyword:`await` expression. Can also be used to distinguish generator-based coroutines from regular - generators:: + generators: + + .. testcode:: + + import types def gen(): yield @@ -409,13 +413,15 @@ attributes (see :ref:`import-mod-attrs` for module attributes): .. function:: isasyncgenfunction(object) Return ``True`` if the object is an :term:`asynchronous generator` function, - for example:: + for example: - >>> async def agen(): - ... yield 1 - ... - >>> inspect.isasyncgenfunction(agen) - True + .. doctest:: + + >>> async def agen(): + ... yield 1 + ... + >>> inspect.isasyncgenfunction(agen) + True .. versionadded:: 3.6 @@ -985,18 +991,20 @@ function. For variable-keyword arguments (``**kwargs``) the default is an empty dict. - :: + .. doctest:: - >>> def foo(a, b='ham', *args): pass - >>> ba = inspect.signature(foo).bind('spam') - >>> ba.apply_defaults() - >>> ba.arguments - {'a': 'spam', 'b': 'ham', 'args': ()} + >>> def foo(a, b='ham', *args): pass + >>> ba = inspect.signature(foo).bind('spam') + >>> ba.apply_defaults() + >>> ba.arguments + {'a': 'spam', 'b': 'ham', 'args': ()} .. versionadded:: 3.5 The :attr:`args` and :attr:`kwargs` properties can be used to invoke - functions:: + functions: + + .. testcode:: def test(a, *, b): ... @@ -1115,20 +1123,22 @@ Classes and functions ``**`` arguments, if any) to their values from *args* and *kwds*. In case of invoking *func* incorrectly, i.e. whenever ``func(*args, **kwds)`` would raise an exception because of incompatible signature, an exception of the same type - and the same or similar message is raised. For example:: - - >>> from inspect import getcallargs - >>> def f(a, b=1, *pos, **named): - ... pass - ... - >>> getcallargs(f, 1, 2, 3) == {'a': 1, 'named': {}, 'b': 2, 'pos': (3,)} - True - >>> getcallargs(f, a=2, x=4) == {'a': 2, 'named': {'x': 4}, 'b': 1, 'pos': ()} - True - >>> getcallargs(f) - Traceback (most recent call last): - ... - TypeError: f() missing 1 required positional argument: 'a' + and the same or similar message is raised. For example: + + .. doctest:: + + >>> from inspect import getcallargs + >>> def f(a, b=1, *pos, **named): + ... pass + ... + >>> getcallargs(f, 1, 2, 3) == {'a': 1, 'named': {}, 'b': 2, 'pos': (3,)} + True + >>> getcallargs(f, a=2, x=4) == {'a': 2, 'named': {'x': 4}, 'b': 1, 'pos': ()} + True + >>> getcallargs(f) + Traceback (most recent call last): + ... + TypeError: f() missing 1 required positional argument: 'a' .. versionadded:: 3.2 From 162d3d428a836850ba29c58bbf37c931843d9e37 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 3 Dec 2023 12:12:49 +0000 Subject: [PATCH 079/442] gh-112620: Fix dis error on show_cache with labels (#112621) --- Lib/dis.py | 17 +++++++++++------ Lib/test/test_dis.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index e08e9a94057689..8d3885d2526b70 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -412,7 +412,6 @@ def _create(cls, op, arg, offset, start_offset, starts_line, line_number, co_consts=None, varname_from_oparg=None, names=None, labels_map=None, exceptions_map=None): - label_width = 4 + len(str(len(labels_map))) argval, argrepr = cls._get_argval_argrepr( op, arg, offset, co_consts, names, varname_from_oparg, labels_map) @@ -420,7 +419,7 @@ def _create(cls, op, arg, offset, start_offset, starts_line, line_number, instr = Instruction(_all_opname[op], op, arg, argval, argrepr, offset, start_offset, starts_line, line_number, label, positions) - instr.label_width = label_width + instr.label_width = 4 + len(str(len(labels_map))) instr.exc_handler = exceptions_map.get(offset, None) return instr @@ -468,12 +467,14 @@ def is_jump_target(self): """True if other code jumps to here, otherwise False""" return self.label is not None - def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0): + def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0, + label_width=0): """Format instruction details for inclusion in disassembly output. *lineno_width* sets the width of the line number field (0 omits it) *mark_as_current* inserts a '-->' marker arrow as part of the line *offset_width* sets the width of the instruction offset field + *label_width* sets the width of the label field """ fields = [] # Column: Source code line number @@ -488,9 +489,9 @@ def _disassemble(self, lineno_width=3, mark_as_current=False, offset_width=0): # Column: Label if self.label is not None: lbl = f"L{self.label}:" - fields.append(f"{lbl:>{self.label_width}}") + fields.append(f"{lbl:>{label_width}}") else: - fields.append(' ' * self.label_width) + fields.append(' ' * label_width) # Column: Instruction offset from start of code sequence if offset_width > 0: fields.append(f"{repr(self.offset):>{offset_width}} ") @@ -648,6 +649,7 @@ def make_labels_map(original_code, exception_entries): return labels_map labels_map = make_labels_map(original_code, exception_entries) + label_width = 4 + len(str(len(labels_map))) exceptions_map = {} for start, end, target, _, _ in exception_entries: @@ -756,6 +758,7 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, else: offset_width = 0 + label_width = -1 for instr in _get_instructions_bytes(code, varname_from_oparg, names, co_consts, linestarts, line_offset=line_offset, @@ -774,7 +777,9 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, # Each CACHE takes 2 bytes is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) - print(instr._disassemble(lineno_width, is_current_instr, offset_width), + label_width = getattr(instr, 'label_width', label_width) + assert label_width >= 0 + print(instr._disassemble(lineno_width, is_current_instr, offset_width, label_width), file=file) if exception_entries: print("ExceptionTable:", file=file) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 805cd4e4c30965..349790ecd7d075 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1896,6 +1896,18 @@ def test_oparg_alias(self): positions=None) self.assertEqual(instruction.arg, instruction.oparg) + def test_show_caches_with_label(self): + def f(x, y, z): + if x: + res = y + else: + res = z + return res + + output = io.StringIO() + dis.dis(f.__code__, file=output, show_caches=True) + self.assertIn("L1:", output.getvalue()) + def test_baseopname_and_baseopcode(self): # Standard instructions for name, code in dis.opmap.items(): From 97857ac0580057c3a4f75d34209841c81ee11a96 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 3 Dec 2023 14:02:37 +0000 Subject: [PATCH 080/442] gh-112645: remove deprecation warning for use of onerror in shutil.rmtree (#112659) --- Doc/whatsnew/3.12.rst | 7 +++---- Lib/shutil.py | 5 ----- Lib/test/test_shutil.py | 15 +++++---------- ...2023-12-03-12-41-48.gh-issue-112645.blMsKf.rst | 1 + 4 files changed, 9 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-03-12-41-48.gh-issue-112645.blMsKf.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 96893527cc91ed..fc17c86665335c 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -838,8 +838,7 @@ shutil * :func:`shutil.rmtree` now accepts a new argument *onexc* which is an error handler like *onerror* but which expects an exception instance - rather than a *(typ, val, tb)* triplet. *onerror* is deprecated and - will be removed in Python 3.14. + rather than a *(typ, val, tb)* triplet. *onerror* is deprecated. (Contributed by Irit Katriel in :gh:`102828`.) * :func:`shutil.which` now consults the *PATHEXT* environment variable to @@ -1261,8 +1260,8 @@ Deprecated :mod:`concurrent.futures` the fix is to use a different :mod:`multiprocessing` start method such as ``"spawn"`` or ``"forkserver"``. -* :mod:`shutil`: The *onerror* argument of :func:`shutil.rmtree` is deprecated and will be removed - in Python 3.14. Use *onexc* instead. (Contributed by Irit Katriel in :gh:`102828`.) +* :mod:`shutil`: The *onerror* argument of :func:`shutil.rmtree` is deprecated; + use *onexc* instead. (Contributed by Irit Katriel in :gh:`102828`.) * :mod:`sqlite3`: diff --git a/Lib/shutil.py b/Lib/shutil.py index 0fed0117a63234..dd93872e83c9e2 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -721,11 +721,6 @@ def rmtree(path, ignore_errors=False, onerror=None, *, onexc=None, dir_fd=None): If both onerror and onexc are set, onerror is ignored and onexc is used. """ - if onerror is not None: - import warnings - warnings.warn("onerror argument is deprecated, use onexc instead", - DeprecationWarning, stacklevel=2) - sys.audit("shutil.rmtree", path, dir_fd) if ignore_errors: def onexc(*args): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index d231e66b7b889f..ae6c6814fcc3ec 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -208,8 +208,7 @@ def test_rmtree_fails_on_symlink_onerror(self): errors = [] def onerror(*args): errors.append(args) - with self.assertWarns(DeprecationWarning): - shutil.rmtree(link, onerror=onerror) + shutil.rmtree(link, onerror=onerror) self.assertEqual(len(errors), 1) self.assertIs(errors[0][0], os.path.islink) self.assertEqual(errors[0][1], link) @@ -270,8 +269,7 @@ def test_rmtree_fails_on_junctions_onerror(self): errors = [] def onerror(*args): errors.append(args) - with self.assertWarns(DeprecationWarning): - shutil.rmtree(link, onerror=onerror) + shutil.rmtree(link, onerror=onerror) self.assertEqual(len(errors), 1) self.assertIs(errors[0][0], os.path.islink) self.assertEqual(errors[0][1], link) @@ -340,8 +338,7 @@ def test_rmtree_errors_onerror(self): errors = [] def onerror(*args): errors.append(args) - with self.assertWarns(DeprecationWarning): - shutil.rmtree(filename, onerror=onerror) + shutil.rmtree(filename, onerror=onerror) self.assertEqual(len(errors), 2) self.assertIs(errors[0][0], os.scandir) self.assertEqual(errors[0][1], filename) @@ -410,8 +407,7 @@ def test_on_error(self): self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) - with self.assertWarns(DeprecationWarning): - shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) + shutil.rmtree(TESTFN, onerror=self.check_args_to_onerror) # Test whether onerror has actually been called. self.assertEqual(self.errorState, 3, "Expected call to onerror function did not happen.") @@ -537,8 +533,7 @@ def onexc(*args): self.addCleanup(os.chmod, self.child_file_path, old_child_file_mode) self.addCleanup(os.chmod, self.child_dir_path, old_child_dir_mode) - with self.assertWarns(DeprecationWarning): - shutil.rmtree(TESTFN, onerror=onerror, onexc=onexc) + shutil.rmtree(TESTFN, onerror=onerror, onexc=onexc) self.assertTrue(onexc_called) self.assertFalse(onerror_called) diff --git a/Misc/NEWS.d/next/Library/2023-12-03-12-41-48.gh-issue-112645.blMsKf.rst b/Misc/NEWS.d/next/Library/2023-12-03-12-41-48.gh-issue-112645.blMsKf.rst new file mode 100644 index 00000000000000..4e8f6ebdb882e0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-03-12-41-48.gh-issue-112645.blMsKf.rst @@ -0,0 +1 @@ +Remove deprecation error on passing ``onerror`` to :func:`shutil.rmtree`. From c27b09c81368bc3b756e94a79a39307ce44a4a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Aur=C3=A9lio=20A=2E=20Barbosa?= Date: Sun, 3 Dec 2023 12:14:14 -0300 Subject: [PATCH 081/442] Fix link to 'The Perils of Floating Point', on the tutorial (GH-112499) Use author link to 'The Perils of Floating Point'. --- Doc/tutorial/floatingpoint.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/tutorial/floatingpoint.rst b/Doc/tutorial/floatingpoint.rst index 30f3dfb6b238b4..0795e2fef98830 100644 --- a/Doc/tutorial/floatingpoint.rst +++ b/Doc/tutorial/floatingpoint.rst @@ -150,7 +150,7 @@ section. See `Examples of Floating Point Problems `_ for a pleasant summary of how binary floating-point works and the kinds of problems commonly encountered in practice. Also see -`The Perils of Floating Point `_ +`The Perils of Floating Point `_ for a more complete account of other common surprises. As that says near the end, "there are no easy answers." Still, don't be unduly From 45650d1c479a8b0370f126d821718dd3c502f333 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 3 Dec 2023 17:32:49 +0000 Subject: [PATCH 082/442] gh-101100: Fix most Sphinx nitpicks in `inspect.rst` (#112662) --- Doc/conf.py | 3 +++ Doc/library/inspect.rst | 15 +++++++++------ Doc/reference/datamodel.rst | 2 ++ Doc/whatsnew/2.6.rst | 5 +++-- Doc/whatsnew/2.7.rst | 3 ++- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index be2a86e12fa2e4..323d443588ceb6 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -163,6 +163,9 @@ ('envvar', 'USER'), ('envvar', 'USERNAME'), ('envvar', 'USERPROFILE'), + # Deprecated function that was never documented: + ('py:func', 'getargspec'), + ('py:func', 'inspect.getargspec'), ] # Temporary undocumented names. diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 815bd54107a987..6fd0d32afe7415 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -273,7 +273,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): :func:`getmembers` will only return class attributes defined in the metaclass when the argument is a class and those attributes have been - listed in the metaclass' custom :meth:`__dir__`. + listed in the metaclass' custom :meth:`~object.__dir__`. .. function:: getmembers_static(object[, predicate]) @@ -487,12 +487,13 @@ attributes (see :ref:`import-mod-attrs` for module attributes): has a :meth:`~object.__get__` method but not a :meth:`~object.__set__` method, but beyond that the set of attributes varies. A :attr:`~definition.__name__` attribute is usually - sensible, and :attr:`__doc__` often is. + sensible, and :attr:`!__doc__` often is. Methods implemented via descriptors that also pass one of the other tests return ``False`` from the :func:`ismethoddescriptor` test, simply because the other tests promise more -- you can, e.g., count on having the - :attr:`__func__` attribute (etc) when an object passes :func:`ismethod`. + :ref:`__func__ ` attribute (etc) when an object passes + :func:`ismethod`. .. function:: isdatadescriptor(object) @@ -503,7 +504,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Examples are properties (defined in Python), getsets, and members. The latter two are defined in C and there are more specific tests available for those types, which is robust across Python implementations. Typically, data - descriptors will also have :attr:`~definition.__name__` and :attr:`__doc__` attributes + descriptors will also have :attr:`~definition.__name__` and :attr:`!__doc__` attributes (properties, getsets, and members have both of these attributes), but this is not guaranteed. @@ -1440,7 +1441,8 @@ Fetching attributes statically Both :func:`getattr` and :func:`hasattr` can trigger code execution when fetching or checking for the existence of attributes. Descriptors, like -properties, will be invoked and :meth:`__getattr__` and :meth:`__getattribute__` +properties, will be invoked and :meth:`~object.__getattr__` and +:meth:`~object.__getattribute__` may be called. For cases where you want passive introspection, like documentation tools, this @@ -1450,7 +1452,8 @@ but avoids executing code when it fetches attributes. .. function:: getattr_static(obj, attr, default=None) Retrieve attributes without triggering dynamic lookup via the - descriptor protocol, :meth:`__getattr__` or :meth:`__getattribute__`. + descriptor protocol, :meth:`~object.__getattr__` + or :meth:`~object.__getattribute__`. Note: this function may not be able to retrieve all attributes that getattr can fetch (like dynamically created attributes) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index f7d3d2d0bbec23..29298b79ef06dd 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -634,6 +634,8 @@ code object; see the description of internal types below. The module. +.. _instance-methods: + Instance methods ^^^^^^^^^^^^^^^^ diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 016de153f3dd6a..8bdbb0fa352ed1 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1677,8 +1677,9 @@ Some smaller changes made to the core Python language are: (:issue:`1591665`) * Instance method objects have new attributes for the object and function - comprising the method; the new synonym for :attr:`im_self` is - :attr:`__self__`, and :attr:`im_func` is also available as :attr:`__func__`. + comprising the method; the new synonym for :attr:`!im_self` is + :ref:`__self__ `, and :attr:`!im_func` is also available as + :ref:`__func__ `. The old names are still supported in Python 2.6, but are gone in 3.0. * An obscure change: when you use the :func:`locals` function inside a diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index da66dd731831bc..4072e040dc9130 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -860,7 +860,8 @@ Some smaller changes made to the core Python language are: * When using ``@classmethod`` and ``@staticmethod`` to wrap methods as class or static methods, the wrapper object now - exposes the wrapped function as their :attr:`__func__` attribute. + exposes the wrapped function as their :ref:`__func__ ` + attribute. (Contributed by Amaury Forgeot d'Arc, after a suggestion by George Sakkis; :issue:`5982`.) From 489aeac3a2d3b347ff033334688e2f44eec7944a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 3 Dec 2023 22:23:09 +0200 Subject: [PATCH 083/442] gh-101100: Fix Sphinx warning in `library/gettext.rst` (#112668) Co-authored-by: Alex Waygood --- Doc/library/gettext.rst | 14 +++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/gettext.rst b/Doc/library/gettext.rst index dc6cf5533fccbe..41beac3e0c7396 100644 --- a/Doc/library/gettext.rst +++ b/Doc/library/gettext.rst @@ -257,7 +257,7 @@ are the methods of :class:`!NullTranslations`: .. method:: info() - Return the "protected" :attr:`_info` variable, a dictionary containing + Return a dictionary containing the metadata found in the message catalog file. @@ -296,9 +296,9 @@ are the methods of :class:`!NullTranslations`: The :class:`GNUTranslations` class ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The :mod:`gettext` module provides one additional class derived from +The :mod:`!gettext` module provides one additional class derived from :class:`NullTranslations`: :class:`GNUTranslations`. This class overrides -:meth:`_parse` to enable reading GNU :program:`gettext` format :file:`.mo` files +:meth:`!_parse` to enable reading GNU :program:`gettext` format :file:`.mo` files in both big-endian and little-endian format. :class:`GNUTranslations` parses optional metadata out of the translation @@ -306,7 +306,7 @@ catalog. It is convention with GNU :program:`gettext` to include metadata as the translation for the empty string. This metadata is in :rfc:`822`\ -style ``key: value`` pairs, and should contain the ``Project-Id-Version`` key. If the key ``Content-Type`` is found, then the ``charset`` property is used to -initialize the "protected" :attr:`_charset` instance variable, defaulting to +initialize the "protected" :attr:`!_charset` instance variable, defaulting to ``None`` if not found. If the charset encoding is specified, then all message ids and message strings read from the catalog are converted to Unicode using this encoding, else ASCII is assumed. @@ -315,7 +315,7 @@ Since message ids are read as Unicode strings too, all ``*gettext()`` methods will assume message ids as Unicode strings, not byte strings. The entire set of key/value pairs are placed into a dictionary and set as the -"protected" :attr:`_info` instance variable. +"protected" :attr:`!_info` instance variable. If the :file:`.mo` file's magic number is invalid, the major version number is unexpected, or if other problems occur while reading the file, instantiating a @@ -636,9 +636,9 @@ implementations, and valuable experience to the creation of this module: .. rubric:: Footnotes -.. [#] The default locale directory is system dependent; for example, on RedHat Linux +.. [#] The default locale directory is system dependent; for example, on Red Hat Linux it is :file:`/usr/share/locale`, but on Solaris it is :file:`/usr/lib/locale`. - The :mod:`gettext` module does not try to support these system dependent + The :mod:`!gettext` module does not try to support these system dependent defaults; instead its default is :file:`{sys.base_prefix}/share/locale` (see :data:`sys.base_prefix`). For this reason, it is always best to call :func:`bindtextdomain` with an explicit absolute path at the start of your diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 1e3e367460147a..8d79a848b7cc7b 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -58,7 +58,6 @@ Doc/library/fcntl.rst Doc/library/ftplib.rst Doc/library/functions.rst Doc/library/functools.rst -Doc/library/gettext.rst Doc/library/http.client.rst Doc/library/http.cookiejar.rst Doc/library/http.cookies.rst From 09505c5c26d6a4c81b54786eb4196379e9cb223c Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 3 Dec 2023 20:35:10 +0000 Subject: [PATCH 084/442] GH-106747: Improve `Path.glob()` expectations in pathlib tests (#112365) Add trailing slashes to expected `Path.glob()` results wherever a pattern has a trailing slash. This matches what `glob.glob()` produces. Due to another bug (GH-65238) pathlib strips all trailing slashes, so this change is academic for now. --- Lib/test/test_pathlib.py | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 427e082f3e16cb..ccaef070974ffd 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1968,9 +1968,9 @@ def _check(glob, expected): _check(p.glob("brokenLink"), ['brokenLink']) if not self.can_symlink: - _check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"]) + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) else: - _check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE", "linkB"]) + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) def test_glob_empty_pattern(self): p = self.cls() @@ -2003,17 +2003,17 @@ def _check(path, glob, expected): _check(p, "*A", ["dirA", "fileA", "linkA"]) _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) - _check(p, "*/", ["dirA", "dirB", "dirC", "dirE", "linkB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."]) - _check(p, "dir*/**/", ["dirA", "dirA/linkC", "dirA/linkC/linkD", "dirB", "dirB/linkD", - "dirC", "dirC/dirD", "dirE"]) + _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _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*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD"]) - _check(p, "*/dirD/**/", ["dirC/dirD"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_glob_no_follow_symlinks_common(self): if not self.can_symlink: @@ -2028,15 +2028,15 @@ def _check(path, glob, expected): _check(p, "*A", ["dirA", "fileA", "linkA"]) _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"]) _check(p, "*/fileB", ["dirB/fileB"]) - _check(p, "*/", ["dirA", "dirB", "dirC", "dirE"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) _check(p, "dir*/*/..", ["dirC/dirD/.."]) - _check(p, "dir*/**/", ["dirA", "dirB", "dirC", "dirC/dirD", "dirE"]) + _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**/", ["dirC/dirD"]) + _check(p, "dir*/*/**/", ["dirC/dirD/"]) _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD"]) - _check(p, "*/dirD/**/", ["dirC/dirD"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) def test_rglob_common(self): def _check(glob, expected): @@ -2058,25 +2058,25 @@ def _check(glob, expected): "dirC/fileC", "dirC/dirD/fileD"]) if not self.can_symlink: _check(p.rglob("*/"), [ - "dirA", "dirB", "dirC", "dirC/dirD", "dirE", + "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", ]) else: _check(p.rglob("*/"), [ - "dirA", "dirA/linkC", "dirB", "dirB/linkD", "dirC", - "dirC/dirD", "dirE", "linkB", + "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", + "dirC/dirD/", "dirE/", "linkB/", ]) - _check(p.rglob(""), ["", "dirA", "dirB", "dirC", "dirE", "dirC/dirD"]) + _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) p = P(BASE, "dirC") _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", "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"]) + _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/dirD"]) + _check(p.rglob("*/"), ["dirC/dirD/"]) + _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) + _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p.rglob("*.txt"), ["dirC/novel.txt"]) _check(p.rglob("*.*"), ["dirC/novel.txt"]) @@ -2095,18 +2095,18 @@ def _check(path, glob, expected): _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) - _check(p, "*/", ["dirA", "dirA/linkC", "dirA/linkC/linkD", "dirB", "dirB/linkD", - "dirC", "dirC/dirD", "dirE", "linkB", "linkB/linkD"]) - _check(p, "", ["", "dirA", "dirA/linkC", "dirA/linkC/linkD", "dirB", "dirB/linkD", - "dirC", "dirE", "dirC/dirD", "linkB", "linkB/linkD"]) + _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) + _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) p = P(BASE, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD"]) - _check(p, "", ["dirC", "dirC/dirD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p, "*.txt", ["dirC/novel.txt"]) _check(p, "*.*", ["dirC/novel.txt"]) @@ -2123,16 +2123,16 @@ def _check(path, glob, expected): _check(p, "*/fileA", []) _check(p, "*/fileB", ["dirB/fileB"]) _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ]) - _check(p, "*/", ["dirA", "dirB", "dirC", "dirC/dirD", "dirE"]) - _check(p, "", ["", "dirA", "dirB", "dirC", "dirE", "dirC/dirD"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) p = P(BASE, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD"]) - _check(p, "", ["dirC", "dirC/dirD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression _check(p, "*.txt", ["dirC/novel.txt"]) _check(p, "*.*", ["dirC/novel.txt"]) @@ -3642,7 +3642,7 @@ def test_glob(self): P = self.cls p = P(BASE) self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") }) - self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA") }) + self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA/") }) self.assertEqual(set(p.glob("F*a")), { P(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"}) @@ -3651,7 +3651,7 @@ def test_rglob(self): P = self.cls p = P(BASE, "dirC") self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) - self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD") }) + self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD/") }) self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"}) def test_expanduser(self): From 5a1b5316af648ae79bb91f28253b6272bbbd2886 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sun, 3 Dec 2023 23:45:56 -0500 Subject: [PATCH 085/442] gh-66819: More IDLE htest updates(3) (#112683) Revise spec-callable pairs from percolator to end. --- Lib/idlelib/help.py | 4 ++-- Lib/idlelib/idle_test/htest.py | 16 ++++++++-------- Lib/idlelib/percolator.py | 12 ++++++------ Lib/idlelib/scrolledlist.py | 3 ++- Lib/idlelib/stackviewer.py | 4 ++-- Lib/idlelib/undo.py | 14 +++++++------- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index cc027b9cef4f5b..580a327f620a79 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -278,7 +278,7 @@ def copy_strip(): out.write(line.rstrip() + b'\n') print(f'{src} copied to {dst}') -def show_idlehelp(parent): +def _helpwindow(parent): "Create HelpWindow; called from Idle Help event handler." filename = join(abspath(dirname(__file__)), 'help.html') if not isfile(filename): @@ -291,4 +291,4 @@ def show_idlehelp(parent): main('idlelib.idle_test.test_help', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(show_idlehelp) + run(_helpwindow) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index a59b474fba47d8..4042106bf44a9f 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -190,6 +190,13 @@ ", [Cancel], or [X] prints None to shell" } +_helpwindow_spec = { + 'file': 'help', + 'kwds': {}, + 'msg': "If the help text displays, this works.\n" + "Text is selectable. Window is scrollable." + } + _io_binding_spec = { 'file': 'iomenu', 'kwds': {}, @@ -312,14 +319,7 @@ "Right clicking an item will display a popup." } -show_idlehelp_spec = { - 'file': 'help', - 'kwds': {}, - 'msg': "If the help text displays, this works.\n" - "Text is selectable. Window is scrollable." - } - -_stack_viewer_spec = { +_stackbrowser_spec = { 'file': 'stackviewer', 'kwds': {}, 'msg': "A stacktrace for a NameError exception.\n" diff --git a/Lib/idlelib/percolator.py b/Lib/idlelib/percolator.py index 1fe34d29f54eb2..91ad7272f4ae56 100644 --- a/Lib/idlelib/percolator.py +++ b/Lib/idlelib/percolator.py @@ -86,11 +86,11 @@ def delete(self, *args): print(self.name, ": delete", args) self.delegate.delete(*args) - box = tk.Toplevel(parent) - box.title("Test Percolator") + top = tk.Toplevel(parent) + top.title("Test Percolator") x, y = map(int, parent.geometry().split('+')[1:]) - box.geometry("+%d+%d" % (x, y + 175)) - text = tk.Text(box) + top.geometry("+%d+%d" % (x, y + 175)) + text = tk.Text(top) p = Percolator(text) pin = p.insertfilter pout = p.removefilter @@ -104,10 +104,10 @@ def toggle2(): text.pack() var1 = tk.IntVar(parent) - cb1 = tk.Checkbutton(box, text="Tracer1", command=toggle1, variable=var1) + cb1 = tk.Checkbutton(top, text="Tracer1", command=toggle1, variable=var1) cb1.pack() var2 = tk.IntVar(parent) - cb2 = tk.Checkbutton(box, text="Tracer2", command=toggle2, variable=var2) + cb2 = tk.Checkbutton(top, text="Tracer2", command=toggle2, variable=var2) cb2.pack() if __name__ == "__main__": diff --git a/Lib/idlelib/scrolledlist.py b/Lib/idlelib/scrolledlist.py index 71fd18ab19ec8a..4f1241a576fca1 100644 --- a/Lib/idlelib/scrolledlist.py +++ b/Lib/idlelib/scrolledlist.py @@ -132,6 +132,7 @@ def _scrolled_list(parent): # htest # top = Toplevel(parent) x, y = map(int, parent.geometry().split('+')[1:]) top.geometry("+%d+%d" % (x+200, y + 175)) + class MyScrolledList(ScrolledList): def fill_menu(self): self.menu.add_command(label="right click") def on_select(self, index): print("select", self.get(index)) @@ -143,7 +144,7 @@ def on_double(self, index): print("double", self.get(index)) if __name__ == '__main__': from unittest import main - main('idlelib.idle_test.test_scrolledlist', verbosity=2,) + main('idlelib.idle_test.test_scrolledlist', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_scrolled_list) diff --git a/Lib/idlelib/stackviewer.py b/Lib/idlelib/stackviewer.py index f8e60fd9b6d818..977c56ef15f2ae 100644 --- a/Lib/idlelib/stackviewer.py +++ b/Lib/idlelib/stackviewer.py @@ -113,7 +113,7 @@ def setfunction(value, key=key, object=self.object): return sublist -def _stack_viewer(parent): # htest # +def _stackbrowser(parent): # htest # from idlelib.pyshell import PyShellFileList top = tk.Toplevel(parent) top.title("Test StackViewer") @@ -131,4 +131,4 @@ def _stack_viewer(parent): # htest # main('idlelib.idle_test.test_stackviewer', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(_stack_viewer) + run(_stackbrowser) diff --git a/Lib/idlelib/undo.py b/Lib/idlelib/undo.py index 5f10c0f05c1acb..f1d03f4c9ed5ef 100644 --- a/Lib/idlelib/undo.py +++ b/Lib/idlelib/undo.py @@ -339,23 +339,23 @@ def bump_depth(self, incr=1): def _undo_delegator(parent): # htest # from tkinter import Toplevel, Text, Button from idlelib.percolator import Percolator - undowin = Toplevel(parent) - undowin.title("Test UndoDelegator") + top = Toplevel(parent) + top.title("Test UndoDelegator") x, y = map(int, parent.geometry().split('+')[1:]) - undowin.geometry("+%d+%d" % (x, y + 175)) + top.geometry("+%d+%d" % (x, y + 175)) - text = Text(undowin, height=10) + text = Text(top, height=10) text.pack() text.focus_set() p = Percolator(text) d = UndoDelegator() p.insertfilter(d) - undo = Button(undowin, text="Undo", command=lambda:d.undo_event(None)) + undo = Button(top, text="Undo", command=lambda:d.undo_event(None)) undo.pack(side='left') - redo = Button(undowin, text="Redo", command=lambda:d.redo_event(None)) + redo = Button(top, text="Redo", command=lambda:d.redo_event(None)) redo.pack(side='left') - dump = Button(undowin, text="Dump", command=lambda:d.dump_event(None)) + dump = Button(top, text="Dump", command=lambda:d.dump_event(None)) dump.pack(side='left') if __name__ == "__main__": From e5b0db0315941b968ebcc2414bfcdd2da44fd3c2 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Mon, 4 Dec 2023 01:36:40 -0500 Subject: [PATCH 086/442] gh-66819: More IDLE htest updates(4) (#112686) Mostly double spacing before 'if __name__...'. --- Lib/idlelib/browser.py | 1 + Lib/idlelib/calltip_w.py | 1 + Lib/idlelib/config.py | 1 + Lib/idlelib/debugobj.py | 1 + Lib/idlelib/delegator.py | 1 + Lib/idlelib/dynoption.py | 2 ++ Lib/idlelib/editor.py | 1 + Lib/idlelib/filelist.py | 1 + Lib/idlelib/grep.py | 6 ++-- Lib/idlelib/help.py | 2 ++ Lib/idlelib/idle_test/htest.py | 60 +++++++++++++++++----------------- Lib/idlelib/iomenu.py | 2 ++ Lib/idlelib/multicall.py | 1 + Lib/idlelib/outwin.py | 1 + Lib/idlelib/percolator.py | 2 ++ Lib/idlelib/pyshell.py | 1 + Lib/idlelib/redirector.py | 1 + Lib/idlelib/replace.py | 1 + Lib/idlelib/scrolledlist.py | 1 + Lib/idlelib/search.py | 1 + Lib/idlelib/sidebar.py | 4 +-- Lib/idlelib/statusbar.py | 1 + Lib/idlelib/tree.py | 1 + Lib/idlelib/undo.py | 1 + Lib/idlelib/util.py | 1 + 25 files changed, 62 insertions(+), 34 deletions(-) diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 672e229ffbca94..8b9060e57072ea 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -250,6 +250,7 @@ def closure(): class Nested_in_closure: pass ModuleBrowser(parent, file, _htest=True) + if __name__ == "__main__": if len(sys.argv) == 1: # If pass file on command line, unittest fails. from unittest import main diff --git a/Lib/idlelib/calltip_w.py b/Lib/idlelib/calltip_w.py index 278546064adde2..9386376058c791 100644 --- a/Lib/idlelib/calltip_w.py +++ b/Lib/idlelib/calltip_w.py @@ -193,6 +193,7 @@ def calltip_hide(event): text.focus_set() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 898efeb4dd1550..92992fd9cce9cd 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -906,6 +906,7 @@ def dumpCfg(cfg): dumpCfg(idleConf.userCfg) print('\nlines = ', line, ', crc = ', crc, sep='') + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_config', verbosity=2, exit=False) diff --git a/Lib/idlelib/debugobj.py b/Lib/idlelib/debugobj.py index 0bf2cb1d5bbfe2..156377f8ed26ac 100644 --- a/Lib/idlelib/debugobj.py +++ b/Lib/idlelib/debugobj.py @@ -135,6 +135,7 @@ def _debug_object_browser(parent): # htest # node = TreeNode(sc.canvas, None, item) node.update() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_debugobj', verbosity=2, exit=False) diff --git a/Lib/idlelib/delegator.py b/Lib/idlelib/delegator.py index 55c95da8532f47..93ae8bbd43ff44 100644 --- a/Lib/idlelib/delegator.py +++ b/Lib/idlelib/delegator.py @@ -28,6 +28,7 @@ def setdelegate(self, delegate): self.resetcache() self.delegate = delegate + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_delegator', verbosity=2) diff --git a/Lib/idlelib/dynoption.py b/Lib/idlelib/dynoption.py index d5dfc3eda13f60..b8937f7106ca75 100644 --- a/Lib/idlelib/dynoption.py +++ b/Lib/idlelib/dynoption.py @@ -29,6 +29,7 @@ def SetMenu(self,valueList,value=None): if value: self.variable.set(value) + def _dyn_option_menu(parent): # htest # from tkinter import Toplevel # + StringVar, Button @@ -49,6 +50,7 @@ def update(): button = Button(top, text="Change option set", command=update) button.pack() + if __name__ == '__main__': # Only module without unittests because of intention to replace. from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 69b27d0683a104..6ad383f460c7ee 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1748,6 +1748,7 @@ def _editor_window(parent): # htest # # Does not stop error, neither does following # edit.text.bind("<>", edit.close_event) + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_editor', verbosity=2, exit=False) diff --git a/Lib/idlelib/filelist.py b/Lib/idlelib/filelist.py index f87781d2570fe0..e27e5d32a0ff63 100644 --- a/Lib/idlelib/filelist.py +++ b/Lib/idlelib/filelist.py @@ -124,6 +124,7 @@ def _test(): # TODO check and convert to htest if flist.inversedict: root.mainloop() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_filelist', verbosity=2) diff --git a/Lib/idlelib/grep.py b/Lib/idlelib/grep.py index 12513594b76f8f..ef14349960bfa2 100644 --- a/Lib/idlelib/grep.py +++ b/Lib/idlelib/grep.py @@ -204,15 +204,17 @@ def _grep_dialog(parent): # htest # frame.pack() text = Text(frame, height=5) text.pack() + text.insert('1.0', 'import grep') def show_grep_dialog(): - text.tag_add(SEL, "1.0", END) + text.tag_add(SEL, "1.0", '1.end') grep(text, flist=flist) - text.tag_remove(SEL, "1.0", END) + text.tag_remove(SEL, "1.0", '1.end') button = Button(frame, text="Show GrepDialog", command=show_grep_dialog) button.pack() + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_grep', verbosity=2, exit=False) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index 580a327f620a79..3cc7e36e35555b 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -278,6 +278,7 @@ def copy_strip(): out.write(line.rstrip() + b'\n') print(f'{src} copied to {dst}') + def _helpwindow(parent): "Create HelpWindow; called from Idle Help event handler." filename = join(abspath(dirname(__file__)), 'help.html') @@ -286,6 +287,7 @@ def _helpwindow(parent): return HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version()) + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_help', verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 4042106bf44a9f..997f85ff5a78b2 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -170,8 +170,8 @@ 'msg': "Click the 'Show GrepDialog' button.\n" "Test the various 'Find-in-files' functions.\n" "The results should be displayed in a new '*Output*' window.\n" - "'Right-click'->'Go to file/line' anywhere in the search results " - "should open that file \nin a new EditorWindow." + "'Right-click'->'Go to file/line' in the search results\n " + "should open that file in a new EditorWindow." } HelpSource_spec = { @@ -210,26 +210,6 @@ "Check that changes were saved by opening the file elsewhere." } -_linenumbers_drag_scrolling_spec = { - 'file': 'sidebar', - 'kwds': {}, - 'msg': textwrap.dedent("""\ - 1. Click on the line numbers and drag down below the edge of the - window, moving the mouse a bit and then leaving it there for a - while. The text and line numbers should gradually scroll down, - with the selection updated continuously. - - 2. With the lines still selected, click on a line number above - or below the selected lines. Only the line whose number was - clicked should be selected. - - 3. Repeat step #1, dragging to above the window. The text and - line numbers should gradually scroll up, with the selection - updated continuously. - - 4. Repeat step #2, clicking a line number below the selection."""), - } - _multi_call_spec = { 'file': 'multicall', 'kwds': {}, @@ -295,6 +275,15 @@ "Click [Close] or [X] to close the 'Replace Dialog'." } +_scrolled_list_spec = { + 'file': 'scrolledlist', + 'kwds': {}, + 'msg': "You should see a scrollable list of items\n" + "Selecting (clicking) or double clicking an item " + "prints the name to the console or Idle shell.\n" + "Right clicking an item will display a popup." + } + _search_dialog_spec = { 'file': 'search', 'kwds': {}, @@ -310,21 +299,31 @@ "Its only action is to close." } -_scrolled_list_spec = { - 'file': 'scrolledlist', +_sidebar_number_scrolling_spec = { + 'file': 'sidebar', 'kwds': {}, - 'msg': "You should see a scrollable list of items\n" - "Selecting (clicking) or double clicking an item " - "prints the name to the console or Idle shell.\n" - "Right clicking an item will display a popup." + 'msg': textwrap.dedent("""\ + 1. Click on the line numbers and drag down below the edge of the + window, moving the mouse a bit and then leaving it there for a + while. The text and line numbers should gradually scroll down, + with the selection updated continuously. + + 2. With the lines still selected, click on a line number above + or below the selected lines. Only the line whose number was + clicked should be selected. + + 3. Repeat step #1, dragging to above the window. The text and + line numbers should gradually scroll up, with the selection + updated continuously. + + 4. Repeat step #2, clicking a line number below the selection."""), } _stackbrowser_spec = { 'file': 'stackviewer', 'kwds': {}, 'msg': "A stacktrace for a NameError exception.\n" - "Expand 'idlelib ...' and ''.\n" - "Check that exc_value, exc_tb, and exc_type are correct.\n" + "Should have NameError and 1 traceback line." } _tooltip_spec = { @@ -438,5 +437,6 @@ def close(_=None): next_test() root.mainloop() + if __name__ == '__main__': run() diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 7629101635b8bb..667623ec71ac98 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -393,6 +393,7 @@ def updaterecentfileslist(self,filename): if self.editwin.flist: self.editwin.update_recent_files_list(filename) + def _io_binding(parent): # htest # from tkinter import Toplevel, Text @@ -430,6 +431,7 @@ def savecopy(self, event): editwin = MyEditWin(text) IOBinding(editwin) + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) diff --git a/Lib/idlelib/multicall.py b/Lib/idlelib/multicall.py index 2aa4a54125156f..41f81813113062 100644 --- a/Lib/idlelib/multicall.py +++ b/Lib/idlelib/multicall.py @@ -442,6 +442,7 @@ def handler(event): bindseq("") bindseq("") + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False) diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 610031e26f1dff..5ed3f35a7af655 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -182,6 +182,7 @@ def setup(self): text.tag_raise('sel') self.write = self.owin.write + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_outwin', verbosity=2, exit=False) diff --git a/Lib/idlelib/percolator.py b/Lib/idlelib/percolator.py index 91ad7272f4ae56..aa73427c4915c8 100644 --- a/Lib/idlelib/percolator.py +++ b/Lib/idlelib/percolator.py @@ -103,6 +103,7 @@ def toggle2(): (pin if var2.get() else pout)(t2) text.pack() + text.focus_set() var1 = tk.IntVar(parent) cb1 = tk.Checkbutton(top, text="Tracer1", command=toggle1, variable=var1) cb1.pack() @@ -110,6 +111,7 @@ def toggle2(): cb2 = tk.Checkbutton(top, text="Tracer2", command=toggle2, variable=var2) cb2.pack() + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_percolator', verbosity=2, exit=False) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 00b3732a7bc4eb..1524fccd5d20f8 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -1694,6 +1694,7 @@ def main(): root.destroy() capture_warnings(False) + if __name__ == "__main__": main() diff --git a/Lib/idlelib/redirector.py b/Lib/idlelib/redirector.py index 4928340e98df68..08728956abd900 100644 --- a/Lib/idlelib/redirector.py +++ b/Lib/idlelib/redirector.py @@ -164,6 +164,7 @@ def my_insert(*args): original_insert(*args) original_insert = redir.register("insert", my_insert) + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_redirector', verbosity=2, exit=False) diff --git a/Lib/idlelib/replace.py b/Lib/idlelib/replace.py index ca83173877ad1d..a29ca591427491 100644 --- a/Lib/idlelib/replace.py +++ b/Lib/idlelib/replace.py @@ -299,6 +299,7 @@ def show_replace(): button = Button(frame, text="Replace", command=show_replace) button.pack() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_replace', verbosity=2, exit=False) diff --git a/Lib/idlelib/scrolledlist.py b/Lib/idlelib/scrolledlist.py index 4f1241a576fca1..4fb418db326255 100644 --- a/Lib/idlelib/scrolledlist.py +++ b/Lib/idlelib/scrolledlist.py @@ -142,6 +142,7 @@ def on_double(self, index): print("double", self.get(index)) for i in range(30): scrolled_list.append("Item %02d" % i) + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_scrolledlist', verbosity=2, exit=False) diff --git a/Lib/idlelib/search.py b/Lib/idlelib/search.py index b35f3b59c3d2e8..935a4832257fa4 100644 --- a/Lib/idlelib/search.py +++ b/Lib/idlelib/search.py @@ -156,6 +156,7 @@ def show_find(): button = Button(frame, text="Search (selection ignored)", command=show_find) button.pack() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_search', verbosity=2, exit=False) diff --git a/Lib/idlelib/sidebar.py b/Lib/idlelib/sidebar.py index 8e7eae5037c90c..ff77b568a786e0 100644 --- a/Lib/idlelib/sidebar.py +++ b/Lib/idlelib/sidebar.py @@ -513,7 +513,7 @@ def update_colors(self): self.change_callback() -def _linenumbers_drag_scrolling(parent): # htest # +def _sidebar_number_scrolling(parent): # htest # from idlelib.idle_test.test_sidebar import Dummy_editwin top = tk.Toplevel(parent) @@ -540,4 +540,4 @@ def _linenumbers_drag_scrolling(parent): # htest # main('idlelib.idle_test.test_sidebar', verbosity=2, exit=False) from idlelib.idle_test.htest import run - run(_linenumbers_drag_scrolling) + run(_sidebar_number_scrolling) diff --git a/Lib/idlelib/statusbar.py b/Lib/idlelib/statusbar.py index 7048bd64b98753..8445d4cc8dfdb9 100644 --- a/Lib/idlelib/statusbar.py +++ b/Lib/idlelib/statusbar.py @@ -43,6 +43,7 @@ def change(): button.pack(side='bottom') frame.pack() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_statusbar', verbosity=2, exit=False) diff --git a/Lib/idlelib/tree.py b/Lib/idlelib/tree.py index 5f30f0f6092bfa..9c2eb47b24aec9 100644 --- a/Lib/idlelib/tree.py +++ b/Lib/idlelib/tree.py @@ -492,6 +492,7 @@ def _tree_widget(parent): # htest # node = TreeNode(sc.canvas, None, item) node.expand() + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_tree', verbosity=2, exit=False) diff --git a/Lib/idlelib/undo.py b/Lib/idlelib/undo.py index f1d03f4c9ed5ef..f52446d5fcdcf8 100644 --- a/Lib/idlelib/undo.py +++ b/Lib/idlelib/undo.py @@ -358,6 +358,7 @@ def _undo_delegator(parent): # htest # dump = Button(top, text="Dump", command=lambda:d.dump_event(None)) dump.pack(side='left') + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_undo', verbosity=2, exit=False) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index ede670a4db5536..5ac69a7b94cb56 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -16,6 +16,7 @@ # .pyw is for Windows; .pyi is for stub files. py_extensions = ('.py', '.pyw', '.pyi') # Order needed for open/save dialogs. + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_util', verbosity=2) From 23e001fa9f1897ba3384c02bbbe634313358a549 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Mon, 4 Dec 2023 02:00:27 -0600 Subject: [PATCH 087/442] gh-112678: Declare `Tkapp_CallDeallocArgs()` as `static` (GH-112679) --- Modules/_tkinter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index f9a18644945c65..64e752c305aae1 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1202,7 +1202,7 @@ typedef struct Tkapp_CallEvent { Tcl_Condition *done; } Tkapp_CallEvent; -void +static void Tkapp_CallDeallocArgs(Tcl_Obj** objv, Tcl_Obj** objStore, int objc) { int i; From 0e732d0997cff08855d98c17af4dd5527f10e419 Mon Sep 17 00:00:00 2001 From: chilaxan Date: Mon, 4 Dec 2023 03:15:43 -0500 Subject: [PATCH 088/442] gh-112625: Protect bytearray from being freed by misbehaving iterator inside bytearray.join (GH-112626) --- Lib/test/test_builtin.py | 17 +++++++++++++++++ ...23-12-03-19-34-51.gh-issue-112625.QWTlwS.rst | 1 + Objects/bytearrayobject.c | 5 ++++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-03-19-34-51.gh-issue-112625.QWTlwS.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index b7966f8f03875b..535856adaea4d3 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2039,6 +2039,23 @@ def test_bytearray_extend_error(self): bad_iter = map(int, "X") self.assertRaises(ValueError, array.extend, bad_iter) + def test_bytearray_join_with_misbehaving_iterator(self): + # Issue #112625 + array = bytearray(b',') + def iterator(): + array.clear() + yield b'A' + yield b'B' + self.assertRaises(BufferError, array.join, iterator()) + + def test_bytearray_join_with_custom_iterator(self): + # Issue #112625 + array = bytearray(b',') + def iterator(): + yield b'A' + yield b'B' + self.assertEqual(bytearray(b'A,B'), array.join(iterator())) + def test_construct_singletons(self): for const in None, Ellipsis, NotImplemented: tp = type(const) diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-03-19-34-51.gh-issue-112625.QWTlwS.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-19-34-51.gh-issue-112625.QWTlwS.rst new file mode 100644 index 00000000000000..4970e10f3f4dcb --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-19-34-51.gh-issue-112625.QWTlwS.rst @@ -0,0 +1 @@ +Fixes a bug where a bytearray object could be cleared while iterating over an argument in the ``bytearray.join()`` method that could result in reading memory after it was freed. diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 67073190cc889d..659de7d3dd5a99 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -2007,7 +2007,10 @@ static PyObject * bytearray_join(PyByteArrayObject *self, PyObject *iterable_of_bytes) /*[clinic end generated code: output=a8516370bf68ae08 input=aba6b1f9b30fcb8e]*/ { - return stringlib_bytes_join((PyObject*)self, iterable_of_bytes); + self->ob_exports++; // this protects `self` from being cleared/resized if `iterable_of_bytes` is a custom iterator + PyObject* ret = stringlib_bytes_join((PyObject*)self, iterable_of_bytes); + self->ob_exports--; // unexport `self` + return ret; } /*[clinic input] From dee7beeb4f9d28fec945c8c495027cc22a512328 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 4 Dec 2023 11:09:06 +0200 Subject: [PATCH 089/442] bpo-34392: Add sys. _is_interned() (GH-8755) --- Doc/library/sys.rst | 12 +++++++ Doc/whatsnew/3.13.rst | 7 ++++ Lib/test/test_sys.py | 14 ++++++++ .../2018-08-13-13-25-15.bpo-34392.9kIlMF.rst | 1 + Python/clinic/sysmodule.c.h | 36 ++++++++++++++++++- Python/sysmodule.c | 18 ++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-08-13-13-25-15.bpo-34392.9kIlMF.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index bf9aaca2a696de..7f359819e6847e 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1205,6 +1205,18 @@ always available. .. versionadded:: 3.12 +.. function:: _is_interned(string) + + Return :const:`True` if the given string is "interned", :const:`False` + otherwise. + + .. versionadded:: 3.13 + + .. impl-detail:: + + It is not guaranteed to exist in all implementations of Python. + + .. data:: last_type last_value last_traceback diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 372e4a45468e68..676305c6e1d06a 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -297,6 +297,13 @@ sqlite3 object is not :meth:`closed ` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +sys +--- + +* Add the :func:`sys._is_interned` function to test if the string was interned. + This function is not guaranteed to exist in all implementations of Python. + (Contributed by Serhiy Storchaka in :gh:`78573`.) + tkinter ------- diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 0028281596fa4b..8c2c1a40f74bf2 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -691,11 +691,23 @@ def test_43581(self): self.assertEqual(sys.__stdout__.encoding, sys.__stderr__.encoding) def test_intern(self): + has_is_interned = (test.support.check_impl_detail(cpython=True) + or hasattr(sys, '_is_interned')) self.assertRaises(TypeError, sys.intern) + self.assertRaises(TypeError, sys.intern, b'abc') + if has_is_interned: + self.assertRaises(TypeError, sys._is_interned) + self.assertRaises(TypeError, sys._is_interned, b'abc') s = "never interned before" + str(random.randrange(0, 10**9)) self.assertTrue(sys.intern(s) is s) + if has_is_interned: + self.assertIs(sys._is_interned(s), True) s2 = s.swapcase().swapcase() + if has_is_interned: + self.assertIs(sys._is_interned(s2), False) self.assertTrue(sys.intern(s2) is s) + if has_is_interned: + self.assertIs(sys._is_interned(s2), False) # Subclasses of string can't be interned, because they # provide too much opportunity for insane things to happen. @@ -707,6 +719,8 @@ def __hash__(self): return 123 self.assertRaises(TypeError, sys.intern, S("abc")) + if has_is_interned: + self.assertIs(sys._is_interned(S("abc")), False) @requires_subinterpreters def test_subinterp_intern_dynamically_allocated(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-08-13-13-25-15.bpo-34392.9kIlMF.rst b/Misc/NEWS.d/next/Core and Builtins/2018-08-13-13-25-15.bpo-34392.9kIlMF.rst new file mode 100644 index 00000000000000..bc4fd1ad1f5c7c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-08-13-13-25-15.bpo-34392.9kIlMF.rst @@ -0,0 +1 @@ +Added :func:`sys._is_interned`. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 98717ecc875b8b..93b8385a5b4097 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -289,6 +289,40 @@ sys_intern(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(sys__is_interned__doc__, +"_is_interned($module, string, /)\n" +"--\n" +"\n" +"Return True if the given string is \"interned\"."); + +#define SYS__IS_INTERNED_METHODDEF \ + {"_is_interned", (PyCFunction)sys__is_interned, METH_O, sys__is_interned__doc__}, + +static int +sys__is_interned_impl(PyObject *module, PyObject *string); + +static PyObject * +sys__is_interned(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + PyObject *string; + int _return_value; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("_is_interned", "argument", "str", arg); + goto exit; + } + string = arg; + _return_value = sys__is_interned_impl(module, string); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(sys__settraceallthreads__doc__, "_settraceallthreads($module, arg, /)\n" "--\n" @@ -1452,4 +1486,4 @@ sys__get_cpu_count_config(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=f36d45c829250775 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3dc3b2cb0ce38ebb input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index c17de44731b703..46878c7c9687f5 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -989,6 +989,23 @@ sys_intern_impl(PyObject *module, PyObject *s) } +/*[clinic input] +sys._is_interned -> bool + + string: unicode + / + +Return True if the given string is "interned". +[clinic start generated code]*/ + +static int +sys__is_interned_impl(PyObject *module, PyObject *string) +/*[clinic end generated code: output=c3678267b4e9d7ed input=039843e17883b606]*/ +{ + return PyUnicode_CHECK_INTERNED(string); +} + + /* * Cached interned string objects used for calling the profile and * trace functions. @@ -2462,6 +2479,7 @@ static PyMethodDef sys_methods[] = { SYS_GETWINDOWSVERSION_METHODDEF SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF SYS_INTERN_METHODDEF + SYS__IS_INTERNED_METHODDEF SYS_IS_FINALIZING_METHODDEF SYS_MDEBUG_METHODDEF SYS_SETSWITCHINTERVAL_METHODDEF From a74902a14cdc0952abf7bfabcf529c9b132c5cce Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 4 Dec 2023 11:42:58 +0100 Subject: [PATCH 090/442] gh-106550: Fix sign conversion in pycore_code.h (#112613) Fix sign conversion in pycore_code.h: use unsigned integers and cast explicitly when needed. --- Include/internal/pycore_code.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index eaf84a9c94fc9b..73df6c3568ffe0 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -394,27 +394,29 @@ write_varint(uint8_t *ptr, unsigned int val) val >>= 6; written++; } - *ptr = val; + *ptr = (uint8_t)val; return written; } static inline int write_signed_varint(uint8_t *ptr, int val) { + unsigned int uval; if (val < 0) { - val = ((-val)<<1) | 1; + // (unsigned int)(-val) has an undefined behavior for INT_MIN + uval = ((0 - (unsigned int)val) << 1) | 1; } else { - val = val << 1; + uval = (unsigned int)val << 1; } - return write_varint(ptr, val); + return write_varint(ptr, uval); } static inline int write_location_entry_start(uint8_t *ptr, int code, int length) { assert((code & 15) == code); - *ptr = 128 | (code << 3) | (length - 1); + *ptr = 128 | (uint8_t)(code << 3) | (uint8_t)(length - 1); return 1; } @@ -454,9 +456,9 @@ write_location_entry_start(uint8_t *ptr, int code, int length) static inline uint16_t -adaptive_counter_bits(int value, int backoff) { - return (value << ADAPTIVE_BACKOFF_BITS) | - (backoff & ((1< MAX_BACKOFF_VALUE) { backoff = MAX_BACKOFF_VALUE; } - unsigned int value = (1 << backoff) - 1; + uint16_t value = (uint16_t)(1 << backoff) - 1; return adaptive_counter_bits(value, backoff); } From cda737924fd616c4e08027888258f97e81f34447 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 4 Dec 2023 11:05:20 +0000 Subject: [PATCH 091/442] gh-101100: Fix Sphinx nitpicks in `library/functions.rst` (#112669) --- Doc/library/functions.rst | 113 ++++++++++++++++++++++---------------- Doc/tools/.nitignore | 1 - 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index b2dd32f925ef4d..c731b6fd333275 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -57,7 +57,8 @@ are always available. They are listed here in alphabetical order. .. function:: abs(x) Return the absolute value of a number. The argument may be an - integer, a floating point number, or an object implementing :meth:`__abs__`. + integer, a floating point number, or an object implementing + :meth:`~object.__abs__`. If the argument is a complex number, its magnitude is returned. @@ -235,7 +236,7 @@ are always available. They are listed here in alphabetical order. :const:`False` if not. If this returns ``True``, it is still possible that a call fails, but if it is ``False``, calling *object* will never succeed. Note that classes are callable (calling a class returns a new instance); - instances are callable if their class has a :meth:`__call__` method. + instances are callable if their class has a :meth:`~object.__call__` method. .. versionadded:: 3.2 This function was first removed in Python 3.0 and then brought back @@ -432,15 +433,18 @@ are always available. They are listed here in alphabetical order. Without arguments, return the list of names in the current local scope. With an argument, attempt to return a list of valid attributes for that object. - If the object has a method named :meth:`__dir__`, this method will be called and + If the object has a method named :meth:`~object.__dir__`, + this method will be called and must return the list of attributes. This allows objects that implement a custom - :func:`__getattr__` or :func:`__getattribute__` function to customize the way + :func:`~object.__getattr__` or :func:`~object.__getattribute__` function + to customize the way :func:`dir` reports their attributes. - If the object does not provide :meth:`__dir__`, the function tries its best to - gather information from the object's :attr:`~object.__dict__` attribute, if defined, and + If the object does not provide :meth:`~object.__dir__`, + the function tries its best to gather information from the object's + :attr:`~object.__dict__` attribute, if defined, and from its type object. The resulting list is not necessarily complete and may - be inaccurate when the object has a custom :func:`__getattr__`. + be inaccurate when the object has a custom :func:`~object.__getattr__`. The default :func:`dir` mechanism behaves differently with different types of objects, as it attempts to produce the most relevant, rather than complete, @@ -664,7 +668,7 @@ are always available. They are listed here in alphabetical order. sign: "+" | "-" infinity: "Infinity" | "inf" nan: "nan" - digitpart: `digit` (["_"] `digit`)* + digitpart: `!digit` (["_"] `!digit`)* number: [`digitpart`] "." `digitpart` | `digitpart` ["."] exponent: ("e" | "E") ["+" | "-"] `digitpart` floatnumber: number [`exponent`] @@ -727,8 +731,8 @@ are always available. They are listed here in alphabetical order. A call to ``format(value, format_spec)`` is translated to ``type(value).__format__(value, format_spec)`` which bypasses the instance - dictionary when searching for the value's :meth:`__format__` method. A - :exc:`TypeError` exception is raised if the method search reaches + dictionary when searching for the value's :meth:`~object.__format__` method. + A :exc:`TypeError` exception is raised if the method search reaches :mod:`object` and the *format_spec* is non-empty, or if either the *format_spec* or the return value are not strings. @@ -792,9 +796,9 @@ are always available. They are listed here in alphabetical order. .. note:: - For objects with custom :meth:`__hash__` methods, note that :func:`hash` + For objects with custom :meth:`~object.__hash__` methods, + note that :func:`hash` truncates the return value based on the bit width of the host machine. - See :meth:`__hash__ ` for details. .. function:: help() help(request) @@ -982,7 +986,8 @@ are always available. They are listed here in alphabetical order. Return an :term:`iterator` object. The first argument is interpreted very differently depending on the presence of the second argument. Without a second argument, *object* must be a collection object which supports the - :term:`iterable` protocol (the :meth:`__iter__` method), or it must support + :term:`iterable` protocol (the :meth:`~object.__iter__` method), + or it must support the sequence protocol (the :meth:`~object.__getitem__` method with integer arguments starting at ``0``). If it does not support either of those protocols, :exc:`TypeError` is raised. If the second argument, *sentinel*, is given, @@ -1500,38 +1505,44 @@ are always available. They are listed here in alphabetical order. """Get the current voltage.""" return self._voltage - The ``@property`` decorator turns the :meth:`voltage` method into a "getter" + The ``@property`` decorator turns the :meth:`!voltage` method into a "getter" for a read-only attribute with the same name, and it sets the docstring for *voltage* to "Get the current voltage." - A property object has :attr:`~property.getter`, :attr:`~property.setter`, - and :attr:`~property.deleter` methods usable as decorators that create a - copy of the property with the corresponding accessor function set to the - decorated function. This is best explained with an example:: + .. decorator:: property.getter + .. decorator:: property.setter + .. decorator:: property.deleter - class C: - def __init__(self): - self._x = None + A property object has ``getter``, ``setter``, + and ``deleter`` methods usable as decorators that create a + copy of the property with the corresponding accessor function set to the + decorated function. This is best explained with an example: - @property - def x(self): - """I'm the 'x' property.""" - return self._x + .. testcode:: - @x.setter - def x(self, value): - self._x = value + class C: + def __init__(self): + self._x = None - @x.deleter - def x(self): - del self._x + @property + def x(self): + """I'm the 'x' property.""" + return self._x - This code is exactly equivalent to the first example. Be sure to give the - additional functions the same name as the original property (``x`` in this - case.) + @x.setter + def x(self, value): + self._x = value - The returned property object also has the attributes ``fget``, ``fset``, and - ``fdel`` corresponding to the constructor arguments. + @x.deleter + def x(self): + del self._x + + This code is exactly equivalent to the first example. Be sure to give the + additional functions the same name as the original property (``x`` in this + case.) + + The returned property object also has the attributes ``fget``, ``fset``, and + ``fdel`` corresponding to the constructor arguments. .. versionchanged:: 3.5 The docstrings of property objects are now writeable. @@ -1554,7 +1565,8 @@ are always available. They are listed here in alphabetical order. representation is a string enclosed in angle brackets that contains the name of the type of the object together with additional information often including the name and address of the object. A class can control what this - function returns for its instances by defining a :meth:`__repr__` method. + function returns for its instances + by defining a :meth:`~object.__repr__` method. If :func:`sys.displayhook` is not accessible, this function will raise :exc:`RuntimeError`. @@ -1562,9 +1574,9 @@ are always available. They are listed here in alphabetical order. .. function:: reversed(seq) Return a reverse :term:`iterator`. *seq* must be an object which has - a :meth:`__reversed__` method or supports the sequence protocol (the - :meth:`__len__` method and the :meth:`~object.__getitem__` method with integer - arguments starting at ``0``). + a :meth:`~object.__reversed__` method or supports the sequence protocol (the + :meth:`~object.__len__` method and the :meth:`~object.__getitem__` method + with integer arguments starting at ``0``). .. function:: round(number, ndigits=None) @@ -1635,13 +1647,21 @@ are always available. They are listed here in alphabetical order. Return a :term:`slice` object representing the set of indices specified by ``range(start, stop, step)``. The *start* and *step* arguments default to - ``None``. Slice objects have read-only data attributes :attr:`~slice.start`, - :attr:`~slice.stop`, and :attr:`~slice.step` which merely return the argument - values (or their default). They have no other explicit functionality; - however, they are used by NumPy and other third-party packages. + ``None``. + + .. attribute:: slice.start + .. attribute:: slice.stop + .. attribute:: slice.step + + Slice objects have read-only data attributes :attr:`!start`, + :attr:`!stop`, and :attr:`!step` which merely return the argument + values (or their default). They have no other explicit functionality; + however, they are used by NumPy and other third-party packages. + Slice objects are also generated when extended indexing syntax is used. For example: ``a[start:stop:step]`` or ``a[start:stop, i]``. See - :func:`itertools.islice` for an alternate version that returns an iterator. + :func:`itertools.islice` for an alternate version that returns an + :term:`iterator`. .. versionchanged:: 3.12 Slice objects are now :term:`hashable` (provided :attr:`~slice.start`, @@ -1808,7 +1828,8 @@ are always available. They are listed here in alphabetical order. Note that :func:`super` is implemented as part of the binding process for explicit dotted attribute lookups such as ``super().__getitem__(name)``. - It does so by implementing its own :meth:`__getattribute__` method for searching + It does so by implementing its own :meth:`~object.__getattribute__` method + for searching classes in a predictable order that supports cooperative multiple inheritance. Accordingly, :func:`super` is undefined for implicit lookups using statements or operators such as ``super()[name]``. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8d79a848b7cc7b..e79b4c3bdecb67 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -56,7 +56,6 @@ Doc/library/exceptions.rst Doc/library/faulthandler.rst Doc/library/fcntl.rst Doc/library/ftplib.rst -Doc/library/functions.rst Doc/library/functools.rst Doc/library/http.client.rst Doc/library/http.cookiejar.rst From da6760bdf5ed8ede203618d5118f4ceb2cb1652d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 4 Dec 2023 13:14:56 +0200 Subject: [PATCH 092/442] gh-65210: Add const qualifiers in PyArg_VaParseTupleAndKeywords() (GH-105958) Change the declaration of the keywords parameter in functions PyArg_ParseTupleAndKeywords() and PyArg_VaParseTupleAndKeywords() from `char **` to `char * const *` in C and `const char * const *` in C++. It makes these functions compatible with argument of type `const char * const *`, `const char **` or `char * const *` in C++ and `char * const *` in C without explicit type cast. Co-authored-by: C.A.M. Gerlach --- Doc/c-api/arg.rst | 26 +++++++++++++++++-- Doc/extending/extending.rst | 2 +- Doc/whatsnew/3.13.rst | 10 +++++++ Include/modsupport.h | 4 +-- Include/pyport.h | 8 ++++++ ...3-06-21-11-53-09.gh-issue-65210.PhFRBJ.rst | 3 +++ Python/getargs.c | 17 ++++++------ 7 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-06-21-11-53-09.gh-issue-65210.PhFRBJ.rst diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst index 62d87d898e682c..834aae9372fe3b 100644 --- a/Doc/c-api/arg.rst +++ b/Doc/c-api/arg.rst @@ -413,7 +413,7 @@ API Functions than a variable number of arguments. -.. c:function:: int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...) +.. c:function:: int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char * const *keywords, ...) Parse the parameters of a function that takes both positional and keyword parameters into local variables. @@ -424,15 +424,24 @@ API Functions Returns true on success; on failure, it returns false and raises the appropriate exception. + .. note:: + + The *keywords* parameter declaration is :c:expr:`char * const *` in C and + :c:expr:`const char * const *` in C++. + This can be overridden with the :c:macro:`PY_CXX_CONST` macro. + .. versionchanged:: 3.6 Added support for :ref:`positional-only parameters `. .. versionchanged:: 3.13 + The *keywords* parameter has now type :c:expr:`char * const *` in C and + :c:expr:`const char * const *` in C++, instead of :c:expr:`char **`. Added support for non-ASCII keyword parameter names. -.. c:function:: int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], va_list vargs) + +.. c:function:: int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char * const *keywords, va_list vargs) Identical to :c:func:`PyArg_ParseTupleAndKeywords`, except that it accepts a va_list rather than a variable number of arguments. @@ -505,6 +514,19 @@ API Functions PyArg_ParseTuple(args, "O|O:ref", &object, &callback) +.. c:macro:: PY_CXX_CONST + + The value to be inserted, if any, before :c:expr:`char * const *` + in the *keywords* parameter declaration of + :c:func:`PyArg_ParseTupleAndKeywords` and + :c:func:`PyArg_VaParseTupleAndKeywords`. + Default empty for C and ``const`` for C++ + (:c:expr:`const char * const *`). + To override, define it to the desired value before including + :file:`Python.h`. + + .. versionadded:: 3.13 + --------------- Building values diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 1ee7f28b2ba220..745fc10a22d161 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -735,7 +735,7 @@ Keyword Parameters for Extension Functions The :c:func:`PyArg_ParseTupleAndKeywords` function is declared as follows:: int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict, - const char *format, char *kwlist[], ...); + const char *format, char * const *kwlist, ...); The *arg* and *format* parameters are identical to those of the :c:func:`PyArg_ParseTuple` function. The *kwdict* parameter is the dictionary of diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 676305c6e1d06a..be890ff314dfa4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1111,6 +1111,16 @@ New Features APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats. (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 *` + 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 + :c:expr:`char * const *` in C++ and :c:expr:`char * const *` in C + without an explicit type cast. + This can be overridden with the :c:macro:`PY_CXX_CONST` macro. + (Contributed by Serhiy Storchaka in :gh:`65210`.) + * Add :c:func:`PyImport_AddModuleRef`: similar to :c:func:`PyImport_AddModule`, but return a :term:`strong reference` instead of a :term:`borrowed reference`. diff --git a/Include/modsupport.h b/Include/modsupport.h index 450cc99c404c17..ea4c0fce9f4562 100644 --- a/Include/modsupport.h +++ b/Include/modsupport.h @@ -9,10 +9,10 @@ extern "C" { PyAPI_FUNC(int) PyArg_Parse(PyObject *, const char *, ...); PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *, const char *, ...); PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords(PyObject *, PyObject *, - const char *, char **, ...); + const char *, PY_CXX_CONST char * const *, ...); PyAPI_FUNC(int) PyArg_VaParse(PyObject *, const char *, va_list); PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords(PyObject *, PyObject *, - const char *, char **, va_list); + const char *, PY_CXX_CONST char * const *, va_list); PyAPI_FUNC(int) PyArg_ValidateKeywordArguments(PyObject *); PyAPI_FUNC(int) PyArg_UnpackTuple(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...); diff --git a/Include/pyport.h b/Include/pyport.h index abb526d503fddd..328471085f959d 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -586,6 +586,14 @@ extern "C" { # define ALIGNOF_MAX_ALIGN_T _Alignof(long double) #endif +#ifndef PY_CXX_CONST +# ifdef __cplusplus +# define PY_CXX_CONST const +# else +# define PY_CXX_CONST +# endif +#endif + #if defined(__sgi) && !defined(_SGI_MP_SOURCE) # define _SGI_MP_SOURCE #endif diff --git a/Misc/NEWS.d/next/C API/2023-06-21-11-53-09.gh-issue-65210.PhFRBJ.rst b/Misc/NEWS.d/next/C API/2023-06-21-11-53-09.gh-issue-65210.PhFRBJ.rst new file mode 100644 index 00000000000000..a15646f4dad127 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-06-21-11-53-09.gh-issue-65210.PhFRBJ.rst @@ -0,0 +1,3 @@ +Change the declaration of the *keywords* parameter of +:c:func:`PyArg_ParseTupleAndKeywords` and +:c:func:`PyArg_VaParseTupleAndKeywords` for better compatibility with C++. diff --git a/Python/getargs.c b/Python/getargs.c index 5ff8f7473609f5..0c4ce282f48764 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -1,6 +1,7 @@ /* New getargs implementation */ +#define PY_CXX_CONST const #include "Python.h" #include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_dict.h" // _PyDict_HasOnlyStringKeys() @@ -12,10 +13,10 @@ PyAPI_FUNC(int) _PyArg_Parse_SizeT(PyObject *, const char *, ...); PyAPI_FUNC(int) _PyArg_ParseTuple_SizeT(PyObject *, const char *, ...); PyAPI_FUNC(int) _PyArg_ParseTupleAndKeywords_SizeT(PyObject *, PyObject *, - const char *, char **, ...); + const char *, const char * const *, ...); PyAPI_FUNC(int) _PyArg_VaParse_SizeT(PyObject *, const char *, va_list); PyAPI_FUNC(int) _PyArg_VaParseTupleAndKeywords_SizeT(PyObject *, PyObject *, - const char *, char **, va_list); + const char *, const char * const *, va_list); #define FLAG_COMPAT 1 @@ -54,7 +55,7 @@ static Py_ssize_t convertbuffer(PyObject *, const void **p, const char **); static int getbuffer(PyObject *, Py_buffer *, const char**); static int vgetargskeywords(PyObject *, PyObject *, - const char *, char **, va_list *, int); + const char *, const char * const *, va_list *, int); static int vgetargskeywordsfast(PyObject *, PyObject *, struct _PyArg_Parser *, va_list *, int); static int vgetargskeywordsfast_impl(PyObject *const *args, Py_ssize_t nargs, @@ -1247,7 +1248,7 @@ int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *keywords, const char *format, - char **kwlist, ...) + const char * const *kwlist, ...) { int retval; va_list va; @@ -1271,7 +1272,7 @@ int _PyArg_ParseTupleAndKeywords_SizeT(PyObject *args, PyObject *keywords, const char *format, - char **kwlist, ...) + const char * const *kwlist, ...) { int retval; va_list va; @@ -1297,7 +1298,7 @@ int PyArg_VaParseTupleAndKeywords(PyObject *args, PyObject *keywords, const char *format, - char **kwlist, va_list va) + const char * const *kwlist, va_list va) { int retval; va_list lva; @@ -1322,7 +1323,7 @@ int _PyArg_VaParseTupleAndKeywords_SizeT(PyObject *args, PyObject *keywords, const char *format, - char **kwlist, va_list va) + const char * const *kwlist, va_list va) { int retval; va_list lva; @@ -1460,7 +1461,7 @@ PyArg_ValidateKeywordArguments(PyObject *kwargs) static int vgetargskeywords(PyObject *args, PyObject *kwargs, const char *format, - char **kwlist, va_list *p_va, int flags) + const char * const *kwlist, va_list *p_va, int flags) { char msgbuf[512]; int levels[32]; From c74e9fb18917ceb287c3ed5be5d0c2a16a646a99 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 4 Dec 2023 13:30:32 +0200 Subject: [PATCH 093/442] gh-110275: Named tuple's __replace__() now raises TypeError for invalid arguments (GH-110299) --- Doc/library/collections.rst | 4 ++++ Lib/collections/__init__.py | 2 +- Lib/test/test_collections.py | 6 +----- Lib/test/test_copy.py | 2 +- .../Library/2023-11-08-16-11-04.gh-issue-110275.Bm6GwR.rst | 2 ++ 5 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-08-16-11-04.gh-issue-110275.Bm6GwR.rst diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 17dd6da7479e50..233b2c6a771f4a 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -981,6 +981,10 @@ field names, the method and attribute names start with an underscore. Named tuples are also supported by generic function :func:`copy.replace`. + .. versionchanged:: 3.13 + Raise :exc:`TypeError` instead of :exc:`ValueError` for invalid + keyword arguments. + .. attribute:: somenamedtuple._fields Tuple of strings listing the field names. Useful for introspection diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index a461550ea40da7..2e527dfd810c43 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -457,7 +457,7 @@ def _make(cls, iterable): def _replace(self, /, **kwds): result = self._make(_map(kwds.pop, field_names, self)) if kwds: - raise ValueError(f'Got unexpected field names: {list(kwds)!r}') + raise TypeError(f'Got unexpected field names: {list(kwds)!r}') return result _replace.__doc__ = (f'Return a new {typename} object replacing specified ' diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index bb8b352518ef3e..7e6f811e17cfa2 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -488,12 +488,8 @@ def test_instance(self): self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method - try: + with self.assertRaises(TypeError): p._replace(x=1, error=2) - except ValueError: - pass - else: - self._fail('Did not detect an incorrect fieldname') # verify that field string can have commas Point = namedtuple('Point', 'x, y') diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 60735ba89a80ee..89102373759ca0 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -952,7 +952,7 @@ class PointFromClass(NamedTuple): self.assertEqual(copy.replace(p, x=1), (1, 22)) self.assertEqual(copy.replace(p, y=2), (11, 2)) self.assertEqual(copy.replace(p, x=1, y=2), (1, 2)) - with self.assertRaisesRegex(ValueError, 'unexpected field name'): + with self.assertRaisesRegex(TypeError, 'unexpected field name'): copy.replace(p, x=1, error=2) def test_dataclass(self): diff --git a/Misc/NEWS.d/next/Library/2023-11-08-16-11-04.gh-issue-110275.Bm6GwR.rst b/Misc/NEWS.d/next/Library/2023-11-08-16-11-04.gh-issue-110275.Bm6GwR.rst new file mode 100644 index 00000000000000..194dd5cb623f0f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-08-16-11-04.gh-issue-110275.Bm6GwR.rst @@ -0,0 +1,2 @@ +Named tuple's methods ``_replace()`` and ``__replace__()`` now raise +TypeError instead of ValueError for invalid keyword arguments. From 6ca9d3e0173c38e2eac50367b187d4c1d43f9892 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 4 Dec 2023 13:47:55 +0200 Subject: [PATCH 094/442] gh-109786: Fix leaks and crash when re-enter itertools.pairwise.__next__() (GH-109788) --- Lib/test/test_itertools.py | 72 +++++++++++++++++++ ...-09-23-14-40-51.gh-issue-109786.UX3pKv.rst | 2 + Modules/itertoolsmodule.c | 13 +++- 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-09-23-14-40-51.gh-issue-109786.UX3pKv.rst diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 512745e45350d1..705e880d98685e 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1152,6 +1152,78 @@ def test_pairwise(self): with self.assertRaises(TypeError): pairwise(None) # non-iterable argument + def test_pairwise_reenter(self): + def check(reenter_at, expected): + class I: + count = 0 + def __iter__(self): + return self + def __next__(self): + self.count +=1 + if self.count in reenter_at: + return next(it) + return [self.count] # new object + + it = pairwise(I()) + for item in expected: + self.assertEqual(next(it), item) + + check({1}, [ + (([2], [3]), [4]), + ([4], [5]), + ]) + check({2}, [ + ([1], ([1], [3])), + (([1], [3]), [4]), + ([4], [5]), + ]) + check({3}, [ + ([1], [2]), + ([2], ([2], [4])), + (([2], [4]), [5]), + ([5], [6]), + ]) + check({1, 2}, [ + ((([3], [4]), [5]), [6]), + ([6], [7]), + ]) + check({1, 3}, [ + (([2], ([2], [4])), [5]), + ([5], [6]), + ]) + check({1, 4}, [ + (([2], [3]), (([2], [3]), [5])), + ((([2], [3]), [5]), [6]), + ([6], [7]), + ]) + check({2, 3}, [ + ([1], ([1], ([1], [4]))), + (([1], ([1], [4])), [5]), + ([5], [6]), + ]) + + def test_pairwise_reenter2(self): + def check(maxcount, expected): + class I: + count = 0 + def __iter__(self): + return self + def __next__(self): + if self.count >= maxcount: + raise StopIteration + self.count +=1 + if self.count == 1: + return next(it, None) + return [self.count] # new object + + it = pairwise(I()) + self.assertEqual(list(it), expected) + + check(1, []) + check(2, []) + check(3, []) + check(4, [(([2], [3]), [4])]) + def test_product(self): for args, result in [ ([], [()]), # zero iterables diff --git a/Misc/NEWS.d/next/Library/2023-09-23-14-40-51.gh-issue-109786.UX3pKv.rst b/Misc/NEWS.d/next/Library/2023-09-23-14-40-51.gh-issue-109786.UX3pKv.rst new file mode 100644 index 00000000000000..07222fa339d703 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-23-14-40-51.gh-issue-109786.UX3pKv.rst @@ -0,0 +1,2 @@ +Fix possible reference leaks and crash when re-enter the ``__next__()`` method of +:class:`itertools.pairwise`. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 4ed34aa5bde827..ab99fa4d873bf5 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -330,21 +330,30 @@ pairwise_next(pairwiseobject *po) return NULL; } if (old == NULL) { - po->old = old = (*Py_TYPE(it)->tp_iternext)(it); + old = (*Py_TYPE(it)->tp_iternext)(it); + Py_XSETREF(po->old, old); if (old == NULL) { Py_CLEAR(po->it); return NULL; } + it = po->it; + if (it == NULL) { + Py_CLEAR(po->old); + return NULL; + } } + Py_INCREF(old); new = (*Py_TYPE(it)->tp_iternext)(it); if (new == NULL) { Py_CLEAR(po->it); Py_CLEAR(po->old); + Py_DECREF(old); return NULL; } /* Future optimization: Reuse the result tuple as we do in enumerate() */ result = PyTuple_Pack(2, old, new); - Py_SETREF(po->old, new); + Py_XSETREF(po->old, new); + Py_DECREF(old); return result; } From 9560e0d6d7a316341939b4016e47e03bd5bf17c3 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 4 Dec 2023 12:42:24 +0000 Subject: [PATCH 095/442] gh-101100: Fix Sphinx nitpicks in `library/abc.rst` (#112703) --- Doc/library/abc.rst | 42 +++++++++++++++++++++--------------------- Doc/tools/.nitignore | 1 - 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Doc/library/abc.rst b/Doc/library/abc.rst index fb4f9da169c5ab..c073ea955abaa4 100644 --- a/Doc/library/abc.rst +++ b/Doc/library/abc.rst @@ -21,7 +21,7 @@ The :mod:`collections` module has some concrete classes that derive from ABCs; these can, of course, be further derived. In addition, the :mod:`collections.abc` submodule has some ABCs that can be used to test whether a class or instance provides a particular interface, for example, if it is -:term:`hashable` or if it is a mapping. +:term:`hashable` or if it is a :term:`mapping`. This module provides the metaclass :class:`ABCMeta` for defining ABCs and @@ -30,7 +30,7 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: .. class:: ABC A helper class that has :class:`ABCMeta` as its metaclass. With this class, - an abstract base class can be created by simply deriving from :class:`ABC` + an abstract base class can be created by simply deriving from :class:`!ABC` avoiding sometimes confusing metaclass usage, for example:: from abc import ABC @@ -38,11 +38,11 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: class MyABC(ABC): pass - Note that the type of :class:`ABC` is still :class:`ABCMeta`, therefore - inheriting from :class:`ABC` requires the usual precautions regarding + Note that the type of :class:`!ABC` is still :class:`ABCMeta`, therefore + inheriting from :class:`!ABC` requires the usual precautions regarding metaclass usage, as multiple inheritance may lead to metaclass conflicts. One may also define an abstract base class by passing the metaclass - keyword and using :class:`ABCMeta` directly, for example:: + keyword and using :class:`!ABCMeta` directly, for example:: from abc import ABCMeta @@ -65,7 +65,7 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: implementations defined by the registering ABC be callable (not even via :func:`super`). [#]_ - Classes created with a metaclass of :class:`ABCMeta` have the following method: + Classes created with a metaclass of :class:`!ABCMeta` have the following method: .. method:: register(subclass) @@ -86,7 +86,7 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: Returns the registered subclass, to allow usage as a class decorator. .. versionchanged:: 3.4 - To detect calls to :meth:`register`, you can use the + To detect calls to :meth:`!register`, you can use the :func:`get_cache_token` function. You can also override this method in an abstract base class: @@ -96,10 +96,10 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: (Must be defined as a class method.) Check whether *subclass* is considered a subclass of this ABC. This means - that you can customize the behavior of ``issubclass`` further without the + that you can customize the behavior of :func:`issubclass` further without the need to call :meth:`register` on every class you want to consider a subclass of the ABC. (This class method is called from the - :meth:`__subclasscheck__` method of the ABC.) + :meth:`~class.__subclasscheck__` method of the ABC.) This method should return ``True``, ``False`` or ``NotImplemented``. If it returns ``True``, the *subclass* is considered a subclass of this ABC. @@ -142,7 +142,7 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: The ABC ``MyIterable`` defines the standard iterable method, :meth:`~iterator.__iter__`, as an abstract method. The implementation given - here can still be called from subclasses. The :meth:`get_iterator` method + here can still be called from subclasses. The :meth:`!get_iterator` method is also part of the ``MyIterable`` abstract base class, but it does not have to be overridden in non-abstract derived classes. @@ -153,14 +153,14 @@ a helper class :class:`ABC` to alternatively define ABCs through inheritance: Finally, the last line makes ``Foo`` a virtual subclass of ``MyIterable``, even though it does not define an :meth:`~iterator.__iter__` method (it uses - the old-style iterable protocol, defined in terms of :meth:`__len__` and + the old-style iterable protocol, defined in terms of :meth:`~object.__len__` and :meth:`~object.__getitem__`). Note that this will not make ``get_iterator`` available as a method of ``Foo``, so it is provided separately. -The :mod:`abc` module also provides the following decorator: +The :mod:`!abc` module also provides the following decorator: .. decorator:: abstractmethod @@ -168,19 +168,19 @@ The :mod:`abc` module also provides the following decorator: Using this decorator requires that the class's metaclass is :class:`ABCMeta` or is derived from it. A class that has a metaclass derived from - :class:`ABCMeta` cannot be instantiated unless all of its abstract methods + :class:`!ABCMeta` cannot be instantiated unless all of its abstract methods and properties are overridden. The abstract methods can be called using any - of the normal 'super' call mechanisms. :func:`abstractmethod` may be used + of the normal 'super' call mechanisms. :func:`!abstractmethod` may be used to declare abstract methods for properties and descriptors. Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are only supported using the :func:`update_abstractmethods` function. The - :func:`abstractmethod` only affects subclasses derived using regular - inheritance; "virtual subclasses" registered with the ABC's :meth:`register` - method are not affected. + :func:`!abstractmethod` only affects subclasses derived using regular + inheritance; "virtual subclasses" registered with the ABC's + :meth:`~ABCMeta.register` method are not affected. - When :func:`abstractmethod` is applied in combination with other method + When :func:`!abstractmethod` is applied in combination with other method descriptors, it should be applied as the innermost decorator, as shown in the following usage examples:: @@ -216,7 +216,7 @@ The :mod:`abc` module also provides the following decorator: In order to correctly interoperate with the abstract base class machinery, the descriptor must identify itself as abstract using - :attr:`__isabstractmethod__`. In general, this attribute should be ``True`` + :attr:`!__isabstractmethod__`. In general, this attribute should be ``True`` if any of the methods used to compose the descriptor are abstract. For example, Python's built-in :class:`property` does the equivalent of:: @@ -236,7 +236,7 @@ The :mod:`abc` module also provides the following decorator: super-call in a framework that uses cooperative multiple-inheritance. -The :mod:`abc` module also supports the following legacy decorators: +The :mod:`!abc` module also supports the following legacy decorators: .. decorator:: abstractclassmethod @@ -323,7 +323,7 @@ The :mod:`abc` module also supports the following legacy decorators: ... -The :mod:`abc` module also provides the following functions: +The :mod:`!abc` module also provides the following functions: .. function:: get_cache_token() diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index e79b4c3bdecb67..50f04d72c0dee0 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -26,7 +26,6 @@ Doc/howto/enum.rst Doc/howto/isolating-extensions.rst Doc/howto/logging.rst Doc/howto/urllib2.rst -Doc/library/abc.rst Doc/library/ast.rst Doc/library/asyncio-extending.rst Doc/library/asyncio-policy.rst From c718ab92a584fa658b6a626a744f5a71a048b47c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 4 Dec 2023 15:41:41 +0000 Subject: [PATCH 096/442] gh-74690: Optimise `isinstance()` and `issubclass()` calls against runtime-checkable protocols by avoiding costly `super()` calls (#112708) --- Lib/typing.py | 14 +++++++++++--- .../2023-12-04-14-05-24.gh-issue-74690.eODKRm.rst | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-04-14-05-24.gh-issue-74690.eODKRm.rst diff --git a/Lib/typing.py b/Lib/typing.py index 4c19aadabe3b87..aa64ed93f76fbf 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1782,6 +1782,14 @@ def _pickle_pskwargs(pskwargs): del _pickle_psargs, _pickle_pskwargs +# Preload these once, as globals, as a micro-optimisation. +# This makes a significant difference to the time it takes +# to do `isinstance()`/`issubclass()` checks +# against runtime-checkable protocols with only one callable member. +_abc_instancecheck = ABCMeta.__instancecheck__ +_abc_subclasscheck = ABCMeta.__subclasscheck__ + + class _ProtocolMeta(ABCMeta): # This metaclass is somewhat unfortunate, # but is necessary for several reasons... @@ -1841,7 +1849,7 @@ def __subclasscheck__(cls, other): "Instance and class checks can only be used with " "@runtime_checkable protocols" ) - return super().__subclasscheck__(other) + return _abc_subclasscheck(cls, other) def __instancecheck__(cls, instance): # We need this method for situations where attributes are @@ -1850,7 +1858,7 @@ def __instancecheck__(cls, instance): return type.__instancecheck__(cls, instance) if not getattr(cls, "_is_protocol", False): # i.e., it's a concrete subclass of a protocol - return super().__instancecheck__(instance) + return _abc_instancecheck(cls, instance) if ( not getattr(cls, '_is_runtime_protocol', False) and @@ -1859,7 +1867,7 @@ def __instancecheck__(cls, instance): raise TypeError("Instance and class checks can only be used with" " @runtime_checkable protocols") - if super().__instancecheck__(instance): + if _abc_instancecheck(cls, instance): return True getattr_static = _lazy_load_getattr_static() diff --git a/Misc/NEWS.d/next/Library/2023-12-04-14-05-24.gh-issue-74690.eODKRm.rst b/Misc/NEWS.d/next/Library/2023-12-04-14-05-24.gh-issue-74690.eODKRm.rst new file mode 100644 index 00000000000000..36d793f787302e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-04-14-05-24.gh-issue-74690.eODKRm.rst @@ -0,0 +1,5 @@ +Speedup :func:`isinstance` checks by roughly 20% for +:func:`runtime-checkable protocols ` +that only have one callable member. +Speedup :func:`issubclass` checks for these protocols by roughly 10%. +Patch by Alex Waygood. From e08b70fab1fbc45fa498020aac522ae1d5da6136 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 4 Dec 2023 17:43:27 +0200 Subject: [PATCH 097/442] gh-108927: Fix removing testing modules from sys.modules (GH-108952) It breaks import machinery if the test module has submodules used in other tests. --- Lib/test/libregrtest/main.py | 18 +++++++++++++----- Lib/test/libregrtest/single.py | 4 ---- .../import_from_tests/test_regrtest_a.py | 11 +++++++++++ .../test_regrtest_b/__init__.py | 9 +++++++++ .../import_from_tests/test_regrtest_b/util.py | 0 .../import_from_tests/test_regrtest_c.py | 11 +++++++++++ Lib/test/test_regrtest.py | 19 +++++++++++++++++++ ...-09-05-20-46-35.gh-issue-108927.TpwWav.rst | 4 ++++ 8 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py create mode 100644 Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py create mode 100644 Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py create mode 100644 Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py create mode 100644 Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 16f6974ae32465..7ca1b1cb65ae40 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -311,7 +311,7 @@ def run_tests_sequentially(self, runtests) -> None: else: tracer = None - save_modules = sys.modules.keys() + save_modules = set(sys.modules) jobs = runtests.get_jobs() if jobs is not None: @@ -335,10 +335,18 @@ def run_tests_sequentially(self, runtests) -> None: result = self.run_test(test_name, runtests, tracer) - # Unload the newly imported modules (best effort finalization) - for module in sys.modules.keys(): - if module not in save_modules and module.startswith("test."): - support.unload(module) + # Unload the newly imported test modules (best effort finalization) + new_modules = [module for module in sys.modules + if module not in save_modules and + module.startswith(("test.", "test_"))] + for module in new_modules: + sys.modules.pop(module, None) + # Remove the attribute of the parent module. + parent, _, name = module.rpartition('.') + try: + delattr(sys.modules[parent], name) + except (KeyError, AttributeError): + pass if result.must_stop(self.fail_fast, self.fail_env_changed): break diff --git a/Lib/test/libregrtest/single.py b/Lib/test/libregrtest/single.py index eafeb5fe26f3f3..235029d8620ff5 100644 --- a/Lib/test/libregrtest/single.py +++ b/Lib/test/libregrtest/single.py @@ -122,10 +122,6 @@ def _load_run_test(result: TestResult, runtests: RunTests) -> None: # Load the test module and run the tests. test_name = result.test_name module_name = abs_module_name(test_name, runtests.test_dir) - - # Remove the module from sys.module to reload it if it was already imported - sys.modules.pop(module_name, None) - test_mod = importlib.import_module(module_name) if hasattr(test_mod, "test_main"): diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py new file mode 100644 index 00000000000000..9c3d0c7cf4bfaa --- /dev/null +++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_a.py @@ -0,0 +1,11 @@ +import sys +import unittest +import test_regrtest_b.util + +class Test(unittest.TestCase): + def test(self): + test_regrtest_b.util # does not fail + self.assertIn('test_regrtest_a', sys.modules) + self.assertIs(sys.modules['test_regrtest_b'], test_regrtest_b) + self.assertIs(sys.modules['test_regrtest_b.util'], test_regrtest_b.util) + self.assertNotIn('test_regrtest_c', sys.modules) diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py new file mode 100644 index 00000000000000..3dfba253455ad2 --- /dev/null +++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/__init__.py @@ -0,0 +1,9 @@ +import sys +import unittest + +class Test(unittest.TestCase): + def test(self): + self.assertNotIn('test_regrtest_a', sys.modules) + self.assertIn('test_regrtest_b', sys.modules) + self.assertNotIn('test_regrtest_b.util', sys.modules) + self.assertNotIn('test_regrtest_c', sys.modules) diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_b/util.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py b/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py new file mode 100644 index 00000000000000..de80769118d709 --- /dev/null +++ b/Lib/test/regrtestdata/import_from_tests/test_regrtest_c.py @@ -0,0 +1,11 @@ +import sys +import unittest +import test_regrtest_b.util + +class Test(unittest.TestCase): + def test(self): + test_regrtest_b.util # does not fail + self.assertNotIn('test_regrtest_a', sys.modules) + self.assertIs(sys.modules['test_regrtest_b'], test_regrtest_b) + self.assertIs(sys.modules['test_regrtest_b.util'], test_regrtest_b.util) + self.assertIn('test_regrtest_c', sys.modules) diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index d7b9f801092498..e828941f6c779d 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -2031,6 +2031,25 @@ def test_dev_mode(self): self.check_executed_tests(output, tests, stats=len(tests), parallel=True) + def test_unload_tests(self): + # Test that unloading test modules does not break tests + # that import from other tests. + # The test execution order matters for this test. + # Both test_regrtest_a and test_regrtest_c which are executed before + # and after test_regrtest_b import a submodule from the test_regrtest_b + # package and use it in testing. test_regrtest_b itself does not import + # that submodule. + # Previously test_regrtest_c failed because test_regrtest_b.util in + # sys.modules was left after test_regrtest_a (making the import + # statement no-op), but new test_regrtest_b without the util attribute + # was imported for test_regrtest_b. + testdir = os.path.join(os.path.dirname(__file__), + 'regrtestdata', 'import_from_tests') + tests = [f'test_regrtest_{name}' for name in ('a', 'b', 'c')] + args = ['-Wd', '-E', '-bb', '-m', 'test', '--testdir=%s' % testdir, *tests] + output = self.run_python(args) + self.check_executed_tests(output, tests, stats=3) + def check_add_python_opts(self, option): # --fast-ci and --slow-ci add "-u -W default -bb -E" options to Python code = textwrap.dedent(r""" diff --git a/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst b/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst new file mode 100644 index 00000000000000..b1a78370afedb2 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-09-05-20-46-35.gh-issue-108927.TpwWav.rst @@ -0,0 +1,4 @@ +Fixed order dependence in running tests in the same process +when a test that has submodules (e.g. test_importlib) follows a test that +imports its submodule (e.g. test_importlib.util) and precedes a test +(e.g. test_unittest or test_compileall) that uses that submodule. From 1e4680ce52ab6c065f5e0bb27e0b156b897aff67 Mon Sep 17 00:00:00 2001 From: Thomas Bininda Date: Mon, 4 Dec 2023 18:27:57 +0100 Subject: [PATCH 098/442] gh-112516: Update bundled pip version to 23.3.1 (gh-112517) --- Lib/ensurepip/__init__.py | 2 +- ...ne-any.whl => pip-23.3.1-py3-none-any.whl} | Bin 2086091 -> 2107242 bytes ...-11-29-10-51-41.gh-issue-112516.rFKUKN.rst | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) rename Lib/ensurepip/_bundled/{pip-23.2.1-py3-none-any.whl => pip-23.3.1-py3-none-any.whl} (80%) create mode 100644 Misc/NEWS.d/next/Library/2023-11-29-10-51-41.gh-issue-112516.rFKUKN.rst diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 1fb1d505cfd0c5..21e4ad99a39628 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] _PACKAGE_NAMES = ('pip',) -_PIP_VERSION = "23.2.1" +_PIP_VERSION = "23.3.1" _PROJECTS = [ ("pip", _PIP_VERSION, "py3"), ] diff --git a/Lib/ensurepip/_bundled/pip-23.2.1-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl similarity index 80% rename from Lib/ensurepip/_bundled/pip-23.2.1-py3-none-any.whl rename to Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl index ba28ef02e265f032560e28e40b2be0bd6e2e964f..a4faf716b663e61faf22bb58fa57d3b2b0e00299 100644 GIT binary patch delta 397715 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

dIF8*FnUbd0S#cy5H)2HTWG_fy6K@XIvE2B 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 z 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�qBhn#x7w!D8Oo#QUxz^EazuzYb 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≠-_?L7^j)9NEj`89gE-;3w(Lo7un`SyBeyDxv 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

  • H{9BJwl<(e#{K`wHG0A6; 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 G)Bgbo^e3GF diff --git a/Misc/NEWS.d/next/Library/2023-11-29-10-51-41.gh-issue-112516.rFKUKN.rst b/Misc/NEWS.d/next/Library/2023-11-29-10-51-41.gh-issue-112516.rFKUKN.rst new file mode 100644 index 00000000000000..530cf992dcd77a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-29-10-51-41.gh-issue-112516.rFKUKN.rst @@ -0,0 +1 @@ +Update the bundled copy of pip to version 23.3.1. From 2ed20d3bd84fdcf511235cc473a241c5e8278a91 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 4 Dec 2023 19:35:46 +0000 Subject: [PATCH 099/442] gh-74690: Avoid a costly type check where possible in `_ProtocolMeta.__subclasscheck__` (#112717) --- Lib/test/test_typing.py | 19 +++++++++++++--- Lib/typing.py | 22 ++++++++++++++++--- ...3-12-04-16-45-11.gh-issue-74690.pQYP5U.rst | 2 ++ 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-04-16-45-11.gh-issue-74690.pQYP5U.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3572df7737f652..2d10c39840ddf3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3533,13 +3533,26 @@ def __subclasshook__(cls, other): def test_issubclass_fails_correctly(self): @runtime_checkable - class P(Protocol): + class NonCallableMembers(Protocol): x = 1 + class NotRuntimeCheckable(Protocol): + def callable_member(self) -> int: ... + + @runtime_checkable + class RuntimeCheckable(Protocol): + def callable_member(self) -> int: ... + class C: pass - with self.assertRaisesRegex(TypeError, r"issubclass\(\) arg 1 must be a class"): - issubclass(C(), P) + # These three all exercise different code paths, + # but should result in the same error message: + for protocol in NonCallableMembers, NotRuntimeCheckable, RuntimeCheckable: + with self.subTest(proto_name=protocol.__name__): + with self.assertRaisesRegex( + TypeError, r"issubclass\(\) arg 1 must be a class" + ): + issubclass(C(), protocol) def test_defining_generic_protocols(self): T = TypeVar('T') diff --git a/Lib/typing.py b/Lib/typing.py index aa64ed93f76fbf..61b88a560e9dc5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1790,6 +1790,23 @@ def _pickle_pskwargs(pskwargs): _abc_subclasscheck = ABCMeta.__subclasscheck__ +def _type_check_issubclass_arg_1(arg): + """Raise TypeError if `arg` is not an instance of `type` + in `issubclass(arg, )`. + + In most cases, this is verified by type.__subclasscheck__. + Checking it again unnecessarily would slow down issubclass() checks, + so, we don't perform this check unless we absolutely have to. + + For various error paths, however, + we want to ensure that *this* error message is shown to the user + where relevant, rather than a typing.py-specific error message. + """ + if not isinstance(arg, type): + # Same error message as for issubclass(1, int). + raise TypeError('issubclass() arg 1 must be a class') + + class _ProtocolMeta(ABCMeta): # This metaclass is somewhat unfortunate, # but is necessary for several reasons... @@ -1829,13 +1846,11 @@ def __subclasscheck__(cls, other): getattr(cls, '_is_protocol', False) and not _allow_reckless_class_checks() ): - if not isinstance(other, type): - # Same error message as for issubclass(1, int). - raise TypeError('issubclass() arg 1 must be a class') if ( not cls.__callable_proto_members_only__ and cls.__dict__.get("__subclasshook__") is _proto_hook ): + _type_check_issubclass_arg_1(other) non_method_attrs = sorted( attr for attr in cls.__protocol_attrs__ if not callable(getattr(cls, attr, None)) @@ -1845,6 +1860,7 @@ def __subclasscheck__(cls, other): f" Non-method members: {str(non_method_attrs)[1:-1]}." ) if not getattr(cls, '_is_runtime_protocol', False): + _type_check_issubclass_arg_1(other) raise TypeError( "Instance and class checks can only be used with " "@runtime_checkable protocols" diff --git a/Misc/NEWS.d/next/Library/2023-12-04-16-45-11.gh-issue-74690.pQYP5U.rst b/Misc/NEWS.d/next/Library/2023-12-04-16-45-11.gh-issue-74690.pQYP5U.rst new file mode 100644 index 00000000000000..8102f02e941c29 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-04-16-45-11.gh-issue-74690.pQYP5U.rst @@ -0,0 +1,2 @@ +Speedup :func:`issubclass` checks against simple :func:`runtime-checkable +protocols ` by around 6%. Patch by Alex Waygood. From a1551b48eebb4a68fda031b5ee9e5cbde8d924dd Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Mon, 4 Dec 2023 20:42:01 +0100 Subject: [PATCH 100/442] gh-103363: Add follow_symlinks argument to `pathlib.Path.owner()` and `group()` (#107962) --- Doc/library/pathlib.rst | 20 ++++- Doc/whatsnew/3.13.rst | 8 +- Lib/pathlib.py | 14 ++-- Lib/test/test_pathlib.py | 73 ++++++++++++++++--- ...-08-14-21-10-52.gh-issue-103363.u64_QI.rst | 2 + 5 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-14-21-10-52.gh-issue-103363.u64_QI.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 7ecfd120db8d15..62d4ed5e3f46b9 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -1017,15 +1017,21 @@ call fails (for example because the path doesn't exist). future Python release, patterns with this ending will match both files and directories. Add a trailing slash to match only directories. -.. method:: Path.group() +.. method:: Path.group(*, follow_symlinks=True) - Return the name of the group owning the file. :exc:`KeyError` is raised + Return the name of the group owning the file. :exc:`KeyError` is raised if the file's gid isn't found in the system database. + This method normally follows symlinks; to get the group of the symlink, add + the argument ``follow_symlinks=False``. + .. versionchanged:: 3.13 Raises :exc:`UnsupportedOperation` if the :mod:`grp` module is not available. In previous versions, :exc:`NotImplementedError` was raised. + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. + .. method:: Path.is_dir(*, follow_symlinks=True) @@ -1291,15 +1297,21 @@ call fails (for example because the path doesn't exist). '#!/usr/bin/env python3\n' -.. method:: Path.owner() +.. method:: Path.owner(*, follow_symlinks=True) - Return the name of the user owning the file. :exc:`KeyError` is raised + Return the name of the user owning the file. :exc:`KeyError` is raised if the file's uid isn't found in the system database. + This method normally follows symlinks; to get the owner of the symlink, add + the argument ``follow_symlinks=False``. + .. versionchanged:: 3.13 Raises :exc:`UnsupportedOperation` if the :mod:`pwd` module is not available. In previous versions, :exc:`NotImplementedError` was raised. + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. + .. method:: Path.read_bytes() diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index be890ff314dfa4..534813f3659c9d 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -270,9 +270,11 @@ pathlib (Contributed by Barney Gale in :gh:`73435`.) * Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.glob`, - :meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`, and - :meth:`~pathlib.Path.is_dir`. - (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`.) + :meth:`~pathlib.Path.rglob`, :meth:`~pathlib.Path.is_file`, + :meth:`~pathlib.Path.is_dir`, :meth:`~pathlib.Path.owner`, + :meth:`~pathlib.Path.group`. + (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and + Kamil Turek in :gh:`107962`). pdb --- diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 81f75cd47ed087..b728a0b3dfdb6c 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1319,13 +1319,13 @@ def rmdir(self): """ self._unsupported("rmdir") - def owner(self): + def owner(self, *, follow_symlinks=True): """ Return the login name of the file owner. """ self._unsupported("owner") - def group(self): + def group(self, *, follow_symlinks=True): """ Return the group name of the file gid. """ @@ -1440,18 +1440,20 @@ def resolve(self, strict=False): return self.with_segments(os.path.realpath(self, strict=strict)) if pwd: - def owner(self): + def owner(self, *, follow_symlinks=True): """ Return the login name of the file owner. """ - return pwd.getpwuid(self.stat().st_uid).pw_name + uid = self.stat(follow_symlinks=follow_symlinks).st_uid + return pwd.getpwuid(uid).pw_name if grp: - def group(self): + def group(self, *, follow_symlinks=True): """ Return the group name of the file gid. """ - return grp.getgrgid(self.stat().st_gid).gr_name + gid = self.stat(follow_symlinks=follow_symlinks).st_gid + return grp.getgrgid(gid).gr_name if hasattr(os, "readlink"): def readlink(self): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index ccaef070974ffd..1b10d6c2f0cb19 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -41,6 +41,9 @@ def test_is_notimplemented(self): 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) # # Tests for the pure classes. @@ -2975,27 +2978,75 @@ def test_chmod_follow_symlinks_true(self): # XXX also need a test for lchmod. - @unittest.skipUnless(pwd, "the pwd module is needed for this test") - def test_owner(self): - p = self.cls(BASE) / 'fileA' - uid = p.stat().st_uid + def _get_pw_name_or_skip_test(self, uid): try: - name = pwd.getpwuid(uid).pw_name + return pwd.getpwuid(uid).pw_name except KeyError: self.skipTest( "user %d doesn't have an entry in the system database" % uid) - self.assertEqual(name, p.owner()) - @unittest.skipUnless(grp, "the grp module is needed for this test") - def test_group(self): + @unittest.skipUnless(pwd, "the pwd module is needed for this test") + def test_owner(self): p = self.cls(BASE) / 'fileA' - gid = p.stat().st_gid + expected_uid = p.stat().st_uid + expected_name = self._get_pw_name_or_skip_test(expected_uid) + + self.assertEqual(expected_name, p.owner()) + + @unittest.skipUnless(pwd, "the pwd module is needed for this test") + @unittest.skipUnless(root_in_posix, "test needs root privilege") + def test_owner_no_follow_symlinks(self): + all_users = [u.pw_uid for u in pwd.getpwall()] + if len(all_users) < 2: + self.skipTest("test needs more than one user") + + target = self.cls(BASE) / 'fileA' + link = self.cls(BASE) / 'linkA' + + uid_1, uid_2 = all_users[:2] + os.chown(target, uid_1, -1) + os.chown(link, uid_2, -1, follow_symlinks=False) + + expected_uid = link.stat(follow_symlinks=False).st_uid + expected_name = self._get_pw_name_or_skip_test(expected_uid) + + self.assertEqual(expected_uid, uid_2) + self.assertEqual(expected_name, link.owner(follow_symlinks=False)) + + def _get_gr_name_or_skip_test(self, gid): try: - name = grp.getgrgid(gid).gr_name + return grp.getgrgid(gid).gr_name except KeyError: self.skipTest( "group %d doesn't have an entry in the system database" % gid) - self.assertEqual(name, p.group()) + + @unittest.skipUnless(grp, "the grp module is needed for this test") + def test_group(self): + p = self.cls(BASE) / 'fileA' + expected_gid = p.stat().st_gid + expected_name = self._get_gr_name_or_skip_test(expected_gid) + + self.assertEqual(expected_name, p.group()) + + @unittest.skipUnless(grp, "the grp module is needed for this test") + @unittest.skipUnless(root_in_posix, "test needs root privilege") + def test_group_no_follow_symlinks(self): + all_groups = [g.gr_gid for g in grp.getgrall()] + if len(all_groups) < 2: + self.skipTest("test needs more than one group") + + target = self.cls(BASE) / 'fileA' + link = self.cls(BASE) / 'linkA' + + gid_1, gid_2 = all_groups[:2] + os.chown(target, -1, gid_1) + os.chown(link, -1, gid_2, follow_symlinks=False) + + expected_gid = link.stat(follow_symlinks=False).st_gid + expected_name = self._get_pw_name_or_skip_test(expected_gid) + + self.assertEqual(expected_gid, gid_2) + self.assertEqual(expected_name, link.group(follow_symlinks=False)) def test_unlink(self): p = self.cls(BASE) / 'fileA' diff --git a/Misc/NEWS.d/next/Library/2023-08-14-21-10-52.gh-issue-103363.u64_QI.rst b/Misc/NEWS.d/next/Library/2023-08-14-21-10-52.gh-issue-103363.u64_QI.rst new file mode 100644 index 00000000000000..d4a27d624eb5e6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-14-21-10-52.gh-issue-103363.u64_QI.rst @@ -0,0 +1,2 @@ +Add *follow_symlinks* keyword-only argument to :meth:`pathlib.Path.owner` +and :meth:`~pathlib.Path.group`, defaulting to ``True``. From 4eddb4c9d9452482c9af7fa9eec223d12b5a9f33 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 4 Dec 2023 12:04:05 -0800 Subject: [PATCH 101/442] gh-105967: Work around a macOS bug, limit zlib C library crc32 API calls to 1gig (#112615) Work around a macOS bug, limit zlib crc32 calls to 1GiB. Without this, `zlib.crc32` and `binascii.crc32` could produce incorrect results on multi-gigabyte inputs depending on the macOS version's Apple supplied zlib implementation. --- ...3-12-01-19-02-21.gh-issue-105967.Puq5Cn.rst | 4 ++++ Modules/binascii.c | 18 +++++++++++++----- Modules/zlibmodule.c | 18 +++++++++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-01-19-02-21.gh-issue-105967.Puq5Cn.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-01-19-02-21.gh-issue-105967.Puq5Cn.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-01-19-02-21.gh-issue-105967.Puq5Cn.rst new file mode 100644 index 00000000000000..c69511218e3e16 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-01-19-02-21.gh-issue-105967.Puq5Cn.rst @@ -0,0 +1,4 @@ +Workaround a bug in Apple's macOS platform zlib library where +:func:`zlib.crc32` and :func:`binascii.crc32` could produce incorrect results +on multi-gigabyte inputs. Including when using :mod:`zipfile` on zips +containing large data. diff --git a/Modules/binascii.c b/Modules/binascii.c index 17970aa5e9456c..86493241a1fb7e 100644 --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -770,12 +770,20 @@ binascii_crc32_impl(PyObject *module, Py_buffer *data, unsigned int crc) Py_BEGIN_ALLOW_THREADS /* Avoid truncation of length for very large buffers. crc32() takes - length as an unsigned int, which may be narrower than Py_ssize_t. */ - while ((size_t)len > UINT_MAX) { - crc = crc32(crc, buf, UINT_MAX); - buf += (size_t) UINT_MAX; - len -= (size_t) UINT_MAX; + length as an unsigned int, which may be narrower than Py_ssize_t. + We further limit size due to bugs in Apple's macOS zlib. + See https://github.com/python/cpython/issues/105967 + */ +#define ZLIB_CRC_CHUNK_SIZE 0x40000000 +#if ZLIB_CRC_CHUNK_SIZE > INT_MAX +# error "unsupported less than 32-bit platform?" +#endif + while ((size_t)len > ZLIB_CRC_CHUNK_SIZE) { + crc = crc32(crc, buf, ZLIB_CRC_CHUNK_SIZE); + buf += (size_t) ZLIB_CRC_CHUNK_SIZE; + len -= (size_t) ZLIB_CRC_CHUNK_SIZE; } +#undef ZLIB_CRC_CHUNK_SIZE crc = crc32(crc, buf, (unsigned int)len); Py_END_ALLOW_THREADS } else { diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 9b76afa0e56f76..fe9a6d8d4150ab 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1896,12 +1896,20 @@ zlib_crc32_impl(PyObject *module, Py_buffer *data, unsigned int value) Py_BEGIN_ALLOW_THREADS /* Avoid truncation of length for very large buffers. crc32() takes - length as an unsigned int, which may be narrower than Py_ssize_t. */ - while ((size_t)len > UINT_MAX) { - value = crc32(value, buf, UINT_MAX); - buf += (size_t) UINT_MAX; - len -= (size_t) UINT_MAX; + length as an unsigned int, which may be narrower than Py_ssize_t. + We further limit size due to bugs in Apple's macOS zlib. + See https://github.com/python/cpython/issues/105967. + */ +#define ZLIB_CRC_CHUNK_SIZE 0x40000000 +#if ZLIB_CRC_CHUNK_SIZE > INT_MAX +# error "unsupported less than 32-bit platform?" +#endif + while ((size_t)len > ZLIB_CRC_CHUNK_SIZE) { + value = crc32(value, buf, ZLIB_CRC_CHUNK_SIZE); + buf += (size_t) ZLIB_CRC_CHUNK_SIZE; + len -= (size_t) ZLIB_CRC_CHUNK_SIZE; } +#undef ZLIB_CRC_CHUNK_SIZE value = crc32(value, buf, (unsigned int)len); Py_END_ALLOW_THREADS } else { From a8ce149628c9eaafb8c38fbf25fbd1ed483d2902 Mon Sep 17 00:00:00 2001 From: Amioplk Date: Mon, 4 Dec 2023 21:52:06 +0100 Subject: [PATCH 102/442] gh-112671: Fixing typo in the Macro Docs (GH-112715) Replace Py_T_STRING_INLINE with Py_T_STRING_INPLACE --- Doc/c-api/structures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 25cb4ed40f63e7..528813c255c0a5 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -592,7 +592,7 @@ Macro name C type Python type (*): Zero-terminated, UTF8-encoded C string. With :c:macro:`!Py_T_STRING` the C representation is a pointer; - with :c:macro:`!Py_T_STRING_INLINE` the string is stored directly + with :c:macro:`!Py_T_STRING_INPLACE` the string is stored directly in the structure. (**): String of length 1. Only ASCII is accepted. From c5fa8a54dbdf564d482e2e3857aa3efa61edd329 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 4 Dec 2023 23:40:06 +0100 Subject: [PATCH 103/442] gh-112535: Add test on _Py_ThreadId() (#112709) Add also test.support.Py_GIL_DISABLED constant. --- Lib/test/support/__init__.py | 3 +- Lib/test/test_capi/test_misc.py | 55 +++++++++++++++++++++++++ Lib/test/test_cppext/__init__.py | 3 +- Lib/test/test_importlib/test_windows.py | 4 +- Lib/test/test_sys.py | 4 +- Modules/_testinternalcapi.c | 14 +++++++ 6 files changed, 75 insertions(+), 8 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 318a0599a75acd..c22d73c231b46e 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -796,7 +796,8 @@ def check_cflags_pgo(): return any(option in cflags_nodist for option in pgo_options) -if sysconfig.get_config_var('Py_GIL_DISABLED'): +Py_GIL_DISABLED = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) +if Py_GIL_DISABLED: _header = 'PHBBInP' else: _header = 'nP' diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 6cbf5d22203804..3d86ae37190475 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2854,5 +2854,60 @@ def testfunc(n, m): self.assertIn("_FOR_ITER_TIER_TWO", uops) +@unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') +class TestPyThreadId(unittest.TestCase): + def test_py_thread_id(self): + # gh-112535: Test _Py_ThreadId(): make sure that thread identifiers + # in a few threads are unique + py_thread_id = _testinternalcapi.py_thread_id + short_sleep = 0.010 + + class GetThreadId(threading.Thread): + def __init__(self): + super().__init__() + self.get_lock = threading.Lock() + self.get_lock.acquire() + self.started_lock = threading.Event() + self.py_tid = None + + def run(self): + self.started_lock.set() + self.get_lock.acquire() + self.py_tid = py_thread_id() + time.sleep(short_sleep) + self.py_tid2 = py_thread_id() + + nthread = 5 + threads = [GetThreadId() for _ in range(nthread)] + + # first make run sure that all threads are running + for thread in threads: + thread.start() + for thread in threads: + thread.started_lock.wait() + + # call _Py_ThreadId() in the main thread + py_thread_ids = [py_thread_id()] + + # now call _Py_ThreadId() in each thread + for thread in threads: + thread.get_lock.release() + + # call _Py_ThreadId() in each thread and wait until threads complete + for thread in threads: + thread.join() + py_thread_ids.append(thread.py_tid) + # _PyThread_Id() should not change for a given thread. + # For example, it should remain the same after a short sleep. + self.assertEqual(thread.py_tid2, thread.py_tid) + + # make sure that all _Py_ThreadId() are unique + for tid in py_thread_ids: + self.assertIsInstance(tid, int) + self.assertGreater(tid, 0) + self.assertEqual(len(set(py_thread_ids)), len(py_thread_ids), + py_thread_ids) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_cppext/__init__.py b/Lib/test/test_cppext/__init__.py index 299a16ada2e32e..c6039bd17b0662 100644 --- a/Lib/test/test_cppext/__init__.py +++ b/Lib/test/test_cppext/__init__.py @@ -2,7 +2,6 @@ # compatible with C++ and does not emit C++ compiler warnings. import os.path import shutil -import sys import unittest import subprocess import sysconfig @@ -15,7 +14,7 @@ # gh-110119: pip does not currently support 't' in the ABI flag use by # --disable-gil builds. Once it does, we can remove this skip. -@unittest.skipIf(sysconfig.get_config_var('Py_GIL_DISABLED') == 1, +@unittest.skipIf(support.Py_GIL_DISABLED, 'test does not work with --disable-gil') @support.requires_subprocess() class TestCPPExt(unittest.TestCase): diff --git a/Lib/test/test_importlib/test_windows.py b/Lib/test/test_importlib/test_windows.py index d25133240b1afd..8a9a8fffcd10d4 100644 --- a/Lib/test/test_importlib/test_windows.py +++ b/Lib/test/test_importlib/test_windows.py @@ -4,8 +4,8 @@ import os import re import sys -import sysconfig import unittest +from test import support from test.support import import_helper from contextlib import contextmanager from test.test_importlib.util import temp_module @@ -112,7 +112,7 @@ def test_module_not_found(self): class WindowsExtensionSuffixTests: def test_tagged_suffix(self): suffixes = self.machinery.EXTENSION_SUFFIXES - abi_flags = "t" if sysconfig.get_config_var("Py_GIL_DISABLED") else "" + abi_flags = "t" if support.Py_GIL_DISABLED else "" ver = sys.version_info platform = re.sub('[^a-zA-Z0-9]', '_', get_platform()) expected_tag = f".cp{ver.major}{ver.minor}{abi_flags}-{platform}.pyd" diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 8c2c1a40f74bf2..db5ba16c4d9739 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1224,9 +1224,7 @@ def test_pystats(self): @test.support.cpython_only @unittest.skipUnless(hasattr(sys, 'abiflags'), 'need sys.abiflags') def test_disable_gil_abi(self): - abi_threaded = 't' in sys.abiflags - py_gil_disabled = (sysconfig.get_config_var('Py_GIL_DISABLED') == 1) - self.assertEqual(py_gil_disabled, abi_threaded) + self.assertEqual('t' in sys.abiflags, support.Py_GIL_DISABLED) @test.support.cpython_only diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 4607a3faf17f74..ba7653f2d9c7aa 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1625,6 +1625,17 @@ get_type_module_name(PyObject *self, PyObject *type) } +#ifdef Py_GIL_DISABLED +static PyObject * +get_py_thread_id(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + uintptr_t tid = _Py_ThreadId(); + Py_BUILD_ASSERT(sizeof(unsigned long long) >= sizeof(tid)); + return PyLong_FromUnsignedLongLong(tid); +} +#endif + + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1688,6 +1699,9 @@ 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}, +#ifdef Py_GIL_DISABLED + {"py_thread_id", get_py_thread_id, METH_NOARGS}, +#endif {NULL, NULL} /* sentinel */ }; From 9fe7655c6ce0b8e9adc229daf681b6d30e6b1610 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 4 Dec 2023 15:08:19 -0800 Subject: [PATCH 104/442] gh-112334: Restore subprocess's use of `vfork()` & fix `extra_groups=[]` behavior (#112617) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. Co-authored-by: Serhiy Storchaka --- Lib/test/test_subprocess.py | 38 +++++++++---------- ...-12-01-21-05-46.gh-issue-112334.DmNXKh.rst | 11 ++++++ Modules/_posixsubprocess.c | 12 +++++- 3 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-01-21-05-46.gh-issue-112334.DmNXKh.rst diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index fe1a3675fced65..319bc0d2638563 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2066,8 +2066,14 @@ def test_group_error(self): def test_extra_groups(self): gid = os.getegid() group_list = [65534 if gid != 65534 else 65533] + self._test_extra_groups_impl(gid=gid, group_list=group_list) + + @unittest.skipUnless(hasattr(os, 'setgroups'), 'no setgroups() on platform') + def test_extra_groups_empty_list(self): + self._test_extra_groups_impl(gid=os.getegid(), group_list=[]) + + def _test_extra_groups_impl(self, *, gid, group_list): name_group = _get_test_grp_name() - perm_error = False if grp is not None: group_list.append(name_group) @@ -2077,11 +2083,8 @@ def test_extra_groups(self): [sys.executable, "-c", "import os, sys, json; json.dump(os.getgroups(), sys.stdout)"], extra_groups=group_list) - except OSError as ex: - if ex.errno != errno.EPERM: - raise - perm_error = True - + except PermissionError: + self.skipTest("setgroup() EPERM; this test may require root.") else: parent_groups = os.getgroups() child_groups = json.loads(output) @@ -2092,12 +2095,15 @@ def test_extra_groups(self): else: desired_gids = group_list - if perm_error: - self.assertEqual(set(child_groups), set(parent_groups)) - else: - self.assertEqual(set(desired_gids), set(child_groups)) + self.assertEqual(set(desired_gids), set(child_groups)) - # make sure we bomb on negative values + if grp is None: + with self.assertRaises(ValueError): + subprocess.check_call(ZERO_RETURN_CMD, + extra_groups=[name_group]) + + # No skip necessary, this test won't make it to a setgroup() call. + def test_extra_groups_invalid_gid_t_values(self): with self.assertRaises(ValueError): subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[-1]) @@ -2106,16 +2112,6 @@ def test_extra_groups(self): cwd=os.curdir, env=os.environ, extra_groups=[2**64]) - if grp is None: - with self.assertRaises(ValueError): - subprocess.check_call(ZERO_RETURN_CMD, - extra_groups=[name_group]) - - @unittest.skipIf(hasattr(os, 'setgroups'), 'setgroups() available on platform') - def test_extra_groups_error(self): - with self.assertRaises(ValueError): - subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[]) - @unittest.skipIf(mswindows or not hasattr(os, 'umask'), 'POSIX umask() is not available.') def test_umask(self): diff --git a/Misc/NEWS.d/next/Library/2023-12-01-21-05-46.gh-issue-112334.DmNXKh.rst b/Misc/NEWS.d/next/Library/2023-12-01-21-05-46.gh-issue-112334.DmNXKh.rst new file mode 100644 index 00000000000000..3a53a8bf84230f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-01-21-05-46.gh-issue-112334.DmNXKh.rst @@ -0,0 +1,11 @@ +Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it +would no longer use the fast-path ``vfork()`` system call when it could have +due to a logic bug, instead falling back to the safe but slower ``fork()``. + +Also fixed a second 3.12.0 potential security bug. If a value of +``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, +the underlying ``setgroups(0, NULL)`` system call to clear the groups list +would not be made in the child process prior to ``exec()``. + +This was identified via code inspection in the process of fixing the first +bug. diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 2898eedc3e3a8f..d0dd8f064e0395 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -767,8 +767,10 @@ child_exec(char *const exec_array[], #endif #ifdef HAVE_SETGROUPS - if (extra_group_size > 0) + if (extra_group_size >= 0) { + assert((extra_group_size == 0) == (extra_groups == NULL)); POSIX_CALL(setgroups(extra_group_size, extra_groups)); + } #endif /* HAVE_SETGROUPS */ #ifdef HAVE_SETREGID @@ -1022,7 +1024,6 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args, pid_t pid = -1; int need_to_reenable_gc = 0; char *const *argv = NULL, *const *envp = NULL; - Py_ssize_t extra_group_size = 0; int need_after_fork = 0; int saved_errno = 0; int *c_fds_to_keep = NULL; @@ -1103,6 +1104,13 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args, cwd = PyBytes_AsString(cwd_obj2); } + // Special initial value meaning that subprocess API was called with + // extra_groups=None leading to _posixsubprocess.fork_exec(gids=None). + // We use this to differentiate between code desiring a setgroups(0, NULL) + // call vs no call at all. The fast vfork() code path could be used when + // there is no setgroups call. + Py_ssize_t extra_group_size = -2; + if (extra_groups_packed != Py_None) { #ifdef HAVE_SETGROUPS if (!PyList_Check(extra_groups_packed)) { From 304a1b3f3a8ed9a734ef1d098cafccb6725162db Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 4 Dec 2023 23:21:39 +0000 Subject: [PATCH 105/442] GH-112727: Speed up `pathlib.Path.absolute()` (#112728) Use `_from_parsed_parts()` to create a pre-joined/pre-parsed path, rather than passing multiple arguments to `with_segments()` Co-authored-by: Alex Waygood --- Lib/pathlib.py | 20 +++++++++++++------ ...-12-04-21-30-34.gh-issue-112727.jpgNRB.rst | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index b728a0b3dfdb6c..c48cff307083a8 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1415,21 +1415,29 @@ def absolute(self): """ if self.is_absolute(): return self - elif self.drive: + if self.root: + drive = os.path.splitroot(os.getcwd())[0] + return self._from_parsed_parts(drive, self.root, self._tail) + if self.drive: # There is a CWD on each drive-letter drive. cwd = os.path.abspath(self.drive) else: cwd = os.getcwd() + if not self._tail: # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). # We pass only one argument to with_segments() to avoid the cost # of joining, and we exploit the fact that getcwd() returns a # fully-normalized string by storing it in _str. This is used to # implement Path.cwd(). - if not self.root and not self._tail: - result = self.with_segments(cwd) - result._str = cwd - return result - return self.with_segments(cwd, self) + result = self.with_segments(cwd) + result._str = cwd + return result + drive, root, rel = os.path.splitroot(cwd) + if not rel: + return self._from_parsed_parts(drive, root, self._tail) + tail = rel.split(self.pathmod.sep) + tail.extend(self._tail) + return self._from_parsed_parts(drive, root, tail) def resolve(self, strict=False): """ diff --git a/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst b/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst new file mode 100644 index 00000000000000..bbe7aae5732d9a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst @@ -0,0 +1 @@ +Speed up :meth:`pathlib.Path.absolute`. Patch by Barney Gale. From dc824c5dc120ffed84bafd23f95e95a99678ed6a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 5 Dec 2023 12:23:17 +0800 Subject: [PATCH 106/442] gh-112736: Refactor del-safe symbol handling in subprocess (#112738) Refactor delete-safe symbol handling in subprocess. Only module globals are force-cleared during interpreter finalization, using a class reference instead of individually listing the constants everywhere is simpler. --- Lib/subprocess.py | 48 +++++++++---------- ...-12-05-01-19-28.gh-issue-112736.rdHDrU.rst | 1 + 2 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-05-01-19-28.gh-issue-112736.rdHDrU.rst diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 6df5dd551ea67e..d6edd1a9807d1b 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -74,8 +74,8 @@ else: _mswindows = True -# wasm32-emscripten and wasm32-wasi do not support processes -_can_fork_exec = sys.platform not in {"emscripten", "wasi"} +# some platforms do not support subprocesses +_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"} if _mswindows: import _winapi @@ -103,18 +103,22 @@ if _can_fork_exec: from _posixsubprocess import fork_exec as _fork_exec # used in methods that are called by __del__ - _waitpid = os.waitpid - _waitstatus_to_exitcode = os.waitstatus_to_exitcode - _WIFSTOPPED = os.WIFSTOPPED - _WSTOPSIG = os.WSTOPSIG - _WNOHANG = os.WNOHANG + class _del_safe: + waitpid = os.waitpid + waitstatus_to_exitcode = os.waitstatus_to_exitcode + WIFSTOPPED = os.WIFSTOPPED + WSTOPSIG = os.WSTOPSIG + WNOHANG = os.WNOHANG + ECHILD = errno.ECHILD else: - _fork_exec = None - _waitpid = None - _waitstatus_to_exitcode = None - _WIFSTOPPED = None - _WSTOPSIG = None - _WNOHANG = None + class _del_safe: + waitpid = None + waitstatus_to_exitcode = None + WIFSTOPPED = None + WSTOPSIG = None + WNOHANG = None + ECHILD = errno.ECHILD + import select import selectors @@ -1951,20 +1955,16 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, raise child_exception_type(err_msg) - def _handle_exitstatus(self, sts, - _waitstatus_to_exitcode=_waitstatus_to_exitcode, - _WIFSTOPPED=_WIFSTOPPED, - _WSTOPSIG=_WSTOPSIG): + def _handle_exitstatus(self, sts, _del_safe=_del_safe): """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. - if _WIFSTOPPED(sts): - self.returncode = -_WSTOPSIG(sts) + if _del_safe.WIFSTOPPED(sts): + self.returncode = -_del_safe.WSTOPSIG(sts) else: - self.returncode = _waitstatus_to_exitcode(sts) + self.returncode = _del_safe.waitstatus_to_exitcode(sts) - def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, - _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD): + def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): """Check if child process has terminated. Returns returncode attribute. @@ -1980,13 +1980,13 @@ def _internal_poll(self, _deadstate=None, _waitpid=_waitpid, try: if self.returncode is not None: return self.returncode # Another thread waited. - pid, sts = _waitpid(self.pid, _WNOHANG) + pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) except OSError as e: if _deadstate is not None: self.returncode = _deadstate - elif e.errno == _ECHILD: + elif e.errno == _del_safe.ECHILD: # This happens if SIGCLD is set to be ignored or # waiting for child processes has otherwise been # disabled for our process. This child is dead, we diff --git a/Misc/NEWS.d/next/Library/2023-12-05-01-19-28.gh-issue-112736.rdHDrU.rst b/Misc/NEWS.d/next/Library/2023-12-05-01-19-28.gh-issue-112736.rdHDrU.rst new file mode 100644 index 00000000000000..6c09e622923af8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-05-01-19-28.gh-issue-112736.rdHDrU.rst @@ -0,0 +1 @@ +The use of del-safe symbols in ``subprocess`` was refactored to allow for use in cross-platform build environments. From aa5bee30abb28d73a838399f4c3a8bcdc5108fe3 Mon Sep 17 00:00:00 2001 From: Constantin Hong Date: Tue, 5 Dec 2023 16:24:56 +0900 Subject: [PATCH 107/442] gh-102130: Support tab completion in cmd for Libedit. (GH-107748) --- Co-authored-by: Tian Gao --- Doc/library/cmd.rst | 10 +++++++ Lib/cmd.py | 10 ++++++- Lib/test/test_cmd.py | 30 +++++++++++++++++++ Misc/ACKS | 1 + ...-08-07-21-11-24.gh-issue-102130._UyI5i.rst | 1 + 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-08-07-21-11-24.gh-issue-102130._UyI5i.rst diff --git a/Doc/library/cmd.rst b/Doc/library/cmd.rst index fd5df96dfd0b3d..1318ffe5a48d53 100644 --- a/Doc/library/cmd.rst +++ b/Doc/library/cmd.rst @@ -26,6 +26,13 @@ interface. key; it defaults to :kbd:`Tab`. If *completekey* is not :const:`None` and :mod:`readline` is available, command completion is done automatically. + The default, ``'tab'``, is treated specially, so that it refers to the + :kbd:`Tab` key on every :data:`readline.backend`. + Specifically, if :data:`readline.backend` is ``editline``, + ``Cmd`` will use ``'^I'`` instead of ``'tab'``. + Note that other values are not treated this way, and might only work + with a specific backend. + The optional arguments *stdin* and *stdout* specify the input and output file objects that the Cmd instance or subclass instance will use for input and output. If not specified, they will default to :data:`sys.stdin` and @@ -35,6 +42,9 @@ interface. :attr:`use_rawinput` attribute to ``False``, otherwise *stdin* will be ignored. + .. versionchanged:: 3.13 + ``completekey='tab'`` is replaced by ``'^I'`` for ``editline``. + .. _cmd-objects: diff --git a/Lib/cmd.py b/Lib/cmd.py index e933b8dbc1470a..2e358d6cd5a02d 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -108,7 +108,15 @@ def cmdloop(self, intro=None): import readline self.old_completer = readline.get_completer() readline.set_completer(self.complete) - readline.parse_and_bind(self.completekey+": complete") + if readline.backend == "editline": + if self.completekey == 'tab': + # libedit uses "^I" instead of "tab" + command_string = "bind ^I rl_complete" + else: + command_string = f"bind {self.completekey} rl_complete" + else: + command_string = f"{self.completekey}: complete" + readline.parse_and_bind(command_string) except ImportError: pass try: diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py index 951336fa08542d..46ec82b704963d 100644 --- a/Lib/test/test_cmd.py +++ b/Lib/test/test_cmd.py @@ -9,7 +9,10 @@ import doctest import unittest import io +import textwrap from test import support +from test.support.import_helper import import_module +from test.support.pty_helper import run_pty class samplecmdclass(cmd.Cmd): """ @@ -259,6 +262,33 @@ class CmdPrintExceptionClass(cmd.Cmd): def default(self, line): print(sys.exc_info()[:2]) + +@support.requires_subprocess() +class CmdTestReadline(unittest.TestCase): + def setUpClass(): + # Ensure that the readline module is loaded + # If this fails, the test is skipped because SkipTest will be raised + readline = import_module('readline') + + def test_basic_completion(self): + script = textwrap.dedent(""" + import cmd + class simplecmd(cmd.Cmd): + def do_tab_completion_test(self, args): + print('tab completion success') + return True + + simplecmd().cmdloop() + """) + + # 't' and complete 'ab_completion_test' to 'tab_completion_test' + input = b"t\t\n" + + output = run_pty(script, input) + + self.assertIn(b'ab_completion_test', output) + self.assertIn(b'tab completion success', output) + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Misc/ACKS b/Misc/ACKS index 1c67d96ed3a528..12335c911ae42a 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -788,6 +788,7 @@ Thomas Holmes Craig Holmquist Philip Homburg Naofumi Honda +Constantin Hong Weipeng Hong Jeffrey Honig Rob Hooft diff --git a/Misc/NEWS.d/next/Library/2023-08-07-21-11-24.gh-issue-102130._UyI5i.rst b/Misc/NEWS.d/next/Library/2023-08-07-21-11-24.gh-issue-102130._UyI5i.rst new file mode 100644 index 00000000000000..f582ad5df39e84 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-08-07-21-11-24.gh-issue-102130._UyI5i.rst @@ -0,0 +1 @@ +Support tab completion in :mod:`cmd` for ``editline``. From 9f92b31339945da55559747c420e170c968e9e2b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 5 Dec 2023 10:34:13 +0300 Subject: [PATCH 108/442] Minor refactoring of Object/abstract.c (UNARY_FUNC macro and more cases for BINARY_FUNC) (GH-112145) * Use BINARY_FUNC macro for some remaining ops * Add UNARY_FUNC macro to define unary PyNumber_* functions --- Objects/abstract.c | 115 ++++++++++----------------------------------- 1 file changed, 25 insertions(+), 90 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 43842fbdd6aedd..1ec5c5b8c3dc2f 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1180,29 +1180,10 @@ PyNumber_Multiply(PyObject *v, PyObject *w) return result; } -PyObject * -PyNumber_MatrixMultiply(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_matrix_multiply), "@"); -} - -PyObject * -PyNumber_FloorDivide(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_floor_divide), "//"); -} - -PyObject * -PyNumber_TrueDivide(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_true_divide), "/"); -} - -PyObject * -PyNumber_Remainder(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_remainder), "%"); -} +BINARY_FUNC(PyNumber_MatrixMultiply, nb_matrix_multiply, "@") +BINARY_FUNC(PyNumber_FloorDivide, nb_floor_divide, "//") +BINARY_FUNC(PyNumber_TrueDivide, nb_true_divide, "/") +BINARY_FUNC(PyNumber_Remainder, nb_remainder, "%") PyObject * PyNumber_Power(PyObject *v, PyObject *w, PyObject *z) @@ -1379,73 +1360,27 @@ _PyNumber_InPlacePowerNoMod(PyObject *lhs, PyObject *rhs) /* Unary operators and functions */ -PyObject * -PyNumber_Negative(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_negative) { - PyObject *res = (*m->nb_negative)(o); - assert(_Py_CheckSlotResult(o, "__neg__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary -: '%.200s'", o); -} - -PyObject * -PyNumber_Positive(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_positive) { - PyObject *res = (*m->nb_positive)(o); - assert(_Py_CheckSlotResult(o, "__pos__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary +: '%.200s'", o); -} - -PyObject * -PyNumber_Invert(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_invert) { - PyObject *res = (*m->nb_invert)(o); - assert(_Py_CheckSlotResult(o, "__invert__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary ~: '%.200s'", o); -} - -PyObject * -PyNumber_Absolute(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_absolute) { - PyObject *res = m->nb_absolute(o); - assert(_Py_CheckSlotResult(o, "__abs__", res != NULL)); - return res; - } - - return type_error("bad operand type for abs(): '%.200s'", o); -} +#define UNARY_FUNC(func, op, meth_name, descr) \ + PyObject * \ + func(PyObject *o) { \ + if (o == NULL) { \ + return null_error(); \ + } \ + \ + PyNumberMethods *m = Py_TYPE(o)->tp_as_number; \ + if (m && m->op) { \ + PyObject *res = (*m->op)(o); \ + assert(_Py_CheckSlotResult(o, #meth_name, res != NULL)); \ + return res; \ + } \ + \ + return type_error("bad operand type for "descr": '%.200s'", o); \ + } + +UNARY_FUNC(PyNumber_Negative, nb_negative, __neg__, "unary -") +UNARY_FUNC(PyNumber_Positive, nb_positive, __pow__, "unary +") +UNARY_FUNC(PyNumber_Invert, nb_invert, __invert__, "unary ~") +UNARY_FUNC(PyNumber_Absolute, nb_absolute, __abs__, "abs()") int From 81ee0260912dc4b55410f3c6ad755b5c4da82f4a Mon Sep 17 00:00:00 2001 From: pan324 <103143968+pan324@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:11:44 +0100 Subject: [PATCH 109/442] gh-82300: Add track parameter to multiprocessing.shared_memory (#110778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a track parameter to shared memory to allow resource tracking via the side-launched resource tracker process to be disabled on platforms that use it (POSIX). This allows people who do not want automated cleanup at process exit because they are using the shared memory with processes not participating in Python's resource tracking to use the shared_memory API. Co-authored-by: Łukasz Langa Co-authored-by: Guido van Rossum Co-authored-by: Antoine Pitrou Co-authored-by: Gregory P. Smith --- Doc/library/multiprocessing.shared_memory.rst | 51 ++++++++++++------ Lib/multiprocessing/shared_memory.py | 24 ++++++--- Lib/test/_test_multiprocessing.py | 53 +++++++++++++++++++ ...3-10-12-18-19-47.gh-issue-82300.P8-O38.rst | 1 + 4 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-12-18-19-47.gh-issue-82300.P8-O38.rst diff --git a/Doc/library/multiprocessing.shared_memory.rst b/Doc/library/multiprocessing.shared_memory.rst index f453e6403d932d..671130d9b29fc0 100644 --- a/Doc/library/multiprocessing.shared_memory.rst +++ b/Doc/library/multiprocessing.shared_memory.rst @@ -36,7 +36,7 @@ or other communications requiring the serialization/deserialization and copying of data. -.. class:: SharedMemory(name=None, create=False, size=0) +.. class:: SharedMemory(name=None, create=False, size=0, *, track=True) Creates a new shared memory block or attaches to an existing shared memory block. Each shared memory block is assigned a unique name. @@ -64,26 +64,45 @@ copying of data. memory block may be larger or equal to the size requested. When attaching to an existing shared memory block, the ``size`` parameter is ignored. + *track*, when enabled, registers the shared memory block with a resource + tracker process on platforms where the OS does not do this automatically. + The resource tracker ensures proper cleanup of the shared memory even + if all other processes with access to the memory exit without doing so. + Python processes created from a common ancestor using :mod:`multiprocessing` + facilities share a single resource tracker process, and the lifetime of + shared memory segments is handled automatically among these processes. + Python processes created in any other way will receive their own + resource tracker when accessing shared memory with *track* enabled. + This will cause the shared memory to be deleted by the resource tracker + of the first process that terminates. + To avoid this issue, users of :mod:`subprocess` or standalone Python + processes should set *track* to ``False`` when there is already another + process in place that does the bookkeeping. + *track* is ignored on Windows, which has its own tracking and + automatically deletes shared memory when all handles to it have been closed. + + .. versionchanged:: 3.13 Added *track* parameter. + .. method:: close() - Closes access to the shared memory from this instance. In order to - ensure proper cleanup of resources, all instances should call - ``close()`` once the instance is no longer needed. Note that calling - ``close()`` does not cause the shared memory block itself to be - destroyed. + Closes the file descriptor/handle to the shared memory from this + instance. :meth:`close()` should be called once access to the shared + memory block from this instance is no longer needed. Depending + on operating system, the underlying memory may or may not be freed + even if all handles to it have been closed. To ensure proper cleanup, + use the :meth:`unlink()` method. .. method:: unlink() - Requests that the underlying shared memory block be destroyed. In - order to ensure proper cleanup of resources, ``unlink()`` should be - called once (and only once) across all processes which have need - for the shared memory block. After requesting its destruction, a - shared memory block may or may not be immediately destroyed and - this behavior may differ across platforms. Attempts to access data - inside the shared memory block after ``unlink()`` has been called may - result in memory access errors. Note: the last process relinquishing - its hold on a shared memory block may call ``unlink()`` and - :meth:`close()` in either order. + Deletes the underlying shared memory block. This should be called only + once per shared memory block regardless of the number of handles to it, + even in other processes. + :meth:`unlink()` and :meth:`close()` can be called in any order, but + trying to access data inside a shared memory block after :meth:`unlink()` + may result in memory access errors, depending on platform. + + This method has no effect on Windows, where the only way to delete a + shared memory block is to close all handles. .. attribute:: buf diff --git a/Lib/multiprocessing/shared_memory.py b/Lib/multiprocessing/shared_memory.py index 9a1e5aa17b87a2..67e70fdc27cf31 100644 --- a/Lib/multiprocessing/shared_memory.py +++ b/Lib/multiprocessing/shared_memory.py @@ -71,8 +71,9 @@ class SharedMemory: _flags = os.O_RDWR _mode = 0o600 _prepend_leading_slash = True if _USE_POSIX else False + _track = True - def __init__(self, name=None, create=False, size=0): + def __init__(self, name=None, create=False, size=0, *, track=True): if not size >= 0: raise ValueError("'size' must be a positive integer") if create: @@ -82,6 +83,7 @@ def __init__(self, name=None, create=False, size=0): if name is None and not self._flags & os.O_EXCL: raise ValueError("'name' can only be None if create=True") + self._track = track if _USE_POSIX: # POSIX Shared Memory @@ -116,8 +118,8 @@ def __init__(self, name=None, create=False, size=0): except OSError: self.unlink() raise - - resource_tracker.register(self._name, "shared_memory") + if self._track: + resource_tracker.register(self._name, "shared_memory") else: @@ -236,12 +238,20 @@ def close(self): def unlink(self): """Requests that the underlying shared memory block be destroyed. - In order to ensure proper cleanup of resources, unlink should be - called once (and only once) across all processes which have access - to the shared memory block.""" + Unlink should be called once (and only once) across all handles + which have access to the shared memory block, even if these + handles belong to different processes. Closing and unlinking may + happen in any order, but trying to access data inside a shared + memory block after unlinking may result in memory errors, + depending on platform. + + This method has no effect on Windows, where the only way to + delete a shared memory block is to close all handles.""" + if _USE_POSIX and self._name: _posixshmem.shm_unlink(self._name) - resource_tracker.unregister(self._name, "shared_memory") + if self._track: + resource_tracker.unregister(self._name, "shared_memory") _encoding = "utf8" diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index ec003d8dc4314d..a94eb6c0ae4b8e 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4455,6 +4455,59 @@ def test_shared_memory_cleaned_after_process_termination(self): "resource_tracker: There appear to be 1 leaked " "shared_memory objects to clean up at shutdown", err) + @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") + def test_shared_memory_untracking(self): + # gh-82300: When a separate Python process accesses shared memory + # with track=False, it must not cause the memory to be deleted + # when terminating. + cmd = '''if 1: + import sys + from multiprocessing.shared_memory import SharedMemory + mem = SharedMemory(create=False, name=sys.argv[1], track=False) + mem.close() + ''' + mem = shared_memory.SharedMemory(create=True, size=10) + # The resource tracker shares pipes with the subprocess, and so + # err existing means that the tracker process has terminated now. + try: + rc, out, err = script_helper.assert_python_ok("-c", cmd, mem.name) + self.assertNotIn(b"resource_tracker", err) + self.assertEqual(rc, 0) + mem2 = shared_memory.SharedMemory(create=False, name=mem.name) + mem2.close() + finally: + try: + mem.unlink() + except OSError: + pass + mem.close() + + @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") + def test_shared_memory_tracking(self): + # gh-82300: When a separate Python process accesses shared memory + # with track=True, it must cause the memory to be deleted when + # terminating. + cmd = '''if 1: + import sys + from multiprocessing.shared_memory import SharedMemory + mem = SharedMemory(create=False, name=sys.argv[1], track=True) + mem.close() + ''' + mem = shared_memory.SharedMemory(create=True, size=10) + try: + rc, out, err = script_helper.assert_python_ok("-c", cmd, mem.name) + self.assertEqual(rc, 0) + self.assertIn( + b"resource_tracker: There appear to be 1 leaked " + b"shared_memory objects to clean up at shutdown", err) + finally: + try: + mem.unlink() + except OSError: + pass + resource_tracker.unregister(mem._name, "shared_memory") + mem.close() + # # Test to verify that `Finalize` works. # diff --git a/Misc/NEWS.d/next/Library/2023-10-12-18-19-47.gh-issue-82300.P8-O38.rst b/Misc/NEWS.d/next/Library/2023-10-12-18-19-47.gh-issue-82300.P8-O38.rst new file mode 100644 index 00000000000000..d7e6b225489b99 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-12-18-19-47.gh-issue-82300.P8-O38.rst @@ -0,0 +1 @@ +Add ``track`` parameter to :class:`multiprocessing.shared_memory.SharedMemory` that allows using shared memory blocks without having to register with the POSIX resource tracker that automatically releases them upon process exit. From d824512059eabbe9d45daeb24d1e2070ad13dd87 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 5 Dec 2023 09:03:32 +0000 Subject: [PATCH 110/442] gh-112535: Update _Py_ThreadId() to support PowerPC (gh-112624) --- Include/object.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Include/object.h b/Include/object.h index 81f777ad21f2f9..dfeb43bda7d841 100644 --- a/Include/object.h +++ b/Include/object.h @@ -261,6 +261,22 @@ _Py_ThreadId(void) __asm__ ("mrs %0, tpidrro_el0" : "=r" (tid)); #elif defined(__aarch64__) __asm__ ("mrs %0, tpidr_el0" : "=r" (tid)); +#elif defined(__powerpc64__) + #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) + tid = (uintptr_t)__builtin_thread_pointer(); + #else + register uintptr_t tp __asm__ ("r13"); + __asm__("" : "=r" (tp)); + tid = tp; + #endif +#elif defined(__powerpc__) + #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) + tid = (uintptr_t)__builtin_thread_pointer(); + #else + register uintptr_t tp __asm__ ("r2"); + __asm__ ("" : "=r" (tp)); + tid = tp; + #endif #else # error "define _Py_ThreadId for this platform" #endif From b31232ddf7f219ca8ff9e8d0401c02eb0b6ffec3 Mon Sep 17 00:00:00 2001 From: Rune Tynan Date: Tue, 5 Dec 2023 04:21:09 -0500 Subject: [PATCH 111/442] gh-62897: Update PyUnicode C API parameter names (GH-12680) Standardize PyUnicode C API parameter names across the documentation. Co-authored-by: Serhiy Storchaka --- Doc/c-api/unicode.rst | 182 +++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index e654412965a727..5541eaa521803b 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -75,19 +75,19 @@ Python: The following APIs are C macros and static inlined functions for fast checks and access to internal read-only data of Unicode objects: -.. c:function:: int PyUnicode_Check(PyObject *o) +.. c:function:: int PyUnicode_Check(PyObject *obj) - Return true if the object *o* is a Unicode object or an instance of a Unicode + Return true if the object *obj* is a Unicode object or an instance of a Unicode subtype. This function always succeeds. -.. c:function:: int PyUnicode_CheckExact(PyObject *o) +.. c:function:: int PyUnicode_CheckExact(PyObject *obj) - Return true if the object *o* is a Unicode object, but not an instance of a + Return true if the object *obj* is a Unicode object, but not an instance of a subtype. This function always succeeds. -.. c:function:: int PyUnicode_READY(PyObject *o) +.. c:function:: int PyUnicode_READY(PyObject *unicode) Returns ``0``. This API is kept only for backward compatibility. @@ -97,17 +97,17 @@ access to internal read-only data of Unicode objects: This API does nothing since Python 3.12. -.. c:function:: Py_ssize_t PyUnicode_GET_LENGTH(PyObject *o) +.. c:function:: Py_ssize_t PyUnicode_GET_LENGTH(PyObject *unicode) - Return the length of the Unicode string, in code points. *o* has to be a + Return the length of the Unicode string, in code points. *unicode* has to be a Unicode object in the "canonical" representation (not checked). .. versionadded:: 3.3 -.. c:function:: Py_UCS1* PyUnicode_1BYTE_DATA(PyObject *o) - Py_UCS2* PyUnicode_2BYTE_DATA(PyObject *o) - Py_UCS4* PyUnicode_4BYTE_DATA(PyObject *o) +.. c:function:: Py_UCS1* PyUnicode_1BYTE_DATA(PyObject *unicode) + Py_UCS2* PyUnicode_2BYTE_DATA(PyObject *unicode) + Py_UCS4* PyUnicode_4BYTE_DATA(PyObject *unicode) Return a pointer to the canonical representation cast to UCS1, UCS2 or UCS4 integer types for direct character access. No checks are performed if the @@ -129,18 +129,18 @@ access to internal read-only data of Unicode objects: ``PyUnicode_WCHAR_KIND`` has been removed. -.. c:function:: int PyUnicode_KIND(PyObject *o) +.. c:function:: int PyUnicode_KIND(PyObject *unicode) Return one of the PyUnicode kind constants (see above) that indicate how many - bytes per character this Unicode object uses to store its data. *o* has to + bytes per character this Unicode object uses to store its data. *unicode* has to be a Unicode object in the "canonical" representation (not checked). .. versionadded:: 3.3 -.. c:function:: void* PyUnicode_DATA(PyObject *o) +.. c:function:: void* PyUnicode_DATA(PyObject *unicode) - Return a void pointer to the raw Unicode buffer. *o* has to be a Unicode + Return a void pointer to the raw Unicode buffer. *unicode* has to be a Unicode object in the "canonical" representation (not checked). .. versionadded:: 3.3 @@ -168,25 +168,25 @@ access to internal read-only data of Unicode objects: .. versionadded:: 3.3 -.. c:function:: Py_UCS4 PyUnicode_READ_CHAR(PyObject *o, Py_ssize_t index) +.. c:function:: Py_UCS4 PyUnicode_READ_CHAR(PyObject *unicode, Py_ssize_t index) - Read a character from a Unicode object *o*, which must be in the "canonical" + Read a character from a Unicode object *unicode*, which must be in the "canonical" representation. This is less efficient than :c:func:`PyUnicode_READ` if you do multiple consecutive reads. .. versionadded:: 3.3 -.. c:function:: Py_UCS4 PyUnicode_MAX_CHAR_VALUE(PyObject *o) +.. c:function:: Py_UCS4 PyUnicode_MAX_CHAR_VALUE(PyObject *unicode) Return the maximum code point that is suitable for creating another string - based on *o*, which must be in the "canonical" representation. This is + based on *unicode*, which must be in the "canonical" representation. This is always an approximation but more efficient than iterating over the string. .. versionadded:: 3.3 -.. c:function:: int PyUnicode_IsIdentifier(PyObject *o) +.. c:function:: int PyUnicode_IsIdentifier(PyObject *unicode) Return ``1`` if the string is a valid identifier according to the language definition, section :ref:`identifiers`. Return ``0`` otherwise. @@ -358,9 +358,9 @@ APIs: .. versionadded:: 3.3 -.. c:function:: PyObject* PyUnicode_FromStringAndSize(const char *u, Py_ssize_t size) +.. c:function:: PyObject* PyUnicode_FromStringAndSize(const char *str, Py_ssize_t size) - Create a Unicode object from the char buffer *u*. The bytes will be + Create a Unicode object from the char buffer *str*. The bytes will be interpreted as being UTF-8 encoded. The buffer is copied into the new object. The return value might be a shared object, i.e. modification of the data is @@ -369,16 +369,16 @@ APIs: This function raises :exc:`SystemError` when: * *size* < 0, - * *u* is ``NULL`` and *size* > 0 + * *str* is ``NULL`` and *size* > 0 .. versionchanged:: 3.12 - *u* == ``NULL`` with *size* > 0 is not allowed anymore. + *str* == ``NULL`` with *size* > 0 is not allowed anymore. -.. c:function:: PyObject *PyUnicode_FromString(const char *u) +.. c:function:: PyObject *PyUnicode_FromString(const char *str) Create a Unicode object from a UTF-8 encoded null-terminated char buffer - *u*. + *str*. .. c:function:: PyObject* PyUnicode_FromFormat(const char *format, ...) @@ -646,29 +646,29 @@ APIs: .. versionadded:: 3.3 -.. c:function:: PyObject* PyUnicode_Substring(PyObject *str, Py_ssize_t start, \ +.. c:function:: PyObject* PyUnicode_Substring(PyObject *unicode, Py_ssize_t start, \ Py_ssize_t end) - Return a substring of *str*, from character index *start* (included) to + Return a substring of *unicode*, from character index *start* (included) to character index *end* (excluded). Negative indices are not supported. .. versionadded:: 3.3 -.. c:function:: Py_UCS4* PyUnicode_AsUCS4(PyObject *u, Py_UCS4 *buffer, \ +.. c:function:: Py_UCS4* PyUnicode_AsUCS4(PyObject *unicode, Py_UCS4 *buffer, \ Py_ssize_t buflen, int copy_null) - Copy the string *u* into a UCS4 buffer, including a null character, if + Copy the string *unicode* into a UCS4 buffer, including a null character, if *copy_null* is set. Returns ``NULL`` and sets an exception on error (in particular, a :exc:`SystemError` if *buflen* is smaller than the length of - *u*). *buffer* is returned on success. + *unicode*). *buffer* is returned on success. .. versionadded:: 3.3 -.. c:function:: Py_UCS4* PyUnicode_AsUCS4Copy(PyObject *u) +.. c:function:: Py_UCS4* PyUnicode_AsUCS4Copy(PyObject *unicode) - Copy the string *u* into a new UCS4 buffer that is allocated using + Copy the string *unicode* into a new UCS4 buffer that is allocated using :c:func:`PyMem_Malloc`. If this fails, ``NULL`` is returned with a :exc:`MemoryError` set. The returned buffer always has an extra null code point appended. @@ -683,7 +683,7 @@ The current locale encoding can be used to decode text from the operating system. .. c:function:: PyObject* PyUnicode_DecodeLocaleAndSize(const char *str, \ - Py_ssize_t len, \ + Py_ssize_t length, \ const char *errors) Decode a string from UTF-8 on Android and VxWorks, or from the current @@ -788,7 +788,7 @@ conversion function: Accepts a :term:`path-like object`. -.. c:function:: PyObject* PyUnicode_DecodeFSDefaultAndSize(const char *s, Py_ssize_t size) +.. c:function:: PyObject* PyUnicode_DecodeFSDefaultAndSize(const char *str, Py_ssize_t size) Decode a string from the :term:`filesystem encoding and error handler`. @@ -804,7 +804,7 @@ conversion function: handler>` is now used. -.. c:function:: PyObject* PyUnicode_DecodeFSDefault(const char *s) +.. c:function:: PyObject* PyUnicode_DecodeFSDefault(const char *str) Decode a null-terminated string from the :term:`filesystem encoding and error handler`. @@ -841,17 +841,17 @@ wchar_t Support :c:type:`wchar_t` support for platforms which support it: -.. c:function:: PyObject* PyUnicode_FromWideChar(const wchar_t *w, Py_ssize_t size) +.. c:function:: PyObject* PyUnicode_FromWideChar(const wchar_t *wstr, Py_ssize_t size) - Create a Unicode object from the :c:type:`wchar_t` buffer *w* of the given *size*. + Create a Unicode object from the :c:type:`wchar_t` buffer *wstr* of the given *size*. Passing ``-1`` as the *size* indicates that the function must itself compute the length, - using wcslen. + using :c:func:`!wcslen`. Return ``NULL`` on failure. -.. c:function:: Py_ssize_t PyUnicode_AsWideChar(PyObject *unicode, wchar_t *w, Py_ssize_t size) +.. c:function:: Py_ssize_t PyUnicode_AsWideChar(PyObject *unicode, wchar_t *wstr, Py_ssize_t size) - Copy the Unicode object contents into the :c:type:`wchar_t` buffer *w*. At most + Copy the Unicode object contents into the :c:type:`wchar_t` buffer *wstr*. At most *size* :c:type:`wchar_t` characters are copied (excluding a possibly trailing null termination character). Return the number of :c:type:`wchar_t` characters copied or ``-1`` in case of an error. Note that the resulting :c:expr:`wchar_t*` @@ -915,10 +915,10 @@ Generic Codecs These are the generic codec APIs: -.. c:function:: PyObject* PyUnicode_Decode(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_Decode(const char *str, Py_ssize_t size, \ const char *encoding, const char *errors) - Create a Unicode object by decoding *size* bytes of the encoded string *s*. + Create a Unicode object by decoding *size* bytes of the encoded string *str*. *encoding* and *errors* have the same meaning as the parameters of the same name in the :func:`str` built-in function. The codec to be used is looked up using the Python codec registry. Return ``NULL`` if an exception was raised by @@ -941,13 +941,13 @@ UTF-8 Codecs These are the UTF-8 codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeUTF8(const char *s, Py_ssize_t size, const char *errors) +.. c:function:: PyObject* PyUnicode_DecodeUTF8(const char *str, Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the UTF-8 encoded string - *s*. Return ``NULL`` if an exception was raised by the codec. + *str*. Return ``NULL`` if an exception was raised by the codec. -.. c:function:: PyObject* PyUnicode_DecodeUTF8Stateful(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF8Stateful(const char *str, Py_ssize_t size, \ const char *errors, Py_ssize_t *consumed) If *consumed* is ``NULL``, behave like :c:func:`PyUnicode_DecodeUTF8`. If @@ -1004,7 +1004,7 @@ UTF-32 Codecs These are the UTF-32 codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeUTF32(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF32(const char *str, Py_ssize_t size, \ const char *errors, int *byteorder) Decode *size* bytes from a UTF-32 encoded buffer string and return the @@ -1031,7 +1031,7 @@ These are the UTF-32 codec APIs: Return ``NULL`` if an exception was raised by the codec. -.. c:function:: PyObject* PyUnicode_DecodeUTF32Stateful(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF32Stateful(const char *str, Py_ssize_t size, \ const char *errors, int *byteorder, Py_ssize_t *consumed) If *consumed* is ``NULL``, behave like :c:func:`PyUnicode_DecodeUTF32`. If @@ -1054,7 +1054,7 @@ UTF-16 Codecs These are the UTF-16 codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeUTF16(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF16(const char *str, Py_ssize_t size, \ const char *errors, int *byteorder) Decode *size* bytes from a UTF-16 encoded buffer string and return the @@ -1082,7 +1082,7 @@ These are the UTF-16 codec APIs: Return ``NULL`` if an exception was raised by the codec. -.. c:function:: PyObject* PyUnicode_DecodeUTF16Stateful(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF16Stateful(const char *str, Py_ssize_t size, \ const char *errors, int *byteorder, Py_ssize_t *consumed) If *consumed* is ``NULL``, behave like :c:func:`PyUnicode_DecodeUTF16`. If @@ -1105,13 +1105,13 @@ UTF-7 Codecs These are the UTF-7 codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeUTF7(const char *s, Py_ssize_t size, const char *errors) +.. c:function:: PyObject* PyUnicode_DecodeUTF7(const char *str, Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the UTF-7 encoded string - *s*. Return ``NULL`` if an exception was raised by the codec. + *str*. Return ``NULL`` if an exception was raised by the codec. -.. c:function:: PyObject* PyUnicode_DecodeUTF7Stateful(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeUTF7Stateful(const char *str, Py_ssize_t size, \ const char *errors, Py_ssize_t *consumed) If *consumed* is ``NULL``, behave like :c:func:`PyUnicode_DecodeUTF7`. If @@ -1126,11 +1126,11 @@ Unicode-Escape Codecs These are the "Unicode Escape" codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeUnicodeEscape(const char *s, \ +.. c:function:: PyObject* PyUnicode_DecodeUnicodeEscape(const char *str, \ Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the Unicode-Escape encoded - string *s*. Return ``NULL`` if an exception was raised by the codec. + string *str*. Return ``NULL`` if an exception was raised by the codec. .. c:function:: PyObject* PyUnicode_AsUnicodeEscapeString(PyObject *unicode) @@ -1146,11 +1146,11 @@ Raw-Unicode-Escape Codecs These are the "Raw Unicode Escape" codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeRawUnicodeEscape(const char *s, \ +.. c:function:: PyObject* PyUnicode_DecodeRawUnicodeEscape(const char *str, \ Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the Raw-Unicode-Escape - encoded string *s*. Return ``NULL`` if an exception was raised by the codec. + encoded string *str*. Return ``NULL`` if an exception was raised by the codec. .. c:function:: PyObject* PyUnicode_AsRawUnicodeEscapeString(PyObject *unicode) @@ -1167,10 +1167,10 @@ These are the Latin-1 codec APIs: Latin-1 corresponds to the first 256 Unicode ordinals and only these are accepted by the codecs during encoding. -.. c:function:: PyObject* PyUnicode_DecodeLatin1(const char *s, Py_ssize_t size, const char *errors) +.. c:function:: PyObject* PyUnicode_DecodeLatin1(const char *str, Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the Latin-1 encoded string - *s*. Return ``NULL`` if an exception was raised by the codec. + *str*. Return ``NULL`` if an exception was raised by the codec. .. c:function:: PyObject* PyUnicode_AsLatin1String(PyObject *unicode) @@ -1187,10 +1187,10 @@ These are the ASCII codec APIs. Only 7-bit ASCII data is accepted. All other codes generate errors. -.. c:function:: PyObject* PyUnicode_DecodeASCII(const char *s, Py_ssize_t size, const char *errors) +.. c:function:: PyObject* PyUnicode_DecodeASCII(const char *str, Py_ssize_t size, const char *errors) Create a Unicode object by decoding *size* bytes of the ASCII encoded string - *s*. Return ``NULL`` if an exception was raised by the codec. + *str*. Return ``NULL`` if an exception was raised by the codec. .. c:function:: PyObject* PyUnicode_AsASCIIString(PyObject *unicode) @@ -1211,10 +1211,10 @@ decode characters. The mapping objects provided must support the These are the mapping codec APIs: -.. c:function:: PyObject* PyUnicode_DecodeCharmap(const char *data, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeCharmap(const char *str, Py_ssize_t length, \ PyObject *mapping, const char *errors) - Create a Unicode object by decoding *size* bytes of the encoded string *s* + Create a Unicode object by decoding *size* bytes of the encoded string *str* using the given *mapping* object. Return ``NULL`` if an exception was raised by the codec. @@ -1241,7 +1241,7 @@ These are the mapping codec APIs: The following codec API is special in that maps Unicode to Unicode. -.. c:function:: PyObject* PyUnicode_Translate(PyObject *str, PyObject *table, const char *errors) +.. c:function:: PyObject* PyUnicode_Translate(PyObject *unicode, PyObject *table, const char *errors) Translate a string by applying a character mapping table to it and return the resulting Unicode object. Return ``NULL`` if an exception was raised by the @@ -1266,13 +1266,13 @@ use the Win32 MBCS converters to implement the conversions. Note that MBCS (or DBCS) is a class of encodings, not just one. The target encoding is defined by the user settings on the machine running the codec. -.. c:function:: PyObject* PyUnicode_DecodeMBCS(const char *s, Py_ssize_t size, const char *errors) +.. c:function:: PyObject* PyUnicode_DecodeMBCS(const char *str, Py_ssize_t size, const char *errors) - Create a Unicode object by decoding *size* bytes of the MBCS encoded string *s*. + Create a Unicode object by decoding *size* bytes of the MBCS encoded string *str*. Return ``NULL`` if an exception was raised by the codec. -.. c:function:: PyObject* PyUnicode_DecodeMBCSStateful(const char *s, Py_ssize_t size, \ +.. c:function:: PyObject* PyUnicode_DecodeMBCSStateful(const char *str, Py_ssize_t size, \ const char *errors, Py_ssize_t *consumed) If *consumed* is ``NULL``, behave like :c:func:`PyUnicode_DecodeMBCS`. If @@ -1318,7 +1318,7 @@ They all return ``NULL`` or ``-1`` if an exception occurs. Concat two strings giving a new Unicode string. -.. c:function:: PyObject* PyUnicode_Split(PyObject *s, PyObject *sep, Py_ssize_t maxsplit) +.. c:function:: PyObject* PyUnicode_Split(PyObject *unicode, PyObject *sep, Py_ssize_t maxsplit) Split a string giving a list of Unicode strings. If *sep* is ``NULL``, splitting will be done at all whitespace substrings. Otherwise, splits occur at the given @@ -1326,10 +1326,10 @@ They all return ``NULL`` or ``-1`` if an exception occurs. set. Separators are not included in the resulting list. -.. c:function:: PyObject* PyUnicode_Splitlines(PyObject *s, int keepend) +.. c:function:: PyObject* PyUnicode_Splitlines(PyObject *unicode, int keepends) Split a Unicode string at line breaks, returning a list of Unicode strings. - CRLF is considered to be one line break. If *keepend* is ``0``, the line break + CRLF is considered to be one line break. If *keepends* is ``0``, the Line break characters are not included in the resulting strings. @@ -1339,28 +1339,28 @@ They all return ``NULL`` or ``-1`` if an exception occurs. Unicode string. -.. c:function:: Py_ssize_t PyUnicode_Tailmatch(PyObject *str, PyObject *substr, \ +.. c:function:: Py_ssize_t PyUnicode_Tailmatch(PyObject *unicode, PyObject *substr, \ Py_ssize_t start, Py_ssize_t end, int direction) - Return ``1`` if *substr* matches ``str[start:end]`` at the given tail end + Return ``1`` if *substr* matches ``unicode[start:end]`` at the given tail end (*direction* == ``-1`` means to do a prefix match, *direction* == ``1`` a suffix match), ``0`` otherwise. Return ``-1`` if an error occurred. -.. c:function:: Py_ssize_t PyUnicode_Find(PyObject *str, PyObject *substr, \ +.. c:function:: Py_ssize_t PyUnicode_Find(PyObject *unicode, PyObject *substr, \ Py_ssize_t start, Py_ssize_t end, int direction) - Return the first position of *substr* in ``str[start:end]`` using the given + Return the first position of *substr* in ``unicode[start:end]`` using the given *direction* (*direction* == ``1`` means to do a forward search, *direction* == ``-1`` a backward search). The return value is the index of the first match; a value of ``-1`` indicates that no match was found, and ``-2`` indicates that an error occurred and an exception has been set. -.. c:function:: Py_ssize_t PyUnicode_FindChar(PyObject *str, Py_UCS4 ch, \ +.. c:function:: Py_ssize_t PyUnicode_FindChar(PyObject *unicode, Py_UCS4 ch, \ Py_ssize_t start, Py_ssize_t end, int direction) - Return the first position of the character *ch* in ``str[start:end]`` using + Return the first position of the character *ch* in ``unicode[start:end]`` using the given *direction* (*direction* == ``1`` means to do a forward search, *direction* == ``-1`` a backward search). The return value is the index of the first match; a value of ``-1`` indicates that no match was found, and ``-2`` @@ -1369,20 +1369,20 @@ They all return ``NULL`` or ``-1`` if an exception occurs. .. versionadded:: 3.3 .. versionchanged:: 3.7 - *start* and *end* are now adjusted to behave like ``str[start:end]``. + *start* and *end* are now adjusted to behave like ``unicode[start:end]``. -.. c:function:: Py_ssize_t PyUnicode_Count(PyObject *str, PyObject *substr, \ +.. c:function:: Py_ssize_t PyUnicode_Count(PyObject *unicode, PyObject *substr, \ Py_ssize_t start, Py_ssize_t end) Return the number of non-overlapping occurrences of *substr* in - ``str[start:end]``. Return ``-1`` if an error occurred. + ``unicode[start:end]``. Return ``-1`` if an error occurred. -.. c:function:: PyObject* PyUnicode_Replace(PyObject *str, PyObject *substr, \ +.. c:function:: PyObject* PyUnicode_Replace(PyObject *unicode, PyObject *substr, \ PyObject *replstr, Py_ssize_t maxcount) - Replace at most *maxcount* occurrences of *substr* in *str* with *replstr* and + Replace at most *maxcount* occurrences of *substr* in *unicode* with *replstr* and return the resulting Unicode object. *maxcount* == ``-1`` means replace all occurrences. @@ -1418,9 +1418,9 @@ They all return ``NULL`` or ``-1`` if an exception occurs. .. versionadded:: 3.13 -.. c:function:: int PyUnicode_CompareWithASCIIString(PyObject *uni, const char *string) +.. c:function:: int PyUnicode_CompareWithASCIIString(PyObject *unicode, const char *string) - Compare a Unicode object, *uni*, with *string* and return ``-1``, ``0``, ``1`` for less + Compare a Unicode object, *unicode*, with *string* and return ``-1``, ``0``, ``1`` for less than, equal, and greater than, respectively. It is best to pass only ASCII-encoded strings, but the function interprets the input string as ISO-8859-1 if it contains non-ASCII characters. @@ -1428,7 +1428,7 @@ They all return ``NULL`` or ``-1`` if an exception occurs. This function does not raise exceptions. -.. c:function:: PyObject* PyUnicode_RichCompare(PyObject *left, PyObject *right, int op) +.. c:function:: PyObject* PyUnicode_RichCompare(PyObject *left, PyObject *right, int op) Rich compare two Unicode strings and return one of the following: @@ -1446,29 +1446,29 @@ They all return ``NULL`` or ``-1`` if an exception occurs. ``format % args``. -.. c:function:: int PyUnicode_Contains(PyObject *container, PyObject *element) +.. c:function:: int PyUnicode_Contains(PyObject *unicode, PyObject *substr) - Check whether *element* is contained in *container* and return true or false + Check whether *substr* is contained in *unicode* and return true or false accordingly. - *element* has to coerce to a one element Unicode string. ``-1`` is returned + *substr* has to coerce to a one element Unicode string. ``-1`` is returned if there was an error. -.. c:function:: void PyUnicode_InternInPlace(PyObject **string) +.. c:function:: void PyUnicode_InternInPlace(PyObject **p_unicode) - Intern the argument *\*string* in place. The argument must be the address of a + Intern the argument :c:expr:`*p_unicode` in place. The argument must be the address of a pointer variable pointing to a Python Unicode string object. If there is an - existing interned string that is the same as *\*string*, it sets *\*string* to + existing interned string that is the same as :c:expr:`*p_unicode`, it sets :c:expr:`*p_unicode` to it (releasing the reference to the old string object and creating a new :term:`strong reference` to the interned string object), otherwise it leaves - *\*string* alone and interns it (creating a new :term:`strong reference`). + :c:expr:`*p_unicode` alone and interns it (creating a new :term:`strong reference`). (Clarification: even though there is a lot of talk about references, think of this function as reference-neutral; you own the object after the call if and only if you owned it before the call.) -.. c:function:: PyObject* PyUnicode_InternFromString(const char *v) +.. c:function:: PyObject* PyUnicode_InternFromString(const char *str) A combination of :c:func:`PyUnicode_FromString` and :c:func:`PyUnicode_InternInPlace`, returning either a new Unicode string From 268415bbb32b1fafccae3d542c43d487b6f0f48d Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher <49998481+websurfer5@users.noreply.github.com> Date: Tue, 5 Dec 2023 01:33:51 -0800 Subject: [PATCH 112/442] gh-81441: shutil.rmtree() FileNotFoundError race condition (GH-14064) Ignore missing files and directories while enumerating directory entries in shutil.rmtree(). Co-authored-by: Serhiy Storchaka --- Doc/library/shutil.rst | 4 ++ Lib/shutil.py | 45 +++++++++++---- Lib/test/test_shutil.py | 57 +++++++++++++++++++ .../2019-06-14-22-37-32.bpo-37260.oecdIf.rst | 2 + 4 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-06-14-22-37-32.bpo-37260.oecdIf.rst diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index d1949d698f5614..d30d289710b129 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -343,6 +343,10 @@ Directory and files operations .. versionchanged:: 3.12 Added the *onexc* parameter, deprecated *onerror*. + .. versionchanged:: 3.13 + :func:`!rmtree` now ignores :exc:`FileNotFoundError` exceptions for all + but the top-level path. + .. attribute:: rmtree.avoids_symlink_attacks Indicates whether the current platform and implementation provides a diff --git a/Lib/shutil.py b/Lib/shutil.py index dd93872e83c9e2..93b00d73a0fd46 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -590,23 +590,21 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, dirs_exist_ok=dirs_exist_ok) if hasattr(os.stat_result, 'st_file_attributes'): - def _rmtree_islink(path): - try: - st = os.lstat(path) - return (stat.S_ISLNK(st.st_mode) or - (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT - and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) - except OSError: - return False + def _rmtree_islink(st): + return (stat.S_ISLNK(st.st_mode) or + (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT + and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) else: - def _rmtree_islink(path): - return os.path.islink(path) + def _rmtree_islink(st): + return stat.S_ISLNK(st.st_mode) # version vulnerable to race conditions def _rmtree_unsafe(path, onexc): try: with os.scandir(path) as scandir_it: entries = list(scandir_it) + except FileNotFoundError: + return except OSError as err: onexc(os.scandir, path, err) entries = [] @@ -614,6 +612,8 @@ def _rmtree_unsafe(path, onexc): fullname = entry.path try: is_dir = entry.is_dir(follow_symlinks=False) + except FileNotFoundError: + continue except OSError: is_dir = False @@ -624,6 +624,8 @@ def _rmtree_unsafe(path, onexc): # a directory with a symlink after the call to # os.scandir or entry.is_dir above. raise OSError("Cannot call rmtree on a symbolic link") + except FileNotFoundError: + continue except OSError as err: onexc(os.path.islink, fullname, err) continue @@ -631,10 +633,14 @@ def _rmtree_unsafe(path, onexc): else: try: os.unlink(fullname) + except FileNotFoundError: + continue except OSError as err: onexc(os.unlink, fullname, err) try: os.rmdir(path) + except FileNotFoundError: + pass except OSError as err: onexc(os.rmdir, path, err) @@ -643,6 +649,8 @@ def _rmtree_safe_fd(topfd, path, onexc): try: with os.scandir(topfd) as scandir_it: entries = list(scandir_it) + except FileNotFoundError: + return except OSError as err: err.filename = path onexc(os.scandir, path, err) @@ -651,6 +659,8 @@ def _rmtree_safe_fd(topfd, path, onexc): fullname = os.path.join(path, entry.name) try: is_dir = entry.is_dir(follow_symlinks=False) + except FileNotFoundError: + continue except OSError: is_dir = False else: @@ -658,6 +668,8 @@ def _rmtree_safe_fd(topfd, path, onexc): try: orig_st = entry.stat(follow_symlinks=False) is_dir = stat.S_ISDIR(orig_st.st_mode) + except FileNotFoundError: + continue except OSError as err: onexc(os.lstat, fullname, err) continue @@ -665,6 +677,8 @@ def _rmtree_safe_fd(topfd, path, onexc): try: dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd) dirfd_closed = False + except FileNotFoundError: + continue except OSError as err: onexc(os.open, fullname, err) else: @@ -675,6 +689,8 @@ def _rmtree_safe_fd(topfd, path, onexc): os.close(dirfd) dirfd_closed = True os.rmdir(entry.name, dir_fd=topfd) + except FileNotFoundError: + continue except OSError as err: onexc(os.rmdir, fullname, err) else: @@ -692,6 +708,8 @@ def _rmtree_safe_fd(topfd, path, onexc): else: try: os.unlink(entry.name, dir_fd=topfd) + except FileNotFoundError: + continue except OSError as err: onexc(os.unlink, fullname, err) @@ -781,7 +799,12 @@ def onexc(*args): if dir_fd is not None: raise NotImplementedError("dir_fd unavailable on this platform") try: - if _rmtree_islink(path): + st = os.lstat(path) + except OSError as err: + onexc(os.lstat, path, err) + return + try: + if _rmtree_islink(st): # symlinks to directories are forbidden, see bug #1669 raise OSError("Cannot call rmtree on a symbolic link") except OSError as err: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index ae6c6814fcc3ec..7ea2496230da47 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -633,6 +633,63 @@ def test_rmtree_on_junction(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) + @unittest.skipIf(sys.platform[:6] == 'cygwin', + "This test can't be run on Cygwin (issue #1071513).") + @os_helper.skip_if_dac_override + @os_helper.skip_unless_working_chmod + def test_rmtree_deleted_race_condition(self): + # bpo-37260 + # + # Test that a file or a directory deleted after it is enumerated + # by scandir() but before unlink() or rmdr() is called doesn't + # generate any errors. + def _onexc(fn, path, exc): + assert fn in (os.rmdir, os.unlink) + if not isinstance(exc, PermissionError): + raise + # Make the parent and the children writeable. + for p, mode in zip(paths, old_modes): + os.chmod(p, mode) + # Remove other dirs except one. + keep = next(p for p in dirs if p != path) + for p in dirs: + if p != keep: + os.rmdir(p) + # Remove other files except one. + keep = next(p for p in files if p != path) + for p in files: + if p != keep: + os.unlink(p) + + os.mkdir(TESTFN) + paths = [TESTFN] + [os.path.join(TESTFN, f'child{i}') + for i in range(6)] + dirs = paths[1::2] + files = paths[2::2] + for path in dirs: + os.mkdir(path) + for path in files: + write_file(path, '') + + old_modes = [os.stat(path).st_mode for path in paths] + + # Make the parent and the children non-writeable. + new_mode = stat.S_IREAD|stat.S_IEXEC + for path in reversed(paths): + os.chmod(path, new_mode) + + try: + shutil.rmtree(TESTFN, onexc=_onexc) + except: + # Test failed, so cleanup artifacts. + for path, mode in zip(paths, old_modes): + try: + os.chmod(path, mode) + except OSError: + pass + shutil.rmtree(TESTFN) + raise + class TestCopyTree(BaseTest, unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2019-06-14-22-37-32.bpo-37260.oecdIf.rst b/Misc/NEWS.d/next/Library/2019-06-14-22-37-32.bpo-37260.oecdIf.rst new file mode 100644 index 00000000000000..a5f2c5e8e18919 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-14-22-37-32.bpo-37260.oecdIf.rst @@ -0,0 +1,2 @@ +Fixed a race condition in :func:`shutil.rmtree` in which directory entries removed by another process or thread while ``shutil.rmtree()`` is running can cause it to raise FileNotFoundError. Patch by Jeffrey Kintscher. + From 2f20cafdbfc39925f9374f36f9d53bac365ed32a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 5 Dec 2023 09:59:52 +0000 Subject: [PATCH 113/442] gh-101100: Fix many easily solvable Sphinx nitpicks in the datamodel docs (#112737) --- Doc/library/exceptions.rst | 8 +++-- Doc/reference/datamodel.rst | 58 ++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index cd85df8723a76b..b67215b8b3a362 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -429,9 +429,11 @@ The following exceptions are the exceptions that are usually raised. :meth:`~iterator.__next__` method to signal that there are no further items produced by the iterator. - The exception object has a single attribute :attr:`value`, which is - given as an argument when constructing the exception, and defaults - to :const:`None`. + .. attribute:: StopIteration.value + + The exception object has a single attribute :attr:`!value`, which is + given as an argument when constructing the exception, and defaults + to :const:`None`. When a :term:`generator` or :term:`coroutine` function returns, a new :exc:`StopIteration` instance is diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 29298b79ef06dd..06e61393fccc24 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -88,7 +88,7 @@ Some objects contain references to "external" resources such as open files or windows. It is understood that these resources are freed when the object is garbage-collected, but since garbage collection is not guaranteed to happen, such objects also provide an explicit way to release the external resource, -usually a :meth:`close` method. Programs are strongly recommended to explicitly +usually a :meth:`!close` method. Programs are strongly recommended to explicitly close such objects. The ':keyword:`try`...\ :keyword:`finally`' statement and the ':keyword:`with`' statement provide convenient ways to do this. @@ -681,8 +681,8 @@ underlying the class method. When an instance method object is called, the underlying function (:attr:`__func__`) is called, inserting the class instance (:attr:`__self__`) in front of the argument list. For instance, when -:class:`C` is a class which contains a definition for a function -:meth:`f`, and ``x`` is an instance of :class:`C`, calling ``x.f(1)`` is +:class:`!C` is a class which contains a definition for a function +:meth:`!f`, and ``x`` is an instance of :class:`!C`, calling ``x.f(1)`` is equivalent to calling ``C.f(x, 1)``. When an instance method object is derived from a class method object, the @@ -795,7 +795,7 @@ Classes Classes are callable. These objects normally act as factories for new instances of themselves, but variations are possible for class types that override :meth:`~object.__new__`. The arguments of the call are passed to -:meth:`__new__` and, in the typical case, to :meth:`~object.__init__` to +:meth:`!__new__` and, in the typical case, to :meth:`~object.__init__` to initialize the new instance. @@ -899,9 +899,9 @@ https://www.python.org/download/releases/2.3/mro/. pair: object; dictionary pair: class; attribute -When a class attribute reference (for class :class:`C`, say) would yield a +When a class attribute reference (for class :class:`!C`, say) would yield a class method object, it is transformed into an instance method object whose -:attr:`__self__` attribute is :class:`C`. When it would yield a static +:attr:`__self__` attribute is :class:`!C`. When it would yield a static method object, it is transformed into the object wrapped by the static method object. See section :ref:`descriptors` for another way in which attributes retrieved from a class may differ from those actually contained in its @@ -1903,13 +1903,17 @@ class' :attr:`~object.__dict__`. Called to delete the attribute on an instance *instance* of the owner class. +Instances of descriptors may also have the :attr:`!__objclass__` attribute +present: -The attribute :attr:`__objclass__` is interpreted by the :mod:`inspect` module -as specifying the class where this object was defined (setting this -appropriately can assist in runtime introspection of dynamic class attributes). -For callables, it may indicate that an instance of the given type (or a -subclass) is expected or required as the first positional argument (for example, -CPython sets this attribute for unbound methods that are implemented in C). +.. attribute:: object.__objclass__ + + The attribute :attr:`!__objclass__` is interpreted by the :mod:`inspect` module + as specifying the class where this object was defined (setting this + appropriately can assist in runtime introspection of dynamic class attributes). + For callables, it may indicate that an instance of the given type (or a + subclass) is expected or required as the first positional argument (for example, + CPython sets this attribute for unbound methods that are implemented in C). .. _descriptor-invocation: @@ -1990,13 +1994,14 @@ For instance bindings, the precedence of descriptor invocation depends on which descriptor methods are defined. A descriptor can define any combination of :meth:`~object.__get__`, :meth:`~object.__set__` and :meth:`~object.__delete__`. If it does not -define :meth:`__get__`, then accessing the attribute will return the descriptor +define :meth:`!__get__`, then accessing the attribute will return the descriptor object itself unless there is a value in the object's instance dictionary. If -the descriptor defines :meth:`__set__` and/or :meth:`__delete__`, it is a data +the descriptor defines :meth:`!__set__` and/or :meth:`!__delete__`, it is a data descriptor; if it defines neither, it is a non-data descriptor. Normally, data -descriptors define both :meth:`__get__` and :meth:`__set__`, while non-data -descriptors have just the :meth:`__get__` method. Data descriptors with -:meth:`__get__` and :meth:`__set__` (and/or :meth:`__delete__`) defined always override a redefinition in an +descriptors define both :meth:`!__get__` and :meth:`!__set__`, while non-data +descriptors have just the :meth:`!__get__` method. Data descriptors with +:meth:`!__get__` and :meth:`!__set__` (and/or :meth:`!__delete__`) defined +always override a redefinition in an instance dictionary. In contrast, non-data descriptors can be overridden by instances. @@ -2573,16 +2578,17 @@ either to emulate a sequence or to emulate a mapping; the difference is that for a sequence, the allowable keys should be the integers *k* for which ``0 <= k < N`` where *N* is the length of the sequence, or :class:`slice` objects, which define a range of items. It is also recommended that mappings provide the methods -:meth:`keys`, :meth:`values`, :meth:`items`, :meth:`get`, :meth:`clear`, -:meth:`setdefault`, :meth:`pop`, :meth:`popitem`, :meth:`!copy`, and -:meth:`update` behaving similar to those for Python's standard :class:`dictionary ` +:meth:`!keys`, :meth:`!values`, :meth:`!items`, :meth:`!get`, :meth:`!clear`, +:meth:`!setdefault`, :meth:`!pop`, :meth:`!popitem`, :meth:`!copy`, and +:meth:`!update` behaving similar to those for Python's standard :class:`dictionary ` objects. The :mod:`collections.abc` module provides a :class:`~collections.abc.MutableMapping` :term:`abstract base class` to help create those methods from a base set of -:meth:`~object.__getitem__`, :meth:`~object.__setitem__`, :meth:`~object.__delitem__`, and :meth:`keys`. -Mutable sequences should provide methods :meth:`append`, :meth:`count`, -:meth:`index`, :meth:`extend`, :meth:`insert`, :meth:`pop`, :meth:`remove`, -:meth:`reverse` and :meth:`sort`, like Python standard :class:`list` +:meth:`~object.__getitem__`, :meth:`~object.__setitem__`, +:meth:`~object.__delitem__`, and :meth:`!keys`. +Mutable sequences should provide methods :meth:`!append`, :meth:`!count`, +:meth:`!index`, :meth:`!extend`, :meth:`!insert`, :meth:`!pop`, :meth:`!remove`, +:meth:`!reverse` and :meth:`!sort`, like Python standard :class:`list` objects. Finally, sequence types should implement addition (meaning concatenation) and multiplication (meaning repetition) by defining the methods @@ -2595,7 +2601,7 @@ operator; for mappings, ``in`` should search the mapping's keys; for sequences, it should search through the values. It is further recommended that both mappings and sequences implement the :meth:`~object.__iter__` method to allow efficient iteration -through the container; for mappings, :meth:`__iter__` should iterate +through the container; for mappings, :meth:`!__iter__` should iterate through the object's keys; for sequences, it should iterate through the values. .. method:: object.__len__(self) @@ -3174,7 +3180,7 @@ generators, coroutines do not directly support iteration. to the :meth:`~generator.send` method of the iterator that caused the coroutine to suspend. The result (return value, :exc:`StopIteration`, or other exception) is the same as when - iterating over the :meth:`__await__` return value, described above. + iterating over the :meth:`!__await__` return value, described above. .. method:: coroutine.throw(value) coroutine.throw(type[, value[, traceback]]) From 5aa317e4ca619c3735e1d67b507f01a8e49a4c49 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 5 Dec 2023 10:44:19 +0000 Subject: [PATCH 114/442] gh-112535: Add comment for ppc32/64 registers (gh-112746) --- Include/object.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Include/object.h b/Include/object.h index dfeb43bda7d841..85abd30b5ad7d6 100644 --- a/Include/object.h +++ b/Include/object.h @@ -265,6 +265,7 @@ _Py_ThreadId(void) #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) tid = (uintptr_t)__builtin_thread_pointer(); #else + // r13 is reserved for use as system thread ID by the Power 64-bit ABI. register uintptr_t tp __asm__ ("r13"); __asm__("" : "=r" (tp)); tid = tp; @@ -273,6 +274,7 @@ _Py_ThreadId(void) #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) tid = (uintptr_t)__builtin_thread_pointer(); #else + // r2 is reserved for use as system thread ID by the Power 32-bit ABI. register uintptr_t tp __asm__ ("r2"); __asm__ ("" : "=r" (tp)); tid = tp; From 8cdfee1bb902fd1e38d79170b751ef13a0907262 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Tue, 5 Dec 2023 21:30:59 +0800 Subject: [PATCH 115/442] bpo-43153: Don't mask `PermissionError` with `NotADirectoryError` during tempdirectory cleanup (GH-29940) Co-authored-by: andrei kulakov Co-authored-by: Serhiy Storchaka --- Lib/tempfile.py | 28 +++++++++++++++++-- Lib/test/test_tempfile.py | 11 ++++++++ .../2021-12-06-22-10-53.bpo-43153.J7mjSy.rst | 4 +++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 2b4f4313247128..55403ad1faf46d 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -41,6 +41,7 @@ import io as _io import os as _os import shutil as _shutil +import stat as _stat import errno as _errno from random import Random as _Random import sys as _sys @@ -889,8 +890,31 @@ def resetperms(path): try: _os.unlink(path) - # PermissionError is raised on FreeBSD for directories - except (IsADirectoryError, PermissionError): + except IsADirectoryError: + cls._rmtree(path, ignore_errors=ignore_errors) + except PermissionError: + # The PermissionError handler was originally added for + # FreeBSD in directories, but it seems that it is raised + # on Windows too. + # bpo-43153: Calling _rmtree again may + # raise NotADirectoryError and mask the PermissionError. + # So we must re-raise the current PermissionError if + # path is not a directory. + try: + st = _os.lstat(path) + except OSError: + if ignore_errors: + return + raise + if (_stat.S_ISLNK(st.st_mode) or + not _stat.S_ISDIR(st.st_mode) or + (hasattr(st, 'st_file_attributes') and + st.st_file_attributes & _stat.FILE_ATTRIBUTE_REPARSE_POINT and + st.st_reparse_tag == _stat.IO_REPARSE_TAG_MOUNT_POINT) + ): + if ignore_errors: + return + raise cls._rmtree(path, ignore_errors=ignore_errors) except FileNotFoundError: pass diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 1673507e2f7c91..f4aef887799ed4 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1641,6 +1641,17 @@ def test_explicit_cleanup_ignore_errors(self): temp_path.exists(), f"TemporaryDirectory {temp_path!s} exists after cleanup") + @unittest.skipUnless(os.name == "nt", "Only on Windows.") + def test_explicit_cleanup_correct_error(self): + with tempfile.TemporaryDirectory() as working_dir: + temp_dir = self.do_create(dir=working_dir) + with open(os.path.join(temp_dir.name, "example.txt"), 'wb'): + # Previously raised NotADirectoryError on some OSes + # (e.g. Windows). See bpo-43153. + with self.assertRaises(PermissionError): + temp_dir.cleanup() + + @os_helper.skip_unless_symlink def test_cleanup_with_symlink_to_a_directory(self): # cleanup() should not follow symlinks to directories (issue #12464) diff --git a/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst b/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst new file mode 100644 index 00000000000000..7800e0a4869adf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-12-06-22-10-53.bpo-43153.J7mjSy.rst @@ -0,0 +1,4 @@ +On Windows, ``tempfile.TemporaryDirectory`` previously masked a +``PermissionError`` with ``NotADirectoryError`` during directory cleanup. It +now correctly raises ``PermissionError`` if errors are not ignored. Patch by +Andrei Kulakov and Ken Jin. From e7e1116a781434763c309b55a31204a98237f7b4 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 5 Dec 2023 05:52:28 -0900 Subject: [PATCH 116/442] gh-105323: Remove `WITH_APPLE_EDITLINE` to use the same declaration for all editline (gh-112513) --- Modules/readline.c | 8 ++------ configure | 17 ----------------- configure.ac | 10 ---------- pyconfig.h.in | 3 --- 4 files changed, 2 insertions(+), 36 deletions(-) diff --git a/Modules/readline.c b/Modules/readline.c index afbb7f8f0ec18f..e29051c37f8827 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1043,10 +1043,8 @@ on_hook(PyObject *func) static int #if defined(_RL_FUNCTION_TYPEDEF) on_startup_hook(void) -#elif defined(WITH_APPLE_EDITLINE) -on_startup_hook(const char *Py_UNUSED(text), int Py_UNUSED(state)) #else -on_startup_hook(void) +on_startup_hook(const char *Py_UNUSED(text), int Py_UNUSED(state)) #endif { int r; @@ -1065,10 +1063,8 @@ on_startup_hook(void) static int #if defined(_RL_FUNCTION_TYPEDEF) on_pre_input_hook(void) -#elif defined(WITH_APPLE_EDITLINE) -on_pre_input_hook(const char *Py_UNUSED(text), int Py_UNUSED(state)) #else -on_pre_input_hook(void) +on_pre_input_hook(const char *Py_UNUSED(text), int Py_UNUSED(state)) #endif { int r; diff --git a/configure b/configure index 319009537f461c..8894122a11e86e 100755 --- a/configure +++ b/configure @@ -23971,7 +23971,6 @@ fi - # Check whether --with-readline was given. if test ${with_readline+y} then : @@ -23994,22 +23993,6 @@ else $as_nop fi -# gh-105323: Need to handle the macOS editline as an alias of readline. -case $ac_sys_system/$ac_sys_release in #( - Darwin/*) : - ac_fn_c_check_type "$LINENO" "Function" "ac_cv_type_Function" "#include -" -if test "x$ac_cv_type_Function" = xyes -then : - printf "%s\n" "#define WITH_APPLE_EDITLINE 1" >>confdefs.h - -fi - ;; #( - *) : - - ;; -esac - if test "x$with_readline" = xreadline then : diff --git a/configure.ac b/configure.ac index b78472e04846b7..1512e6d9e8c42a 100644 --- a/configure.ac +++ b/configure.ac @@ -5914,7 +5914,6 @@ dnl library (tinfo ncursesw ncurses termcap). We now assume that libreadline dnl or readline.pc provide correct linker information. AH_TEMPLATE([WITH_EDITLINE], [Define to build the readline module against libedit.]) -AH_TEMPLATE([WITH_APPLE_EDITLINE], [Define to build the readline module against Apple BSD editline.]) AC_ARG_WITH( [readline], @@ -5931,15 +5930,6 @@ AC_ARG_WITH( [with_readline=readline] ) -# gh-105323: Need to handle the macOS editline as an alias of readline. -AS_CASE([$ac_sys_system/$ac_sys_release], - [Darwin/*], [AC_CHECK_TYPE([Function], - [AC_DEFINE([WITH_APPLE_EDITLINE])], - [], - [@%:@include ])], - [] -) - AS_VAR_IF([with_readline], [readline], [ PKG_CHECK_MODULES([LIBREADLINE], [readline], [ LIBREADLINE=readline diff --git a/pyconfig.h.in b/pyconfig.h.in index bf708926e22c43..2978fa2c17301f 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1800,9 +1800,6 @@ /* Define if WINDOW in curses.h offers a field _flags. */ #undef WINDOW_HAS_FLAGS -/* Define to build the readline module against Apple BSD editline. */ -#undef WITH_APPLE_EDITLINE - /* Define if you want build the _decimal module using a coroutine-local rather than a thread-local context */ #undef WITH_DECIMAL_CONTEXTVAR From bc68f4a4abcfbea60bb1db1ccadb07613561931c Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Tue, 5 Dec 2023 15:07:50 +0000 Subject: [PATCH 117/442] gh-110190: Fix ctypes structs with array on Arm (#112604) Set MAX_STRUCT_SIZE to 32 in stgdict.c when on Arm platforms. This because on Arm platforms structs with at most 4 elements of any floating point type values can be passed through registers. If the type is double the maximum size of the struct is 32 bytes. On x86-64 Linux, it's maximum 16 bytes hence we need to differentiate. --- Lib/test/test_ctypes/test_structures.py | 123 +++++++++++++++++- ...-12-01-18-05-09.gh-issue-110190.5bf-c9.rst | 1 + Modules/_ctypes/_ctypes_test.c | 36 +++++ Modules/_ctypes/stgdict.c | 53 +++++--- 4 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-01-18-05-09.gh-issue-110190.5bf-c9.rst diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py index f05ee5e491a41e..771205ffb32ecc 100644 --- a/Lib/test/test_ctypes/test_structures.py +++ b/Lib/test/test_ctypes/test_structures.py @@ -2,7 +2,7 @@ import struct import sys import unittest -from ctypes import (CDLL, Structure, Union, POINTER, sizeof, byref, alignment, +from ctypes import (CDLL, Array, Structure, Union, POINTER, sizeof, byref, alignment, c_void_p, c_char, c_wchar, c_byte, c_ubyte, c_uint8, c_uint16, c_uint32, c_short, c_ushort, c_int, c_uint, @@ -494,12 +494,59 @@ class Test3B(Test3A): ('more_data', c_float * 2), ] + class Test3C1(Structure): + _fields_ = [ + ("data", c_double * 4) + ] + + class DataType4(Array): + _type_ = c_double + _length_ = 4 + + class Test3C2(Structure): + _fields_ = [ + ("data", DataType4) + ] + + class Test3C3(Structure): + _fields_ = [ + ("x", c_double), + ("y", c_double), + ("z", c_double), + ("t", c_double) + ] + + class Test3D1(Structure): + _fields_ = [ + ("data", c_double * 5) + ] + + class DataType5(Array): + _type_ = c_double + _length_ = 5 + + class Test3D2(Structure): + _fields_ = [ + ("data", DataType5) + ] + + class Test3D3(Structure): + _fields_ = [ + ("x", c_double), + ("y", c_double), + ("z", c_double), + ("t", c_double), + ("u", c_double) + ] + + # Load the shared library + dll = CDLL(_ctypes_test.__file__) + s = Test2() expected = 0 for i in range(16): s.data[i] = i expected += i - dll = CDLL(_ctypes_test.__file__) func = dll._testfunc_array_in_struct1 func.restype = c_int func.argtypes = (Test2,) @@ -540,6 +587,78 @@ class Test3B(Test3A): self.assertAlmostEqual(s.more_data[0], -3.0, places=6) self.assertAlmostEqual(s.more_data[1], -2.0, places=6) + # Tests for struct Test3C + expected = (1.0, 2.0, 3.0, 4.0) + func = dll._testfunc_array_in_struct_set_defaults_3C + func.restype = Test3C1 + result = func() + # check the default values have been set properly + self.assertEqual( + (result.data[0], + result.data[1], + result.data[2], + result.data[3]), + expected + ) + + func = dll._testfunc_array_in_struct_set_defaults_3C + func.restype = Test3C2 + result = func() + # check the default values have been set properly + self.assertEqual( + (result.data[0], + result.data[1], + result.data[2], + result.data[3]), + expected + ) + + func = dll._testfunc_array_in_struct_set_defaults_3C + func.restype = Test3C3 + result = func() + # check the default values have been set properly + self.assertEqual((result.x, result.y, result.z, result.t), expected) + + # Tests for struct Test3D + expected = (1.0, 2.0, 3.0, 4.0, 5.0) + func = dll._testfunc_array_in_struct_set_defaults_3D + func.restype = Test3D1 + result = func() + # check the default values have been set properly + self.assertEqual( + (result.data[0], + result.data[1], + result.data[2], + result.data[3], + result.data[4]), + expected + ) + + func = dll._testfunc_array_in_struct_set_defaults_3D + func.restype = Test3D2 + result = func() + # check the default values have been set properly + self.assertEqual( + (result.data[0], + result.data[1], + result.data[2], + result.data[3], + result.data[4]), + expected + ) + + func = dll._testfunc_array_in_struct_set_defaults_3D + func.restype = Test3D3 + result = func() + # check the default values have been set properly + self.assertEqual( + (result.x, + result.y, + result.z, + result.t, + result.u), + expected) + def test_38368(self): class U(Union): _fields_ = [ diff --git a/Misc/NEWS.d/next/Library/2023-12-01-18-05-09.gh-issue-110190.5bf-c9.rst b/Misc/NEWS.d/next/Library/2023-12-01-18-05-09.gh-issue-110190.5bf-c9.rst new file mode 100644 index 00000000000000..730b9d49119805 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-01-18-05-09.gh-issue-110190.5bf-c9.rst @@ -0,0 +1 @@ +Fix ctypes structs with array on Arm platform by setting ``MAX_STRUCT_SIZE`` to 32 in stgdict. Patch by Diego Russo. diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index d33e6fc7586d28..fc9fc131f6249a 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -150,6 +150,42 @@ _testfunc_array_in_struct2a(Test3B in) return result; } +/* + * See gh-110190. structs containing arrays of up to four floating point types + * (max 32 bytes) are passed in registers on Arm. + */ + +typedef struct { + double data[4]; +} Test3C; + +EXPORT(Test3C) +_testfunc_array_in_struct_set_defaults_3C(void) +{ + Test3C s; + s.data[0] = 1.0; + s.data[1] = 2.0; + s.data[2] = 3.0; + s.data[3] = 4.0; + return s; +} + +typedef struct { + double data[5]; +} Test3D; + +EXPORT(Test3D) +_testfunc_array_in_struct_set_defaults_3D(void) +{ + Test3D s; + s.data[0] = 1.0; + s.data[1] = 2.0; + s.data[2] = 3.0; + s.data[3] = 4.0; + s.data[4] = 5.0; + return s; +} + typedef union { long a_long; struct { diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 6fbcf77a115371..04dd9bae32cd5e 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -697,29 +697,43 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct stgdict->align = total_align; stgdict->length = len; /* ADD ffi_ofs? */ -#define MAX_STRUCT_SIZE 16 +/* + * On Arm platforms, structs with at most 4 elements of any floating point + * type values can be passed through registers. If the type is double the + * maximum size of the struct is 32 bytes. + * By Arm platforms it is meant both 32 and 64-bit. +*/ +#if defined(__aarch64__) || defined(__arm__) +# define MAX_STRUCT_SIZE 32 +#else +# define MAX_STRUCT_SIZE 16 +#endif if (arrays_seen && (size <= MAX_STRUCT_SIZE)) { /* - * See bpo-22273. Arrays are normally treated as pointers, which is - * fine when an array name is being passed as parameter, but not when - * passing structures by value that contain arrays. On 64-bit Linux, - * small structures passed by value are passed in registers, and in - * order to do this, libffi needs to know the true type of the array - * members of structs. Treating them as pointers breaks things. + * See bpo-22273 and gh-110190. Arrays are normally treated as + * pointers, which is fine when an array name is being passed as + * parameter, but not when passing structures by value that contain + * arrays. + * On x86_64 Linux and Arm platforms, small structures passed by + * value are passed in registers, and in order to do this, libffi needs + * to know the true type of the array members of structs. Treating them + * as pointers breaks things. * - * By small structures, we mean ones that are 16 bytes or less. In that - * case, there can't be more than 16 elements after unrolling arrays, - * as we (will) disallow bitfields. So we can collect the true ffi_type - * values in a fixed-size local array on the stack and, if any arrays - * were seen, replace the ffi_type_pointer.elements with a more - * accurate set, to allow libffi to marshal them into registers - * correctly. It means one more loop over the fields, but if we got - * here, the structure is small, so there aren't too many of those. + * By small structures, we mean ones that are 16 bytes or less on + * x86-64 and 32 bytes or less on Arm. In that case, there can't be + * more than 16 or 32 elements after unrolling arrays, as we (will) + * disallow bitfields. So we can collect the true ffi_type values in + * a fixed-size local array on the stack and, if any arrays were seen, + * replace the ffi_type_pointer.elements with a more accurate set, + * to allow libffi to marshal them into registers correctly. + * It means one more loop over the fields, but if we got here, + * the structure is small, so there aren't too many of those. * - * Although the passing in registers is specific to 64-bit Linux, the - * array-in-struct vs. pointer problem is general. But we restrict the - * type transformation to small structs nonetheless. + * Although the passing in registers is specific to x86_64 Linux + * and Arm platforms, the array-in-struct vs. pointer problem is + * general. But we restrict the type transformation to small structs + * nonetheless. * * Note that although a union may be small in terms of memory usage, it * could contain many overlapping declarations of arrays, e.g. @@ -745,6 +759,9 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; * } * + * The same principle applies for a struct 32 bytes in size like in + * the case of Arm platforms. + * * So the struct/union needs setting up as follows: all non-array * elements copied across as is, and all array elements replaced with * an equivalent struct which has as many fields as the array has From 563ccded6e83bfdd8c5622663c4edb679e96e08b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 5 Dec 2023 17:40:49 +0200 Subject: [PATCH 118/442] gh-94692: Only catch OSError in shutil.rmtree() (#112756) Previously a symlink attack resistant version of shutil.rmtree() could ignore or pass to the error handler arbitrary exception when invalid arguments were provided. --- Lib/shutil.py | 4 +-- Lib/test/test_shutil.py | 33 +++++++++---------- ...3-12-05-16-20-40.gh-issue-94692.-e5C3c.rst | 4 +++ 3 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-05-16-20-40.gh-issue-94692.-e5C3c.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index 93b00d73a0fd46..bdbbcbc282e266 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -768,13 +768,13 @@ def onexc(*args): # lstat()/open()/fstat() trick. try: orig_st = os.lstat(path, dir_fd=dir_fd) - except Exception as err: + except OSError as err: onexc(os.lstat, path, err) return try: fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd) fd_closed = False - except Exception as err: + except OSError as err: onexc(os.open, path, err) return try: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 7ea2496230da47..9b8ec42a99dd69 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -317,7 +317,7 @@ def test_rmtree_works_on_junctions(self): self.assertTrue(os.path.exists(dir3)) self.assertTrue(os.path.exists(file1)) - def test_rmtree_errors_onerror(self): + def test_rmtree_errors(self): # filename is guaranteed not to exist filename = tempfile.mktemp(dir=self.mkdtemp()) self.assertRaises(FileNotFoundError, shutil.rmtree, filename) @@ -326,8 +326,8 @@ def test_rmtree_errors_onerror(self): # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") + write_file(filename, "") with self.assertRaises(NotADirectoryError) as cm: shutil.rmtree(filename) self.assertEqual(cm.exception.filename, filename) @@ -335,6 +335,19 @@ def test_rmtree_errors_onerror(self): # test that ignore_errors option is honored shutil.rmtree(filename, ignore_errors=True) self.assertTrue(os.path.exists(filename)) + + self.assertRaises(TypeError, shutil.rmtree, None) + self.assertRaises(TypeError, shutil.rmtree, None, ignore_errors=True) + exc = TypeError if shutil.rmtree.avoids_symlink_attacks else NotImplementedError + with self.assertRaises(exc): + shutil.rmtree(filename, dir_fd='invalid') + with self.assertRaises(exc): + shutil.rmtree(filename, dir_fd='invalid', ignore_errors=True) + + def test_rmtree_errors_onerror(self): + tmpdir = self.mkdtemp() + filename = os.path.join(tmpdir, "tstfile") + write_file(filename, "") errors = [] def onerror(*args): errors.append(args) @@ -350,23 +363,9 @@ def onerror(*args): self.assertEqual(errors[1][2][1].filename, filename) def test_rmtree_errors_onexc(self): - # filename is guaranteed not to exist - filename = tempfile.mktemp(dir=self.mkdtemp()) - self.assertRaises(FileNotFoundError, shutil.rmtree, filename) - # test that ignore_errors option is honored - shutil.rmtree(filename, ignore_errors=True) - - # existing file tmpdir = self.mkdtemp() - write_file((tmpdir, "tstfile"), "") filename = os.path.join(tmpdir, "tstfile") - with self.assertRaises(NotADirectoryError) as cm: - shutil.rmtree(filename) - self.assertEqual(cm.exception.filename, filename) - self.assertTrue(os.path.exists(filename)) - # test that ignore_errors option is honored - shutil.rmtree(filename, ignore_errors=True) - self.assertTrue(os.path.exists(filename)) + write_file(filename, "") errors = [] def onexc(*args): errors.append(args) diff --git a/Misc/NEWS.d/next/Library/2023-12-05-16-20-40.gh-issue-94692.-e5C3c.rst b/Misc/NEWS.d/next/Library/2023-12-05-16-20-40.gh-issue-94692.-e5C3c.rst new file mode 100644 index 00000000000000..c67ba6c9ececdb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-05-16-20-40.gh-issue-94692.-e5C3c.rst @@ -0,0 +1,4 @@ +:func:`shutil.rmtree` now only catches OSError exceptions. Previously a +symlink attack resistant version of ``shutil.rmtree()`` could ignore or pass +to the error handler arbitrary exception when invalid arguments were +provided. From de6bca956432cc852a4a41e2a2cee9cdacd19f35 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 5 Dec 2023 08:27:36 -0800 Subject: [PATCH 119/442] gh-112328: [Enum] Make some private attributes public. (GH-112514) * [Enum] Make some private attributes public. - ``_EnumDict`` --> ``EnumDict`` - ``EnumDict._member_names`` --> ``EnumDict.member_names`` - ``Enum._add_alias_`` - ``Enum._add_value_alias_`` --------- Co-authored-by: Alex Waygood Co-authored-by: Nikita Sobolev --- Doc/howto/enum.rst | 67 ++++-- Doc/library/enum.rst | 60 ++++-- Lib/enum.py | 203 +++++++++++------- Lib/test/test_enum.py | 86 +++++++- ...-11-28-20-47-39.gh-issue-112328.Z2AxEY.rst | 2 + 5 files changed, 301 insertions(+), 117 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-28-20-47-39.gh-issue-112328.Z2AxEY.rst diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index ffdafb749c73a9..1e9ac9b6761b64 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -868,7 +868,7 @@ Others While :class:`IntEnum` is part of the :mod:`enum` module, it would be very simple to implement independently:: - class IntEnum(int, Enum): + class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum pass This demonstrates how similar derived enumerations can be defined; for example @@ -876,8 +876,8 @@ a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`. Some rules: -1. When subclassing :class:`Enum`, mix-in types must appear before - :class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum` +1. When subclassing :class:`Enum`, mix-in types must appear before the + :class:`Enum` class itself in the sequence of bases, as in the :class:`IntEnum` example above. 2. Mix-in types must be subclassable. For example, :class:`bool` and :class:`range` are not subclassable and will throw an error during Enum @@ -961,30 +961,34 @@ all the members are created it is no longer used. Supported ``_sunder_`` names """""""""""""""""""""""""""" -- ``_name_`` -- name of the member -- ``_value_`` -- value of the member; can be set / modified in ``__new__`` +- :attr:`~Enum._name_` -- name of the member +- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__`` +- :meth:`~Enum._missing_` -- a lookup function used when a value is not found; + may be overridden +- :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 +- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for + an enum member; may be overridden +- :meth:`~Enum._add_alias_` -- adds a new name as an alias to an existing + member. +- :meth:`~Enum._add_value_alias_` -- adds a new value as an alias to an + existing member. See `MultiValueEnum`_ for an example. -- ``_missing_`` -- a lookup function used when a value is not found; may be - overridden -- ``_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 -- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent - (class attribute, removed during class creation) -- ``_generate_next_value_`` -- used by the `Functional API`_ and by - :class:`auto` to get an appropriate value for an enum member; may be - overridden + .. note:: -.. note:: + For standard :class:`Enum` classes the next value chosen is the highest + value seen incremented by one. - For standard :class:`Enum` classes the next value chosen is the last value seen - incremented by one. + For :class:`Flag` classes the next value chosen will be the next highest + power-of-two. - For :class:`Flag` classes the next value chosen will be the next highest - power-of-two, regardless of the last value seen. + .. versionchanged:: 3.13 + Prior versions would use the last seen value instead of the highest value. .. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_`` .. versionadded:: 3.7 ``_ignore_`` +.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_`` To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can be provided. It will be checked against the actual order of the enumeration @@ -1447,6 +1451,29 @@ alias:: disallowing aliases, the :func:`unique` decorator can be used instead. +MultiValueEnum +^^^^^^^^^^^^^^^^^ + +Supports having more than one value per member:: + + >>> class MultiValueEnum(Enum): + ... def __new__(cls, value, *values): + ... self = object.__new__(cls) + ... self._value_ = value + ... for v in values: + ... self._add_value_alias_(v) + ... return self + ... + >>> class DType(MultiValueEnum): + ... float32 = 'f', 8 + ... double64 = 'd', 9 + ... + >>> DType('f') + + >>> DType(9) + + + Planet ^^^^^^ diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 2d5ae361c3f1e3..20222bfb3611ab 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -235,6 +235,10 @@ Data Types >>> len(Color) 3 + .. attribute:: EnumType.__members__ + + Returns a mapping of every enum name to its member, including aliases + .. method:: EnumType.__reversed__(cls) Returns each member in *cls* in reverse definition order:: @@ -242,9 +246,19 @@ Data Types >>> list(reversed(Color)) [, , ] + .. method:: EnumType._add_alias_ + + Adds a new name as an alias to an existing member. Raises a + :exc:`NameError` if the name is already assigned to a different member. + + .. method:: EnumType._add_value_alias_ + + Adds a new value as an alias to an existing member. Raises a + :exc:`ValueError` if the value is already linked with a different member. + .. versionadded:: 3.11 - Before 3.11 ``enum`` used ``EnumMeta`` type, which is kept as an alias. + Before 3.11 ``EnumType`` was called ``EnumMeta``, which is still available as an alias. .. class:: Enum @@ -323,7 +337,7 @@ Data Types >>> PowersOfThree.SECOND.value 9 - .. method:: Enum.__init_subclass__(cls, **kwds) + .. method:: Enum.__init_subclass__(cls, \**kwds) A *classmethod* that is used to further configure subsequent subclasses. By default, does nothing. @@ -549,7 +563,7 @@ Data Types .. method:: __invert__(self): - Returns all the flags in *type(self)* that are not in self:: + Returns all the flags in *type(self)* that are not in *self*:: >>> ~white @@ -769,37 +783,41 @@ Supported ``__dunder__`` names :attr:`~EnumType.__members__` is a read-only ordered mapping of ``member_name``:``member`` items. It is only available on the class. -:meth:`~object.__new__`, if specified, must create and return the enum members; it is -also a very good idea to set the member's :attr:`!_value_` appropriately. Once -all the members are created it is no longer used. +:meth:`~object.__new__`, if specified, must create and return the enum members; +it is also a very good idea to set the member's :attr:`!_value_` appropriately. +Once all the members are created it is no longer used. Supported ``_sunder_`` names """""""""""""""""""""""""""" -- ``_name_`` -- name of the member -- ``_value_`` -- value of the member; can be set / modified in ``__new__`` - -- ``_missing_`` -- a lookup function used when a value is not found; may be - overridden -- ``_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 -- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent - (class attribute, removed during class creation) -- ``_generate_next_value_`` -- used to get an appropriate value for an enum - member; may be overridden +- :meth:`~EnumType._add_alias_` -- adds a new name as an alias to an existing + member. +- :meth:`~EnumType._add_value_alias_` -- adds a new value as an alias to an + existing member. +- :attr:`~Enum._name_` -- name of the member +- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__`` +- :meth:`~Enum._missing_` -- a lookup function used when a value is not found; + may be overridden +- :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) +- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for + an enum member; may be overridden .. note:: - For standard :class:`Enum` classes the next value chosen is the last value seen - incremented by one. + For standard :class:`Enum` classes the next value chosen is the highest + value seen incremented by one. For :class:`Flag` classes the next value chosen will be the next highest - power-of-two, regardless of the last value seen. + power-of-two. .. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_`` .. versionadded:: 3.7 ``_ignore_`` +.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_`` --------------- diff --git a/Lib/enum.py b/Lib/enum.py index 648401e80be685..a8a50a58380375 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -4,7 +4,7 @@ __all__ = [ - 'EnumType', 'EnumMeta', + 'EnumType', 'EnumMeta', 'EnumDict', 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum', 'auto', 'unique', 'property', 'verify', 'member', 'nonmember', 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', @@ -313,45 +313,8 @@ def __set_name__(self, enum_class, member_name): ): # no other instances found, record this member in _member_names_ enum_class._member_names_.append(member_name) - # if necessary, get redirect in place and then add it to _member_map_ - found_descriptor = None - descriptor_type = None - class_type = None - for base in enum_class.__mro__[1:]: - attr = base.__dict__.get(member_name) - if attr is not None: - if isinstance(attr, (property, DynamicClassAttribute)): - found_descriptor = attr - class_type = base - descriptor_type = 'enum' - break - elif _is_descriptor(attr): - found_descriptor = attr - descriptor_type = descriptor_type or 'desc' - class_type = class_type or base - continue - else: - descriptor_type = 'attr' - class_type = base - if found_descriptor: - redirect = property() - redirect.member = enum_member - redirect.__set_name__(enum_class, member_name) - if descriptor_type in ('enum','desc'): - # earlier descriptor found; copy fget, fset, fdel to this one. - redirect.fget = getattr(found_descriptor, 'fget', None) - redirect._get = getattr(found_descriptor, '__get__', None) - redirect.fset = getattr(found_descriptor, 'fset', None) - redirect._set = getattr(found_descriptor, '__set__', None) - redirect.fdel = getattr(found_descriptor, 'fdel', None) - redirect._del = getattr(found_descriptor, '__delete__', None) - redirect._attr_type = descriptor_type - redirect._cls_type = class_type - setattr(enum_class, member_name, redirect) - else: - setattr(enum_class, member_name, enum_member) - # now add to _member_map_ (even aliases) - enum_class._member_map_[member_name] = enum_member + + enum_class._add_member_(member_name, enum_member) try: # This may fail if value is not hashable. We can't add the value # to the map, and by-value lookups for this value will be @@ -360,9 +323,10 @@ def __set_name__(self, enum_class, member_name): except TypeError: # keep track of the value in a list so containment checks are quick enum_class._unhashable_values_.append(value) + enum_class._unhashable_values_map_.setdefault(member_name, []).append(value) -class _EnumDict(dict): +class EnumDict(dict): """ Track enum member order and ensure member names are not reused. @@ -371,7 +335,7 @@ class _EnumDict(dict): """ def __init__(self): super().__init__() - self._member_names = {} # use a dict to keep insertion order + self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7 self._last_values = [] self._ignore = [] self._auto_called = False @@ -393,6 +357,7 @@ def __setitem__(self, key, value): '_order_', '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_', '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', + '_add_alias_', '_add_value_alias_', ): raise ValueError( '_sunder_ names, such as %r, are reserved for future Enum use' @@ -468,6 +433,10 @@ def __setitem__(self, key, value): self._last_values.append(value) super().__setitem__(key, value) + @property + def member_names(self): + return list(self._member_names) + def update(self, members, **more_members): try: for name in members.keys(): @@ -478,6 +447,8 @@ def update(self, members, **more_members): for name, value in more_members.items(): self[name] = value +_EnumDict = EnumDict # keep private name for backwards compatibility + class EnumType(type): """ @@ -489,7 +460,7 @@ def __prepare__(metacls, cls, bases, **kwds): # check that previous enum members do not exist metacls._check_for_existing_members_(cls, bases) # create the namespace dict - enum_dict = _EnumDict() + enum_dict = EnumDict() enum_dict._cls_name = cls # inherit previous flags and _generate_next_value_ function member_type, first_enum = metacls._get_mixins_(cls, bases) @@ -552,6 +523,7 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k classdict['_member_map_'] = {} classdict['_value2member_map_'] = {} classdict['_unhashable_values_'] = [] + classdict['_unhashable_values_map_'] = {} classdict['_member_type_'] = member_type # now set the __repr__ for the value classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases) @@ -754,7 +726,10 @@ def __contains__(cls, value): """ if isinstance(value, cls): return True - return value in cls._value2member_map_ or value in cls._unhashable_values_ + try: + return value in cls._value2member_map_ + except TypeError: + return value in cls._unhashable_values_ def __delattr__(cls, attr): # nicer error message when someone tries to delete an attribute @@ -1050,7 +1025,57 @@ def _find_new_(mcls, classdict, member_type, first_enum): else: use_args = True return __new__, save_new, use_args -EnumMeta = EnumType + + def _add_member_(cls, name, member): + # _value_ structures are not updated + if name in cls._member_map_: + if cls._member_map_[name] is not member: + raise NameError('%r is already bound: %r' % (name, cls._member_map_[name])) + return + # + # if necessary, get redirect in place and then add it to _member_map_ + found_descriptor = None + descriptor_type = None + class_type = None + for base in cls.__mro__[1:]: + attr = base.__dict__.get(name) + if attr is not None: + if isinstance(attr, (property, DynamicClassAttribute)): + found_descriptor = attr + class_type = base + descriptor_type = 'enum' + break + elif _is_descriptor(attr): + found_descriptor = attr + descriptor_type = descriptor_type or 'desc' + class_type = class_type or base + continue + else: + descriptor_type = 'attr' + class_type = base + if found_descriptor: + redirect = property() + redirect.member = member + redirect.__set_name__(cls, name) + if descriptor_type in ('enum', 'desc'): + # earlier descriptor found; copy fget, fset, fdel to this one. + redirect.fget = getattr(found_descriptor, 'fget', None) + redirect._get = getattr(found_descriptor, '__get__', None) + redirect.fset = getattr(found_descriptor, 'fset', None) + redirect._set = getattr(found_descriptor, '__set__', None) + redirect.fdel = getattr(found_descriptor, 'fdel', None) + redirect._del = getattr(found_descriptor, '__delete__', None) + redirect._attr_type = descriptor_type + redirect._cls_type = class_type + setattr(cls, name, redirect) + else: + setattr(cls, name, member) + # now add to _member_map_ (even aliases) + cls._member_map_[name] = member + # + cls._member_map_[name] = member + +EnumMeta = EnumType # keep EnumMeta name for backwards compatibility class Enum(metaclass=EnumType): @@ -1116,9 +1141,9 @@ def __new__(cls, value): pass except TypeError: # not there, now do long search -- O(n) behavior - for member in cls._member_map_.values(): - if member._value_ == value: - return member + for name, values in cls._unhashable_values_map_.items(): + if value in values: + return cls[name] # still not found -- verify that members exist, in-case somebody got here mistakenly # (such as via super when trying to override __new__) if not cls._member_map_: @@ -1159,6 +1184,33 @@ def __new__(cls, value): def __init__(self, *args, **kwds): pass + def _add_alias_(self, name): + self.__class__._add_member_(name, self) + + def _add_value_alias_(self, value): + cls = self.__class__ + try: + if value in cls._value2member_map_: + if cls._value2member_map_[value] is not self: + raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value])) + return + except TypeError: + # unhashable value, do long search + for m in cls._member_map_.values(): + if m._value_ == value: + if m is not self: + raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value])) + return + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + cls._value2member_map_.setdefault(value, self) + except TypeError: + # keep track of the value in a list so containment checks are quick + cls._unhashable_values_.append(value) + cls._unhashable_values_map_.setdefault(self.name, []).append(value) + @staticmethod def _generate_next_value_(name, start, count, last_values): """ @@ -1671,7 +1723,8 @@ def convert_class(cls): body['_member_names_'] = member_names = [] body['_member_map_'] = member_map = {} body['_value2member_map_'] = value2member_map = {} - body['_unhashable_values_'] = [] + body['_unhashable_values_'] = unhashable_values = [] + body['_unhashable_values_map_'] = {} body['_member_type_'] = member_type = etype._member_type_ body['_value_repr_'] = etype._value_repr_ if issubclass(etype, Flag): @@ -1718,14 +1771,9 @@ def convert_class(cls): for name, value in attrs.items(): if isinstance(value, auto) and auto.value is _auto_null: value = gnv(name, 1, len(member_names), gnv_last_values) - if value in value2member_map: + if value in value2member_map or value in unhashable_values: # an alias to an existing member - member = value2member_map[value] - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member + enum_class(value)._add_alias_(name) else: # create the member if use_args: @@ -1740,12 +1788,12 @@ def convert_class(cls): member._name_ = name member.__objclass__ = enum_class member.__init__(value) - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member member._sort_order_ = len(member_names) + if name not in ('name', 'value'): + setattr(enum_class, name, member) + member_map[name] = member + else: + enum_class._add_member_(name, member) value2member_map[value] = member if _is_single_bit(value): # not a multi-bit alias, record in _member_names_ and _flag_mask_ @@ -1768,14 +1816,13 @@ def convert_class(cls): if value.value is _auto_null: value.value = gnv(name, 1, len(member_names), gnv_last_values) value = value.value - if value in value2member_map: + try: + contained = value in value2member_map + except TypeError: + contained = value in unhashable_values + if contained: # an alias to an existing member - member = value2member_map[value] - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member + enum_class(value)._add_alias_(name) else: # create the member if use_args: @@ -1791,14 +1838,22 @@ def convert_class(cls): member.__objclass__ = enum_class member.__init__(value) member._sort_order_ = len(member_names) - redirect = property() - redirect.member = member - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member - value2member_map[value] = member + if name not in ('name', 'value'): + setattr(enum_class, name, member) + member_map[name] = member + else: + enum_class._add_member_(name, member) member_names.append(name) gnv_last_values.append(value) + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + enum_class._value2member_map_.setdefault(value, member) + except TypeError: + # keep track of the value in a list so containment checks are quick + enum_class._unhashable_values_.append(value) + enum_class._unhashable_values_map_.setdefault(name, []).append(value) if '__new__' in body: enum_class.__new_member__ = enum_class.__new__ enum_class.__new__ = Enum.__new__ diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index c602913ca69277..f99d4ca204b5a7 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -514,6 +514,7 @@ def test_contains_tf(self): self.assertFalse('first' in MainEnum) val = MainEnum.dupe self.assertIn(val, MainEnum) + self.assertNotIn(float('nan'), MainEnum) # class OtherEnum(Enum): one = auto() @@ -3268,6 +3269,65 @@ def __new__(cls, value): member._value_ = Base(value) return member + def test_extra_member_creation(self): + class IDEnumMeta(EnumMeta): + def __new__(metacls, cls, bases, classdict, **kwds): + # add new entries to classdict + for name in classdict.member_names: + classdict[f'{name}_DESC'] = f'-{classdict[name]}' + return super().__new__(metacls, cls, bases, classdict, **kwds) + class IDEnum(StrEnum, metaclass=IDEnumMeta): + pass + class MyEnum(IDEnum): + ID = 'id' + NAME = 'name' + self.assertEqual(list(MyEnum), [MyEnum.ID, MyEnum.NAME, MyEnum.ID_DESC, MyEnum.NAME_DESC]) + + def test_add_alias(self): + class mixin: + @property + def ORG(self): + return 'huh' + class Color(mixin, Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + Color.RED._add_alias_('ROJO') + self.assertIs(Color.RED, Color['ROJO']) + self.assertIs(Color.RED, Color.ROJO) + Color.BLUE._add_alias_('ORG') + self.assertIs(Color.BLUE, Color['ORG']) + self.assertIs(Color.BLUE, Color.ORG) + self.assertEqual(Color.RED.ORG, 'huh') + self.assertEqual(Color.GREEN.ORG, 'huh') + self.assertEqual(Color.BLUE.ORG, 'huh') + self.assertEqual(Color.ORG.ORG, 'huh') + + def test_add_value_alias_after_creation(self): + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + Color.RED._add_value_alias_(5) + self.assertIs(Color.RED, Color(5)) + + def test_add_value_alias_during_creation(self): + class Types(Enum): + Unknown = 0, + Source = 1, 'src' + NetList = 2, 'nl' + def __new__(cls, int_value, *value_aliases): + member = object.__new__(cls) + member._value_ = int_value + for alias in value_aliases: + member._add_value_alias_(alias) + return member + self.assertIs(Types(0), Types.Unknown) + self.assertIs(Types(1), Types.Source) + self.assertIs(Types('src'), Types.Source) + self.assertIs(Types(2), Types.NetList) + self.assertIs(Types('nl'), Types.NetList) + class TestOrder(unittest.TestCase): "test usage of the `_order_` attribute" @@ -4941,12 +5001,14 @@ class CheckedColor(Enum): @bltns.property def zeroth(self): return 'zeroed %s' % self.name - self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None) + _test_simple_enum(CheckedColor, SimpleColor) SimpleColor.MAGENTA._value_ = 9 self.assertRaisesRegex( TypeError, "enum mismatch", _test_simple_enum, CheckedColor, SimpleColor, ) + # + # class CheckedMissing(IntFlag, boundary=KEEP): SIXTY_FOUR = 64 ONE_TWENTY_EIGHT = 128 @@ -4963,8 +5025,28 @@ class Missing: ALL = 2048 + 128 + 64 + 12 M = Missing self.assertEqual(list(CheckedMissing), [M.SIXTY_FOUR, M.ONE_TWENTY_EIGHT, M.TWENTY_FORTY_EIGHT]) - # _test_simple_enum(CheckedMissing, Missing) + # + # + class CheckedUnhashable(Enum): + ONE = dict() + TWO = set() + name = 'python' + self.assertIn(dict(), CheckedUnhashable) + self.assertIn('python', CheckedUnhashable) + self.assertEqual(CheckedUnhashable.name.value, 'python') + self.assertEqual(CheckedUnhashable.name.name, 'name') + # + @_simple_enum() + class Unhashable: + ONE = dict() + TWO = set() + name = 'python' + self.assertIn(dict(), Unhashable) + self.assertIn('python', Unhashable) + self.assertEqual(Unhashable.name.value, 'python') + self.assertEqual(Unhashable.name.name, 'name') + _test_simple_enum(Unhashable, Unhashable) class MiscTestCase(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2023-11-28-20-47-39.gh-issue-112328.Z2AxEY.rst b/Misc/NEWS.d/next/Library/2023-11-28-20-47-39.gh-issue-112328.Z2AxEY.rst new file mode 100644 index 00000000000000..6e6902486b7bc9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-28-20-47-39.gh-issue-112328.Z2AxEY.rst @@ -0,0 +1,2 @@ +[Enum] Make ``EnumDict``, ``EnumDict.member_names``, +``EnumType._add_alias_`` and ``EnumType._add_value_alias_`` public. From 11d88a178b077e42025da538b890db3151a47070 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Tue, 5 Dec 2023 09:09:39 -0800 Subject: [PATCH 120/442] bpo-35332: Handle os.close() errors in shutil.rmtree() (GH-23766) * Ignore os.close() errors when ignore_errors is True. * Pass os.close() errors to the error handler if specified. * os.close no longer retried after error. Co-authored-by: Serhiy Storchaka --- Lib/shutil.py | 20 +++++++++-- Lib/test/test_shutil.py | 35 +++++++++++++++++++ .../2020-12-14-09-31-13.bpo-35332.s22wAx.rst | 3 ++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-14-09-31-13.bpo-35332.s22wAx.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index bdbbcbc282e266..dc3aac3e07f910 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -687,7 +687,12 @@ def _rmtree_safe_fd(topfd, path, onexc): _rmtree_safe_fd(dirfd, fullname, onexc) try: os.close(dirfd) + except OSError as err: + # close() should not be retried after an error. dirfd_closed = True + onexc(os.close, fullname, err) + dirfd_closed = True + try: os.rmdir(entry.name, dir_fd=topfd) except FileNotFoundError: continue @@ -704,7 +709,10 @@ def _rmtree_safe_fd(topfd, path, onexc): onexc(os.path.islink, fullname, err) finally: if not dirfd_closed: - os.close(dirfd) + try: + os.close(dirfd) + except OSError as err: + onexc(os.close, fullname, err) else: try: os.unlink(entry.name, dir_fd=topfd) @@ -782,7 +790,12 @@ def onexc(*args): _rmtree_safe_fd(fd, path, onexc) try: os.close(fd) + except OSError as err: + # close() should not be retried after an error. fd_closed = True + onexc(os.close, path, err) + fd_closed = True + try: os.rmdir(path, dir_fd=dir_fd) except OSError as err: onexc(os.rmdir, path, err) @@ -794,7 +807,10 @@ def onexc(*args): onexc(os.path.islink, path, err) finally: if not fd_closed: - os.close(fd) + try: + os.close(fd) + except OSError as err: + onexc(os.close, path, err) else: if dir_fd is not None: raise NotImplementedError("dir_fd unavailable on this platform") diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 9b8ec42a99dd69..d7061b2f9d8724 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -576,6 +576,41 @@ def _raiser(*args, **kwargs): self.assertFalse(shutil._use_fd_functions) self.assertFalse(shutil.rmtree.avoids_symlink_attacks) + @unittest.skipUnless(shutil._use_fd_functions, "requires safe rmtree") + def test_rmtree_fails_on_close(self): + # Test that the error handler is called for failed os.close() and that + # os.close() is only called once for a file descriptor. + tmp = self.mkdtemp() + dir1 = os.path.join(tmp, 'dir1') + os.mkdir(dir1) + dir2 = os.path.join(dir1, 'dir2') + os.mkdir(dir2) + def close(fd): + orig_close(fd) + nonlocal close_count + close_count += 1 + raise OSError + + close_count = 0 + with support.swap_attr(os, 'close', close) as orig_close: + with self.assertRaises(OSError): + shutil.rmtree(dir1) + self.assertTrue(os.path.isdir(dir2)) + self.assertEqual(close_count, 2) + + close_count = 0 + errors = [] + def onexc(*args): + errors.append(args) + with support.swap_attr(os, 'close', close) as orig_close: + shutil.rmtree(dir1, onexc=onexc) + self.assertEqual(len(errors), 2) + self.assertIs(errors[0][0], close) + self.assertEqual(errors[0][1], dir2) + self.assertIs(errors[1][0], close) + self.assertEqual(errors[1][1], dir1) + self.assertEqual(close_count, 2) + @unittest.skipUnless(shutil._use_fd_functions, "dir_fd is not supported") def test_rmtree_with_dir_fd(self): tmp_dir = self.mkdtemp() diff --git a/Misc/NEWS.d/next/Library/2020-12-14-09-31-13.bpo-35332.s22wAx.rst b/Misc/NEWS.d/next/Library/2020-12-14-09-31-13.bpo-35332.s22wAx.rst new file mode 100644 index 00000000000000..80564b99a079c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-14-09-31-13.bpo-35332.s22wAx.rst @@ -0,0 +1,3 @@ +The :func:`shutil.rmtree` function now ignores errors when calling +:func:`os.close` when *ignore_errors* is ``True``, and +:func:`os.close` no longer retried after error. From c2e2df83560a3d4cb602f6d57cb70ac8aad7f9e6 Mon Sep 17 00:00:00 2001 From: "Jurjen N. E. Bos" Date: Tue, 5 Dec 2023 19:44:06 +0100 Subject: [PATCH 121/442] Minor stylistic edit to the grouper recipe (gh112759) --- Doc/library/itertools.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index ebb4ebcfa7618a..8a4254cf15ebe2 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -916,9 +916,9 @@ which incur interpreter overhead. args = [iter(iterable)] * n if incomplete == 'fill': return zip_longest(*args, fillvalue=fillvalue) - if incomplete == 'strict': + elif incomplete == 'strict': return zip(*args, strict=True) - if incomplete == 'ignore': + elif incomplete == 'ignore': return zip(*args) else: raise ValueError('Expected fill, strict, or ignore') From d109f637c048c2b5fc95dc7fdfd50f8ac41a7747 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 5 Dec 2023 19:27:59 +0000 Subject: [PATCH 122/442] gh-101100: Properly document frame object attributes (#112735) --- Doc/c-api/frame.rst | 8 ++-- Doc/c-api/init.rst | 5 ++- Doc/library/dis.rst | 4 +- Doc/library/inspect.rst | 4 +- Doc/library/sys.rst | 9 ++-- Doc/library/types.rst | 3 +- Doc/reference/datamodel.rst | 84 ++++++++++++++++++++++++++----------- Doc/whatsnew/2.3.rst | 4 +- Doc/whatsnew/3.10.rst | 7 ++-- Doc/whatsnew/3.11.rst | 5 ++- Doc/whatsnew/3.6.rst | 2 +- Doc/whatsnew/3.7.rst | 2 +- 12 files changed, 88 insertions(+), 49 deletions(-) diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst index 1accee2767a485..6bb1e9b5803b58 100644 --- a/Doc/c-api/frame.rst +++ b/Doc/c-api/frame.rst @@ -50,7 +50,7 @@ See also :ref:`Reflection `. .. c:function:: PyObject* PyFrame_GetBuiltins(PyFrameObject *frame) - Get the *frame*'s ``f_builtins`` attribute. + Get the *frame*'s :attr:`~frame.f_builtins` attribute. Return a :term:`strong reference`. The result cannot be ``NULL``. @@ -81,7 +81,7 @@ See also :ref:`Reflection `. .. c:function:: PyObject* PyFrame_GetGlobals(PyFrameObject *frame) - Get the *frame*'s ``f_globals`` attribute. + Get the *frame*'s :attr:`~frame.f_globals` attribute. Return a :term:`strong reference`. The result cannot be ``NULL``. @@ -90,7 +90,7 @@ See also :ref:`Reflection `. .. c:function:: int PyFrame_GetLasti(PyFrameObject *frame) - Get the *frame*'s ``f_lasti`` attribute. + Get the *frame*'s :attr:`~frame.f_lasti` attribute. Returns -1 if ``frame.f_lasti`` is ``None``. @@ -120,7 +120,7 @@ See also :ref:`Reflection `. .. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame) - Get the *frame*'s ``f_locals`` attribute (:class:`dict`). + Get the *frame*'s :attr:`~frame.f_locals` attribute (:class:`dict`). Return a :term:`strong reference`. diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e89641f74c7491..f8fd48e781d6da 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1662,7 +1662,8 @@ Python-level trace functions in previous versions. The value passed as the *what* parameter to a :c:type:`Py_tracefunc` function (but not a profiling function) when a line-number event is being reported. - It may be disabled for a frame by setting :attr:`f_trace_lines` to *0* on that frame. + It may be disabled for a frame by setting :attr:`~frame.f_trace_lines` to + *0* on that frame. .. c:var:: int PyTrace_RETURN @@ -1694,7 +1695,7 @@ Python-level trace functions in previous versions. The value for the *what* parameter to :c:type:`Py_tracefunc` functions (but not profiling functions) when a new opcode is about to be executed. This event is not emitted by default: it must be explicitly requested by setting - :attr:`f_trace_opcodes` to *1* on the frame. + :attr:`~frame.f_trace_opcodes` to *1* on the frame. .. c:function:: void PyEval_SetProfile(Py_tracefunc func, PyObject *obj) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 7e97f1a4524554..cf238f81b9cc64 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -865,8 +865,8 @@ iterations of the loop. .. opcode:: RERAISE Re-raises the exception currently on top of the stack. If oparg is non-zero, - pops an additional value from the stack which is used to set ``f_lasti`` - of the current frame. + pops an additional value from the stack which is used to set + :attr:`~frame.f_lasti` of the current frame. .. versionadded:: 3.9 diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 6fd0d32afe7415..08f15ae09b1b87 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1605,8 +1605,8 @@ the following flags: .. data:: CO_NEWLOCALS - If set, a new dict will be created for the frame's ``f_locals`` when - the code object is executed. + If set, a new dict will be created for the frame's :attr:`~frame.f_locals` + when the code object is executed. .. data:: CO_VARARGS diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 7f359819e6847e..aaf79205d44282 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1600,7 +1600,8 @@ always available. :file:`Objects/lnotab_notes.txt` for a detailed explanation of how this works. Per-line events may be disabled for a frame by setting - :attr:`!f_trace_lines` to :const:`False` on that :ref:`frame `. + :attr:`~frame.f_trace_lines` to :const:`False` on that + :ref:`frame `. ``'return'`` A function (or other code block) is about to return. The local trace @@ -1618,7 +1619,7 @@ always available. opcode details). The local trace function is called; *arg* is ``None``; the return value specifies the new local trace function. Per-opcode events are not emitted by default: they must be explicitly - requested by setting :attr:`!f_trace_opcodes` to :const:`True` on the + requested by setting :attr:`~frame.f_trace_opcodes` to :const:`True` on the :ref:`frame `. Note that as an exception is propagated down the chain of callers, an @@ -1648,8 +1649,8 @@ always available. .. versionchanged:: 3.7 - ``'opcode'`` event type added; :attr:`!f_trace_lines` and - :attr:`!f_trace_opcodes` attributes added to frames + ``'opcode'`` event type added; :attr:`~frame.f_trace_lines` and + :attr:`~frame.f_trace_opcodes` attributes added to frames .. function:: set_asyncgen_hooks(firstiter, finalizer) diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 54c3907dec98cc..22766462822af9 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -388,7 +388,8 @@ Standard names are defined for the following types: .. data:: GetSetDescriptorType The type of objects defined in extension modules with ``PyGetSetDef``, such - as ``FrameType.f_locals`` or ``array.array.typecode``. This type is used as + as :attr:`FrameType.f_locals ` or ``array.array.typecode``. + This type is used as descriptor for object attributes; it has the same purpose as the :class:`property` type, but for classes defined in extension modules. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 06e61393fccc24..8a94b7bb22c362 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1174,16 +1174,36 @@ Frame objects represent execution frames. They may occur in traceback objects single: f_lasti (frame attribute) single: f_builtins (frame attribute) -Special read-only attributes: :attr:`f_back` is to the previous stack frame -(towards the caller), or ``None`` if this is the bottom stack frame; -:attr:`f_code` is the code object being executed in this frame; :attr:`f_locals` -is the dictionary used to look up local variables; :attr:`f_globals` is used for -global variables; :attr:`f_builtins` is used for built-in (intrinsic) names; -:attr:`f_lasti` gives the precise instruction (this is an index into the -bytecode string of the code object). +Special read-only attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Accessing ``f_code`` raises an :ref:`auditing event ` -``object.__getattr__`` with arguments ``obj`` and ``"f_code"``. +.. list-table:: + + * - .. attribute:: frame.f_back + - Points to the previous stack frame (towards the caller), + or ``None`` if this is the bottom stack frame + + * - .. attribute:: frame.f_code + - The :ref:`code object ` being executed in this frame. + Accessing this attribute raises an :ref:`auditing event ` + ``object.__getattr__`` with arguments ``obj`` and ``"f_code"``. + + * - .. attribute:: frame.f_locals + - The dictionary used by the frame to look up + :ref:`local variables ` + + * - .. attribute:: frame.f_globals + - The dictionary used by the frame to look up + :ref:`global variables ` + + * - .. attribute:: frame.f_builtins + - The dictionary used by the frame to look up + :ref:`built-in (intrinsic) names ` + + * - .. attribute:: frame.f_lasti + - The "precise instruction" of the frame object + (this is an index into the :term:`bytecode` string of the + :ref:`code object `) .. index:: single: f_trace (frame attribute) @@ -1191,30 +1211,44 @@ Accessing ``f_code`` raises an :ref:`auditing event ` single: f_trace_opcodes (frame attribute) single: f_lineno (frame attribute) -Special writable attributes: :attr:`f_trace`, if not ``None``, is a function -called for various events during code execution (this is used by the debugger). -Normally an event is triggered for each new source line - this can be -disabled by setting :attr:`f_trace_lines` to :const:`False`. +Special writable attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + + * - .. attribute:: frame.f_trace + - If not ``None``, this is a function called for various events during + code execution (this is used by debuggers). Normally an event is + triggered for each new source line (see :attr:`~frame.f_trace_lines`). + + * - .. attribute:: frame.f_trace_lines + - Set this attribute to :const:`False` to disable triggering a tracing + event for each source line. + + * - .. attribute:: frame.f_trace_opcodes + - Set this attribute to :const:`True` to allow per-opcode events to be + requested. Note that this may lead to + undefined interpreter behaviour if exceptions raised by the trace + function escape to the function being traced. -Implementations *may* allow per-opcode events to be requested by setting -:attr:`f_trace_opcodes` to :const:`True`. Note that this may lead to -undefined interpreter behaviour if exceptions raised by the trace -function escape to the function being traced. + * - .. attribute:: frame.f_lineno + - The current line number of the frame -- writing to this + from within a trace function jumps to the given line (only for the bottom-most + frame). A debugger can implement a Jump command (aka Set Next Statement) + by writing to this attribute. -:attr:`f_lineno` is the current line number of the frame --- writing to this -from within a trace function jumps to the given line (only for the bottom-most -frame). A debugger can implement a Jump command (aka Set Next Statement) -by writing to f_lineno. +Frame object methods +~~~~~~~~~~~~~~~~~~~~ Frame objects support one method: .. method:: frame.clear() - This method clears all references to local variables held by the - frame. Also, if the frame belonged to a generator, the generator + This method clears all references to :ref:`local variables ` held by the + frame. Also, if the frame belonged to a :term:`generator`, the generator is finalized. This helps break reference cycles involving frame - objects (for example when catching an exception and storing its - traceback for later use). + objects (for example when catching an :ref:`exception ` + and storing its :ref:`traceback ` for later use). :exc:`RuntimeError` is raised if the frame is currently executing or suspended. diff --git a/Doc/whatsnew/2.3.rst b/Doc/whatsnew/2.3.rst index af332b28a28231..c989e6d3fa5787 100644 --- a/Doc/whatsnew/2.3.rst +++ b/Doc/whatsnew/2.3.rst @@ -1998,13 +1998,13 @@ Some of the more notable changes are: It would be difficult to detect any resulting difference from Python code, apart from a slight speed up when Python is run without :option:`-O`. - C extensions that access the :attr:`f_lineno` field of frame objects should + C extensions that access the :attr:`~frame.f_lineno` field of frame objects should instead call ``PyCode_Addr2Line(f->f_code, f->f_lasti)``. This will have the added effect of making the code work as desired under "python -O" in earlier versions of Python. A nifty new feature is that trace functions can now assign to the - :attr:`f_lineno` attribute of frame objects, changing the line that will be + :attr:`~frame.f_lineno` attribute of frame objects, changing the line that will be executed next. A ``jump`` command has been added to the :mod:`pdb` debugger taking advantage of this new feature. (Implemented by Richie Hindle.) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index df821d68eb8d9f..15479cc979624f 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -399,7 +399,8 @@ PEP 626: Precise line numbers for debugging and other tools PEP 626 brings more precise and reliable line numbers for debugging, profiling and coverage tools. Tracing events, with the correct line number, are generated for all lines of code executed and only for lines of code that are executed. -The ``f_lineno`` attribute of frame objects will always contain the expected line number. +The :attr:`~frame.f_lineno` attribute of frame objects will always contain the +expected line number. The ``co_lnotab`` attribute of code objects is deprecated and will be removed in 3.12. Code that needs to convert from offset to line number should use the new ``co_lines()`` method instead. @@ -1959,11 +1960,11 @@ Changes in the C API source_buf = PyBytes_AsString(source_bytes_object); code = Py_CompileString(source_buf, filename, Py_file_input); - * For ``FrameObject`` objects, the ``f_lasti`` member now represents a wordcode + * For ``FrameObject`` objects, the :attr:`~frame.f_lasti` member now represents a wordcode offset instead of a simple offset into the bytecode string. This means that this number needs to be multiplied by 2 to be used with APIs that expect a byte offset instead (like :c:func:`PyCode_Addr2Line` for example). Notice as well that the - ``f_lasti`` member of ``FrameObject`` objects is not considered stable: please + :attr:`!f_lasti` member of ``FrameObject`` objects is not considered stable: please use :c:func:`PyFrame_GetLineNumber` instead. CPython bytecode changes diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 48a0e621baad02..8db133b90a7a4b 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -2458,11 +2458,12 @@ Porting to Python 3.11 * ``f_valuestack``: removed. The Python frame object is now created lazily. A side effect is that the - ``f_back`` member must not be accessed directly, since its value is now also + :attr:`~frame.f_back` member must not be accessed directly, + since its value is now also computed lazily. The :c:func:`PyFrame_GetBack` function must be called instead. - Debuggers that accessed the ``f_locals`` directly *must* call + 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`, in fact they should not call those functions. The necessary updating of the diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index c15d8be651fd17..2f618929793fc6 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -2162,7 +2162,7 @@ Changes in the Python API * The format of the ``co_lnotab`` attribute of code objects changed to support a negative line number delta. By default, Python does not emit bytecode with - a negative line number delta. Functions using ``frame.f_lineno``, + a negative line number delta. Functions using :attr:`frame.f_lineno`, ``PyFrame_GetLineNumber()`` or ``PyCode_Addr2Line()`` are not affected. Functions directly decoding ``co_lnotab`` should be updated to use a signed 8-bit integer type for the line number delta, but this is only required to diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index a7d5c3db6ddcb2..99f280af84ab01 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -1891,7 +1891,7 @@ Other CPython Implementation Changes * Trace hooks may now opt out of receiving the ``line`` and opt into receiving the ``opcode`` events from the interpreter by setting the corresponding new - ``f_trace_lines`` and ``f_trace_opcodes`` attributes on the + :attr:`~frame.f_trace_lines` and :attr:`~frame.f_trace_opcodes` attributes on the frame being traced. (Contributed by Nick Coghlan in :issue:`31344`.) * Fixed some consistency problems with namespace package module attributes. From d384813ff18b33280a90b6d2011654528a2b6ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 5 Dec 2023 20:39:28 +0100 Subject: [PATCH 123/442] gh-112769: test_zlib: Fix comparison of ZLIB_RUNTIME_VERSION with non-int suffix (GH-112771) zlib-ng defines the version as "1.3.0.zlib-ng". --- Lib/test/test_zlib.py | 29 +++++++++++-------- ...-12-05-19-50-03.gh-issue-112769.kdLJmS.rst | 3 ++ 2 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2023-12-05-19-50-03.gh-issue-112769.kdLJmS.rst diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 1dc8b91a453f92..ef02c64f886f8a 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -18,6 +18,21 @@ hasattr(zlib.decompressobj(), "copy"), 'requires Decompress.copy()') + +def _zlib_runtime_version_tuple(zlib_version=zlib.ZLIB_RUNTIME_VERSION): + # Register "1.2.3" as "1.2.3.0" + # or "1.2.0-linux","1.2.0.f","1.2.0.f-linux" + v = zlib_version.split('-', 1)[0].split('.') + if len(v) < 4: + v.append('0') + elif not v[-1].isnumeric(): + v[-1] = '0' + return tuple(map(int, v)) + + +ZLIB_RUNTIME_VERSION_TUPLE = _zlib_runtime_version_tuple() + + # bpo-46623: On s390x, when a hardware accelerator is used, using different # ways to compress data with zlib can produce different compressed data. # Simplified test_pair() code: @@ -473,9 +488,8 @@ def test_flushes(self): sync_opt = ['Z_NO_FLUSH', 'Z_SYNC_FLUSH', 'Z_FULL_FLUSH', 'Z_PARTIAL_FLUSH'] - ver = tuple(int(v) for v in zlib.ZLIB_RUNTIME_VERSION.split('.')) # Z_BLOCK has a known failure prior to 1.2.5.3 - if ver >= (1, 2, 5, 3): + if ZLIB_RUNTIME_VERSION_TUPLE >= (1, 2, 5, 3): sync_opt.append('Z_BLOCK') sync_opt = [getattr(zlib, opt) for opt in sync_opt @@ -793,16 +807,7 @@ def test_large_unconsumed_tail(self, size): def test_wbits(self): # wbits=0 only supported since zlib v1.2.3.5 - # Register "1.2.3" as "1.2.3.0" - # or "1.2.0-linux","1.2.0.f","1.2.0.f-linux" - v = zlib.ZLIB_RUNTIME_VERSION.split('-', 1)[0].split('.') - if len(v) < 4: - v.append('0') - elif not v[-1].isnumeric(): - v[-1] = '0' - - v = tuple(map(int, v)) - supports_wbits_0 = v >= (1, 2, 3, 5) + supports_wbits_0 = ZLIB_RUNTIME_VERSION_TUPLE >= (1, 2, 3, 5) co = zlib.compressobj(level=1, wbits=15) zlib15 = co.compress(HAMLET_SCENE) + co.flush() diff --git a/Misc/NEWS.d/next/Tests/2023-12-05-19-50-03.gh-issue-112769.kdLJmS.rst b/Misc/NEWS.d/next/Tests/2023-12-05-19-50-03.gh-issue-112769.kdLJmS.rst new file mode 100644 index 00000000000000..1bbbb26fc322fa --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-12-05-19-50-03.gh-issue-112769.kdLJmS.rst @@ -0,0 +1,3 @@ +The tests now correctly compare zlib version when +:const:`zlib.ZLIB_RUNTIME_VERSION` contains non-integer suffixes. For +example zlib-ng defines the version as ``1.3.0.zlib-ng``. From a2a46f9f1e08be26fa1f732a2b92e355ad812abf Mon Sep 17 00:00:00 2001 From: Matt Prodani Date: Wed, 6 Dec 2023 01:54:57 -0500 Subject: [PATCH 124/442] gh-112606: Use sem_clockwait with monotonic time when supported in parking_lot.c (gh-112733) --- Python/parking_lot.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Python/parking_lot.c b/Python/parking_lot.c index 664e622cc17474..d44c1b4b93b4d2 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -118,10 +118,19 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) if (timeout >= 0) { struct timespec ts; +#if defined(CLOCK_MONOTONIC) && defined(HAVE_SEM_CLOCKWAIT) + _PyTime_t deadline = _PyTime_Add(_PyTime_GetMonotonicClock(), timeout); + + _PyTime_AsTimespec_clamp(deadline, &ts); + + err = sem_clockwait(&sema->platform_sem, CLOCK_MONOTONIC, &ts); +#else _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); - _PyTime_AsTimespec(deadline, &ts); + + _PyTime_AsTimespec_clamp(deadline, &ts); err = sem_timedwait(&sema->platform_sem, &ts); +#endif } else { err = sem_wait(&sema->platform_sem); @@ -151,7 +160,7 @@ _PySemaphore_PlatformWait(_PySemaphore *sema, _PyTime_t timeout) struct timespec ts; _PyTime_t deadline = _PyTime_Add(_PyTime_GetSystemClock(), timeout); - _PyTime_AsTimespec(deadline, &ts); + _PyTime_AsTimespec_clamp(deadline, &ts); err = pthread_cond_timedwait(&sema->cond, &sema->mutex, &ts); } From f8c0198e3bfa2f6f65e426765a5efddd8ece78b0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 6 Dec 2023 09:48:27 +0200 Subject: [PATCH 125/442] gh-108927: Include new dir test/regrtestdata in the installation (GH-112765) Co-authored-by: Victor Stinner --- Makefile.pre.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile.pre.in b/Makefile.pre.in index e7f8abce43d648..b5edb4e6748fb0 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2194,6 +2194,9 @@ TESTSUBDIRS= idlelib/idle_test \ test/leakers \ test/libregrtest \ test/mathdata \ + test/regrtestdata \ + test/regrtestdata/import_from_tests \ + test/regrtestdata/import_from_tests/test_regrtest_b \ test/subprocessdata \ test/support \ test/support/_hypothesis_stubs \ From e3f670e13792305cfb977d5cffd8e6aa03e8fe7f Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 6 Dec 2023 08:44:06 +0000 Subject: [PATCH 126/442] gh-101100: Fix most Sphinx nitpicks in the glossary and `stdtypes.rst` (#112757) --- Doc/glossary.rst | 47 ++++++++++++++++++++++------------------ Doc/library/stdtypes.rst | 26 ++++++++++++---------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/Doc/glossary.rst b/Doc/glossary.rst index 29f2f80cebd5f0..601443d5aade94 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -151,9 +151,9 @@ Glossary A :term:`file object` able to read and write :term:`bytes-like objects `. Examples of binary files are files opened in binary mode (``'rb'``, - ``'wb'`` or ``'rb+'``), :data:`sys.stdin.buffer`, - :data:`sys.stdout.buffer`, and instances of :class:`io.BytesIO` and - :class:`gzip.GzipFile`. + ``'wb'`` or ``'rb+'``), :data:`sys.stdin.buffer `, + :data:`sys.stdout.buffer `, and instances of + :class:`io.BytesIO` and :class:`gzip.GzipFile`. See also :term:`text file` for a file object able to read and write :class:`str` objects. @@ -304,8 +304,9 @@ Glossary :ref:`class definitions ` for more about decorators. descriptor - Any object which defines the methods :meth:`__get__`, :meth:`__set__`, or - :meth:`__delete__`. When a class attribute is a descriptor, its special + Any object which defines the methods :meth:`~object.__get__`, + :meth:`~object.__set__`, or :meth:`~object.__delete__`. + When a class attribute is a descriptor, its special binding behavior is triggered upon attribute lookup. Normally, using *a.b* to get, set or delete an attribute looks up the object named *b* in the class dictionary for *a*, but if *b* is a descriptor, the respective @@ -319,7 +320,8 @@ Glossary dictionary An associative array, where arbitrary keys are mapped to values. The - keys can be any object with :meth:`__hash__` and :meth:`__eq__` methods. + keys can be any object with :meth:`~object.__hash__` and + :meth:`~object.__eq__` methods. Called a hash in Perl. dictionary comprehension @@ -383,7 +385,7 @@ Glossary file object An object exposing a file-oriented API (with methods such as - :meth:`read()` or :meth:`write()`) to an underlying resource. Depending + :meth:`!read` or :meth:`!write`) to an underlying resource. Depending on the way it was created, a file object can mediate access to a real on-disk file or to another type of storage or communication device (for example standard input/output, in-memory buffers, sockets, pipes, @@ -559,8 +561,9 @@ Glossary hashable An object is *hashable* if it has a hash value which never changes during - its lifetime (it needs a :meth:`__hash__` method), and can be compared to - other objects (it needs an :meth:`__eq__` method). Hashable objects which + its lifetime (it needs a :meth:`~object.__hash__` method), and can be + compared to other objects (it needs an :meth:`~object.__eq__` method). + Hashable objects which compare equal must have the same hash value. Hashability makes an object usable as a dictionary key and a set member, @@ -646,7 +649,8 @@ Glossary iterables include all sequence types (such as :class:`list`, :class:`str`, and :class:`tuple`) and some non-sequence types like :class:`dict`, :term:`file objects `, and objects of any classes you define - with an :meth:`__iter__` method or with a :meth:`~object.__getitem__` method + with an :meth:`~iterator.__iter__` method or with a + :meth:`~object.__getitem__` method that implements :term:`sequence` semantics. Iterables can be @@ -655,7 +659,7 @@ Glossary as an argument to the built-in function :func:`iter`, it returns an iterator for the object. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call - :func:`iter` or deal with iterator objects yourself. The ``for`` + :func:`iter` or deal with iterator objects yourself. The :keyword:`for` statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop. See also :term:`iterator`, :term:`sequence`, and :term:`generator`. @@ -666,8 +670,8 @@ Glossary :func:`next`) return successive items in the stream. When no more data are available a :exc:`StopIteration` exception is raised instead. At this point, the iterator object is exhausted and any further calls to its - :meth:`__next__` method just raise :exc:`StopIteration` again. Iterators - are required to have an :meth:`__iter__` method that returns the iterator + :meth:`!__next__` method just raise :exc:`StopIteration` again. Iterators + are required to have an :meth:`~iterator.__iter__` method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted. One notable exception is code which attempts multiple iteration passes. A container object (such as a @@ -681,7 +685,7 @@ Glossary .. impl-detail:: CPython does not consistently apply the requirement that an iterator - define :meth:`__iter__`. + define :meth:`~iterator.__iter__`. key function A key function or collation function is a callable that returns a value @@ -875,7 +879,8 @@ Glossary Old name for the flavor of classes now used for all class objects. In earlier Python versions, only new-style classes could use Python's newer, versatile features like :attr:`~object.__slots__`, descriptors, - properties, :meth:`__getattribute__`, class methods, and static methods. + properties, :meth:`~object.__getattribute__`, class methods, and static + methods. object Any data with state (attributes or value) and defined behavior @@ -955,7 +960,7 @@ Glossary finders implement. path entry hook - A callable on the :data:`sys.path_hook` list which returns a :term:`path + A callable on the :data:`sys.path_hooks` list which returns a :term:`path entry finder` if it knows how to find modules on a specific :term:`path entry`. @@ -1089,18 +1094,18 @@ Glossary sequence An :term:`iterable` which supports efficient element access using integer indices via the :meth:`~object.__getitem__` special method and defines a - :meth:`__len__` method that returns the length of the sequence. + :meth:`~object.__len__` method that returns the length of the sequence. Some built-in sequence types are :class:`list`, :class:`str`, :class:`tuple`, and :class:`bytes`. Note that :class:`dict` also - supports :meth:`~object.__getitem__` and :meth:`__len__`, but is considered a + supports :meth:`~object.__getitem__` and :meth:`!__len__`, but is considered a mapping rather than a sequence because the lookups use arbitrary :term:`immutable` keys rather than integers. The :class:`collections.abc.Sequence` abstract base class defines a much richer interface that goes beyond just - :meth:`~object.__getitem__` and :meth:`__len__`, adding :meth:`count`, - :meth:`index`, :meth:`__contains__`, and - :meth:`__reversed__`. Types that implement this expanded + :meth:`~object.__getitem__` and :meth:`~object.__len__`, adding + :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`. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index f204b287b565eb..44c13bd9474ea1 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -44,7 +44,8 @@ Any object can be tested for truth value, for use in an :keyword:`if` or .. index:: single: true By default, an object is considered true unless its class defines either a -:meth:`~object.__bool__` method that returns ``False`` or a :meth:`__len__` method that +:meth:`~object.__bool__` method that returns ``False`` or a +:meth:`~object.__len__` method that returns zero, when called with the object. [1]_ Here are most of the built-in objects considered false: @@ -197,7 +198,7 @@ exception. Two more operations with the same syntactic priority, :keyword:`in` and :keyword:`not in`, are supported by types that are :term:`iterable` or -implement the :meth:`__contains__` method. +implement the :meth:`~object.__contains__` method. .. _typesnumeric: @@ -717,7 +718,7 @@ that's defined for any rational number, and hence applies to all instances of :class:`int` and :class:`fractions.Fraction`, and all finite instances of :class:`float` and :class:`decimal.Decimal`. Essentially, this function is given by reduction modulo ``P`` for a fixed prime ``P``. The value of ``P`` is -made available to Python as the :attr:`modulus` attribute of +made available to Python as the :attr:`~sys.hash_info.modulus` attribute of :data:`sys.hash_info`. .. impl-detail:: @@ -906,9 +907,9 @@ Generator Types --------------- Python's :term:`generator`\s provide a convenient way to implement the iterator -protocol. If a container object's :meth:`__iter__` method is implemented as a +protocol. If a container object's :meth:`~iterator.__iter__` method is implemented as a generator, it will automatically return an iterator object (technically, a -generator object) supplying the :meth:`__iter__` and :meth:`~generator.__next__` +generator object) supplying the :meth:`!__iter__` and :meth:`~generator.__next__` methods. More information about generators can be found in :ref:`the documentation for the yield expression `. @@ -3672,7 +3673,7 @@ The conversion types are: +------------+-----------------------------------------------------+-------+ | ``'b'`` | Bytes (any object that follows the | \(5) | | | :ref:`buffer protocol ` or has | | -| | :meth:`__bytes__`). | | +| | :meth:`~object.__bytes__`). | | +------------+-----------------------------------------------------+-------+ | ``'s'`` | ``'s'`` is an alias for ``'b'`` and should only | \(6) | | | be used for Python2/3 code bases. | | @@ -4410,7 +4411,8 @@ The constructors for both classes work the same: :meth:`symmetric_difference_update` methods will accept any iterable as an argument. - Note, the *elem* argument to the :meth:`__contains__`, :meth:`remove`, and + Note, the *elem* argument to the :meth:`~object.__contains__`, + :meth:`remove`, and :meth:`discard` methods may be a set. To support searching for an equivalent frozenset, a temporary one is created from *elem*. @@ -5236,9 +5238,11 @@ instantiated from the type:: TypeError: cannot create 'types.UnionType' instances .. note:: - The :meth:`__or__` method for type objects was added to support the syntax - ``X | Y``. If a metaclass implements :meth:`__or__`, the Union may - override it:: + The :meth:`!__or__` method for type objects was added to support the syntax + ``X | Y``. If a metaclass implements :meth:`!__or__`, the Union may + override it: + + .. doctest:: >>> class M(type): ... def __or__(self, other): @@ -5250,7 +5254,7 @@ instantiated from the type:: >>> C | int 'Hello' >>> int | C - int | __main__.C + int | C .. seealso:: From 00cce0fe495ee820cd3ca5878bdbe3dd65b1be7b Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Wed, 6 Dec 2023 03:44:41 -0600 Subject: [PATCH 127/442] gh-111178: Docs: fix `traverseproc`, `inquiry`, and `destructor` parameters in slot typedefs table (GH-112742) In the slot typedefs table, the parameter of `destructor` and the first parameter of `traverseproc` should both be `PyObject *` rather than `void *`. Same for `inquiry`. --- Doc/c-api/typeobj.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 10c05beda7c66f..8a26f237652d12 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -343,13 +343,13 @@ slot typedefs | | :c:type:`PyTypeObject` * | | | | :c:type:`Py_ssize_t` | | +-----------------------------+-----------------------------+----------------------+ -| :c:type:`destructor` | void * | void | +| :c:type:`destructor` | :c:type:`PyObject` * | void | +-----------------------------+-----------------------------+----------------------+ | :c:type:`freefunc` | void * | void | +-----------------------------+-----------------------------+----------------------+ | :c:type:`traverseproc` | .. line-block:: | int | | | | | -| | void * | | +| | :c:type:`PyObject` * | | | | :c:type:`visitproc` | | | | void * | | +-----------------------------+-----------------------------+----------------------+ @@ -426,7 +426,7 @@ slot typedefs | | :c:type:`PyObject` * | | | | :c:type:`Py_buffer` * | | +-----------------------------+-----------------------------+----------------------+ -| :c:type:`inquiry` | void * | int | +| :c:type:`inquiry` | :c:type:`PyObject` * | int | +-----------------------------+-----------------------------+----------------------+ | :c:type:`unaryfunc` | .. line-block:: | :c:type:`PyObject` * | | | | | From f8852634edf1232ac1aa4561e34796b52f9f7aa2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 6 Dec 2023 12:55:58 +0100 Subject: [PATCH 128/442] gh-108223: Refer to PEP 703 as Free Threading (#112780) --- Doc/using/configure.rst | 5 ++++- Lib/test/libregrtest/utils.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 56d2d6dc4ab5f1..cb7eda42fe3fad 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -292,7 +292,10 @@ General Options .. option:: --disable-gil Enables **experimental** support for running Python without the - :term:`global interpreter lock` (GIL). + :term:`global interpreter lock` (GIL): free threading build. + + Defines the ``Py_GIL_DISABLED`` macro and adds ``"t"`` to + :data:`sys.abiflags`. See :pep:`703` "Making the Global Interpreter Lock Optional in CPython". diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index d4972ce4a50d2a..26481e71221ade 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -291,7 +291,7 @@ def get_build_info(): # --disable-gil if sysconfig.get_config_var('Py_GIL_DISABLED'): - build.append("nogil") + build.append("free_threading") if hasattr(sys, 'gettotalrefcount'): # --with-pydebug From 828451dfde324f9499ffebc023a22b84dc5a125b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 6 Dec 2023 15:09:22 +0100 Subject: [PATCH 129/442] gh-111545: Add Py_HashPointer() function (#112096) * Implement _Py_HashPointerRaw() as a static inline function. * Add Py_HashPointer() tests to test_capi.test_hash. * Keep _Py_HashPointer() function as an alias to Py_HashPointer(). --- Doc/c-api/hash.rst | 10 ++++ Doc/whatsnew/3.13.rst | 3 ++ Include/cpython/pyhash.h | 6 ++- Include/internal/pycore_pyhash.h | 16 ++++++- Lib/test/test_capi/test_hash.py | 48 ++++++++++++++++++- ...-11-15-01-26-59.gh-issue-111545.iAoFtA.rst | 2 + Modules/_testcapi/hash.c | 16 +++++++ Python/hashtable.c | 2 +- Python/pyhash.c | 22 ++------- 9 files changed, 103 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2023-11-15-01-26-59.gh-issue-111545.iAoFtA.rst diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst index 3bfaf8b9f54c14..91d88ae27bc9f4 100644 --- a/Doc/c-api/hash.rst +++ b/Doc/c-api/hash.rst @@ -49,3 +49,13 @@ See also the :c:member:`PyTypeObject.tp_hash` member. :pep:`456` "Secure and interchangeable hash algorithm". .. versionadded:: 3.4 + + +.. c:function:: Py_hash_t Py_HashPointer(const void *ptr) + + Hash a pointer value: process the pointer value as an integer (cast it to + ``uintptr_t`` internally). The pointer is not dereferenced. + + The function cannot fail: it cannot return ``-1``. + + .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 534813f3659c9d..c9facad6375ef3 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1249,6 +1249,9 @@ New Features :exc:`KeyError` if the key missing. (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.) +* Add :c:func:`Py_HashPointer` function to hash a pointer. + (Contributed by Victor Stinner in :gh:`111545`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/pyhash.h b/Include/cpython/pyhash.h index 6f7113daa5fe4d..396c208e1b106a 100644 --- a/Include/cpython/pyhash.h +++ b/Include/cpython/pyhash.h @@ -21,7 +21,9 @@ /* Helpers for hash functions */ PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject *, double); -PyAPI_FUNC(Py_hash_t) _Py_HashPointer(const void*); + +// Kept for backward compatibility +#define _Py_HashPointer Py_HashPointer /* hash function definition */ @@ -33,3 +35,5 @@ typedef struct { } PyHash_FuncDef; PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void); + +PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr); diff --git a/Include/internal/pycore_pyhash.h b/Include/internal/pycore_pyhash.h index c3b72d90de3a69..0ce08900e96f0b 100644 --- a/Include/internal/pycore_pyhash.h +++ b/Include/internal/pycore_pyhash.h @@ -5,8 +5,20 @@ # error "this header requires Py_BUILD_CORE define" #endif -// Similar to _Py_HashPointer(), but don't replace -1 with -2 -extern Py_hash_t _Py_HashPointerRaw(const void*); +// Similar to Py_HashPointer(), but don't replace -1 with -2. +static inline Py_hash_t +_Py_HashPointerRaw(const void *ptr) +{ + uintptr_t x = (uintptr_t)ptr; + Py_BUILD_ASSERT(sizeof(x) == sizeof(ptr)); + + // Bottom 3 or 4 bits are likely to be 0; rotate x by 4 to the right + // to avoid excessive hash collisions for dicts and sets. + x = (x >> 4) | (x << (8 * sizeof(uintptr_t) - 4)); + + Py_BUILD_ASSERT(sizeof(x) == sizeof(Py_hash_t)); + return (Py_hash_t)x; +} // Export for '_datetime' shared extension PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t); diff --git a/Lib/test/test_capi/test_hash.py b/Lib/test/test_capi/test_hash.py index 59dec15bc21445..8436da7c32df10 100644 --- a/Lib/test/test_capi/test_hash.py +++ b/Lib/test/test_capi/test_hash.py @@ -4,7 +4,8 @@ _testcapi = import_helper.import_module('_testcapi') -SIZEOF_PY_HASH_T = _testcapi.SIZEOF_VOID_P +SIZEOF_VOID_P = _testcapi.SIZEOF_VOID_P +SIZEOF_PY_HASH_T = SIZEOF_VOID_P class CAPITest(unittest.TestCase): @@ -31,3 +32,48 @@ def test_hash_getfuncdef(self): self.assertEqual(func_def.name, hash_info.algorithm) self.assertEqual(func_def.hash_bits, hash_info.hash_bits) self.assertEqual(func_def.seed_bits, hash_info.seed_bits) + + def test_hash_pointer(self): + # Test Py_HashPointer() + hash_pointer = _testcapi.hash_pointer + + UHASH_T_MASK = ((2 ** (8 * SIZEOF_PY_HASH_T)) - 1) + HASH_T_MAX = (2 ** (8 * SIZEOF_PY_HASH_T - 1) - 1) + + def python_hash_pointer(x): + # Py_HashPointer() rotates the pointer bits by 4 bits to the right + x = (x >> 4) | ((x & 15) << (8 * SIZEOF_VOID_P - 4)) + + # Convert unsigned uintptr_t (Py_uhash_t) to signed Py_hash_t + if HASH_T_MAX < x: + x = (~x) + 1 + x &= UHASH_T_MASK + x = (~x) + 1 + return x + + if SIZEOF_VOID_P == 8: + values = ( + 0xABCDEF1234567890, + 0x1234567890ABCDEF, + 0xFEE4ABEDD1CECA5E, + ) + else: + values = ( + 0x12345678, + 0x1234ABCD, + 0xDEADCAFE, + ) + + for value in values: + expected = python_hash_pointer(value) + with self.subTest(value=value): + self.assertEqual(hash_pointer(value), expected, + f"hash_pointer({value:x}) = " + f"{hash_pointer(value):x} != {expected:x}") + + # Py_HashPointer(NULL) returns 0 + self.assertEqual(hash_pointer(0), 0) + + # Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2 + VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1) + self.assertEqual(hash_pointer(VOID_P_MAX), -2) diff --git a/Misc/NEWS.d/next/C API/2023-11-15-01-26-59.gh-issue-111545.iAoFtA.rst b/Misc/NEWS.d/next/C API/2023-11-15-01-26-59.gh-issue-111545.iAoFtA.rst new file mode 100644 index 00000000000000..7bde2498acf999 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-15-01-26-59.gh-issue-111545.iAoFtA.rst @@ -0,0 +1,2 @@ +Add :c:func:`Py_HashPointer` function to hash a pointer. Patch by Victor +Stinner. diff --git a/Modules/_testcapi/hash.c b/Modules/_testcapi/hash.c index d0b8127020c5c1..aee76787dcddb3 100644 --- a/Modules/_testcapi/hash.c +++ b/Modules/_testcapi/hash.c @@ -44,8 +44,24 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) return result; } + +static PyObject * +hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg) +{ + void *ptr = PyLong_AsVoidPtr(arg); + if (ptr == NULL && PyErr_Occurred()) { + return NULL; + } + + Py_hash_t hash = Py_HashPointer(ptr); + Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash)); + return PyLong_FromLongLong(hash); +} + + static PyMethodDef test_methods[] = { {"hash_getfuncdef", hash_getfuncdef, METH_NOARGS}, + {"hash_pointer", hash_pointer, METH_O}, {NULL}, }; diff --git a/Python/hashtable.c b/Python/hashtable.c index 8f5e8168ba1339..faf68fe4ff0bca 100644 --- a/Python/hashtable.c +++ b/Python/hashtable.c @@ -45,7 +45,7 @@ */ #include "Python.h" -#include "pycore_hashtable.h" +#include "pycore_hashtable.h" // export _Py_hashtable_new() #include "pycore_pyhash.h" // _Py_HashPointerRaw() #define HASHTABLE_MIN_SIZE 16 diff --git a/Python/pyhash.c b/Python/pyhash.c index f9060b8003a0a7..141407c265677a 100644 --- a/Python/pyhash.c +++ b/Python/pyhash.c @@ -83,8 +83,6 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0}; */ -Py_hash_t _Py_HashPointer(const void *); - Py_hash_t _Py_HashDouble(PyObject *inst, double v) { @@ -132,23 +130,13 @@ _Py_HashDouble(PyObject *inst, double v) } Py_hash_t -_Py_HashPointerRaw(const void *p) -{ - size_t y = (size_t)p; - /* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid - excessive hash collisions for dicts and sets */ - y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4)); - return (Py_hash_t)y; -} - -Py_hash_t -_Py_HashPointer(const void *p) +Py_HashPointer(const void *ptr) { - Py_hash_t x = _Py_HashPointerRaw(p); - if (x == -1) { - x = -2; + Py_hash_t hash = _Py_HashPointerRaw(ptr); + if (hash == -1) { + hash = -2; } - return x; + return hash; } Py_hash_t From cc7e45cc572dd818412a649970fdee579417701f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 6 Dec 2023 16:42:15 +0200 Subject: [PATCH 130/442] gh-75666: Tkinter: "unbind(sequence, funcid)" now only unbinds "funcid" (GH-111322) Previously, "widget.unbind(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. Co-authored-by: GiovanniL <13402461+GiovaLomba@users.noreply.github.com> --- Lib/test/test_tkinter/test_misc.py | 34 +++++++++++++++---- Lib/tkinter/__init__.py | 22 +++++++++--- ...3-10-25-16-37-13.gh-issue-75666.BpsWut.rst | 6 ++++ 3 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index ca99caaf88b80d..6639eaaa59936a 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -479,26 +479,46 @@ def test2(e): pass def test_unbind2(self): f = self.frame + f.wait_visibility() + f.focus_force() + f.update_idletasks() event = '' self.assertEqual(f.bind(), ()) self.assertEqual(f.bind(event), '') - def test1(e): pass - def test2(e): pass + def test1(e): events.append('a') + def test2(e): events.append('b') + def test3(e): events.append('c') funcid = f.bind(event, test1) funcid2 = f.bind(event, test2, add=True) + funcid3 = f.bind(event, test3, add=True) + events = [] + f.event_generate(event) + self.assertEqual(events, ['a', 'b', 'c']) - f.unbind(event, funcid) + f.unbind(event, funcid2) script = f.bind(event) - self.assertNotIn(funcid, script) - self.assertCommandNotExist(funcid) - self.assertCommandExist(funcid2) + self.assertNotIn(funcid2, script) + self.assertIn(funcid, script) + self.assertIn(funcid3, script) + self.assertEqual(f.bind(), (event,)) + self.assertCommandNotExist(funcid2) + self.assertCommandExist(funcid) + self.assertCommandExist(funcid3) + events = [] + f.event_generate(event) + self.assertEqual(events, ['a', 'c']) - f.unbind(event, funcid2) + f.unbind(event, funcid) + f.unbind(event, funcid3) self.assertEqual(f.bind(event), '') self.assertEqual(f.bind(), ()) self.assertCommandNotExist(funcid) self.assertCommandNotExist(funcid2) + self.assertCommandNotExist(funcid3) + events = [] + f.event_generate(event) + self.assertEqual(events, []) # non-idempotent self.assertRaises(tkinter.TclError, f.unbind, event, funcid2) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 0df7f9d889413c..124882420c255c 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1527,10 +1527,24 @@ def bind(self, sequence=None, func=None, add=None): return self._bind(('bind', self._w), sequence, func, add) def unbind(self, sequence, funcid=None): - """Unbind for this widget for event SEQUENCE the - function identified with FUNCID.""" - self.tk.call('bind', self._w, sequence, '') - if funcid: + """Unbind for this widget the event SEQUENCE. + + If FUNCID is given, only unbind the function identified with FUNCID + and also delete the corresponding Tcl command. + + Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE + unbound. + """ + if funcid is None: + self.tk.call('bind', self._w, sequence, '') + else: + lines = self.tk.call('bind', self._w, sequence).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.deletecommand(funcid) def bind_all(self, sequence=None, func=None, add=None): diff --git a/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst new file mode 100644 index 00000000000000..d774cc4f7c687f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-25-16-37-13.gh-issue-75666.BpsWut.rst @@ -0,0 +1,6 @@ +Fix the behavior of :mod:`tkinter` widget's ``unbind()`` method with two +arguments. Previously, ``widget.unbind(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 b920d6ceaa5532bf2bc8128e006229ec583374d0 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Wed, 6 Dec 2023 09:30:37 -0600 Subject: [PATCH 131/442] gh-111178: Define `visitproc` callback functions properly and remove unnecessary casts in gcmodule.c (#112687) --- Modules/gcmodule.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 568e02a4210a2b..8233fc56159b60 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -491,15 +491,16 @@ subtract_refs(PyGC_Head *containers) PyObject *op = FROM_GC(gc); traverse = Py_TYPE(op)->tp_traverse; (void) traverse(op, - (visitproc)visit_decref, + visit_decref, op); } } /* A traversal callback for move_unreachable. */ static int -visit_reachable(PyObject *op, PyGC_Head *reachable) +visit_reachable(PyObject *op, void *arg) { + PyGC_Head *reachable = arg; OBJECT_STAT_INC(object_visits); if (!_PyObject_IS_GC(op)) { return 0; @@ -603,7 +604,7 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable) // NOTE: visit_reachable may change gc->_gc_next when // young->_gc_prev == gc. Don't do gc = GC_NEXT(gc) before! (void) traverse(op, - (visitproc)visit_reachable, + visit_reachable, (void *)young); // relink gc_prev to prev element. _PyGCHead_SET_PREV(gc, prev); @@ -726,8 +727,9 @@ clear_unreachable_mask(PyGC_Head *unreachable) /* A traversal callback for move_legacy_finalizer_reachable. */ static int -visit_move(PyObject *op, PyGC_Head *tolist) +visit_move(PyObject *op, void *arg) { + PyGC_Head *tolist = arg; OBJECT_STAT_INC(object_visits); if (_PyObject_IS_GC(op)) { PyGC_Head *gc = AS_GC(op); @@ -751,7 +753,7 @@ move_legacy_finalizer_reachable(PyGC_Head *finalizers) /* Note that the finalizers list may grow during this. */ traverse = Py_TYPE(FROM_GC(gc))->tp_traverse; (void) traverse(FROM_GC(gc), - (visitproc)visit_move, + visit_move, (void *)finalizers); } } @@ -1684,8 +1686,9 @@ gc_get_count_impl(PyObject *module) } static int -referrersvisit(PyObject* obj, PyObject *objs) +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) @@ -1704,7 +1707,7 @@ gc_referrers_for(PyObject *objs, PyGC_Head *list, PyObject *resultlist) traverse = Py_TYPE(obj)->tp_traverse; if (obj == objs || obj == resultlist) continue; - if (traverse(obj, (visitproc)referrersvisit, objs)) { + if (traverse(obj, referrersvisit, objs)) { if (PyList_Append(resultlist, obj) < 0) return 0; /* error */ } @@ -1740,8 +1743,9 @@ gc_get_referrers(PyObject *self, PyObject *args) /* Append obj to list; return true if error (out of memory), false if OK. */ static int -referentsvisit(PyObject *obj, PyObject *list) +referentsvisit(PyObject *obj, void *arg) { + PyObject *list = arg; return PyList_Append(list, obj) < 0; } @@ -1770,7 +1774,7 @@ gc_get_referents(PyObject *self, PyObject *args) traverse = Py_TYPE(obj)->tp_traverse; if (! traverse) continue; - if (traverse(obj, (visitproc)referentsvisit, result)) { + if (traverse(obj, referentsvisit, result)) { Py_DECREF(result); return NULL; } From e9707d3c3deb45a8352e85dbd5cf41afdb4a2a26 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 6 Dec 2023 20:15:46 +0000 Subject: [PATCH 132/442] gh-101100: Improve documentation of code object attributes (#112781) --- Doc/c-api/function.rst | 4 +- Doc/c-api/import.rst | 2 +- Doc/library/dis.rst | 19 +++--- Doc/library/inspect.rst | 4 +- Doc/reference/datamodel.rst | 114 ++++++++++++++++++++++++++---------- Doc/whatsnew/2.7.rst | 2 +- Doc/whatsnew/3.10.rst | 3 +- Doc/whatsnew/3.12.rst | 5 +- Doc/whatsnew/3.13.rst | 5 +- Doc/whatsnew/3.6.rst | 7 ++- 10 files changed, 112 insertions(+), 53 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 5857dba82c11c6..0a18e63c7e7a2c 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -37,7 +37,7 @@ There are a few functions specific to Python functions. The function's docstring and name are retrieved from the code object. *__module__* is retrieved from *globals*. The argument defaults, annotations and closure are set to ``NULL``. *__qualname__* is set to the same value as the code object's - ``co_qualname`` field. + :attr:`~codeobject.co_qualname` field. .. c:function:: PyObject* PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname) @@ -45,7 +45,7 @@ There are a few functions specific to Python functions. As :c:func:`PyFunction_New`, but also allows setting the function object's ``__qualname__`` attribute. *qualname* should be a unicode object or ``NULL``; if ``NULL``, the ``__qualname__`` attribute is set to the same value as the - code object's ``co_qualname`` field. + code object's :attr:`~codeobject.co_qualname` field. .. versionadded:: 3.3 diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 137780cc359cf9..51c20b202f091c 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -154,7 +154,7 @@ Importing Modules :class:`~importlib.machinery.SourceFileLoader` otherwise. The module's :attr:`__file__` attribute will be set to the code object's - :attr:`!co_filename`. If applicable, :attr:`__cached__` will also + :attr:`~codeobject.co_filename`. If applicable, :attr:`__cached__` will also be set. This function will reload the module if it was already imported. See diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index cf238f81b9cc64..0d93bc9f5da774 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -346,8 +346,9 @@ operation is being performed, so the intermediate analysis object isn't useful: Line numbers can be decreasing. Before, they were always increasing. .. versionchanged:: 3.10 - The :pep:`626` ``co_lines`` method is used instead of the ``co_firstlineno`` - and ``co_lnotab`` attributes of the code object. + The :pep:`626` ``co_lines`` method is used instead of the + :attr:`~codeobject.co_firstlineno` and :attr:`~codeobject.co_lnotab` + attributes of the code object. .. versionchanged:: 3.13 Line numbers can be ``None`` for bytecode that does not map to source lines. @@ -983,13 +984,13 @@ iterations of the loop. .. opcode:: STORE_NAME (namei) Implements ``name = STACK.pop()``. *namei* is the index of *name* in the attribute - :attr:`!co_names` of the :ref:`code object `. + :attr:`~codeobject.co_names` of the :ref:`code object `. The compiler tries to use :opcode:`STORE_FAST` or :opcode:`STORE_GLOBAL` if possible. .. opcode:: DELETE_NAME (namei) - Implements ``del name``, where *namei* is the index into :attr:`!co_names` + Implements ``del name``, where *namei* is the index into :attr:`~codeobject.co_names` attribute of the :ref:`code object `. @@ -1029,7 +1030,7 @@ iterations of the loop. value = STACK.pop() obj.name = value - where *namei* is the index of name in :attr:`!co_names` of the + where *namei* is the index of name in :attr:`~codeobject.co_names` of the :ref:`code object `. .. opcode:: DELETE_ATTR (namei) @@ -1039,7 +1040,7 @@ iterations of the loop. obj = STACK.pop() del obj.name - where *namei* is the index of name into :attr:`!co_names` of the + where *namei* is the index of name into :attr:`~codeobject.co_names` of the :ref:`code object `. @@ -1402,7 +1403,7 @@ iterations of the loop. Pushes a reference to the object the cell contains on the stack. .. versionchanged:: 3.11 - ``i`` is no longer offset by the length of ``co_varnames``. + ``i`` is no longer offset by the length of :attr:`~codeobject.co_varnames`. .. opcode:: LOAD_FROM_DICT_OR_DEREF (i) @@ -1424,7 +1425,7 @@ iterations of the loop. storage. .. versionchanged:: 3.11 - ``i`` is no longer offset by the length of ``co_varnames``. + ``i`` is no longer offset by the length of :attr:`~codeobject.co_varnames`. .. opcode:: DELETE_DEREF (i) @@ -1435,7 +1436,7 @@ iterations of the loop. .. versionadded:: 3.2 .. versionchanged:: 3.11 - ``i`` is no longer offset by the length of ``co_varnames``. + ``i`` is no longer offset by the length of :attr:`~codeobject.co_varnames`. .. opcode:: COPY_FREE_VARS (n) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 08f15ae09b1b87..0138557f5fd84c 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1596,8 +1596,8 @@ updated as expected: Code Objects Bit Flags ---------------------- -Python code objects have a ``co_flags`` attribute, which is a bitmap of -the following flags: +Python code objects have a :attr:`~codeobject.co_flags` attribute, +which is a bitmap of the following flags: .. data:: CO_OPTIMIZED diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 8a94b7bb22c362..3bcc170faa087a 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1077,57 +1077,111 @@ indirectly) to mutable objects. single: co_freevars (code object attribute) single: co_qualname (code object attribute) -Special read-only attributes: :attr:`co_name` gives the function name; -:attr:`co_qualname` gives the fully qualified function name; -:attr:`co_argcount` is the total number of positional arguments -(including positional-only arguments and arguments with default values); -:attr:`co_posonlyargcount` is the number of positional-only arguments -(including arguments with default values); :attr:`co_kwonlyargcount` is -the number of keyword-only arguments (including arguments with default -values); :attr:`co_nlocals` is the number of local variables used by the -function (including arguments); :attr:`co_varnames` is a tuple containing -the names of the local variables (starting with the argument names); -:attr:`co_cellvars` is a tuple containing the names of local variables -that are referenced by nested functions; :attr:`co_freevars` is a tuple -containing the names of free variables; :attr:`co_code` is a string -representing the sequence of bytecode instructions; :attr:`co_consts` is -a tuple containing the literals used by the bytecode; :attr:`co_names` is -a tuple containing the names used by the bytecode; :attr:`co_filename` is -the filename from which the code was compiled; :attr:`co_firstlineno` is -the first line number of the function; :attr:`co_lnotab` is a string -encoding the mapping from bytecode offsets to line numbers (for details -see the source code of the interpreter, is deprecated since 3.12 -and may be removed in 3.14); :attr:`co_stacksize` is the -required stack size; :attr:`co_flags` is an integer encoding a number -of flags for the interpreter. +Special read-only attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + + * - .. attribute:: codeobject.co_name + - The function name + + * - .. attribute:: codeobject.co_qualname + - The fully qualified function name + + * - .. attribute:: codeobject.co_argcount + - The total number of positional :term:`parameters ` + (including positional-only parameters and parameters with default values) + that the function has + + * - .. attribute:: codeobject.co_posonlyargcount + - The number of positional-only :term:`parameters ` + (including arguments with default values) that the function has + + * - .. attribute:: codeobject.co_kwonlyargcount + - The number of keyword-only :term:`parameters ` + (including arguments with default values) that the function has + + * - .. attribute:: codeobject.co_nlocals + - The number of :ref:`local variables ` used by the function + (including parameters) + + * - .. attribute:: codeobject.co_varnames + - A :class:`tuple` containing the names of the local variables in the + function (starting with the parameter names) + + * - .. attribute:: codeobject.co_cellvars + - A :class:`tuple` containing the names of :ref:`local variables ` + that are referenced by nested functions inside the function + + * - .. attribute:: codeobject.co_freevars + - A :class:`tuple` containing the names of free variables in the function + + * - .. attribute:: codeobject.co_code + - A string representing the sequence of :term:`bytecode` instructions in + the function + + * - .. attribute:: codeobject.co_consts + - A :class:`tuple` containing the literals used by the :term:`bytecode` in + the function + + * - .. attribute:: codeobject.co_names + - A :class:`tuple` containing the names used by the :term:`bytecode` in + the function + + * - .. attribute:: codeobject.co_filename + - The name of the file from which the code was compiled + + * - .. attribute:: codeobject.co_firstlineno + - The line number of the first line of the function + + * - .. attribute:: codeobject.co_lnotab + - A string encoding the mapping from :term:`bytecode` offsets to line + numbers. For details, see the source code of the interpreter. + + .. deprecated:: 3.12 + This attribute of code objects is deprecated, and may be removed in + Python 3.14. + + * - .. attribute:: codeobject.co_stacksize + - The required stack size of the code object + + * - .. attribute:: codeobject.co_flags + - An :class:`integer ` encoding a number of flags for the + interpreter. .. index:: pair: object; generator -The following flag bits are defined for :attr:`co_flags`: bit ``0x04`` is set if +The following flag bits are defined for :attr:`~codeobject.co_flags`: +bit ``0x04`` is set if the function uses the ``*arguments`` syntax to accept an arbitrary number of positional arguments; bit ``0x08`` is set if the function uses the ``**keywords`` syntax to accept arbitrary keyword arguments; bit ``0x20`` is set -if the function is a generator. +if the function is a generator. See :ref:`inspect-module-co-flags` for details +on the semantics of each flags that might be present. Future feature declarations (``from __future__ import division``) also use bits -in :attr:`co_flags` to indicate whether a code object was compiled with a +in :attr:`~codeobject.co_flags` to indicate whether a code object was compiled with a particular feature enabled: bit ``0x2000`` is set if the function was compiled with future division enabled; bits ``0x10`` and ``0x1000`` were used in earlier versions of Python. -Other bits in :attr:`co_flags` are reserved for internal use. +Other bits in :attr:`~codeobject.co_flags` are reserved for internal use. .. index:: single: documentation string -If a code object represents a function, the first item in :attr:`co_consts` is +If a code object represents a function, the first item in +:attr:`~codeobject.co_consts` is the documentation string of the function, or ``None`` if undefined. +The :meth:`!co_positions` method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .. method:: codeobject.co_positions() - Returns an iterable over the source code positions of each bytecode + Returns an iterable over the source code positions of each :term:`bytecode` instruction in the code object. - The iterator returns tuples containing the ``(start_line, end_line, + The iterator returns :class:`tuple`\s containing the ``(start_line, end_line, start_column, end_column)``. The *i-th* tuple corresponds to the position of the source code that compiled to the *i-th* instruction. Column information is 0-indexed utf-8 byte offsets on the given source diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 4072e040dc9130..162dd74637479a 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -2405,7 +2405,7 @@ Other Changes and Fixes :issue:`5464`.) * When importing a module from a :file:`.pyc` or :file:`.pyo` file - with an existing :file:`.py` counterpart, the :attr:`co_filename` + with an existing :file:`.py` counterpart, the :attr:`~codeobject.co_filename` attributes of the resulting code objects are overwritten when the original filename is obsolete. This can happen if the file has been renamed, moved, or is accessed through different paths. (Patch by diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 15479cc979624f..2da90b7ed55744 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -402,7 +402,8 @@ Tracing events, with the correct line number, are generated for all lines of cod The :attr:`~frame.f_lineno` attribute of frame objects will always contain the expected line number. -The ``co_lnotab`` attribute of code objects is deprecated and will be removed in 3.12. +The :attr:`~codeobject.co_lnotab` attribute of code objects is deprecated and +will be removed in 3.12. Code that needs to convert from offset to line number should use the new ``co_lines()`` method instead. PEP 634: Structural Pattern Matching diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index fc17c86665335c..07d22a4a5fb773 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1323,7 +1323,8 @@ Deprecated ``int``, convert to int explicitly: ``~int(x)``. (Contributed by Tim Hoffmann in :gh:`103487`.) -* Accessing ``co_lnotab`` on code objects was deprecated in Python 3.10 via :pep:`626`, +* Accessing :attr:`~codeobject.co_lnotab` on code objects was deprecated in + Python 3.10 via :pep:`626`, but it only got a proper :exc:`DeprecationWarning` in 3.12, therefore it will be removed in 3.14. (Contributed by Nikita Sobolev in :gh:`101866`.) @@ -1430,7 +1431,7 @@ and will be removed in Python 3.14. * The ``__package__`` and ``__cached__`` attributes on module objects. -* The ``co_lnotab`` attribute of code objects. +* The :attr:`~codeobject.co_lnotab` attribute of code objects. Pending Removal in Python 3.15 ------------------------------ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c9facad6375ef3..f5723e050a2762 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -572,7 +572,8 @@ Pending Removal in Python 3.14 * date and datetime adapter, date and timestamp converter: see the :mod:`sqlite3` documentation for suggested replacement recipes. -* :class:`types.CodeType`: Accessing ``co_lnotab`` was deprecated in :pep:`626` +* :class:`types.CodeType`: Accessing :attr:`~codeobject.co_lnotab` was + deprecated in :pep:`626` since 3.10 and was planned to be removed in 3.12, but it only got a proper :exc:`DeprecationWarning` in 3.12. May be removed in 3.14. @@ -735,7 +736,7 @@ although there is currently no date scheduled for their removal. * :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. -* ``types.CodeType.co_lnotab``: use the ``co_lines`` attribute instead. +* :attr:`~codeobject.co_lnotab`: use the ``co_lines`` attribute instead. * :class:`typing.Text` (:gh:`92332`). diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 2f618929793fc6..5a3cea0ec87cb2 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -2160,14 +2160,15 @@ Changes in the Python API * :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg** argument is not set. Previously only ``NULL`` was returned. -* The format of the ``co_lnotab`` attribute of code objects changed to support +* The format of the :attr:`~codeobject.co_lnotab` attribute of code objects + changed to support a negative line number delta. By default, Python does not emit bytecode with a negative line number delta. Functions using :attr:`frame.f_lineno`, ``PyFrame_GetLineNumber()`` or ``PyCode_Addr2Line()`` are not affected. - Functions directly decoding ``co_lnotab`` should be updated to use a signed + Functions directly decoding :attr:`!co_lnotab` should be updated to use a signed 8-bit integer type for the line number delta, but this is only required to support applications using a negative line number delta. See - ``Objects/lnotab_notes.txt`` for the ``co_lnotab`` format and how to decode + ``Objects/lnotab_notes.txt`` for the :attr:`!co_lnotab` format and how to decode it, and see the :pep:`511` for the rationale. * The functions in the :mod:`compileall` module now return booleans instead From 3870d19d151c31d77b737d6a480aa946b4e87af6 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 6 Dec 2023 20:16:12 +0000 Subject: [PATCH 133/442] gh-101100: Fix Sphinx nitpicks in `library/reprlib.rst` (#112811) --- Doc/library/reprlib.rst | 52 ++++++++++++++++++++++++----------------- Doc/tools/.nitignore | 1 - 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Doc/library/reprlib.rst b/Doc/library/reprlib.rst index 5ebb0a7780c37b..678a11c6f45490 100644 --- a/Doc/library/reprlib.rst +++ b/Doc/library/reprlib.rst @@ -10,7 +10,7 @@ -------------- -The :mod:`reprlib` module provides a means for producing object representations +The :mod:`!reprlib` module provides a means for producing object representations with limits on the size of the resulting strings. This is used in the Python debugger and may be useful in other contexts as well. @@ -58,29 +58,31 @@ This module provides a class, an instance, and a function: limits on most sizes. In addition to size-limiting tools, the module also provides a decorator for -detecting recursive calls to :meth:`__repr__` and substituting a placeholder -string instead. +detecting recursive calls to :meth:`~object.__repr__` and substituting a +placeholder string instead. .. index:: single: ...; placeholder .. decorator:: recursive_repr(fillvalue="...") - Decorator for :meth:`__repr__` methods to detect recursive calls within the + Decorator for :meth:`~object.__repr__` methods to detect recursive calls within the same thread. If a recursive call is made, the *fillvalue* is returned, - otherwise, the usual :meth:`__repr__` call is made. For example: - - >>> from reprlib import recursive_repr - >>> class MyList(list): - ... @recursive_repr() - ... def __repr__(self): - ... return '<' + '|'.join(map(repr, self)) + '>' - ... - >>> m = MyList('abc') - >>> m.append(m) - >>> m.append('x') - >>> print(m) - <'a'|'b'|'c'|...|'x'> + otherwise, the usual :meth:`!__repr__` call is made. For example: + + .. doctest:: + + >>> from reprlib import recursive_repr + >>> class MyList(list): + ... @recursive_repr() + ... def __repr__(self): + ... return '<' + '|'.join(map(repr, self)) + '>' + ... + >>> m = MyList('abc') + >>> m.append(m) + >>> m.append('x') + >>> print(m) + <'a'|'b'|'c'|...|'x'> .. versionadded:: 3.2 @@ -148,10 +150,10 @@ which format specific object types. with no line breaks or indentation, like the standard :func:`repr`. For example: - .. code-block:: pycon + .. doctest:: indent >>> example = [ - 1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] + ... 1, 'spam', {'a': 2, 'b': 'spam eggs', 'c': {3: 4.5, 6: []}}, 'ham'] >>> import reprlib >>> aRepr = reprlib.Repr() >>> print(aRepr.repr(example)) @@ -160,7 +162,7 @@ which format specific object types. If :attr:`~Repr.indent` is set to a string, each recursion level is placed on its own line, indented by that string: - .. code-block:: pycon + .. doctest:: indent >>> aRepr.indent = '-->' >>> print(aRepr.repr(example)) @@ -181,7 +183,7 @@ which format specific object types. Setting :attr:`~Repr.indent` to a positive integer value behaves as if it was set to a string with that number of spaces: - .. code-block:: pycon + .. doctest:: indent >>> aRepr.indent = 4 >>> print(aRepr.repr(example)) @@ -234,7 +236,9 @@ Subclassing Repr Objects The use of dynamic dispatching by :meth:`Repr.repr1` allows subclasses of :class:`Repr` to add support for additional built-in object types or to modify the handling of types already supported. This example shows how special support -for file objects could be added:: +for file objects could be added: + +.. testcode:: import reprlib import sys @@ -248,3 +252,7 @@ for file objects could be added:: aRepr = MyRepr() print(aRepr.repr(sys.stdin)) # prints '' + +.. testoutput:: + + diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 50f04d72c0dee0..ada1fc5fafc9c9 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -84,7 +84,6 @@ Doc/library/pydoc.rst Doc/library/pyexpat.rst Doc/library/random.rst Doc/library/readline.rst -Doc/library/reprlib.rst Doc/library/resource.rst Doc/library/rlcompleter.rst Doc/library/select.rst From 16448cab44e23d350824e9ac75e699f5bcc48a14 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Wed, 6 Dec 2023 22:29:54 +0000 Subject: [PATCH 134/442] gh-112730: Use color to highlight error locations (gh-112732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Galindo Co-authored-by: Łukasz Langa --- Doc/using/cmdline.rst | 27 +++ Doc/whatsnew/3.13.rst | 6 + Lib/test/test_traceback.py | 126 ++++++++++- Lib/traceback.py | 197 ++++++++++++++---- ...-12-04-23-09-07.gh-issue-112730.BXHlFa.rst | 1 + Modules/clinic/posixmodule.c.h | 28 ++- Modules/posixmodule.c | 22 ++ Python/initconfig.c | 2 + 8 files changed, 369 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-04-23-09-07.gh-issue-112730.BXHlFa.rst diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 39c8d114f1e2c5..56235bf4c28c7c 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -612,6 +612,27 @@ Miscellaneous options .. versionadded:: 3.13 The ``-X presite`` option. +Controlling Color +~~~~~~~~~~~~~~~~~ + +The Python interpreter is configured by default to use colors to highlight +output in certain situations such as when displaying tracebacks. This +behavior can be controlled by setting different environment variables. + +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. + +If the environment variable ``NO_COLOR`` is set, Python will disable all color +in the output. This takes precedence over ``FORCE_COLOR``. + +All these environment variables are used also by other tools to control color +output. To control the color output only in the Python interpreter, the +:envvar:`PYTHON_COLORS` environment variable can be used. This variable takes +precedence over ``NO_COLOR``, which in turn takes precedence over +``FORCE_COLOR``. Options you shouldn't use ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1110,6 +1131,12 @@ conflict. .. versionadded:: 3.13 +.. envvar:: PYTHON_COLORS + + If this variable is set to ``1``, the interpreter will colorize various kinds + of output. Setting it to ``0`` deactivates this behavior. + + .. versionadded:: 3.13 Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index f5723e050a2762..9adf7a3893bd70 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -85,7 +85,13 @@ Important deprecations, removals or restrictions: New Features ============ +Improved Error Messages +----------------------- +* The interpreter now colorizes error messages when displaying tracebacks by default. + This feature can be controlled via the new :envvar:`PYTHON_COLORS` environment + variable as well as the canonical ``NO_COLOR`` and ``FORCE_COLOR`` environment + variables. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) Other Language Changes ====================== diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index b60e06ff37f494..a6708119b81191 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -8,6 +8,7 @@ import inspect import builtins import unittest +import unittest.mock import re import tempfile import random @@ -24,6 +25,7 @@ import json import textwrap import traceback +import contextlib from functools import partial from pathlib import Path @@ -41,6 +43,14 @@ class TracebackCases(unittest.TestCase): # For now, a very minimal set of tests. I want to be sure that # formatting of SyntaxErrors works based on changes for 2.1. + def setUp(self): + super().setUp() + self.colorize = traceback._COLORIZE + traceback._COLORIZE = False + + def tearDown(self): + super().tearDown() + traceback._COLORIZE = self.colorize def get_exception_format(self, func, exc): try: @@ -521,7 +531,7 @@ def test_signatures(self): self.assertEqual( str(inspect.signature(traceback.print_exception)), ('(exc, /, value=, tb=, ' - 'limit=None, file=None, chain=True)')) + 'limit=None, file=None, chain=True, **kwargs)')) self.assertEqual( str(inspect.signature(traceback.format_exception)), @@ -3031,7 +3041,7 @@ def some_inner(k, v): def test_custom_format_frame(self): class CustomStackSummary(traceback.StackSummary): - def format_frame_summary(self, frame_summary): + def format_frame_summary(self, frame_summary, colorize=False): return f'{frame_summary.filename}:{frame_summary.lineno}' def some_inner(): @@ -3056,7 +3066,7 @@ def g(): tb = g() class Skip_G(traceback.StackSummary): - def format_frame_summary(self, frame_summary): + def format_frame_summary(self, frame_summary, colorize=False): if frame_summary.name == 'g': return None return super().format_frame_summary(frame_summary) @@ -3076,7 +3086,6 @@ def __repr__(self) -> str: raise Exception("Unrepresentable") class TestTracebackException(unittest.TestCase): - def do_test_smoke(self, exc, expected_type_str): try: raise exc @@ -4245,6 +4254,115 @@ def test_levenshtein_distance_short_circuit(self): res3 = traceback._levenshtein_distance(a, b, threshold) self.assertGreater(res3, threshold, msg=(a, b, threshold)) +class TestColorizedTraceback(unittest.TestCase): + def test_colorized_traceback(self): + def foo(*args): + x = {'a':{'b': None}} + y = x['a']['b']['c'] + + def baz(*args): + return foo(1,2,3,4) + + def bar(): + return baz(1, + 2,3 + ,4) + try: + bar() + except Exception as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + lines = "".join(exc.format(colorize=True)) + red = traceback._ANSIColors.RED + boldr = traceback._ANSIColors.BOLD_RED + reset = traceback._ANSIColors.RESET + self.assertIn("y = " + red + "x['a']['b']" + reset + boldr + "['c']" + reset, lines) + self.assertIn("return " + red + "foo" + reset + boldr + "(1,2,3,4)" + reset, lines) + self.assertIn("return " + red + "baz" + reset + boldr + "(1," + reset, lines) + self.assertIn(boldr + "2,3" + reset, lines) + self.assertIn(boldr + ",4)" + reset, lines) + self.assertIn(red + "bar" + reset + boldr + "()" + reset, lines) + + def test_colorized_syntax_error(self): + try: + compile("a $ b", "", "exec") + except SyntaxError as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + actual = "".join(exc.format(colorize=True)) + red = traceback._ANSIColors.RED + magenta = traceback._ANSIColors.MAGENTA + boldm = traceback._ANSIColors.BOLD_MAGENTA + boldr = traceback._ANSIColors.BOLD_RED + reset = traceback._ANSIColors.RESET + expected = "".join([ + f' File {magenta}""{reset}, line {magenta}1{reset}\n', + f' a {boldr}${reset} b\n', + f' {boldr}^{reset}\n', + f'{boldm}SyntaxError{reset}: {magenta}invalid syntax{reset}\n'] + ) + self.assertIn(expected, actual) + + def test_colorized_traceback_is_the_default(self): + def foo(): + 1/0 + + from _testcapi import exception_print + try: + foo() + self.fail("No exception thrown.") + except Exception as e: + with captured_output("stderr") as tbstderr: + with unittest.mock.patch('traceback._can_colorize', return_value=True): + exception_print(e) + actual = tbstderr.getvalue().splitlines() + + red = traceback._ANSIColors.RED + boldr = traceback._ANSIColors.BOLD_RED + magenta = traceback._ANSIColors.MAGENTA + boldm = traceback._ANSIColors.BOLD_MAGENTA + reset = traceback._ANSIColors.RESET + lno_foo = foo.__code__.co_firstlineno + expected = ['Traceback (most recent call last):', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+5}{reset}, in {magenta}test_colorized_traceback_is_the_default{reset}', + f' {red}foo{reset+boldr}(){reset}', + f' {red}~~~{reset+boldr}^^{reset}', + f' File {magenta}"{__file__}"{reset}, ' + f'line {magenta}{lno_foo+1}{reset}, in {magenta}foo{reset}', + f' {red}1{reset+boldr}/{reset+red}0{reset}', + f' {red}~{reset+boldr}^{reset+red}~{reset}', + f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] + self.assertEqual(actual, expected) + + def test_colorized_detection_checks_for_environment_variables(self): + if sys.platform == "win32": + virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", return_value=True) + else: + virtual_patching = contextlib.nullcontext() + with virtual_patching: + with unittest.mock.patch("os.isatty") as isatty_mock: + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): + self.assertEqual(traceback._can_colorize(), False) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): + self.assertEqual(traceback._can_colorize(), False) + isatty_mock.return_value = False + self.assertEqual(traceback._can_colorize(), False) if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index a0485a7023d07d..1cf008c7e9da97 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1,5 +1,7 @@ """Extract, format and print information about Python stack traces.""" +import os +import io import collections.abc import itertools import linecache @@ -19,6 +21,8 @@ # Formatting and printing lists of traceback lines. # +_COLORIZE = True + def print_list(extracted_list, file=None): """Print the list of tuples as returned by extract_tb() or extract_stack() as a formatted stack trace to the given file.""" @@ -110,7 +114,7 @@ def _parse_value_tb(exc, value, tb): def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ - file=None, chain=True): + file=None, chain=True, **kwargs): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. This differs from print_tb() in the following ways: (1) if @@ -121,17 +125,44 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ occurred with a caret on the next line indicating the approximate position of the error. """ + colorize = kwargs.get("colorize", False) value, tb = _parse_value_tb(exc, value, tb) te = TracebackException(type(value), value, tb, limit=limit, compact=True) - te.print(file=file, chain=chain) + te.print(file=file, chain=chain, colorize=colorize) BUILTIN_EXCEPTION_LIMIT = object() +def _can_colorize(): + if sys.platform == "win32": + try: + import nt + if not nt._supports_virtual_terminal(): + return False + except (ImportError, AttributeError): + return False + + if os.environ.get("PYTHON_COLORS") == "0": + return False + if os.environ.get("PYTHON_COLORS") == "1": + return True + if "NO_COLOR" in os.environ: + return False + if not _COLORIZE: + return False + if "FORCE_COLOR" in os.environ: + return True + if os.environ.get("TERM") == "dumb": + return False + try: + return os.isatty(sys.stderr.fileno()) + except io.UnsupportedOperation: + return sys.stderr.isatty() def _print_exception_bltin(exc, /): file = sys.stderr if sys.stderr is not None else sys.__stderr__ - return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file) + colorize = _can_colorize() + return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ @@ -172,13 +203,19 @@ def format_exception_only(exc, /, value=_sentinel, *, show_group=False): # -- not official API but folk probably use these two functions. -def _format_final_exc_line(etype, value, *, insert_final_newline=True): +def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=False): valuestr = _safe_string(value, 'exception') end_char = "\n" if insert_final_newline else "" - if value is None or not valuestr: - line = f"{etype}{end_char}" + if colorize: + if value is None or not valuestr: + line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}{end_char}" + else: + line = f"{_ANSIColors.BOLD_MAGENTA}{etype}{_ANSIColors.RESET}: {_ANSIColors.MAGENTA}{valuestr}{_ANSIColors.RESET}{end_char}" else: - line = f"{etype}: {valuestr}{end_char}" + if value is None or not valuestr: + line = f"{etype}{end_char}" + else: + line = f"{etype}: {valuestr}{end_char}" return line def _safe_string(value, what, func=str): @@ -406,6 +443,14 @@ def _get_code_position(code, instruction_index): _RECURSIVE_CUTOFF = 3 # Also hardcoded in traceback.c. +class _ANSIColors: + RED = '\x1b[31m' + BOLD_RED = '\x1b[1;31m' + MAGENTA = '\x1b[35m' + BOLD_MAGENTA = '\x1b[1;35m' + GREY = '\x1b[90m' + RESET = '\x1b[0m' + class StackSummary(list): """A list of FrameSummary objects, representing a stack of frames.""" @@ -496,18 +541,33 @@ def from_list(klass, a_list): result.append(FrameSummary(filename, lineno, name, line=line)) return result - def format_frame_summary(self, frame_summary): + def format_frame_summary(self, frame_summary, **kwargs): """Format the lines for a single FrameSummary. Returns a string representing one frame involved in the stack. This gets called for every frame to be printed in the stack summary. """ + colorize = kwargs.get("colorize", False) row = [] filename = frame_summary.filename if frame_summary.filename.startswith("-"): filename = "" - row.append(' File "{}", line {}, in {}\n'.format( - filename, frame_summary.lineno, frame_summary.name)) + if colorize: + row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format( + _ANSIColors.MAGENTA, + filename, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, + frame_summary.lineno, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, + frame_summary.name, + _ANSIColors.RESET, + ) + ) + else: + row.append(' File "{}", line {}, in {}\n'.format( + filename, frame_summary.lineno, frame_summary.name)) if frame_summary._dedented_lines and frame_summary._dedented_lines.strip(): if ( frame_summary.colno is None or @@ -619,7 +679,31 @@ def output_line(lineno): carets.append(secondary_char) else: carets.append(primary_char) - result.append("".join(carets) + "\n") + if colorize: + # Replace the previous line with a red version of it only in the parts covered + # by the carets. + line = result[-1] + colorized_line_parts = [] + colorized_carets_parts = [] + + for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]): + caret_group = list(group) + if color == "^": + colorized_line_parts.append(_ANSIColors.BOLD_RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) + colorized_carets_parts.append(_ANSIColors.BOLD_RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) + elif color == "~": + colorized_line_parts.append(_ANSIColors.RED + "".join(char for char, _ in caret_group) + _ANSIColors.RESET) + colorized_carets_parts.append(_ANSIColors.RED + "".join(caret for _, caret in caret_group) + _ANSIColors.RESET) + else: + colorized_line_parts.append("".join(char for char, _ in caret_group)) + colorized_carets_parts.append("".join(caret for _, caret in caret_group)) + + colorized_line = "".join(colorized_line_parts) + colorized_carets = "".join(colorized_carets_parts) + result[-1] = colorized_line + result.append(colorized_carets + "\n") + else: + result.append("".join(carets) + "\n") # display significant lines sig_lines_list = sorted(significant_lines) @@ -643,7 +727,7 @@ def output_line(lineno): return ''.join(row) - def format(self): + def format(self, **kwargs): """Format the stack ready for printing. Returns a list of strings ready for printing. Each string in the @@ -655,13 +739,14 @@ def format(self): repetitions are shown, followed by a summary line stating the exact number of further repetitions. """ + colorize = kwargs.get("colorize", False) result = [] last_file = None last_line = None last_name = None count = 0 for frame_summary in self: - formatted_frame = self.format_frame_summary(frame_summary) + formatted_frame = self.format_frame_summary(frame_summary, colorize=colorize) if formatted_frame is None: continue if (last_file is None or last_file != frame_summary.filename or @@ -1118,7 +1203,7 @@ def __eq__(self, other): def __str__(self): return self._str - def format_exception_only(self, *, show_group=False, _depth=0): + def format_exception_only(self, *, show_group=False, _depth=0, **kwargs): """Format the exception part of the traceback. The return value is a generator of strings, each ending in a newline. @@ -1135,10 +1220,11 @@ def format_exception_only(self, *, show_group=False, _depth=0): :exc:`BaseExceptionGroup`, the nested exceptions are included as well, recursively, with indentation relative to their nesting depth. """ + colorize = kwargs.get("colorize", False) indent = 3 * _depth * ' ' if not self._have_exc_type: - yield indent + _format_final_exc_line(None, self._str) + yield indent + _format_final_exc_line(None, self._str, colorize=colorize) return stype = self.exc_type_str @@ -1146,16 +1232,16 @@ def format_exception_only(self, *, show_group=False, _depth=0): if _depth > 0: # Nested exceptions needs correct handling of multiline messages. formatted = _format_final_exc_line( - stype, self._str, insert_final_newline=False, + stype, self._str, insert_final_newline=False, colorize=colorize ).split('\n') yield from [ indent + l + '\n' for l in formatted ] else: - yield _format_final_exc_line(stype, self._str) + yield _format_final_exc_line(stype, self._str, colorize=colorize) else: - yield from [indent + l for l in self._format_syntax_error(stype)] + yield from [indent + l for l in self._format_syntax_error(stype, colorize=colorize)] if ( isinstance(self.__notes__, collections.abc.Sequence) @@ -1169,15 +1255,26 @@ def format_exception_only(self, *, show_group=False, _depth=0): if self.exceptions and show_group: for ex in self.exceptions: - yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1) + yield from ex.format_exception_only(show_group=show_group, _depth=_depth+1, colorize=colorize) - def _format_syntax_error(self, stype): + def _format_syntax_error(self, stype, **kwargs): """Format SyntaxError exceptions (internal helper).""" # Show exactly where the problem was found. + colorize = kwargs.get("colorize", False) filename_suffix = '' if self.lineno is not None: - yield ' File "{}", line {}\n'.format( - self.filename or "", self.lineno) + if colorize: + yield ' File {}"{}"{}, line {}{}{}\n'.format( + _ANSIColors.MAGENTA, + self.filename or "", + _ANSIColors.RESET, + _ANSIColors.MAGENTA, + self.lineno, + _ANSIColors.RESET, + ) + else: + yield ' File "{}", line {}\n'.format( + self.filename or "", self.lineno) elif self.filename is not None: filename_suffix = ' ({})'.format(self.filename) @@ -1189,9 +1286,9 @@ def _format_syntax_error(self, stype): rtext = text.rstrip('\n') ltext = rtext.lstrip(' \n\f') spaces = len(rtext) - len(ltext) - yield ' {}\n'.format(ltext) - - if self.offset is not None: + if self.offset is None: + yield ' {}\n'.format(ltext) + else: offset = self.offset end_offset = self.end_offset if self.end_offset not in {None, 0} else offset if self.text and offset > len(self.text): @@ -1204,14 +1301,43 @@ def _format_syntax_error(self, stype): # Convert 1-based column offset to 0-based index into stripped text colno = offset - 1 - spaces end_colno = end_offset - 1 - spaces + caretspace = ' ' if colno >= 0: # non-space whitespace (likes tabs) must be kept for alignment caretspace = ((c if c.isspace() else ' ') for c in ltext[:colno]) - yield ' {}{}'.format("".join(caretspace), ('^' * (end_colno - colno) + "\n")) + start_color = end_color = "" + if colorize: + # colorize from colno to end_colno + ltext = ( + ltext[:colno] + + _ANSIColors.BOLD_RED + ltext[colno:end_colno] + _ANSIColors.RESET + + ltext[end_colno:] + ) + start_color = _ANSIColors.BOLD_RED + end_color = _ANSIColors.RESET + yield ' {}\n'.format(ltext) + yield ' {}{}{}{}\n'.format( + "".join(caretspace), + start_color, + ('^' * (end_colno - colno)), + end_color, + ) + else: + yield ' {}\n'.format(ltext) msg = self.msg or "" - yield "{}: {}{}\n".format(stype, msg, filename_suffix) + if colorize: + yield "{}{}{}: {}{}{}{}\n".format( + _ANSIColors.BOLD_MAGENTA, + stype, + _ANSIColors.RESET, + _ANSIColors.MAGENTA, + msg, + _ANSIColors.RESET, + filename_suffix) + else: + yield "{}: {}{}\n".format(stype, msg, filename_suffix) - def format(self, *, chain=True, _ctx=None): + def format(self, *, chain=True, _ctx=None, **kwargs): """Format the exception. If chain is not *True*, *__cause__* and *__context__* will not be formatted. @@ -1223,7 +1349,7 @@ def format(self, *, chain=True, _ctx=None): The message indicating which exception occurred is always the last string in the output. """ - + colorize = kwargs.get("colorize", False) if _ctx is None: _ctx = _ExceptionPrintContext() @@ -1253,8 +1379,8 @@ def format(self, *, chain=True, _ctx=None): if exc.exceptions is None: if exc.stack: yield from _ctx.emit('Traceback (most recent call last):\n') - yield from _ctx.emit(exc.stack.format()) - yield from _ctx.emit(exc.format_exception_only()) + yield from _ctx.emit(exc.stack.format(colorize=colorize)) + yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) elif _ctx.exception_group_depth > self.max_group_depth: # exception group, but depth exceeds limit yield from _ctx.emit( @@ -1269,9 +1395,9 @@ def format(self, *, chain=True, _ctx=None): yield from _ctx.emit( 'Exception Group Traceback (most recent call last):\n', margin_char = '+' if is_toplevel else None) - yield from _ctx.emit(exc.stack.format()) + yield from _ctx.emit(exc.stack.format(colorize=colorize)) - yield from _ctx.emit(exc.format_exception_only()) + yield from _ctx.emit(exc.format_exception_only(colorize=colorize)) num_excs = len(exc.exceptions) if num_excs <= self.max_group_width: n = num_excs @@ -1312,11 +1438,12 @@ def format(self, *, chain=True, _ctx=None): _ctx.exception_group_depth = 0 - def print(self, *, file=None, chain=True): + def print(self, *, file=None, chain=True, **kwargs): """Print the result of self.format(chain=chain) to 'file'.""" + colorize = kwargs.get("colorize", False) if file is None: file = sys.stderr - for line in self.format(chain=chain): + for line in self.format(chain=chain, colorize=colorize): print(line, file=file, end="") diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-04-23-09-07.gh-issue-112730.BXHlFa.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-04-23-09-07.gh-issue-112730.BXHlFa.rst new file mode 100644 index 00000000000000..51758dd5f4c318 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-04-23-09-07.gh-issue-112730.BXHlFa.rst @@ -0,0 +1 @@ +Use color to highlight error locations in tracebacks. Patch by Pablo Galindo diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index a6c76370f241be..9d6cd337f4a2f4 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -11756,6 +11756,28 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #endif /* (defined(WIFEXITED) || defined(MS_WINDOWS)) */ +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__supports_virtual_terminal__doc__, +"_supports_virtual_terminal($module, /)\n" +"--\n" +"\n" +"Checks if virtual terminal is supported in windows"); + +#define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF \ + {"_supports_virtual_terminal", (PyCFunction)os__supports_virtual_terminal, METH_NOARGS, os__supports_virtual_terminal__doc__}, + +static PyObject * +os__supports_virtual_terminal_impl(PyObject *module); + +static PyObject * +os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return os__supports_virtual_terminal_impl(module); +} + +#endif /* defined(MS_WINDOWS) */ + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -12395,4 +12417,8 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=2900675ac5219924 input=a9049054013a1b77]*/ + +#ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF + #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF +#endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ +/*[clinic end generated code: output=ff0ec3371de19904 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 70d107a297f315..ddbb4cd43babfc 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -16073,6 +16073,26 @@ os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj) } #endif +#if defined(MS_WINDOWS) +/*[clinic input] +os._supports_virtual_terminal + +Checks if virtual terminal is supported in windows +[clinic start generated code]*/ + +static PyObject * +os__supports_virtual_terminal_impl(PyObject *module) +/*[clinic end generated code: output=bd0556a6d9d99fe6 input=0752c98e5d321542]*/ +{ + DWORD mode = 0; + HANDLE handle = GetStdHandle(STD_ERROR_HANDLE); + if (!GetConsoleMode(handle, &mode)) { + Py_RETURN_FALSE; + } + return PyBool_FromLong(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING); +} +#endif + static PyMethodDef posix_methods[] = { @@ -16277,6 +16297,8 @@ static PyMethodDef posix_methods[] = { OS__PATH_ISFILE_METHODDEF OS__PATH_ISLINK_METHODDEF OS__PATH_EXISTS_METHODDEF + + OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF {NULL, NULL} /* Sentinel */ }; diff --git a/Python/initconfig.c b/Python/initconfig.c index d7f3195ed5fcf0..06e317907b8ec9 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -293,6 +293,8 @@ static const char usage_envvars[] = "PYTHON_FROZEN_MODULES : if this variable is set, it determines whether or not \n" " frozen modules should be used. The default is \"on\" (or \"off\" if you are \n" " running a local build).\n" +"PYTHON_COLORS : If this variable is set to 1, the interpreter will" +" colorize various kinds of output. Setting it to 0 deactivates this behavior.\n" "These variables have equivalent command-line parameters (see --help for details):\n" "PYTHONDEBUG : enable parser debug mode (-d)\n" "PYTHONDONTWRITEBYTECODE : don't write .pyc files (-B)\n" From 953ee622b3901d3467e65e3484dcfa75ba6fcddf Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 7 Dec 2023 10:30:15 +0100 Subject: [PATCH 135/442] gh-109981: Fix support.fd_count() on macOS 14 (#112797) Use scanning "/dev/fd/" on macOS in support.fd_count(). That's both more efficient than scanning all possible file descriptors, and avoids crashing the interpreter when there are open "guarded" file descriptors. "Guarded" file descriptors are a macOS feature where file descriptors used by system libraries are marked and cause hard crashes when used by "user" code. Co-authored-by: Victor Stinner --- Lib/test/support/os_helper.py | 11 +++++++++-- .../2023-12-06-12-11-13.gh-issue-109981.mOHg10.rst | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-06-12-11-13.gh-issue-109981.mOHg10.rst diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 46ae53aa11a91f..7a67d87fb9e846 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -592,10 +592,17 @@ def fd_count(): """Count the number of open file descriptors. """ if sys.platform.startswith(('linux', 'freebsd', 'emscripten')): + fd_path = "/proc/self/fd" + elif sys.platform == "darwin": + fd_path = "/dev/fd" + else: + fd_path = None + + if fd_path is not None: try: - names = os.listdir("/proc/self/fd") + names = os.listdir(fd_path) # Subtract one because listdir() internally opens a file - # descriptor to list the content of the /proc/self/fd/ directory. + # descriptor to list the content of the directory. return len(names) - 1 except FileNotFoundError: pass diff --git a/Misc/NEWS.d/next/macOS/2023-12-06-12-11-13.gh-issue-109981.mOHg10.rst b/Misc/NEWS.d/next/macOS/2023-12-06-12-11-13.gh-issue-109981.mOHg10.rst new file mode 100644 index 00000000000000..f86ab2c37ee6ec --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-06-12-11-13.gh-issue-109981.mOHg10.rst @@ -0,0 +1,3 @@ +Use ``/dev/fd`` on macOS to determine the number of open files in +``test.support.os_helper.fd_count`` to avoid a crash with "guarded" file +descriptors when probing for open files. From 8660fb7fd7cdcbfe58ef304f5720efe97ca7c842 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Dec 2023 12:19:43 +0200 Subject: [PATCH 136/442] gh-112660: Do not clear arbitrary errors on import (GH-112661) Previously arbitrary errors could be cleared during formatting error messages for ImportError or AttributeError for modules. Now all unexpected errors are reported. --- ...-12-03-15-29-53.gh-issue-112660.gldBvh.rst | 2 + Objects/moduleobject.c | 57 ++++++++----------- Python/ceval.c | 46 ++++++++++----- Python/import.c | 25 ++++---- 4 files changed, 72 insertions(+), 58 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-03-15-29-53.gh-issue-112660.gldBvh.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-03-15-29-53.gh-issue-112660.gldBvh.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-15-29-53.gh-issue-112660.gldBvh.rst new file mode 100644 index 00000000000000..ea9052b3e35c48 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-03-15-29-53.gh-issue-112660.gldBvh.rst @@ -0,0 +1,2 @@ +Do not clear unexpected errors during formatting error messages for +ImportError and AttributeError for modules. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index bba77ce8ab7e7b..e2741fef6debd3 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -749,27 +749,20 @@ module_repr(PyModuleObject *m) } /* Check if the "_initializing" attribute of the module spec is set to true. - Clear the exception and return 0 if spec is NULL. */ int _PyModuleSpec_IsInitializing(PyObject *spec) { - if (spec != NULL) { - PyObject *value; - int ok = PyObject_GetOptionalAttr(spec, &_Py_ID(_initializing), &value); - if (ok == 0) { - return 0; - } - if (value != NULL) { - int initializing = PyObject_IsTrue(value); - Py_DECREF(value); - if (initializing >= 0) { - return initializing; - } - } + if (spec == NULL) { + return 0; } - PyErr_Clear(); - return 0; + PyObject *value; + int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(_initializing), &value); + if (rc > 0) { + rc = PyObject_IsTrue(value); + Py_DECREF(value); + } + return rc; } /* Check if the submodule name is in the "_uninitialized_submodules" attribute @@ -782,17 +775,13 @@ _PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name) return 0; } - PyObject *value = PyObject_GetAttr(spec, &_Py_ID(_uninitialized_submodules)); - if (value == NULL) { - return 0; + PyObject *value; + int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(_uninitialized_submodules), &value); + if (rc > 0) { + rc = PySequence_Contains(value, name); + Py_DECREF(value); } - - int is_uninitialized = PySequence_Contains(value, name); - Py_DECREF(value); - if (is_uninitialized == -1) { - return 0; - } - return is_uninitialized; + return rc; } PyObject* @@ -840,23 +829,27 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) return NULL; } if (suppress != 1) { - if (_PyModuleSpec_IsInitializing(spec)) { + int rc = _PyModuleSpec_IsInitializing(spec); + if (rc > 0) { PyErr_Format(PyExc_AttributeError, "partially initialized " "module '%U' has no attribute '%U' " "(most likely due to a circular import)", mod_name, name); } - else if (_PyModuleSpec_IsUninitializedSubmodule(spec, name)) { - PyErr_Format(PyExc_AttributeError, + else if (rc == 0) { + rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name); + if (rc > 0) { + PyErr_Format(PyExc_AttributeError, "cannot access submodule '%U' of module '%U' " "(most likely due to a circular import)", name, mod_name); - } - else { - PyErr_Format(PyExc_AttributeError, + } + else if (rc == 0) { + PyErr_Format(PyExc_AttributeError, "module '%U' has no attribute '%U'", mod_name, name); + } } } Py_XDECREF(spec); diff --git a/Python/ceval.c b/Python/ceval.c index 1806ceb7fa9681..f8fa50eb46c75e 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2614,11 +2614,10 @@ import_from(PyThreadState *tstate, PyObject *v, PyObject *name) /* Issue #17636: in case this failed because of a circular relative import, try to fallback on reading the module directly from sys.modules. */ - pkgname = PyObject_GetAttr(v, &_Py_ID(__name__)); - if (pkgname == NULL) { - goto error; + if (PyObject_GetOptionalAttr(v, &_Py_ID(__name__), &pkgname) < 0) { + return NULL; } - if (!PyUnicode_Check(pkgname)) { + if (pkgname == NULL || !PyUnicode_Check(pkgname)) { Py_CLEAR(pkgname); goto error; } @@ -2635,42 +2634,59 @@ import_from(PyThreadState *tstate, PyObject *v, PyObject *name) Py_DECREF(pkgname); return x; error: - pkgpath = PyModule_GetFilenameObject(v); if (pkgname == NULL) { pkgname_or_unknown = PyUnicode_FromString(""); if (pkgname_or_unknown == NULL) { - Py_XDECREF(pkgpath); return NULL; } } else { pkgname_or_unknown = pkgname; } + pkgpath = NULL; + if (PyModule_Check(v)) { + pkgpath = PyModule_GetFilenameObject(v); + if (pkgpath == NULL) { + if (!PyErr_ExceptionMatches(PyExc_SystemError)) { + Py_DECREF(pkgname_or_unknown); + return NULL; + } + // module filename missing + _PyErr_Clear(tstate); + } + } if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) { - _PyErr_Clear(tstate); + Py_CLEAR(pkgpath); errmsg = PyUnicode_FromFormat( "cannot import name %R from %R (unknown location)", name, pkgname_or_unknown ); - /* NULL checks for errmsg and pkgname done by PyErr_SetImportError. */ - _PyErr_SetImportErrorWithNameFrom(errmsg, pkgname, NULL, name); } else { - PyObject *spec = PyObject_GetAttr(v, &_Py_ID(__spec__)); + PyObject *spec; + int rc = PyObject_GetOptionalAttr(v, &_Py_ID(__spec__), &spec); + if (rc > 0) { + rc = _PyModuleSpec_IsInitializing(spec); + Py_DECREF(spec); + } + if (rc < 0) { + Py_DECREF(pkgname_or_unknown); + Py_DECREF(pkgpath); + return NULL; + } const char *fmt = - _PyModuleSpec_IsInitializing(spec) ? + rc ? "cannot import name %R from partially initialized module %R " "(most likely due to a circular import) (%S)" : "cannot import name %R from %R (%S)"; - Py_XDECREF(spec); errmsg = PyUnicode_FromFormat(fmt, name, pkgname_or_unknown, pkgpath); - /* NULL checks for errmsg and pkgname done by PyErr_SetImportError. */ - _PyErr_SetImportErrorWithNameFrom(errmsg, pkgname, pkgpath, name); } + /* NULL checks for errmsg and pkgname done by PyErr_SetImportError. */ + _PyErr_SetImportErrorWithNameFrom(errmsg, pkgname, pkgpath, name); Py_XDECREF(errmsg); - Py_XDECREF(pkgname_or_unknown); + Py_DECREF(pkgname_or_unknown); Py_XDECREF(pkgpath); return NULL; } diff --git a/Python/import.c b/Python/import.c index f37393bbdc4269..ef81f46a4d65c1 100644 --- a/Python/import.c +++ b/Python/import.c @@ -252,18 +252,21 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n NOTE: because of this, initializing must be set *before* stuffing the new module in sys.modules. */ - spec = PyObject_GetAttr(mod, &_Py_ID(__spec__)); - int busy = _PyModuleSpec_IsInitializing(spec); - Py_XDECREF(spec); - if (busy) { - /* Wait until module is done importing. */ - PyObject *value = PyObject_CallMethodOneArg( - IMPORTLIB(interp), &_Py_ID(_lock_unlock_module), name); - if (value == NULL) { - return -1; - } - Py_DECREF(value); + int rc = PyObject_GetOptionalAttr(mod, &_Py_ID(__spec__), &spec); + if (rc > 0) { + rc = _PyModuleSpec_IsInitializing(spec); + Py_DECREF(spec); + } + if (rc <= 0) { + return rc; } + /* Wait until module is done importing. */ + PyObject *value = PyObject_CallMethodOneArg( + IMPORTLIB(interp), &_Py_ID(_lock_unlock_module), name); + if (value == NULL) { + return -1; + } + Py_DECREF(value); return 0; } From 4ba15de19153bb97308996ec85242bdeda358387 Mon Sep 17 00:00:00 2001 From: Kushal Das Date: Thu, 7 Dec 2023 11:22:52 +0100 Subject: [PATCH 137/442] gh-74616: Raise ValueError in case of null character in input prompt (GH-1738) If the input prompt to the builtin input function on terminal has any null character, then raise ValueError instead of silently truncating it. Co-authored-by: Serhiy Storchaka --- Lib/test/test_builtin.py | 44 +++++++++++++++---- ...3-12-07-12-00-04.gh-issue-74616.kgTGVb.rst | 2 + Python/bltinmodule.c | 5 +++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-07-12-00-04.gh-issue-74616.kgTGVb.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 535856adaea4d3..558715383c82ee 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -2269,7 +2269,10 @@ def _run_child(self, child, terminal_input): return lines - def check_input_tty(self, prompt, terminal_input, stdio_encoding=None): + def check_input_tty(self, prompt, terminal_input, stdio_encoding=None, *, + expected=None, + stdin_errors='surrogateescape', + stdout_errors='replace'): if not sys.stdin.isatty() or not sys.stdout.isatty(): self.skipTest("stdin and stdout must be ttys") def child(wpipe): @@ -2277,22 +2280,26 @@ def child(wpipe): if stdio_encoding: sys.stdin = io.TextIOWrapper(sys.stdin.detach(), encoding=stdio_encoding, - errors='surrogateescape') + errors=stdin_errors) sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding=stdio_encoding, - errors='replace') + errors=stdout_errors) print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe) - print(ascii(input(prompt)), file=wpipe) + try: + print(ascii(input(prompt)), file=wpipe) + except BaseException as e: + print(ascii(f'{e.__class__.__name__}: {e!s}'), file=wpipe) lines = self.run_child(child, terminal_input + b"\r\n") # Check we did exercise the GNU readline path self.assertIn(lines[0], {'tty = True', 'tty = False'}) if lines[0] != 'tty = True': self.skipTest("standard IO in should have been a tty") input_result = eval(lines[1]) # ascii() -> eval() roundtrip - if stdio_encoding: - expected = terminal_input.decode(stdio_encoding, 'surrogateescape') - else: - expected = terminal_input.decode(sys.stdin.encoding) # what else? + if expected is None: + if stdio_encoding: + expected = terminal_input.decode(stdio_encoding, 'surrogateescape') + else: + expected = terminal_input.decode(sys.stdin.encoding) # what else? self.assertEqual(input_result, expected) def test_input_tty(self): @@ -2313,13 +2320,32 @@ def skip_if_readline(self): def test_input_tty_non_ascii(self): self.skip_if_readline() # Check stdin/stdout encoding is used when invoking PyOS_Readline() - self.check_input_tty("prompté", b"quux\xe9", "utf-8") + self.check_input_tty("prompté", b"quux\xc3\xa9", "utf-8") def test_input_tty_non_ascii_unicode_errors(self): self.skip_if_readline() # Check stdin/stdout error handler is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "ascii") + def test_input_tty_null_in_prompt(self): + self.check_input_tty("prompt\0", b"", + expected='ValueError: input: prompt string cannot contain ' + 'null characters') + + def test_input_tty_nonencodable_prompt(self): + self.skip_if_readline() + self.check_input_tty("prompté", b"quux", "ascii", stdout_errors='strict', + expected="UnicodeEncodeError: 'ascii' codec can't encode " + "character '\\xe9' in position 6: ordinal not in " + "range(128)") + + def test_input_tty_nondecodable_input(self): + self.skip_if_readline() + self.check_input_tty("prompt", b"quux\xe9", "ascii", stdin_errors='strict', + expected="UnicodeDecodeError: 'ascii' codec can't decode " + "byte 0xe9 in position 4: ordinal not in " + "range(128)") + def test_input_no_stdout_fileno(self): # Issue #24402: If stdin is the original terminal but stdout.fileno() # fails, do not use the original stdout file descriptor diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-07-12-00-04.gh-issue-74616.kgTGVb.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-07-12-00-04.gh-issue-74616.kgTGVb.rst new file mode 100644 index 00000000000000..5c345be9de6d0b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-07-12-00-04.gh-issue-74616.kgTGVb.rst @@ -0,0 +1,2 @@ +:func:`input` now raises a ValueError when output on the terminal if the +prompt contains embedded null characters instead of silently truncating it. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 7a9625134761f9..960bca01990c83 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2262,6 +2262,11 @@ builtin_input_impl(PyObject *module, PyObject *prompt) goto _readline_errors; assert(PyBytes_Check(po)); promptstr = PyBytes_AS_STRING(po); + if ((Py_ssize_t)strlen(promptstr) != PyBytes_GET_SIZE(po)) { + PyErr_SetString(PyExc_ValueError, + "input: prompt string cannot contain null characters"); + goto _readline_errors; + } } else { po = NULL; From 4b125dd31a634871d3b2d06ebfd1b9aef539766b Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 7 Dec 2023 11:27:30 +0100 Subject: [PATCH 138/442] gh-51944: Add missing macOS constants to termios (#112823) * gh-51944: Add some macOS constants to termios This changeset adds all public constants in and on macOS that weren't present already. Based on the macOS 14.2 SDK Co-authored-by: Serhiy Storchaka --- ...3-12-06-14-06-14.gh-issue-51944.-5qq_L.rst | 6 ++ Modules/termios.c | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-12-06-14-06-14.gh-issue-51944.-5qq_L.rst diff --git a/Misc/NEWS.d/next/Library/2023-12-06-14-06-14.gh-issue-51944.-5qq_L.rst b/Misc/NEWS.d/next/Library/2023-12-06-14-06-14.gh-issue-51944.-5qq_L.rst new file mode 100644 index 00000000000000..821eefa7cffcd5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-06-14-06-14.gh-issue-51944.-5qq_L.rst @@ -0,0 +1,6 @@ +Add the following constants to the :mod:`termios` module. These values are +present in macOS system headers: ``ALTWERASE``, ``B14400``, ``B28800``, +``B7200``, ``B76800``, ``CCAR_OFLOW``, ``CCTS_OFLOW``, ``CDSR_OFLOW``, +``CDTR_IFLOW``, ``CIGNORE``, ``CRTS_IFLOW``, ``EXTPROC``, ``IUTF8``, +``MDMBUF``, ``NL2``, ``NL3``, ``NOKERNINFO``, ``ONOEOT``, ``OXTABS``, +``VDSUSP``, ``VSTATUS``. diff --git a/Modules/termios.c b/Modules/termios.c index 9fc2673ce0e788..1d97a3a2757966 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -702,6 +702,9 @@ static struct constant { #ifdef IMAXBEL {"IMAXBEL", IMAXBEL}, #endif +#ifdef IUTF8 + {"IUTF8", IUTF8}, +#endif /* struct termios.c_oflag constants */ {"OPOST", OPOST}, @@ -726,6 +729,12 @@ static struct constant { #ifdef OFDEL {"OFDEL", OFDEL}, #endif +#ifdef OXTABS + {"OXTABS", OXTABS}, +#endif +#ifdef ONOEOT + {"ONOEOT", ONOEOT}, +#endif #ifdef NLDLY {"NLDLY", NLDLY}, #endif @@ -752,6 +761,12 @@ static struct constant { #ifdef NL1 {"NL1", NL1}, #endif +#ifdef NL2 + {"NL2", NL2}, +#endif +#ifdef NL3 + {"NL3", NL3}, +#endif #ifdef CR0 {"CR0", CR0}, #endif @@ -799,6 +814,9 @@ static struct constant { #endif /* struct termios.c_cflag constants */ +#ifdef CIGNORE + {"CIGNORE", CIGNORE}, +#endif {"CSIZE", CSIZE}, {"CSTOPB", CSTOPB}, {"CREAD", CREAD}, @@ -813,6 +831,22 @@ static struct constant { {"CRTSCTS", (long)CRTSCTS}, #endif +#ifdef CRTS_IFLOW + {"CRTS_IFLOW", CRTS_IFLOW}, +#endif +#ifdef CDTR_IFLOW + {"CDTR_IFLOW", CDTR_IFLOW}, +#endif +#ifdef CDSR_OFLOW + {"CDSR_OFLOW", CDSR_OFLOW}, +#endif +#ifdef CCAR_OFLOW + {"CCAR_OFLOW", CCAR_OFLOW}, +#endif +#ifdef MDMBUF + {"MDMBUF", MDMBUF}, +#endif + /* struct termios.c_cflag-related values (character size) */ {"CS5", CS5}, {"CS6", CS6}, @@ -820,6 +854,9 @@ static struct constant { {"CS8", CS8}, /* struct termios.c_lflag constants */ +#ifdef ALTWERASE + {"ALTWERASE", ALTWERASE}, +#endif {"ISIG", ISIG}, {"ICANON", ICANON}, #ifdef XCASE @@ -840,6 +877,9 @@ static struct constant { #endif #ifdef FLUSHO {"FLUSHO", FLUSHO}, +#endif +#ifdef NOKERNINFO + {"NOKERNINFO", NOKERNINFO}, #endif {"NOFLSH", NOFLSH}, {"TOSTOP", TOSTOP}, @@ -847,6 +887,9 @@ static struct constant { {"PENDIN", PENDIN}, #endif {"IEXTEN", IEXTEN}, +#ifdef EXTPROC + {"EXTPROC", EXTPROC}, +#endif /* indexes into the control chars array returned by tcgetattr() */ {"VINTR", VINTR}, @@ -855,6 +898,9 @@ static struct constant { {"VKILL", VKILL}, {"VEOF", VEOF}, {"VTIME", VTIME}, +#ifdef VSTATUS + {"VSTATUS", VSTATUS}, +#endif {"VMIN", VMIN}, #ifdef VSWTC /* The #defines above ensure that if either is defined, both are, @@ -865,6 +911,9 @@ static struct constant { {"VSTART", VSTART}, {"VSTOP", VSTOP}, {"VSUSP", VSUSP}, +#ifdef VDSUSP + {"VDSUSP", VREPRINT}, +#endif {"VEOL", VEOL}, #ifdef VREPRINT {"VREPRINT", VREPRINT}, @@ -883,6 +932,18 @@ static struct constant { #endif +#ifdef B7200 + {"B7200", B7200}, +#endif +#ifdef B14400 + {"B14400", B14400}, +#endif +#ifdef B28800 + {"B28800", B28800}, +#endif +#ifdef B76800 + {"B76800", B76800}, +#endif #ifdef B460800 {"B460800", B460800}, #endif From 3d712a9f4c9f366edbe16b804ec4f6ae50b0a59f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Thu, 7 Dec 2023 02:19:33 -0900 Subject: [PATCH 139/442] gh-102980: Redirect output of pdb's `interact` command, add tests and improve docs (#111194) --- Doc/library/pdb.rst | 19 ++++++- Lib/pdb.py | 17 ++++-- Lib/test/test_pdb.py | 53 +++++++++++++++++++ ...-10-23-03-49-34.gh-issue-102980.aXBd54.rst | 1 + 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-23-03-49-34.gh-issue-102980.aXBd54.rst diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index bbc6aacc62aafa..2495dcf50bb17f 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -570,10 +570,27 @@ can be overridden by the local file. Start an interactive interpreter (using the :mod:`code` module) whose global namespace contains all the (global and local) names found in the current - scope. + scope. Use ``exit()`` or ``quit()`` to exit the interpreter and return to + the debugger. + + .. note:: + + Because interact creates a new global namespace with the current global + and local namespace for execution, assignment to variables will not + affect the original namespaces. + However, modification to the mutable objects will be reflected in the + original namespaces. .. versionadded:: 3.2 + .. versionadded:: 3.13 + ``exit()`` and ``quit()`` can be used to exit :pdbcmd:`interact` + command. + + .. versionchanged:: 3.13 + :pdbcmd:`interact` directs its output to the debugger's + output channel rather than :data:`sys.stderr`. + .. _debugger-aliases: .. pdbcommand:: alias [name [command]] diff --git a/Lib/pdb.py b/Lib/pdb.py index 9d124189df11cf..83b7fefec63636 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -207,6 +207,15 @@ def namespace(self): ) +class _PdbInteractiveConsole(code.InteractiveConsole): + def __init__(self, ns, message): + self._message = message + super().__init__(locals=ns, local_exit=True) + + def write(self, data): + self._message(data, end='') + + # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may # be to your liking. You can set it once pdb is imported using the @@ -672,8 +681,8 @@ def handle_command_def(self, line): # interface abstraction functions - def message(self, msg): - print(msg, file=self.stdout) + def message(self, msg, end='\n'): + print(msg, end=end, file=self.stdout) def error(self, msg): print('***', msg, file=self.stdout) @@ -1786,7 +1795,9 @@ def do_interact(self, arg): contains all the (global and local) names found in the current scope. """ ns = {**self.curframe.f_globals, **self.curframe_locals} - code.interact("*interactive*", local=ns, local_exit=True) + console = _PdbInteractiveConsole(ns, message=self.message) + console.interact(banner="*pdb interact start*", + exitmsg="*exit from pdb interact command*") def do_alias(self, arg): """alias [name [command]] diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 50d8c8f52a909d..d53fe3c611bc35 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -778,6 +778,59 @@ def test_pdb_where_command(): (Pdb) continue """ +def test_pdb_interact_command(): + """Test interact command + + >>> g = 0 + >>> dict_g = {} + + >>> 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-- + > (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/Misc/NEWS.d/next/Library/2023-10-23-03-49-34.gh-issue-102980.aXBd54.rst b/Misc/NEWS.d/next/Library/2023-10-23-03-49-34.gh-issue-102980.aXBd54.rst new file mode 100644 index 00000000000000..d4bae4790d6fa4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-23-03-49-34.gh-issue-102980.aXBd54.rst @@ -0,0 +1 @@ +Redirect the output of ``interact`` command of :mod:`pdb` to the same channel as the debugger. Add tests and improve docs. From b449415b2f1b41e1c44cb453428657fdf6ff1d36 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 7 Dec 2023 12:49:40 +0000 Subject: [PATCH 140/442] GH-111485: Separate out parsing, analysis and code-gen phases of tier 1 code generator (GH-112299) --- Include/internal/pycore_opcode_metadata.h | 6 +- Makefile.pre.in | 3 +- Python/abstract_interp_cases.c.h | 3 +- Python/bytecodes.c | 5 +- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 1047 +++++++++++---------- Tools/cases_generator/analyzer.py | 456 +++++++++ Tools/cases_generator/cwriter.py | 111 +++ Tools/cases_generator/generate_cases.py | 1 - Tools/cases_generator/lexer.py | 9 +- Tools/cases_generator/mypy.ini | 2 + Tools/cases_generator/parser.py | 55 ++ Tools/cases_generator/parsing.py | 3 +- Tools/cases_generator/stack.py | 81 ++ Tools/cases_generator/tier1_generator.py | 417 ++++++++ 15 files changed, 1675 insertions(+), 526 deletions(-) create mode 100644 Tools/cases_generator/analyzer.py create mode 100644 Tools/cases_generator/cwriter.py create mode 100644 Tools/cases_generator/parser.py create mode 100644 Tools/cases_generator/stack.py create mode 100644 Tools/cases_generator/tier1_generator.py diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 4ae15e71e8d318..774c0f99379ed6 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1285,11 +1285,11 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case _INIT_CALL_PY_EXACT_ARGS: return 1; case _PUSH_FRAME: - return 1; + return 0; case CALL_BOUND_METHOD_EXACT_ARGS: - return 1; + return 0; case CALL_PY_EXACT_ARGS: - return 1; + return 0; case CALL_PY_WITH_DEFAULTS: return 1; case CALL_TYPE_1: diff --git a/Makefile.pre.in b/Makefile.pre.in index b5edb4e6748fb0..6ac68d59c8c47f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1587,7 +1587,6 @@ regen-cases: $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/generate_cases.py \ $(CASESFLAG) \ - -o $(srcdir)/Python/generated_cases.c.h.new \ -n $(srcdir)/Include/opcode_ids.h.new \ -t $(srcdir)/Python/opcode_targets.h.new \ -m $(srcdir)/Include/internal/pycore_opcode_metadata.h.new \ @@ -1595,6 +1594,8 @@ regen-cases: -p $(srcdir)/Lib/_opcode_metadata.py.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) \ + $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new $(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new diff --git a/Python/abstract_interp_cases.c.h b/Python/abstract_interp_cases.c.h index 0d7fbe8a39a5d4..96ac0aabd1b59f 100644 --- a/Python/abstract_interp_cases.c.h +++ b/Python/abstract_interp_cases.c.h @@ -774,7 +774,8 @@ } case _PUSH_FRAME: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); + STACK_SHRINK(1); + PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(0)), true); break; } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2075c195df3d38..bcad8dcf0e7dab 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -800,11 +800,11 @@ dummy_func( // We also push it onto the stack on exit, but that's a // different frame, and it's accounted for by _PUSH_FRAME. op(_POP_FRAME, (retval --)) { - assert(EMPTY()); #if TIER_ONE assert(frame != &entry_frame); #endif STORE_SP(); + assert(EMPTY()); _Py_LeaveRecursiveCallPy(tstate); // GH-99729: We need to unlink the frame *before* clearing it: _PyInterpreterFrame *dying = frame; @@ -1165,7 +1165,6 @@ dummy_func( } } - inst(STORE_NAME, (v -- )) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); PyObject *ns = LOCALS(); @@ -3130,7 +3129,7 @@ dummy_func( // The 'unused' output effect represents the return value // (which will be pushed when the frame returns). // It is needed so CALL_PY_EXACT_ARGS matches its family. - op(_PUSH_FRAME, (new_frame: _PyInterpreterFrame* -- unused)) { + op(_PUSH_FRAME, (new_frame: _PyInterpreterFrame* -- unused if (0))) { // Write it out explicitly because it's subtly different. // Eventually this should be the only occurrence of this code. assert(tstate->interp->eval_frame == NULL); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 547be6f13237dd..974e3f28a411b8 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -661,11 +661,11 @@ PyObject *retval; retval = stack_pointer[-1]; STACK_SHRINK(1); - assert(EMPTY()); #if TIER_ONE assert(frame != &entry_frame); #endif STORE_SP(); + assert(EMPTY()); _Py_LeaveRecursiveCallPy(tstate); // GH-99729: We need to unlink the frame *before* clearing it: _PyInterpreterFrame *dying = frame; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 0ac99e759deb12..24243ecfb5b8df 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1,6 +1,6 @@ -// This file is generated by Tools/cases_generator/generate_cases.py +// This file is generated by Tools/cases_generator/tier1_generator.py // from: -// Python/bytecodes.c +// ['./Python/bytecodes.c'] // Do not edit! #ifdef TIER_TWO @@ -8,6 +8,7 @@ #endif #define TIER_ONE 1 + TARGET(BEFORE_ASYNC_WITH) { frame->instr_ptr = next_instr; next_instr += 1; @@ -45,9 +46,9 @@ Py_DECREF(exit); if (true) goto pop_1_error; } - STACK_GROW(1); - stack_pointer[-2] = exit; - stack_pointer[-1] = res; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -91,9 +92,9 @@ Py_DECREF(exit); if (true) goto pop_1_error; } - STACK_GROW(1); - stack_pointer[-2] = exit; - stack_pointer[-1] = res; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -103,7 +104,6 @@ INSTRUCTION_STATS(BINARY_OP); PREDICTED(BINARY_OP); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *rhs; PyObject *lhs; PyObject *res; @@ -133,8 +133,8 @@ Py_DECREF(rhs); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -142,6 +142,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_ADD_FLOAT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -156,12 +157,12 @@ { STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval + - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval + + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -169,6 +170,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_ADD_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -187,8 +189,8 @@ _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -196,6 +198,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_ADD_UNICODE); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -214,8 +217,8 @@ _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -223,6 +226,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_INPLACE_ADD_UNICODE); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; // _GUARD_BOTH_UNICODE @@ -258,7 +262,7 @@ assert(next_instr->op.code == STORE_FAST); SKIP_OVER(1); } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -266,6 +270,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_MULTIPLY_FLOAT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -280,12 +285,12 @@ { STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval * - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval * + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -293,6 +298,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_MULTIPLY_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -311,8 +317,8 @@ _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -320,6 +326,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_SUBTRACT_FLOAT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -334,12 +341,12 @@ { STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval - - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval - + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -347,6 +354,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_OP_SUBTRACT_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -365,8 +373,8 @@ _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -393,8 +401,8 @@ } Py_DECREF(container); if (res == NULL) goto pop_3_error; - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; DISPATCH(); } @@ -404,7 +412,6 @@ INSTRUCTION_STATS(BINARY_SUBSCR); PREDICTED(BINARY_SUBSCR); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *container; PyObject *res; @@ -431,8 +438,8 @@ Py_DECREF(sub); if (res == NULL) goto pop_2_error; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -440,6 +447,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_SUBSCR_DICT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *dict; PyObject *res; @@ -454,8 +462,9 @@ Py_DECREF(dict); Py_DECREF(sub); if (rc <= 0) goto pop_2_error; - STACK_SHRINK(1); - stack_pointer[-1] = res; + // not found or error + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -463,6 +472,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_SUBSCR_GETITEM); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *container; sub = stack_pointer[-1]; @@ -494,6 +504,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_SUBSCR_LIST_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *list; PyObject *res; @@ -501,7 +512,6 @@ list = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); DEOPT_IF(!PyList_CheckExact(list), BINARY_SUBSCR); - // Deopt unless 0 <= sub < PyList_Size(list) DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), BINARY_SUBSCR); Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; @@ -512,8 +522,8 @@ Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -521,6 +531,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_SUBSCR_STR_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *str; PyObject *res; @@ -538,8 +549,8 @@ res = (PyObject*)&_Py_SINGLETON(strings).ascii[c]; _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(str); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -547,6 +558,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(BINARY_SUBSCR_TUPLE_INT); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *tuple; PyObject *res; @@ -554,7 +566,6 @@ tuple = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); DEOPT_IF(!PyTuple_CheckExact(tuple), BINARY_SUBSCR); - // Deopt unless 0 <= sub < PyTuple_Size(list) DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), BINARY_SUBSCR); Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; @@ -565,8 +576,8 @@ Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(tuple); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -578,7 +589,7 @@ PyObject **values; PyObject *map; keys = stack_pointer[-1]; - values = stack_pointer - 1 - oparg; + values = &stack_pointer[-1 - oparg]; if (!PyTuple_CheckExact(keys) || PyTuple_GET_SIZE(keys) != (Py_ssize_t)oparg) { _PyErr_SetString(tstate, PyExc_SystemError, @@ -586,15 +597,15 @@ GOTO_ERROR(error); // Pop the keys and values. } map = _PyDict_FromItems( - &PyTuple_GET_ITEM(keys, 0), 1, - values, 1, oparg); + &PyTuple_GET_ITEM(keys, 0), 1, + values, 1, oparg); for (int _i = oparg; --_i >= 0;) { Py_DECREF(values[_i]); } Py_DECREF(keys); - if (map == NULL) { STACK_SHRINK(oparg); goto pop_1_error; } - STACK_SHRINK(oparg); - stack_pointer[-1] = map; + if (map == NULL) { stack_pointer += -1 - oparg; goto error; } + stack_pointer[-1 - oparg] = map; + stack_pointer += -oparg; DISPATCH(); } @@ -604,12 +615,11 @@ INSTRUCTION_STATS(BUILD_LIST); PyObject **values; PyObject *list; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; list = _PyList_FromArraySteal(values, oparg); - if (list == NULL) { STACK_SHRINK(oparg); goto error; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = list; + if (list == NULL) { stack_pointer += -oparg; goto error; } + stack_pointer[-oparg] = list; + stack_pointer += 1 - oparg; DISPATCH(); } @@ -619,18 +629,17 @@ INSTRUCTION_STATS(BUILD_MAP); PyObject **values; PyObject *map; - values = stack_pointer - oparg*2; + values = &stack_pointer[-oparg*2]; map = _PyDict_FromItems( - values, 2, - values+1, 2, - oparg); + values, 2, + values+1, 2, + oparg); for (int _i = oparg*2; --_i >= 0;) { Py_DECREF(values[_i]); } - if (map == NULL) { STACK_SHRINK(oparg*2); goto error; } - STACK_SHRINK(oparg*2); - STACK_GROW(1); - stack_pointer[-1] = map; + if (map == NULL) { stack_pointer += -oparg*2; goto error; } + stack_pointer[-oparg*2] = map; + stack_pointer += 1 - oparg*2; DISPATCH(); } @@ -640,24 +649,23 @@ INSTRUCTION_STATS(BUILD_SET); PyObject **values; PyObject *set; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; set = PySet_New(NULL); if (set == NULL) - GOTO_ERROR(error); + GOTO_ERROR(error); int err = 0; for (int i = 0; i < oparg; i++) { PyObject *item = values[i]; if (err == 0) - err = PySet_Add(set, item); + err = PySet_Add(set, item); Py_DECREF(item); } if (err != 0) { Py_DECREF(set); - if (true) { STACK_SHRINK(oparg); goto error; } + if (true) { stack_pointer += -oparg; goto error; } } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = set; + stack_pointer[-oparg] = set; + stack_pointer += 1 - oparg; DISPATCH(); } @@ -669,17 +677,16 @@ PyObject *stop; PyObject *start; PyObject *slice; - if (oparg == 3) { step = stack_pointer[-(oparg == 3 ? 1 : 0)]; } - stop = stack_pointer[-1 - (oparg == 3 ? 1 : 0)]; - start = stack_pointer[-2 - (oparg == 3 ? 1 : 0)]; + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; + start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - if (slice == NULL) { STACK_SHRINK(((oparg == 3) ? 1 : 0)); goto pop_2_error; } - STACK_SHRINK(((oparg == 3) ? 1 : 0)); - STACK_SHRINK(1); - stack_pointer[-1] = slice; + if (slice == NULL) { stack_pointer += -2 - (((oparg == 3) ? 1 : 0)); goto error; } + stack_pointer[-2 - (((oparg == 3) ? 1 : 0))] = slice; + stack_pointer += -1 - (((oparg == 3) ? 1 : 0)); DISPATCH(); } @@ -689,15 +696,14 @@ INSTRUCTION_STATS(BUILD_STRING); PyObject **pieces; PyObject *str; - pieces = stack_pointer - oparg; + pieces = &stack_pointer[-oparg]; str = _PyUnicode_JoinArray(&_Py_STR(empty), pieces, oparg); for (int _i = oparg; --_i >= 0;) { Py_DECREF(pieces[_i]); } - if (str == NULL) { STACK_SHRINK(oparg); goto error; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = str; + if (str == NULL) { stack_pointer += -oparg; goto error; } + stack_pointer[-oparg] = str; + stack_pointer += 1 - oparg; DISPATCH(); } @@ -707,12 +713,11 @@ INSTRUCTION_STATS(BUILD_TUPLE); PyObject **values; PyObject *tup; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; tup = _PyTuple_FromArraySteal(values, oparg); - if (tup == NULL) { STACK_SHRINK(oparg); goto error; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = tup; + if (tup == NULL) { stack_pointer += -oparg; goto error; } + stack_pointer[-oparg] = tup; + stack_pointer += 1 - oparg; DISPATCH(); } @@ -730,13 +735,12 @@ INSTRUCTION_STATS(CALL); PREDICTED(CALL); _Py_CODEUNIT *this_instr = next_instr - 4; - static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; // _SPECIALIZE_CALL - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; { @@ -793,12 +797,12 @@ } /* Callable is not a normal Python function */ res = PyObject_Vectorcall( - callable, args, - total_args | PY_VECTORCALL_ARGUMENTS_OFFSET, - NULL); + callable, args, + total_args | PY_VECTORCALL_ARGUMENTS_OFFSET, + NULL); if (opcode == INSTRUMENTED_CALL) { PyObject *arg = total_args == 0 ? - &_PyInstrumentation_MISSING : args[0]; + &_PyInstrumentation_MISSING : args[0]; if (res == NULL) { _Py_call_instrumentation_exc2( tstate, PY_MONITORING_EVENT_C_RAISE, @@ -818,11 +822,10 @@ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -831,10 +834,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_ALLOC_AND_ENTER_INIT); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *null; PyObject *callable; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* This instruction does the following: @@ -890,13 +894,15 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_BOUND_METHOD_EXACT_ARGS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject *null; PyObject *callable; + PyObject *func; PyObject *self; PyObject *self_or_null; - PyObject *func; PyObject **args; _PyInterpreterFrame *new_frame; + /* Skip 1 cache entry */ // _CHECK_PEP_523 { DEOPT_IF(tstate->interp->eval_frame, CALL); @@ -936,7 +942,8 @@ DEOPT_IF(tstate->py_recursion_remaining <= 1, CALL); } // _INIT_CALL_PY_EXACT_ARGS - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; { int argcount = oparg; if (self_or_null != NULL) { @@ -960,26 +967,26 @@ #endif } // _PUSH_FRAME - STACK_SHRINK(oparg); - STACK_SHRINK(2); { // Write it out explicitly because it's subtly different. // Eventually this should be the only occurrence of this code. assert(tstate->interp->eval_frame == NULL); - STORE_SP(); + stack_pointer += -2 - oparg; + _PyFrame_SetStackPointer(frame, stack_pointer); new_frame->previous = frame; CALL_STAT_INC(inlined_py_calls); frame = tstate->current_frame = new_frame; tstate->py_recursion_remaining--; LOAD_SP(); LOAD_IP(0); - #if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } - #endif + #endif } + stack_pointer += (((0) ? 1 : 0)); DISPATCH(); } @@ -987,11 +994,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_BUILTIN_CLASS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -1009,10 +1017,9 @@ Py_DECREF(args[i]); } Py_DECREF(tp); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1021,11 +1028,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_BUILTIN_FAST); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_FASTCALL functions, without keywords */ @@ -1044,21 +1052,19 @@ args, total_args); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - /* Not deopting because this doesn't mean our optimization was - wrong. `res` can be NULL for valid reasons. Eg. getattr(x, - 'invalid'). In those cases an exception is set, so we must - handle it. - */ - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + /* Not deopting because this doesn't mean our optimization was + wrong. `res` can be NULL for valid reasons. Eg. getattr(x, + 'invalid'). In those cases an exception is set, so we must + handle it. + */ + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1067,11 +1073,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_BUILTIN_FAST_WITH_KEYWORDS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ @@ -1085,20 +1092,18 @@ STAT_INC(CALL, hit); /* res = func(self, args, nargs, kwnames) */ _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable); + (_PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunction_GET_FUNCTION(callable); res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1107,11 +1112,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_BUILTIN_O); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_O functions */ @@ -1134,13 +1140,11 @@ res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(arg); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1155,9 +1159,9 @@ PyObject *callargs; PyObject *func; PyObject *result; - if (oparg & 1) { kwargs = stack_pointer[-(oparg & 1 ? 1 : 0)]; } - callargs = stack_pointer[-1 - (oparg & 1 ? 1 : 0)]; - func = stack_pointer[-3 - (oparg & 1 ? 1 : 0)]; + if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } + callargs = stack_pointer[-1 - ((oparg & 1))]; + func = stack_pointer[-3 - ((oparg & 1))]; // DICT_MERGE is called before this opcode if there are kwargs. // It converts all dict subtypes in kwargs into regular dicts. assert(kwargs == NULL || PyDict_CheckExact(kwargs)); @@ -1177,7 +1181,7 @@ !PyFunction_Check(func) && !PyMethod_Check(func) ) { PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? - PyTuple_GET_ITEM(callargs, 0) : Py_None; + PyTuple_GET_ITEM(callargs, 0) : Py_None; int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, frame, this_instr, func, arg); @@ -1205,10 +1209,9 @@ Py_ssize_t nargs = PyTuple_GET_SIZE(callargs); int code_flags = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_flags; PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(func)); - _PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit_Ex(tstate, - (PyFunctionObject *)func, locals, - nargs, callargs, kwargs); + (PyFunctionObject *)func, locals, + nargs, callargs, kwargs); // Need to manually shrink the stack since we exit with DISPATCH_INLINED. STACK_SHRINK(oparg + 3); if (new_frame == NULL) { @@ -1224,10 +1227,9 @@ Py_DECREF(callargs); Py_XDECREF(kwargs); assert(PEEK(2 + (oparg & 1)) == NULL); - if (result == NULL) { STACK_SHRINK(((oparg & 1) ? 1 : 0)); goto pop_3_error; } - STACK_SHRINK(((oparg & 1) ? 1 : 0)); - STACK_SHRINK(2); - stack_pointer[-1] = result; + if (result == NULL) { stack_pointer += -3 - ((oparg & 1)); goto error; } + stack_pointer[-3 - ((oparg & 1))] = result; + stack_pointer += -2 - ((oparg & 1)); CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1261,8 +1263,8 @@ Py_DECREF(value2); Py_DECREF(value1); if (res == NULL) goto pop_2_error; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -1270,11 +1272,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_ISINSTANCE); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* isinstance(o, o2) */ @@ -1295,14 +1298,12 @@ } res = PyBool_FromLong(retval); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(inst); Py_DECREF(cls); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; DISPATCH(); } @@ -1318,7 +1319,7 @@ PyObject *callable; PyObject *res; kwnames = stack_pointer[-1]; - args = stack_pointer - 1 - oparg; + args = &stack_pointer[-1 - oparg]; self_or_null = stack_pointer[-2 - oparg]; callable = stack_pointer[-3 - oparg]; // oparg counts all of the args, but *not* self: @@ -1363,12 +1364,12 @@ } /* Callable is not a normal Python function */ res = PyObject_Vectorcall( - callable, args, - positional_args | PY_VECTORCALL_ARGUMENTS_OFFSET, - kwnames); + callable, args, + positional_args | PY_VECTORCALL_ARGUMENTS_OFFSET, + kwnames); if (opcode == INSTRUMENTED_CALL_KW) { PyObject *arg = total_args == 0 ? - &_PyInstrumentation_MISSING : args[0]; + &_PyInstrumentation_MISSING : args[0]; if (res == NULL) { _Py_call_instrumentation_exc2( tstate, PY_MONITORING_EVENT_C_RAISE, @@ -1389,10 +1390,9 @@ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } - if (res == NULL) { STACK_SHRINK(oparg); goto pop_3_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(2); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -3 - oparg; goto error; } + stack_pointer[-3 - oparg] = res; + stack_pointer += -2 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1401,11 +1401,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_LEN); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* len(o) */ @@ -1425,13 +1426,11 @@ } res = PyLong_FromSsize_t(len_i); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(callable); Py_DECREF(arg); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; DISPATCH(); } @@ -1439,10 +1438,11 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_LIST_APPEND); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self; PyObject *callable; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); @@ -1467,11 +1467,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_METHOD_DESCRIPTOR_FAST); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -1488,7 +1489,7 @@ DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); STAT_INC(CALL, hit); _PyCFunctionFast cfunc = - (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + (_PyCFunctionFast)(void(*)(void))meth->ml_meth; int nargs = total_args - 1; res = cfunc(self, args + 1, nargs); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -1497,10 +1498,9 @@ Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1509,11 +1509,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -1531,19 +1532,17 @@ STAT_INC(CALL, hit); int nargs = total_args - 1; _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; res = cfunc(self, args + 1, nargs, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1552,11 +1551,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_METHOD_DESCRIPTOR_NOARGS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 0 || oparg == 1); @@ -1584,10 +1584,9 @@ assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); Py_DECREF(self); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1596,11 +1595,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_METHOD_DESCRIPTOR_O); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -1629,10 +1629,9 @@ Py_DECREF(self); Py_DECREF(arg); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1641,10 +1640,12 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_PY_EXACT_ARGS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject *self_or_null; PyObject *callable; PyObject **args; _PyInterpreterFrame *new_frame; + /* Skip 1 cache entry */ // _CHECK_PEP_523 { DEOPT_IF(tstate->interp->eval_frame, CALL); @@ -1668,7 +1669,8 @@ DEOPT_IF(tstate->py_recursion_remaining <= 1, CALL); } // _INIT_CALL_PY_EXACT_ARGS - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; + self_or_null = stack_pointer[-1 - oparg]; { int argcount = oparg; if (self_or_null != NULL) { @@ -1692,26 +1694,26 @@ #endif } // _PUSH_FRAME - STACK_SHRINK(oparg); - STACK_SHRINK(2); { // Write it out explicitly because it's subtly different. // Eventually this should be the only occurrence of this code. assert(tstate->interp->eval_frame == NULL); - STORE_SP(); + stack_pointer += -2 - oparg; + _PyFrame_SetStackPointer(frame, stack_pointer); new_frame->previous = frame; CALL_STAT_INC(inlined_py_calls); frame = tstate->current_frame = new_frame; tstate->py_recursion_remaining--; LOAD_SP(); LOAD_IP(0); - #if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } - #endif + #endif } + stack_pointer += (((0) ? 1 : 0)); DISPATCH(); } @@ -1719,10 +1721,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_PY_WITH_DEFAULTS); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *self_or_null; PyObject *callable; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; uint32_t func_version = read_u32(&this_instr[2].cache); @@ -1763,11 +1766,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_STR_1); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); @@ -1778,10 +1782,9 @@ res = PyObject_Str(arg); Py_DECREF(arg); Py_DECREF(&PyUnicode_Type); // I.e., callable - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1790,11 +1793,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_TUPLE_1); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); @@ -1805,10 +1809,9 @@ res = PySequence_Tuple(arg); Py_DECREF(arg); Py_DECREF(&PyTuple_Type); // I.e., tuple - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1817,11 +1820,12 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(CALL_TYPE_1); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); @@ -1832,9 +1836,8 @@ res = Py_NewRef(Py_TYPE(obj)); Py_DECREF(obj); Py_DECREF(&PyType_Type); // I.e., callable - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; DISPATCH(); } @@ -1853,18 +1856,15 @@ Py_DECREF(match_type); if (true) goto pop_2_error; } - match = NULL; rest = NULL; int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, - &match, &rest); + &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); if (res < 0) goto pop_2_error; - assert((match == NULL) == (rest == NULL)); if (match == NULL) goto pop_2_error; - if (!Py_IsNone(match)) { PyErr_SetHandledException(match); } @@ -1884,10 +1884,9 @@ left = stack_pointer[-2]; assert(PyExceptionInstance_Check(left)); if (_PyEval_CheckExceptTypeValid(tstate, right) < 0) { - Py_DECREF(right); - if (true) goto pop_1_error; + Py_DECREF(right); + if (true) goto pop_1_error; } - int res = PyErr_GivenExceptionMatches(left, right); Py_DECREF(right); b = res ? Py_True : Py_False; @@ -1922,9 +1921,9 @@ monitor_reraise(tstate, frame, this_instr); goto exception_unwind; } - STACK_SHRINK(1); - stack_pointer[-2] = none; - stack_pointer[-1] = value; + stack_pointer[-3] = none; + stack_pointer[-2] = value; + stack_pointer += -1; DISPATCH(); } @@ -1934,7 +1933,6 @@ INSTRUCTION_STATS(COMPARE_OP); PREDICTED(COMPARE_OP); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -1968,8 +1966,8 @@ res = res_bool ? Py_True : Py_False; } } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -1977,6 +1975,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(COMPARE_OP_FLOAT); + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -1993,8 +1992,8 @@ _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -2002,6 +2001,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(COMPARE_OP_INT); + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -2022,8 +2022,8 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -2031,6 +2031,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(COMPARE_OP_STR); + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *res; @@ -2048,8 +2049,8 @@ assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS); res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -2067,8 +2068,8 @@ Py_DECREF(right); if (res < 0) goto pop_2_error; b = (res ^ oparg) ? Py_True : Py_False; - STACK_SHRINK(1); - stack_pointer[-1] = b; + stack_pointer[-2] = b; + stack_pointer += -1; DISPATCH(); } @@ -2098,8 +2099,8 @@ bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = Py_NewRef(bottom); - STACK_GROW(1); - stack_pointer[-1] = top; + stack_pointer[0] = top; + stack_pointer += 1; DISPATCH(); } @@ -2130,7 +2131,7 @@ int err = PyObject_DelAttr(owner, name); Py_DECREF(owner); if (err) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -2172,7 +2173,7 @@ if (err != 0) { if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + NAME_ERROR_MSG, name); } GOTO_ERROR(error); } @@ -2195,8 +2196,8 @@ // Can't use ERROR_IF here. if (err != 0) { _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, - name); + NAME_ERROR_MSG, + name); GOTO_ERROR(error); } DISPATCH(); @@ -2215,7 +2216,7 @@ Py_DECREF(container); Py_DECREF(sub); if (err) goto pop_2_error; - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -2235,7 +2236,7 @@ if (true) goto pop_1_error; } Py_DECREF(update); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -2250,14 +2251,14 @@ if (PyDict_Update(dict, update) < 0) { if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) { _PyErr_Format(tstate, PyExc_TypeError, - "'%.200s' object is not a mapping", - Py_TYPE(update)->tp_name); + "'%.200s' object is not a mapping", + Py_TYPE(update)->tp_name); } Py_DECREF(update); if (true) goto pop_1_error; } Py_DECREF(update); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -2281,7 +2282,7 @@ monitor_reraise(tstate, frame, this_instr); goto exception_unwind; } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -2290,17 +2291,17 @@ next_instr += 1; INSTRUCTION_STATS(END_FOR); PyObject *value; - // POP_TOP + // _POP_TOP value = stack_pointer[-1]; { Py_DECREF(value); } - // POP_TOP + // _POP_TOP value = stack_pointer[-2]; { Py_DECREF(value); } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -2313,8 +2314,8 @@ value = stack_pointer[-1]; receiver = stack_pointer[-2]; Py_DECREF(receiver); - STACK_SHRINK(1); - stack_pointer[-1] = value; + stack_pointer[-2] = value; + stack_pointer += -1; DISPATCH(); } @@ -2324,7 +2325,6 @@ INSTRUCTION_STATS(ENTER_EXECUTOR); TIER_ONE_ONLY CHECK_EVAL_BREAKER(); - PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; int original_oparg = executor->vm_data.oparg | (oparg & 0xfffff00); @@ -2352,11 +2352,11 @@ assert(STACK_LEVEL() == 2); if (should_be_none != Py_None) { PyErr_Format(PyExc_TypeError, - "__init__() should return None, not '%.200s'", - Py_TYPE(should_be_none)->tp_name); + "__init__() should return None, not '%.200s'", + Py_TYPE(should_be_none)->tp_name); GOTO_ERROR(error); } - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -2405,8 +2405,8 @@ Py_DECREF(value); Py_DECREF(fmt_spec); if (res == NULL) goto pop_2_error; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -2416,7 +2416,6 @@ INSTRUCTION_STATS(FOR_ITER); PREDICTED(FOR_ITER); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; PyObject *next; // _SPECIALIZE_FOR_ITER @@ -2448,7 +2447,7 @@ } /* iterator ended normally */ assert(next_instr[oparg].op.code == END_FOR || - next_instr[oparg].op.code == INSTRUMENTED_END_FOR); + next_instr[oparg].op.code == INSTRUMENTED_END_FOR); Py_DECREF(iter); STACK_SHRINK(1); /* Jump forward oparg, then skip following END_FOR instruction */ @@ -2457,8 +2456,8 @@ } // Common case: no jump, leave it to the code generator } - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; DISPATCH(); } @@ -2466,6 +2465,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(FOR_ITER_GEN); + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; iter = stack_pointer[-1]; DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); @@ -2489,8 +2489,10 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(FOR_ITER_LIST); + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; PyObject *next; + /* Skip 1 cache entry */ // _ITER_CHECK_LIST iter = stack_pointer[-1]; { @@ -2523,8 +2525,8 @@ assert(it->it_index < PyList_GET_SIZE(seq)); next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); } - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; DISPATCH(); } @@ -2532,8 +2534,10 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(FOR_ITER_RANGE); + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; PyObject *next; + /* Skip 1 cache entry */ // _ITER_CHECK_RANGE iter = stack_pointer[-1]; { @@ -2564,8 +2568,8 @@ next = PyLong_FromLong(value); if (next == NULL) goto error; } - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; DISPATCH(); } @@ -2573,8 +2577,10 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(FOR_ITER_TUPLE); + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; PyObject *next; + /* Skip 1 cache entry */ // _ITER_CHECK_TUPLE iter = stack_pointer[-1]; { @@ -2607,8 +2613,8 @@ assert(it->it_index < PyTuple_GET_SIZE(seq)); next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); } - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; DISPATCH(); } @@ -2621,11 +2627,9 @@ obj = stack_pointer[-1]; unaryfunc getter = NULL; PyTypeObject *type = Py_TYPE(obj); - if (type->tp_as_async != NULL) { getter = type->tp_as_async->am_aiter; } - if (getter == NULL) { _PyErr_Format(tstate, PyExc_TypeError, "'async for' requires an object with " @@ -2634,14 +2638,11 @@ Py_DECREF(obj); if (true) goto pop_1_error; } - iter = (*getter)(obj); Py_DECREF(obj); if (iter == NULL) goto pop_1_error; - if (Py_TYPE(iter)->tp_as_async == NULL || - Py_TYPE(iter)->tp_as_async->am_anext == NULL) { - + Py_TYPE(iter)->tp_as_async->am_anext == NULL) { _PyErr_Format(tstate, PyExc_TypeError, "'async for' received an object from __aiter__ " "that does not implement __anext__: %.100s", @@ -2663,7 +2664,6 @@ unaryfunc getter = NULL; PyObject *next_iter = NULL; PyTypeObject *type = Py_TYPE(aiter); - if (PyAsyncGen_CheckExact(aiter)) { awaitable = type->tp_as_async->am_anext(aiter); if (awaitable == NULL) { @@ -2673,7 +2673,6 @@ if (type->tp_as_async != NULL){ getter = type->tp_as_async->am_anext; } - if (getter != NULL) { next_iter = (*getter)(aiter); if (next_iter == NULL) { @@ -2687,7 +2686,6 @@ type->tp_name); GOTO_ERROR(error); } - awaitable = _PyCoro_GetAwaitableIter(next_iter); if (awaitable == NULL) { _PyErr_FormatFromCause( @@ -2695,15 +2693,14 @@ "'async for' received an invalid object " "from __anext__: %.100s", Py_TYPE(next_iter)->tp_name); - Py_DECREF(next_iter); GOTO_ERROR(error); } else { Py_DECREF(next_iter); } } - STACK_GROW(1); - stack_pointer[-1] = awaitable; + stack_pointer[0] = awaitable; + stack_pointer += 1; DISPATCH(); } @@ -2715,13 +2712,10 @@ PyObject *iter; iterable = stack_pointer[-1]; iter = _PyCoro_GetAwaitableIter(iterable); - if (iter == NULL) { _PyEval_FormatAwaitableError(tstate, Py_TYPE(iterable), oparg); } - Py_DECREF(iterable); - if (iter != NULL && PyCoro_CheckExact(iter)) { PyObject *yf = _PyGen_yf((PyGenObject*)iter); if (yf != NULL) { @@ -2735,7 +2729,6 @@ /* The code below jumps to `error` if `iter` is NULL. */ } } - if (iter == NULL) goto pop_1_error; stack_pointer[-1] = iter; DISPATCH(); @@ -2768,8 +2761,8 @@ if (len_i < 0) goto error; len_o = PyLong_FromSsize_t(len_i); if (len_o == NULL) goto error; - STACK_GROW(1); - stack_pointer[-1] = len_o; + stack_pointer[0] = len_o; + stack_pointer += 1; DISPATCH(); } @@ -2819,8 +2812,8 @@ PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_from(tstate, from, name); if (res == NULL) goto error; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -2839,8 +2832,8 @@ Py_DECREF(level); Py_DECREF(fromlist); if (res == NULL) goto pop_2_error; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } @@ -2852,10 +2845,10 @@ int total_args = oparg + is_meth; PyObject *function = PEEK(oparg + 2); PyObject *arg = total_args == 0 ? - &_PyInstrumentation_MISSING : PEEK(total_args); + &_PyInstrumentation_MISSING : PEEK(total_args); int err = _Py_call_instrumentation_2args( - tstate, PY_MONITORING_EVENT_CALL, - frame, this_instr, function, arg); + tstate, PY_MONITORING_EVENT_CALL, + frame, this_instr, function, arg); if (err) goto error; INCREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); GO_TO_INSTRUCTION(CALL); @@ -2876,10 +2869,10 @@ int total_args = oparg + is_meth; PyObject *function = PEEK(oparg + 3); PyObject *arg = total_args == 0 ? &_PyInstrumentation_MISSING - : PEEK(total_args + 1); + : PEEK(total_args + 1); int err = _Py_call_instrumentation_2args( - tstate, PY_MONITORING_EVENT_CALL, - frame, this_instr, function, arg); + tstate, PY_MONITORING_EVENT_CALL, + frame, this_instr, function, arg); if (err) goto error; GO_TO_INSTRUCTION(CALL_KW); } @@ -2904,7 +2897,7 @@ } Py_DECREF(receiver); Py_DECREF(value); - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -2925,8 +2918,8 @@ PyErr_SetRaisedException(NULL); } Py_DECREF(receiver); - STACK_SHRINK(1); - stack_pointer[-1] = value; + stack_pointer[-2] = value; + stack_pointer += -1; DISPATCH(); } @@ -3094,7 +3087,7 @@ } _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation( - tstate, oparg > 0, frame, this_instr); + tstate, oparg > 0, frame, this_instr); stack_pointer = _PyFrame_GetStackPointer(frame); if (err) goto error; if (frame->instr_ptr != this_instr) { @@ -3112,8 +3105,8 @@ INSTRUCTION_STATS(INSTRUMENTED_RETURN_CONST); PyObject *retval = GETITEM(FRAME_CO_CONSTS, oparg); int err = _Py_call_instrumentation_arg( - tstate, PY_MONITORING_EVENT_PY_RETURN, - frame, this_instr, retval); + tstate, PY_MONITORING_EVENT_PY_RETURN, + frame, this_instr, retval); if (err) GOTO_ERROR(error); Py_INCREF(retval); assert(EMPTY()); @@ -3136,8 +3129,8 @@ PyObject *retval; retval = stack_pointer[-1]; int err = _Py_call_instrumentation_arg( - tstate, PY_MONITORING_EVENT_PY_RETURN, - frame, this_instr, retval); + tstate, PY_MONITORING_EVENT_PY_RETURN, + frame, this_instr, retval); if (err) GOTO_ERROR(error); STACK_SHRINK(1); assert(EMPTY()); @@ -3167,8 +3160,8 @@ gen->gi_frame_state = FRAME_SUSPENDED + oparg; _PyFrame_SetStackPointer(frame, stack_pointer - 1); int err = _Py_call_instrumentation_arg( - tstate, PY_MONITORING_EVENT_PY_YIELD, - frame, this_instr, retval); + tstate, PY_MONITORING_EVENT_PY_YIELD, + frame, this_instr, retval); if (err) GOTO_ERROR(error); tstate->exc_info = gen->gi_exc_state.previous_item; gen->gi_exc_state.previous_item = NULL; @@ -3211,8 +3204,8 @@ Py_DECREF(left); Py_DECREF(right); b = res ? Py_True : Py_False; - STACK_SHRINK(1); - stack_pointer[-1] = b; + stack_pointer[-2] = b; + stack_pointer += -1; DISPATCH(); } @@ -3286,7 +3279,7 @@ v = stack_pointer[-1]; list = stack_pointer[-2 - (oparg-1)]; if (_PyList_AppendTakeRef((PyListObject *)list, v) < 0) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -3301,19 +3294,19 @@ PyObject *none_val = _PyList_Extend((PyListObject *)list, iterable); if (none_val == NULL) { if (_PyErr_ExceptionMatches(tstate, PyExc_TypeError) && - (Py_TYPE(iterable)->tp_iter == NULL && !PySequence_Check(iterable))) + (Py_TYPE(iterable)->tp_iter == NULL && !PySequence_Check(iterable))) { _PyErr_Clear(tstate); _PyErr_Format(tstate, PyExc_TypeError, - "Value after * must be an iterable, not %.200s", - Py_TYPE(iterable)->tp_name); + "Value after * must be an iterable, not %.200s", + Py_TYPE(iterable)->tp_name); } Py_DECREF(iterable); if (true) goto pop_1_error; } assert(Py_IsNone(none_val)); Py_DECREF(iterable); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -3323,8 +3316,8 @@ INSTRUCTION_STATS(LOAD_ASSERTION_ERROR); PyObject *value; value = Py_NewRef(PyExc_AssertionError); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3334,7 +3327,6 @@ INSTRUCTION_STATS(LOAD_ATTR); PREDICTED(LOAD_ATTR); _Py_CODEUNIT *this_instr = next_instr - 10; - static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *self_or_null = NULL; @@ -3374,7 +3366,7 @@ the second element of the stack to NULL, to signal CALL that it's not a method call. NULL | meth | arg1 | ... | argN - */ + */ Py_DECREF(owner); if (attr == NULL) goto pop_1_error; self_or_null = NULL; @@ -3387,9 +3379,9 @@ if (attr == NULL) goto pop_1_error; } } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = self_or_null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = self_or_null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3397,9 +3389,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_CLASS); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *null = NULL; + /* Skip 1 cache entry */ // _CHECK_ATTR_CLASS owner = stack_pointer[-1]; { @@ -3408,6 +3402,7 @@ assert(type_version != 0); DEOPT_IF(((PyTypeObject *)owner)->tp_version_tag != type_version, LOAD_ATTR); } + /* Skip 2 cache entries */ // _LOAD_ATTR_CLASS { PyObject *descr = read_obj(&this_instr[6].cache); @@ -3417,9 +3412,9 @@ null = NULL; Py_DECREF(owner); } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3427,6 +3422,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); @@ -3445,7 +3441,6 @@ assert(code->co_argcount == 2); DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), LOAD_ATTR); STAT_INC(LOAD_ATTR, hit); - PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); Py_INCREF(f); _PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f, 2); @@ -3461,9 +3456,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_INSTANCE_VALUE); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *null = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3490,9 +3487,10 @@ null = NULL; Py_DECREF(owner); } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + /* Skip 5 cache entries */ + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3500,9 +3498,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_METHOD_LAZY_DICT); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3519,6 +3519,7 @@ /* This object has a __dict__, just not yet created */ DEOPT_IF(dict != NULL, LOAD_ATTR); } + /* Skip 2 cache entries */ // _LOAD_ATTR_METHOD_LAZY_DICT { PyObject *descr = read_obj(&this_instr[6].cache); @@ -3529,9 +3530,9 @@ attr = Py_NewRef(descr); self = owner; } - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3539,9 +3540,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_METHOD_NO_DICT); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3550,6 +3553,7 @@ assert(type_version != 0); DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); } + /* Skip 2 cache entries */ // _LOAD_ATTR_METHOD_NO_DICT { PyObject *descr = read_obj(&this_instr[6].cache); @@ -3561,9 +3565,9 @@ attr = Py_NewRef(descr); self = owner; } - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3571,9 +3575,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_METHOD_WITH_VALUES); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3606,9 +3612,9 @@ assert(_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)); self = owner; } - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3616,9 +3622,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_MODULE); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *null = NULL; + /* Skip 1 cache entry */ // _CHECK_ATTR_MODULE owner = stack_pointer[-1]; { @@ -3642,9 +3650,10 @@ null = NULL; Py_DECREF(owner); } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + /* Skip 5 cache entries */ + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3652,8 +3661,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_NONDESCRIPTOR_NO_DICT); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3662,6 +3673,7 @@ assert(type_version != 0); DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); } + /* Skip 2 cache entries */ // _LOAD_ATTR_NONDESCRIPTOR_NO_DICT { PyObject *descr = read_obj(&this_instr[6].cache); @@ -3673,6 +3685,7 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; + stack_pointer += (((0) ? 1 : 0)); DISPATCH(); } @@ -3680,8 +3693,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3713,6 +3728,7 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; + stack_pointer += (((0) ? 1 : 0)); DISPATCH(); } @@ -3720,6 +3736,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_PROPERTY); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); @@ -3727,7 +3744,6 @@ PyObject *fget = read_obj(&this_instr[6].cache); assert((oparg & 1) == 0); DEOPT_IF(tstate->interp->eval_frame, LOAD_ATTR); - PyTypeObject *cls = Py_TYPE(owner); assert(type_version != 0); DEOPT_IF(cls->tp_version_tag != type_version, LOAD_ATTR); @@ -3752,9 +3768,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_SLOT); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *null = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3774,9 +3792,10 @@ null = NULL; Py_DECREF(owner); } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + /* Skip 5 cache entries */ + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3784,9 +3803,11 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; INSTRUCTION_STATS(LOAD_ATTR_WITH_HINT); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; PyObject *attr; PyObject *null = NULL; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -3827,9 +3848,10 @@ null = NULL; Py_DECREF(owner); } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + /* Skip 5 cache entries */ + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3844,8 +3866,8 @@ "__build_class__ not found"); if (true) goto error; } - STACK_GROW(1); - stack_pointer[-1] = bc; + stack_pointer[0] = bc; + stack_pointer += 1; DISPATCH(); } @@ -3856,8 +3878,8 @@ PyObject *value; value = GETITEM(FRAME_CO_CONSTS, oparg); Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3873,8 +3895,8 @@ if (true) goto error; } Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3886,8 +3908,8 @@ value = GETLOCAL(oparg); assert(value != NULL); Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3899,8 +3921,8 @@ value = GETLOCAL(oparg); // do not use SETLOCAL here, it decrefs the old value GETLOCAL(oparg) = NULL; - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3912,8 +3934,8 @@ value = GETLOCAL(oparg); if (value == NULL) goto unbound_local_error; Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; DISPATCH(); } @@ -3929,9 +3951,9 @@ value2 = GETLOCAL(oparg2); Py_INCREF(value1); Py_INCREF(value2); - STACK_GROW(2); - stack_pointer[-2] = value1; - stack_pointer[-1] = value2; + stack_pointer[0] = value1; + stack_pointer[1] = value2; + stack_pointer += 2; DISPATCH(); } @@ -3984,8 +4006,8 @@ } if (v == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } } @@ -4001,7 +4023,6 @@ INSTRUCTION_STATS(LOAD_GLOBAL); PREDICTED(LOAD_GLOBAL); _Py_CODEUNIT *this_instr = next_instr - 5; - static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *res; PyObject *null = NULL; // _SPECIALIZE_LOAD_GLOBAL @@ -4026,14 +4047,14 @@ && PyDict_CheckExact(BUILTINS())) { res = _PyDict_LoadGlobal((PyDictObject *)GLOBALS(), - (PyDictObject *)BUILTINS(), - name); + (PyDictObject *)BUILTINS(), + name); if (res == NULL) { if (!_PyErr_Occurred(tstate)) { /* _PyDict_LoadGlobal() returns NULL without raising * an exception if the key doesn't exist */ _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + NAME_ERROR_MSG, name); } if (true) goto error; } @@ -4048,18 +4069,17 @@ if (PyMapping_GetOptionalItem(BUILTINS(), name, &res) < 0) goto error; if (res == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); if (true) goto error; } } } null = NULL; } - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4067,8 +4087,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 5; INSTRUCTION_STATS(LOAD_GLOBAL_BUILTIN); + static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *res; PyObject *null = NULL; + /* Skip 1 cache entry */ // _GUARD_GLOBALS_VERSION { uint16_t version = read_u16(&this_instr[2].cache); @@ -4096,10 +4118,9 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; } - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4107,8 +4128,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 5; INSTRUCTION_STATS(LOAD_GLOBAL_MODULE); + static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *res; PyObject *null = NULL; + /* Skip 1 cache entry */ // _GUARD_GLOBALS_VERSION { uint16_t version = read_u16(&this_instr[2].cache); @@ -4117,6 +4140,7 @@ DEOPT_IF(dict->ma_keys->dk_version != version, LOAD_GLOBAL); assert(DK_IS_UNICODE(dict->ma_keys)); } + /* Skip 1 cache entry */ // _LOAD_GLOBAL_MODULE { uint16_t index = read_u16(&this_instr[4].cache); @@ -4128,10 +4152,9 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; } - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4147,8 +4170,8 @@ if (true) goto error; } Py_INCREF(locals); - STACK_GROW(1); - stack_pointer[-1] = locals; + stack_pointer[0] = locals; + stack_pointer += 1; DISPATCH(); } @@ -4177,14 +4200,14 @@ } if (v == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } } } - STACK_GROW(1); - stack_pointer[-1] = v; + stack_pointer[0] = v; + stack_pointer += 1; DISPATCH(); } @@ -4194,7 +4217,6 @@ INSTRUCTION_STATS(LOAD_SUPER_ATTR); PREDICTED(LOAD_SUPER_ATTR); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *class; PyObject *global_super; PyObject *self; @@ -4224,8 +4246,8 @@ if (opcode == INSTRUMENTED_LOAD_SUPER_ATTR) { PyObject *arg = oparg & 2 ? class : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( - tstate, PY_MONITORING_EVENT_CALL, - frame, this_instr, global_super, arg); + tstate, PY_MONITORING_EVENT_CALL, + frame, this_instr, global_super, arg); if (err) goto pop_3_error; } // we make no attempt to optimize here; specializations should @@ -4258,10 +4280,9 @@ if (attr == NULL) goto pop_3_error; null = NULL; } - STACK_SHRINK(2); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-3] = attr; + if (oparg & 1) stack_pointer[-2] = null; + stack_pointer += -2 + ((oparg & 1)); DISPATCH(); } @@ -4269,6 +4290,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(LOAD_SUPER_ATTR_ATTR); + static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self; PyObject *class; PyObject *global_super; @@ -4286,8 +4308,8 @@ Py_DECREF(class); Py_DECREF(self); if (attr == NULL) goto pop_3_error; - STACK_SHRINK(2); - stack_pointer[-1] = attr; + stack_pointer[-3] = attr; + stack_pointer += -2 + (((0) ? 1 : 0)); DISPATCH(); } @@ -4295,6 +4317,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(LOAD_SUPER_ATTR_METHOD); + static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self; PyObject *class; PyObject *global_super; @@ -4324,9 +4347,9 @@ Py_DECREF(self); self_or_null = NULL; } - STACK_SHRINK(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self_or_null; + stack_pointer[-3] = attr; + stack_pointer[-2] = self_or_null; + stack_pointer += -1; DISPATCH(); } @@ -4352,17 +4375,14 @@ PyObject *codeobj; PyObject *func; codeobj = stack_pointer[-1]; - PyFunctionObject *func_obj = (PyFunctionObject *) - PyFunction_New(codeobj, GLOBALS()); - + PyFunction_New(codeobj, GLOBALS()); Py_DECREF(codeobj); if (func_obj == NULL) { GOTO_ERROR(error); } - _PyFunction_SetVersion( - func_obj, ((PyCodeObject *)codeobj)->co_version); + func_obj, ((PyCodeObject *)codeobj)->co_version); func = (PyObject *)func_obj; stack_pointer[-1] = func; DISPATCH(); @@ -4382,7 +4402,7 @@ /* dict[key] = value */ // Do not DECREF INPUTS because the function steals the references if (_PyDict_SetItem_Take2((PyDictObject *)dict, key, value) != 0) goto pop_2_error; - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -4409,10 +4429,11 @@ } else { if (_PyErr_Occurred(tstate)) goto pop_3_error; + // Error! attrs = Py_None; // Failure! } - STACK_SHRINK(2); - stack_pointer[-1] = attrs; + stack_pointer[-3] = attrs; + stack_pointer += -2; DISPATCH(); } @@ -4428,8 +4449,8 @@ // On successful match, PUSH(values). Otherwise, PUSH(None). values_or_none = _PyEval_MatchKeys(tstate, subject, keys); if (values_or_none == NULL) goto error; - STACK_GROW(1); - stack_pointer[-1] = values_or_none; + stack_pointer[0] = values_or_none; + stack_pointer += 1; DISPATCH(); } @@ -4442,8 +4463,8 @@ subject = stack_pointer[-1]; int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING; res = match ? Py_True : Py_False; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -4456,8 +4477,8 @@ subject = stack_pointer[-1]; int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE; res = match ? Py_True : Py_False; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -4476,7 +4497,7 @@ exc_value = stack_pointer[-1]; _PyErr_StackItem *exc_info = tstate->exc_info; Py_XSETREF(exc_info->exc_value, exc_value); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4492,7 +4513,7 @@ this_instr[1].cache = (this_instr[1].cache << 1) | flag; #endif JUMPBY(oparg * flag); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4524,7 +4545,7 @@ #endif JUMPBY(oparg * flag); } - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4556,7 +4577,7 @@ #endif JUMPBY(oparg * flag); } - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4572,7 +4593,7 @@ this_instr[1].cache = (this_instr[1].cache << 1) | flag; #endif JUMPBY(oparg * flag); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4583,7 +4604,7 @@ PyObject *value; value = stack_pointer[-1]; Py_DECREF(value); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4603,9 +4624,9 @@ } assert(PyExceptionInstance_Check(new_exc)); exc_info->exc_value = Py_NewRef(new_exc); - STACK_GROW(1); - stack_pointer[-2] = prev_exc; - stack_pointer[-1] = new_exc; + stack_pointer[-1] = prev_exc; + stack_pointer[0] = new_exc; + stack_pointer += 1; DISPATCH(); } @@ -4615,8 +4636,8 @@ INSTRUCTION_STATS(PUSH_NULL); PyObject *res; res = NULL; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -4625,29 +4646,29 @@ next_instr += 1; INSTRUCTION_STATS(RAISE_VARARGS); PyObject **args; - args = stack_pointer - oparg; + args = &stack_pointer[-oparg]; TIER_ONE_ONLY PyObject *cause = NULL, *exc = NULL; switch (oparg) { - case 2: + case 2: cause = args[1]; /* fall through */ - case 1: + case 1: exc = args[0]; /* fall through */ - case 0: + case 0: if (do_raise(tstate, exc, cause)) { assert(oparg == 0); monitor_reraise(tstate, frame, this_instr); goto exception_unwind; } break; - default: + default: _PyErr_SetString(tstate, PyExc_SystemError, "bad RAISE_VARARGS oparg"); break; } - if (true) { STACK_SHRINK(oparg); goto error; } + if (true) { stack_pointer += -oparg; goto error; } } TARGET(RERAISE) { @@ -4657,7 +4678,7 @@ PyObject *exc; PyObject **values; exc = stack_pointer[-1]; - values = stack_pointer - 1 - oparg; + values = &stack_pointer[-1 - oparg]; TIER_ONE_ONLY assert(oparg >= 0 && oparg <= 2); if (oparg) { @@ -4693,12 +4714,11 @@ INSTRUCTION_STATS(RESUME); PREDICTED(RESUME); _Py_CODEUNIT *this_instr = next_instr - 1; - static_assert(0 == 0, "incorrect cache size"); TIER_ONE_ONLY assert(frame == tstate->current_frame); uintptr_t global_version = - _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & - ~_PY_EVAL_EVENTS_MASK; + _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & + ~_PY_EVAL_EVENTS_MASK; uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((code_version & 255) == 0); if (code_version != global_version) { @@ -4719,10 +4739,11 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RESUME_CHECK); -#if defined(__EMSCRIPTEN__) + static_assert(0 == 0, "incorrect cache size"); + #if defined(__EMSCRIPTEN__) DEOPT_IF(_Py_emscripten_signal_clock == 0, RESUME); _Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING; -#endif + #endif uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); @@ -4736,7 +4757,7 @@ INSTRUCTION_STATS(RETURN_CONST); PyObject *value; PyObject *retval; - // LOAD_CONST + // _LOAD_CONST { value = GETITEM(FRAME_CO_CONSTS, oparg); Py_INCREF(value); @@ -4744,11 +4765,11 @@ // _POP_FRAME retval = value; { - assert(EMPTY()); #if TIER_ONE assert(frame != &entry_frame); #endif - STORE_SP(); + _PyFrame_SetStackPointer(frame, stack_pointer); + assert(EMPTY()); _Py_LeaveRecursiveCallPy(tstate); // GH-99729: We need to unlink the frame *before* clearing it: _PyInterpreterFrame *dying = frame; @@ -4757,12 +4778,12 @@ _PyFrame_StackPush(frame, retval); LOAD_SP(); LOAD_IP(frame->return_offset); - #if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } - #endif + #endif } DISPATCH(); } @@ -4801,12 +4822,12 @@ INSTRUCTION_STATS(RETURN_VALUE); PyObject *retval; retval = stack_pointer[-1]; - STACK_SHRINK(1); - assert(EMPTY()); #if TIER_ONE assert(frame != &entry_frame); #endif - STORE_SP(); + stack_pointer += -1; + _PyFrame_SetStackPointer(frame, stack_pointer); + assert(EMPTY()); _Py_LeaveRecursiveCallPy(tstate); // GH-99729: We need to unlink the frame *before* clearing it: _PyInterpreterFrame *dying = frame; @@ -4815,12 +4836,12 @@ _PyFrame_StackPush(frame, retval); LOAD_SP(); LOAD_IP(frame->return_offset); -#if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } -#endif + #endif DISPATCH(); } @@ -4830,7 +4851,6 @@ INSTRUCTION_STATS(SEND); PREDICTED(SEND); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *receiver; PyObject *v; PyObject *retval; @@ -4897,6 +4917,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(SEND_GEN); + static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v; PyObject *receiver; v = stack_pointer[-1]; @@ -4955,7 +4976,7 @@ int err = PySet_Add(set, v); Py_DECREF(v); if (err) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -4971,28 +4992,28 @@ PyFunctionObject *func_obj = (PyFunctionObject *)func; switch(oparg) { case MAKE_FUNCTION_CLOSURE: - assert(func_obj->func_closure == NULL); - func_obj->func_closure = attr; - break; + assert(func_obj->func_closure == NULL); + func_obj->func_closure = attr; + break; case MAKE_FUNCTION_ANNOTATIONS: - assert(func_obj->func_annotations == NULL); - func_obj->func_annotations = attr; - break; + assert(func_obj->func_annotations == NULL); + func_obj->func_annotations = attr; + break; case MAKE_FUNCTION_KWDEFAULTS: - assert(PyDict_CheckExact(attr)); - assert(func_obj->func_kwdefaults == NULL); - func_obj->func_kwdefaults = attr; - break; + assert(PyDict_CheckExact(attr)); + assert(func_obj->func_kwdefaults == NULL); + func_obj->func_kwdefaults = attr; + break; case MAKE_FUNCTION_DEFAULTS: - assert(PyTuple_CheckExact(attr)); - assert(func_obj->func_defaults == NULL); - func_obj->func_defaults = attr; - break; + assert(PyTuple_CheckExact(attr)); + assert(func_obj->func_defaults == NULL); + func_obj->func_defaults = attr; + break; default: - Py_UNREACHABLE(); + Py_UNREACHABLE(); } - STACK_SHRINK(1); - stack_pointer[-1] = func; + stack_pointer[-2] = func; + stack_pointer += -1; DISPATCH(); } @@ -5007,7 +5028,7 @@ int err = _PySet_Update(set, iterable); Py_DECREF(iterable); if (err < 0) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -5017,7 +5038,6 @@ INSTRUCTION_STATS(STORE_ATTR); PREDICTED(STORE_ATTR); _Py_CODEUNIT *this_instr = next_instr - 5; - static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *v; // _SPECIALIZE_STORE_ATTR @@ -5045,7 +5065,7 @@ Py_DECREF(owner); if (err) goto pop_2_error; } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -5053,8 +5073,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 5; INSTRUCTION_STATS(STORE_ATTR_INSTANCE_VALUE); + static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *value; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -5086,7 +5108,7 @@ } Py_DECREF(owner); } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -5094,8 +5116,10 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 5; INSTRUCTION_STATS(STORE_ATTR_SLOT); + static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *value; + /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION owner = stack_pointer[-1]; { @@ -5115,7 +5139,7 @@ Py_XDECREF(old_value); Py_DECREF(owner); } - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -5123,6 +5147,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 5; INSTRUCTION_STATS(STORE_ATTR_WITH_HINT); + static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *value; owner = stack_pointer[-1]; @@ -5167,7 +5192,7 @@ /* PEP 509 */ dict->ma_version_tag = new_version; Py_DECREF(owner); - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -5181,7 +5206,7 @@ PyObject *oldobj = PyCell_GET(cell); PyCell_SET(cell, v); Py_XDECREF(oldobj); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -5192,7 +5217,7 @@ PyObject *value; value = stack_pointer[-1]; SETLOCAL(oparg, value); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -5224,7 +5249,7 @@ uint32_t oparg2 = oparg & 15; SETLOCAL(oparg1, value1); SETLOCAL(oparg2, value2); - STACK_SHRINK(2); + stack_pointer += -2; DISPATCH(); } @@ -5238,7 +5263,7 @@ int err = PyDict_SetItem(GLOBALS(), name, v); Py_DECREF(v); if (err) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -5258,12 +5283,12 @@ if (true) goto pop_1_error; } if (PyDict_CheckExact(ns)) - err = PyDict_SetItem(ns, name, v); + err = PyDict_SetItem(ns, name, v); else - err = PyObject_SetItem(ns, name, v); + err = PyObject_SetItem(ns, name, v); Py_DECREF(v); if (err) goto pop_1_error; - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } @@ -5291,7 +5316,7 @@ Py_DECREF(v); Py_DECREF(container); if (err) goto pop_4_error; - STACK_SHRINK(4); + stack_pointer += -4; DISPATCH(); } @@ -5301,7 +5326,6 @@ INSTRUCTION_STATS(STORE_SUBSCR); PREDICTED(STORE_SUBSCR); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *container; PyObject *v; @@ -5331,7 +5355,7 @@ Py_DECREF(sub); if (err) goto pop_3_error; } - STACK_SHRINK(3); + stack_pointer += -3; DISPATCH(); } @@ -5339,6 +5363,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(STORE_SUBSCR_DICT); + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *dict; PyObject *value; @@ -5350,7 +5375,7 @@ int err = _PyDict_SetItem_Take2((PyDictObject *)dict, sub, value); Py_DECREF(dict); if (err) goto pop_3_error; - STACK_SHRINK(3); + stack_pointer += -3; DISPATCH(); } @@ -5358,6 +5383,7 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(STORE_SUBSCR_LIST_INT); + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *list; PyObject *value; @@ -5366,21 +5392,19 @@ value = stack_pointer[-3]; DEOPT_IF(!PyLong_CheckExact(sub), STORE_SUBSCR); DEOPT_IF(!PyList_CheckExact(list), STORE_SUBSCR); - // Ensure nonnegative, zero-or-one-digit ints. DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), STORE_SUBSCR); Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; // Ensure index < len(list) DEOPT_IF(index >= PyList_GET_SIZE(list), STORE_SUBSCR); STAT_INC(STORE_SUBSCR, hit); - PyObject *old_value = PyList_GET_ITEM(list, index); PyList_SET_ITEM(list, index, value); assert(old_value != NULL); Py_DECREF(old_value); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - STACK_SHRINK(3); + stack_pointer += -3; DISPATCH(); } @@ -5404,7 +5428,6 @@ INSTRUCTION_STATS(TO_BOOL); PREDICTED(TO_BOOL); _Py_CODEUNIT *this_instr = next_instr - 4; - static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; // _SPECIALIZE_TO_BOOL @@ -5437,6 +5460,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_ALWAYS_TRUE); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -5455,6 +5479,7 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_BOOL); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; value = stack_pointer[-1]; DEOPT_IF(!PyBool_Check(value), TO_BOOL); @@ -5466,6 +5491,7 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_INT); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -5487,6 +5513,7 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_LIST); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -5502,6 +5529,7 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_NONE); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -5517,6 +5545,7 @@ frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(TO_BOOL_STR); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -5587,7 +5616,7 @@ int res = _PyEval_UnpackIterable(tstate, seq, oparg & 0xFF, oparg >> 8, top); Py_DECREF(seq); if (res == 0) goto pop_1_error; - STACK_GROW((oparg & 0xFF) + (oparg >> 8)); + stack_pointer += (oparg >> 8) + (oparg & 0xFF); DISPATCH(); } @@ -5597,7 +5626,6 @@ INSTRUCTION_STATS(UNPACK_SEQUENCE); PREDICTED(UNPACK_SEQUENCE); _Py_CODEUNIT *this_instr = next_instr - 2; - static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; // _SPECIALIZE_UNPACK_SEQUENCE seq = stack_pointer[-1]; @@ -5623,8 +5651,7 @@ Py_DECREF(seq); if (res == 0) goto pop_1_error; } - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; DISPATCH(); } @@ -5632,10 +5659,11 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(UNPACK_SEQUENCE_LIST); + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; seq = stack_pointer[-1]; - values = stack_pointer - 1; + values = &stack_pointer[-1]; DEOPT_IF(!PyList_CheckExact(seq), UNPACK_SEQUENCE); DEOPT_IF(PyList_GET_SIZE(seq) != oparg, UNPACK_SEQUENCE); STAT_INC(UNPACK_SEQUENCE, hit); @@ -5644,8 +5672,7 @@ *values++ = Py_NewRef(items[i]); } Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; DISPATCH(); } @@ -5653,10 +5680,11 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(UNPACK_SEQUENCE_TUPLE); + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; seq = stack_pointer[-1]; - values = stack_pointer - 1; + values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); DEOPT_IF(PyTuple_GET_SIZE(seq) != oparg, UNPACK_SEQUENCE); STAT_INC(UNPACK_SEQUENCE, hit); @@ -5665,8 +5693,7 @@ *values++ = Py_NewRef(items[i]); } Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; DISPATCH(); } @@ -5674,10 +5701,11 @@ frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(UNPACK_SEQUENCE_TWO_TUPLE); + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; seq = stack_pointer[-1]; - values = stack_pointer - 1; + values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); DEOPT_IF(PyTuple_GET_SIZE(seq) != 2, UNPACK_SEQUENCE); assert(oparg == 2); @@ -5685,8 +5713,7 @@ values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; DISPATCH(); } @@ -5708,9 +5735,8 @@ - exit_func: FOURTH = the context.__exit__ bound method We call FOURTH(type(TOP), TOP, GetTraceback(TOP)). Then we push the __exit__ return value. - */ + */ PyObject *exc, *tb; - assert(val && PyExceptionInstance_Check(val)); exc = PyExceptionInstance_Class(val); tb = PyException_GetTraceback(val); @@ -5724,10 +5750,10 @@ (void)lasti; // Shut up compiler warning if asserts are off PyObject *stack[4] = {NULL, exc, val, tb}; res = PyObject_Vectorcall(exit_func, stack + 1, - 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); + 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (res == NULL) goto error; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } @@ -5759,5 +5785,4 @@ LOAD_IP(1 + INLINE_CACHE_ENTRIES_SEND); goto resume_frame; } - #undef TIER_ONE diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py new file mode 100644 index 00000000000000..027f9861a1c0eb --- /dev/null +++ b/Tools/cases_generator/analyzer.py @@ -0,0 +1,456 @@ +from dataclasses import dataclass +import lexer +import parser +from typing import Optional + + +@dataclass +class Properties: + escapes: bool + infallible: bool + deopts: bool + oparg: bool + jumps: bool + ends_with_eval_breaker: bool + needs_this: bool + always_exits: bool + stores_sp: bool + + def dump(self, indent: str) -> None: + print(indent, end="") + text = ", ".join([f"{key}: {value}" for (key, value) in self.__dict__.items()]) + print(indent, text, sep="") + + @staticmethod + def from_list(properties: list["Properties"]) -> "Properties": + return Properties( + escapes=any(p.escapes for p in properties), + infallible=all(p.infallible for p in properties), + deopts=any(p.deopts for p in properties), + oparg=any(p.oparg for p in properties), + jumps=any(p.jumps for p in properties), + ends_with_eval_breaker=any(p.ends_with_eval_breaker for p in properties), + needs_this=any(p.needs_this for p in properties), + always_exits=any(p.always_exits for p in properties), + stores_sp=any(p.stores_sp for p in properties), + ) + + +SKIP_PROPERTIES = Properties( + escapes=False, + infallible=True, + deopts=False, + oparg=False, + jumps=False, + ends_with_eval_breaker=False, + needs_this=False, + always_exits=False, + stores_sp=False, +) + + +@dataclass +class Skip: + "Unused cache entry" + size: int + + @property + def name(self) -> str: + return f"unused/{self.size}" + + @property + def properties(self) -> Properties: + return SKIP_PROPERTIES + + +@dataclass +class StackItem: + name: str + type: str | None + condition: str | None + size: str + peek: bool = False + + def __str__(self) -> str: + cond = f" if ({self.condition})" if self.condition else "" + size = f"[{self.size}]" if self.size != "1" else "" + type = "" if self.type is None else f"{self.type} " + return f"{type}{self.name}{size}{cond} {self.peek}" + + def is_array(self) -> bool: + return self.type == "PyObject **" + + +@dataclass +class StackEffect: + inputs: list[StackItem] + outputs: list[StackItem] + + def __str__(self) -> str: + return f"({', '.join([str(i) for i in self.inputs])} -- {', '.join([str(i) for i in self.outputs])})" + + +@dataclass +class CacheEntry: + name: str + size: int + + def __str__(self) -> str: + return f"{self.name}/{self.size}" + + +@dataclass +class Uop: + name: str + context: parser.Context | None + annotations: list[str] + stack: StackEffect + caches: list[CacheEntry] + body: list[lexer.Token] + properties: Properties + _size: int = -1 + + def dump(self, indent: str) -> None: + print( + indent, self.name, ", ".join(self.annotations) if self.annotations else "" + ) + print(indent, self.stack, ", ".join([str(c) for c in self.caches])) + self.properties.dump(" " + indent) + + @property + def size(self) -> int: + if self._size < 0: + self._size = sum(c.size for c in self.caches) + return self._size + + +Part = Uop | Skip + + +@dataclass +class Instruction: + name: str + parts: list[Part] + _properties: Properties | None + is_target: bool = False + family: Optional["Family"] = None + + @property + def properties(self) -> Properties: + if self._properties is None: + self._properties = self._compute_properties() + return self._properties + + def _compute_properties(self) -> Properties: + return Properties.from_list([part.properties for part in self.parts]) + + def dump(self, indent: str) -> None: + print(indent, self.name, "=", ", ".join([part.name for part in self.parts])) + self.properties.dump(" " + indent) + + @property + def size(self) -> int: + return 1 + sum(part.size for part in self.parts) + + +@dataclass +class PseudoInstruction: + name: str + targets: list[Instruction] + flags: list[str] + + def dump(self, indent: str) -> None: + print(indent, self.name, "->", " or ".join([t.name for t in self.targets])) + + +@dataclass +class Family: + name: str + size: str + members: list[Instruction] + + def dump(self, indent: str) -> None: + print(indent, self.name, "= ", ", ".join([m.name for m in self.members])) + + +@dataclass +class Analysis: + instructions: dict[str, Instruction] + uops: dict[str, Uop] + families: dict[str, Family] + pseudos: dict[str, PseudoInstruction] + + +def analysis_error(message: str, tkn: lexer.Token) -> SyntaxError: + # To do -- support file and line output + # Construct a SyntaxError instance from message and token + return lexer.make_syntax_error(message, "", tkn.line, tkn.column, "") + + +def override_error( + name: str, + context: parser.Context | None, + prev_context: parser.Context | None, + token: lexer.Token, +) -> SyntaxError: + return analysis_error( + f"Duplicate definition of '{name}' @ {context} " + f"previous definition @ {prev_context}", + token, + ) + + +def convert_stack_item(item: parser.StackEffect) -> StackItem: + return StackItem(item.name, item.type, item.cond, (item.size or "1")) + + +def analyze_stack(op: parser.InstDef) -> StackEffect: + inputs: list[StackItem] = [ + convert_stack_item(i) for i in op.inputs if isinstance(i, parser.StackEffect) + ] + outputs: list[StackItem] = [convert_stack_item(i) for i in op.outputs] + for input, output in zip(inputs, outputs): + if input.name == output.name: + input.peek = output.peek = True + return StackEffect(inputs, outputs) + + +def analyze_caches(op: parser.InstDef) -> list[CacheEntry]: + caches: list[parser.CacheEffect] = [ + i for i in op.inputs if isinstance(i, parser.CacheEffect) + ] + return [CacheEntry(i.name, int(i.size)) for i in caches] + + +def variable_used(node: parser.InstDef, name: str) -> bool: + """Determine whether a variable with a given name is used in a node.""" + return any( + token.kind == "IDENTIFIER" and token.text == name for token in node.tokens + ) + + +def is_infallible(op: parser.InstDef) -> bool: + return not ( + variable_used(op, "ERROR_IF") + or variable_used(op, "error") + or variable_used(op, "pop_1_error") + or variable_used(op, "exception_unwind") + or variable_used(op, "resume_with_error") + ) + + +from flags import makes_escaping_api_call + +EXITS = { + "DISPATCH", + "GO_TO_INSTRUCTION", + "Py_UNREACHABLE", + "DISPATCH_INLINED", + "DISPATCH_GOTO", +} + + +def eval_breaker_at_end(op: parser.InstDef) -> bool: + return op.tokens[-5].text == "CHECK_EVAL_BREAKER" + + +def always_exits(op: parser.InstDef) -> bool: + depth = 0 + tkn_iter = iter(op.tokens) + for tkn in tkn_iter: + if tkn.kind == "LBRACE": + depth += 1 + elif tkn.kind == "RBRACE": + depth -= 1 + elif depth > 1: + continue + elif tkn.kind == "GOTO" or tkn.kind == "RETURN": + return True + elif tkn.kind == "KEYWORD": + if tkn.text in EXITS: + return True + elif tkn.kind == "IDENTIFIER": + if tkn.text in EXITS: + return True + if tkn.text == "DEOPT_IF" or tkn.text == "ERROR_IF": + next(tkn_iter) # '(' + t = next(tkn_iter) + if t.text == "true": + return True + return False + + +def compute_properties(op: parser.InstDef) -> Properties: + return Properties( + escapes=makes_escaping_api_call(op), + infallible=is_infallible(op), + deopts=variable_used(op, "DEOPT_IF"), + oparg=variable_used(op, "oparg"), + jumps=variable_used(op, "JUMPBY"), + ends_with_eval_breaker=eval_breaker_at_end(op), + needs_this=variable_used(op, "this_instr"), + always_exits=always_exits(op), + stores_sp=variable_used(op, "STORE_SP"), + ) + + +def make_uop(name: str, op: parser.InstDef) -> Uop: + return Uop( + name=name, + context=op.context, + annotations=op.annotations, + stack=analyze_stack(op), + caches=analyze_caches(op), + body=op.block.tokens, + properties=compute_properties(op), + ) + + +def add_op(op: parser.InstDef, uops: dict[str, Uop]) -> None: + assert op.kind == "op" + if op.name in uops: + if "override" not in op.annotations: + raise override_error( + op.name, op.context, uops[op.name].context, op.tokens[0] + ) + uops[op.name] = make_uop(op.name, op) + + +def add_instruction( + name: str, parts: list[Part], instructions: dict[str, Instruction] +) -> None: + instructions[name] = Instruction(name, parts, None) + + +def desugar_inst( + inst: parser.InstDef, instructions: dict[str, Instruction], uops: dict[str, Uop] +) -> None: + assert inst.kind == "inst" + name = inst.name + uop = make_uop("_" + inst.name, inst) + uops[inst.name] = uop + add_instruction(name, [uop], instructions) + + +def add_macro( + macro: parser.Macro, instructions: dict[str, Instruction], uops: dict[str, Uop] +) -> None: + parts: list[Uop | Skip] = [] + for part in macro.uops: + match part: + case parser.OpName(): + if part.name not in uops: + analysis_error(f"No Uop named {part.name}", macro.tokens[0]) + parts.append(uops[part.name]) + case parser.CacheEffect(): + parts.append(Skip(part.size)) + case _: + assert False + assert parts + add_instruction(macro.name, parts, instructions) + + +def add_family( + pfamily: parser.Family, + instructions: dict[str, Instruction], + families: dict[str, Family], +) -> None: + family = Family( + pfamily.name, + pfamily.size, + [instructions[member_name] for member_name in pfamily.members], + ) + for member in family.members: + member.family = family + # The head of the family is an implicit jump target for DEOPTs + instructions[family.name].is_target = True + families[family.name] = family + + +def add_pseudo( + pseudo: parser.Pseudo, + instructions: dict[str, Instruction], + pseudos: dict[str, PseudoInstruction], +) -> None: + pseudos[pseudo.name] = PseudoInstruction( + pseudo.name, + [instructions[target] for target in pseudo.targets], + pseudo.flags, + ) + + +def analyze_forest(forest: list[parser.AstNode]) -> Analysis: + instructions: dict[str, Instruction] = {} + uops: dict[str, Uop] = {} + families: dict[str, Family] = {} + pseudos: dict[str, PseudoInstruction] = {} + for node in forest: + match node: + case parser.InstDef(name): + if node.kind == "inst": + desugar_inst(node, instructions, uops) + else: + assert node.kind == "op" + add_op(node, uops) + case parser.Macro(): + pass + case parser.Family(): + pass + case parser.Pseudo(): + pass + case _: + assert False + for node in forest: + if isinstance(node, parser.Macro): + add_macro(node, instructions, uops) + for node in forest: + match node: + case parser.Family(): + add_family(node, instructions, families) + case parser.Pseudo(): + add_pseudo(node, instructions, pseudos) + case _: + pass + for uop in uops.values(): + tkn_iter = iter(uop.body) + for tkn in tkn_iter: + if tkn.kind == "IDENTIFIER" and tkn.text == "GO_TO_INSTRUCTION": + if next(tkn_iter).kind != "LPAREN": + continue + target = next(tkn_iter) + if target.kind != "IDENTIFIER": + continue + if target.text in instructions: + instructions[target.text].is_target = True + # Hack + instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] + return Analysis(instructions, uops, families, pseudos) + + +def analyze_files(filenames: list[str]) -> Analysis: + return analyze_forest(parser.parse_files(filenames)) + + +def dump_analysis(analysis: Analysis) -> None: + print("Uops:") + for u in analysis.uops.values(): + u.dump(" ") + print("Instructions:") + for i in analysis.instructions.values(): + i.dump(" ") + print("Families:") + for f in analysis.families.values(): + f.dump(" ") + print("Pseudos:") + for p in analysis.pseudos.values(): + p.dump(" ") + + +if __name__ == "__main__": + import sys + + if len(sys.argv) < 2: + print("No input") + else: + filenames = sys.argv[1:] + dump_analysis(analyze_files(filenames)) diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py new file mode 100644 index 00000000000000..0b7edd03fd9e47 --- /dev/null +++ b/Tools/cases_generator/cwriter.py @@ -0,0 +1,111 @@ +from lexer import Token +from typing import TextIO + + +class CWriter: + "A writer that understands tokens and how to format C code" + + last_token: Token | None + + def __init__(self, out: TextIO, indent: int, line_directives: bool): + self.out = out + self.base_column = indent * 4 + self.indents = [i * 4 for i in range(indent + 1)] + self.line_directives = line_directives + self.last_token = None + self.newline = True + + def set_position(self, tkn: Token) -> None: + if self.last_token is not None: + if self.last_token.line < tkn.line: + self.out.write("\n") + if self.line_directives: + self.out.write(f'#line {tkn.line} "{tkn.filename}"\n') + self.out.write(" " * self.indents[-1]) + else: + gap = tkn.column - self.last_token.end_column + self.out.write(" " * gap) + elif self.newline: + self.out.write(" " * self.indents[-1]) + self.last_token = tkn + self.newline = False + + def emit_at(self, txt: str, where: Token) -> None: + self.set_position(where) + self.out.write(txt) + + def maybe_dedent(self, txt: str) -> None: + parens = txt.count("(") - txt.count(")") + if parens < 0: + self.indents.pop() + elif "}" in txt or is_label(txt): + self.indents.pop() + + def maybe_indent(self, txt: str) -> None: + parens = txt.count("(") - txt.count(")") + if parens > 0 and self.last_token: + offset = self.last_token.end_column - 1 + if offset <= self.indents[-1] or offset > 40: + offset = self.indents[-1] + 4 + self.indents.append(offset) + elif "{" in txt or is_label(txt): + self.indents.append(self.indents[-1] + 4) + + def emit_text(self, txt: str) -> None: + self.out.write(txt) + + def emit_multiline_comment(self, tkn: Token) -> None: + self.set_position(tkn) + lines = tkn.text.splitlines(True) + first = True + for line in lines: + text = line.lstrip() + if first: + spaces = 0 + else: + spaces = self.indents[-1] + if text.startswith("*"): + spaces += 1 + else: + spaces += 3 + first = False + self.out.write(" " * spaces) + self.out.write(text) + + def emit_token(self, tkn: Token) -> None: + if tkn.kind == "COMMENT" and "\n" in tkn.text: + return self.emit_multiline_comment(tkn) + self.maybe_dedent(tkn.text) + self.set_position(tkn) + self.emit_text(tkn.text) + self.maybe_indent(tkn.text) + + def emit_str(self, txt: str) -> None: + self.maybe_dedent(txt) + if self.newline and txt: + if txt[0] != "\n": + self.out.write(" " * self.indents[-1]) + self.newline = False + self.emit_text(txt) + if txt.endswith("\n"): + self.newline = True + self.maybe_indent(txt) + self.last_token = None + + def emit(self, txt: str | Token) -> None: + if isinstance(txt, Token): + self.emit_token(txt) + elif isinstance(txt, str): + self.emit_str(txt) + else: + assert False + + def start_line(self) -> None: + if not self.newline: + self.out.write("\n") + self.newline = True + self.last_token = None + + +def is_label(txt: str) -> bool: + return not txt.startswith("//") and txt.endswith(":") diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 1f94c1fedb2ac7..4b7f028970bd0c 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -883,7 +883,6 @@ def main() -> None: return # These raise OSError if output can't be written - a.write_instructions(args.output, args.emit_line_directives) a.assign_opcode_ids() a.write_opcode_ids(args.opcode_ids_h, args.opcode_targets_h) diff --git a/Tools/cases_generator/lexer.py b/Tools/cases_generator/lexer.py index 1185c855785939..c3c2954a42083f 100644 --- a/Tools/cases_generator/lexer.py +++ b/Tools/cases_generator/lexer.py @@ -112,7 +112,7 @@ def choice(*opts: str) -> str: char = r"\'.\'" # TODO: escape sequence CHARACTER = "CHARACTER" -comment_re = r"//.*|/\*([^*]|\*[^/])*\*/" +comment_re = r"(//.*)|/\*([^*]|\*[^/])*\*/" COMMENT = "COMMENT" newline = r"\n" @@ -234,6 +234,7 @@ def make_syntax_error( @dataclass(slots=True) class Token: + filename: str kind: str text: str begin: tuple[int, int] @@ -261,7 +262,7 @@ def width(self) -> int: def replaceText(self, txt: str) -> "Token": assert isinstance(txt, str) - return Token(self.kind, txt, self.begin, self.end) + return Token(self.filename, self.kind, txt, self.begin, self.end) def __repr__(self) -> str: b0, b1 = self.begin @@ -272,7 +273,7 @@ def __repr__(self) -> str: return f"{self.kind}({self.text!r}, {b0}:{b1}, {e0}:{e1})" -def tokenize(src: str, line: int = 1, filename: str | None = None) -> Iterator[Token]: +def tokenize(src: str, line: int = 1, filename: str = "") -> Iterator[Token]: linestart = -1 for m in matcher.finditer(src): start, end = m.span() @@ -323,7 +324,7 @@ def tokenize(src: str, line: int = 1, filename: str | None = None) -> Iterator[T else: begin = line, start - linestart if kind != "\n": - yield Token(kind, text, begin, (line, start - linestart + len(text))) + yield Token(filename, kind, text, begin, (line, start - linestart + len(text))) def to_text(tkns: list[Token], dedent: int = 0) -> str: diff --git a/Tools/cases_generator/mypy.ini b/Tools/cases_generator/mypy.ini index e7175e263350b2..8e5a31851c596e 100644 --- a/Tools/cases_generator/mypy.ini +++ b/Tools/cases_generator/mypy.ini @@ -11,3 +11,5 @@ strict = True strict_concatenate = True enable_error_code = ignore-without-code,redundant-expr,truthy-bool,possibly-undefined warn_unreachable = True +allow_redefinition = True +implicit_reexport = True diff --git a/Tools/cases_generator/parser.py b/Tools/cases_generator/parser.py new file mode 100644 index 00000000000000..12173a61199700 --- /dev/null +++ b/Tools/cases_generator/parser.py @@ -0,0 +1,55 @@ +from parsing import ( + InstDef, + Macro, + Pseudo, + Family, + Parser, + Context, + CacheEffect, + StackEffect, + OpName, + AstNode, +) +from formatting import prettify_filename + + +BEGIN_MARKER = "// BEGIN BYTECODES //" +END_MARKER = "// END BYTECODES //" + + +def parse_files(filenames: list[str]) -> list[AstNode]: + result: list[AstNode] = [] + for filename in filenames: + with open(filename) as file: + src = file.read() + + psr = Parser(src, filename=prettify_filename(filename)) + + # Skip until begin marker + while tkn := psr.next(raw=True): + if tkn.text == BEGIN_MARKER: + break + else: + raise psr.make_syntax_error( + f"Couldn't find {BEGIN_MARKER!r} in {psr.filename}" + ) + start = psr.getpos() + + # Find end marker, then delete everything after it + while tkn := psr.next(raw=True): + if tkn.text == END_MARKER: + break + del psr.tokens[psr.getpos() - 1 :] + + # Parse from start + psr.setpos(start) + thing_first_token = psr.peek() + while node := psr.definition(): + assert node is not None + result.append(node) # type: ignore[arg-type] + if not psr.eof(): + psr.backup() + raise psr.make_syntax_error( + f"Extra stuff at the end of {filename}", psr.next(True) + ) + return result diff --git a/Tools/cases_generator/parsing.py b/Tools/cases_generator/parsing.py index 7800adf16794bb..60c185dcef58e9 100644 --- a/Tools/cases_generator/parsing.py +++ b/Tools/cases_generator/parsing.py @@ -141,10 +141,11 @@ class Pseudo(Node): flags: list[str] # instr flags to set on the pseudo instruction targets: list[str] # opcodes this can be replaced by +AstNode = InstDef | Macro | Pseudo | Family class Parser(PLexer): @contextual - def definition(self) -> InstDef | Macro | Pseudo | Family | None: + def definition(self) -> AstNode | None: if macro := self.macro_def(): return macro if family := self.family_def(): diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py new file mode 100644 index 00000000000000..9cd8e1a3b2edf8 --- /dev/null +++ b/Tools/cases_generator/stack.py @@ -0,0 +1,81 @@ +import sys +from analyzer import StackItem +from dataclasses import dataclass +from formatting import maybe_parenthesize + + +def var_size(var: StackItem) -> str: + if var.condition: + # Special case simplification + if var.condition == "oparg & 1" and var.size == "1": + return f"({var.condition})" + else: + return f"(({var.condition}) ? {var.size} : 0)" + else: + return var.size + + +class StackOffset: + "The stack offset of the virtual base of the stack from the physical stack pointer" + + def __init__(self) -> None: + self.popped: list[str] = [] + self.pushed: list[str] = [] + + def pop(self, item: StackItem) -> None: + self.popped.append(var_size(item)) + + def push(self, item: StackItem) -> None: + self.pushed.append(var_size(item)) + + def simplify(self) -> None: + "Remove matching values from both the popped and pushed list" + if not self.popped or not self.pushed: + return + # Sort the list so the lexically largest element is last. + popped = sorted(self.popped) + pushed = sorted(self.pushed) + self.popped = [] + self.pushed = [] + while popped and pushed: + pop = popped.pop() + push = pushed.pop() + if pop == push: + pass + elif pop > push: + # if pop > push, there can be no element in pushed matching pop. + self.popped.append(pop) + pushed.append(push) + else: + self.pushed.append(push) + popped.append(pop) + self.popped.extend(popped) + self.pushed.extend(pushed) + + def to_c(self) -> str: + self.simplify() + int_offset = 0 + symbol_offset = "" + for item in self.popped: + try: + int_offset -= int(item) + except ValueError: + symbol_offset += f" - {maybe_parenthesize(item)}" + for item in self.pushed: + try: + int_offset += int(item) + except ValueError: + symbol_offset += f" + {maybe_parenthesize(item)}" + if symbol_offset and not int_offset: + res = symbol_offset + else: + res = f"{int_offset}{symbol_offset}" + if res.startswith(" + "): + res = res[3:] + if res.startswith(" - "): + res = "-" + res[3:] + return res + + def clear(self) -> None: + self.popped = [] + self.pushed = [] diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py new file mode 100644 index 00000000000000..eba926435d2415 --- /dev/null +++ b/Tools/cases_generator/tier1_generator.py @@ -0,0 +1,417 @@ +"""Generate the main interpreter switch. +Reads the instruction definitions from bytecodes.c. +Writes the cases to generated_cases.c.h, which is #included in ceval.c. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + Uop, + Part, + analyze_files, + Skip, + StackItem, + analysis_error, +) +from cwriter import CWriter +from typing import TextIO, Iterator +from lexer import Token +from stack import StackOffset + + +HERE = os.path.dirname(__file__) +ROOT = os.path.join(HERE, "../..") +THIS = os.path.relpath(__file__, ROOT).replace(os.path.sep, "/") + +DEFAULT_INPUT = os.path.relpath(os.path.join(ROOT, "Python/bytecodes.c")) +DEFAULT_OUTPUT = os.path.relpath(os.path.join(ROOT, "Python/generated_cases.c.h")) + + +def write_header(filename: str, outfile: TextIO) -> None: + outfile.write( + f"""// This file is generated by {THIS} +// from: +// {filename} +// Do not edit! + +#ifdef TIER_TWO + #error "This file is for Tier 1 only" +#endif +#define TIER_ONE 1 +""" + ) + + +FOOTER = "#undef TIER_ONE\n" + + +class SizeMismatch(Exception): + pass + + +class Stack: + def __init__(self) -> None: + self.top_offset = StackOffset() + self.base_offset = StackOffset() + self.peek_offset = StackOffset() + self.variables: list[StackItem] = [] + self.defined: set[str] = set() + + def pop(self, var: StackItem) -> str: + self.top_offset.pop(var) + if not var.peek: + self.peek_offset.pop(var) + indirect = "&" if var.is_array() else "" + if self.variables: + popped = self.variables.pop() + if popped.size != var.size: + raise SizeMismatch( + f"Size mismatch when popping '{popped.name}' from stack to assign to {var.name}. " + f"Expected {var.size} got {popped.size}" + ) + if popped.name == var.name: + return "" + elif popped.name == "unused": + self.defined.add(var.name) + return ( + f"{var.name} = {indirect}stack_pointer[{self.top_offset.to_c()}];\n" + ) + elif var.name == "unused": + return "" + else: + self.defined.add(var.name) + return f"{var.name} = {popped.name};\n" + self.base_offset.pop(var) + if var.name == "unused": + return "" + else: + self.defined.add(var.name) + assign = f"{var.name} = {indirect}stack_pointer[{self.base_offset.to_c()}];" + if var.condition: + return f"if ({var.condition}) {{ {assign} }}\n" + return f"{assign}\n" + + def push(self, var: StackItem) -> str: + self.variables.append(var) + if var.is_array() and var.name not in self.defined and var.name != "unused": + c_offset = self.top_offset.to_c() + self.top_offset.push(var) + self.defined.add(var.name) + return f"{var.name} = &stack_pointer[{c_offset}];\n" + else: + self.top_offset.push(var) + return "" + + def flush(self, out: CWriter) -> None: + for var in self.variables: + if not var.peek: + if var.name != "unused" and not var.is_array(): + if var.condition: + out.emit(f" if ({var.condition}) ") + out.emit( + f"stack_pointer[{self.base_offset.to_c()}] = {var.name};\n" + ) + self.base_offset.push(var) + if self.base_offset.to_c() != self.top_offset.to_c(): + print("base", self.base_offset.to_c(), "top", self.top_offset.to_c()) + assert False + number = self.base_offset.to_c() + if number != "0": + out.emit(f"stack_pointer += {number};\n") + self.variables = [] + self.base_offset.clear() + self.top_offset.clear() + self.peek_offset.clear() + + def as_comment(self) -> str: + return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" + + +def declare_variables(inst: Instruction, out: CWriter) -> None: + variables = {"unused"} + for uop in inst.parts: + if isinstance(uop, Uop): + for var in reversed(uop.stack.inputs): + if var.name not in variables: + type = var.type if var.type else "PyObject *" + variables.add(var.name) + if var.condition: + out.emit(f"{type}{var.name} = NULL;\n") + else: + out.emit(f"{type}{var.name};\n") + for var in uop.stack.outputs: + if var.name not in variables: + variables.add(var.name) + type = var.type if var.type else "PyObject *" + if var.condition: + out.emit(f"{type}{var.name} = NULL;\n") + else: + out.emit(f"{type}{var.name};\n") + + +def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + out.emit(tkn) + + +def replace_deopt( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + unused: Stack, + inst: Instruction, +) -> None: + out.emit_at("DEOPT_IF", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "RPAREN") + next(tkn_iter) # Semi colon + out.emit(", ") + assert inst.family is not None + out.emit(inst.family.name) + out.emit(");\n") + + +def replace_error( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction, +) -> None: + out.emit_at("if ", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "COMMA") + label = next(tkn_iter).text + next(tkn_iter) # RPAREN + next(tkn_iter) # Semi colon + out.emit(") ") + c_offset = stack.peek_offset.to_c() + try: + offset = -int(c_offset) + close = ";\n" + except ValueError: + offset = None + out.emit(f"{{ stack_pointer += {c_offset}; ") + close = "; }\n" + out.emit("goto ") + if offset: + out.emit(f"pop_{offset}_") + out.emit(label) + out.emit(close) + + +def replace_decrefs( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + out.emit_at("", tkn) + for var in uop.stack.inputs: + if var.name == "unused" or var.name == "null" or var.peek: + continue + if var.size != "1": + out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") + out.emit(f"Py_DECREF({var.name}[_i]);\n") + out.emit("}\n") + elif var.condition: + out.emit(f"Py_XDECREF({var.name});\n") + else: + out.emit(f"Py_DECREF({var.name});\n") + + +def replace_store_sp( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + out.emit_at("", tkn) + stack.flush(out) + out.emit("_PyFrame_SetStackPointer(frame, stack_pointer);\n") + + +def replace_check_eval_breaker( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + if not uop.properties.ends_with_eval_breaker: + out.emit_at("CHECK_EVAL_BREAKER();", tkn) + + +REPLACEMENT_FUNCTIONS = { + "DEOPT_IF": replace_deopt, + "ERROR_IF": replace_error, + "DECREF_INPUTS": replace_decrefs, + "CHECK_EVAL_BREAKER": replace_check_eval_breaker, + "STORE_SP": replace_store_sp, +} + + +# Move this to formatter +def emit_tokens(out: CWriter, uop: Uop, stack: Stack, inst: Instruction) -> None: + tkns = uop.body[1:-1] + if not tkns: + return + tkn_iter = iter(tkns) + out.start_line() + for tkn in tkn_iter: + if tkn.kind == "IDENTIFIER" and tkn.text in REPLACEMENT_FUNCTIONS: + REPLACEMENT_FUNCTIONS[tkn.text](out, tkn, tkn_iter, uop, stack, inst) + else: + out.emit(tkn) + + +def write_uop( + uop: Part, out: CWriter, offset: int, stack: Stack, inst: Instruction, braces: bool +) -> int: + # out.emit(stack.as_comment() + "\n") + if isinstance(uop, Skip): + entries = "entries" if uop.size > 1 else "entry" + out.emit(f"/* Skip {uop.size} cache {entries} */\n") + return offset + uop.size + try: + out.start_line() + if braces: + out.emit(f"// {uop.name}\n") + for var in reversed(uop.stack.inputs): + out.emit(stack.pop(var)) + if braces: + out.emit("{\n") + if not uop.properties.stores_sp: + for i, var in enumerate(uop.stack.outputs): + out.emit(stack.push(var)) + for cache in uop.caches: + if cache.name != "unused": + if cache.size == 4: + type = "PyObject *" + reader = "read_obj" + else: + type = f"uint{cache.size*16}_t " + reader = f"read_u{cache.size*16}" + out.emit( + f"{type}{cache.name} = {reader}(&this_instr[{offset}].cache);\n" + ) + offset += cache.size + emit_tokens(out, uop, stack, inst) + if uop.properties.stores_sp: + for i, var in enumerate(uop.stack.outputs): + out.emit(stack.push(var)) + if braces: + out.start_line() + out.emit("}\n") + # out.emit(stack.as_comment() + "\n") + return offset + except SizeMismatch as ex: + raise analysis_error(ex.args[0], uop.body[0]) + + +def uses_this(inst: Instruction) -> bool: + if inst.properties.needs_this: + return True + for uop in inst.parts: + if isinstance(uop, Skip): + continue + for cache in uop.caches: + if cache.name != "unused": + return True + return False + + +def generate_tier1( + filenames: str, analysis: Analysis, outfile: TextIO, lines: bool +) -> None: + write_header(filenames, outfile) + out = CWriter(outfile, 2, lines) + out.emit("\n") + for name, inst in sorted(analysis.instructions.items()): + needs_this = uses_this(inst) + out.emit("\n") + out.emit(f"TARGET({name}) {{\n") + if needs_this and not inst.is_target: + out.emit(f"_Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr;\n") + else: + out.emit(f"frame->instr_ptr = next_instr;\n") + out.emit(f"next_instr += {inst.size};\n") + out.emit(f"INSTRUCTION_STATS({name});\n") + if inst.is_target: + out.emit(f"PREDICTED({name});\n") + if needs_this: + out.emit(f"_Py_CODEUNIT *this_instr = next_instr - {inst.size};\n") + if inst.family is not None: + out.emit( + f"static_assert({inst.family.size} == {inst.size-1}" + ', "incorrect cache size");\n' + ) + declare_variables(inst, out) + offset = 1 # The instruction itself + stack = Stack() + for part in inst.parts: + # Only emit braces if more than one uop + offset = write_uop(part, out, offset, stack, inst, len(inst.parts) > 1) + out.start_line() + if not inst.parts[-1].properties.always_exits: + stack.flush(out) + if inst.parts[-1].properties.ends_with_eval_breaker: + out.emit("CHECK_EVAL_BREAKER();\n") + out.emit("DISPATCH();\n") + out.start_line() + out.emit("}") + out.emit("\n") + outfile.write(FOOTER) + + +arg_parser = argparse.ArgumentParser( + description="Generate the code for the interpreter switch.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "-l", "--emit-line-directives", help="Emit #line directives", action="store_true" +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_tier1(args.input, data, outfile, args.emit_line_directives) From 9c3458e05865093dd55d7608810a9d0ef0765978 Mon Sep 17 00:00:00 2001 From: andrewluotechnologies <44252973+andrewluotechnologies@users.noreply.github.com> Date: Thu, 7 Dec 2023 04:56:01 -0800 Subject: [PATCH 141/442] gh-112125: Fix None.__ne__(None) returning NotImplemented instead of False (#112504) --- Include/internal/pycore_typeobject.h | 2 ++ Lib/test/test_builtin.py | 5 +++++ .../2023-12-07-13-19-55.gh-issue-112125.4ADN7i.rst | 1 + Objects/object.c | 2 +- Objects/typeobject.c | 6 ++++++ 5 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-07-13-19-55.gh-issue-112125.4ADN7i.rst diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index bbf8544b09f0fb..f983de56049631 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -135,6 +135,8 @@ extern PyObject* _Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int *suppress_missing_attribute); extern PyObject* _Py_type_getattro(PyTypeObject *type, PyObject *name); +extern PyObject* _Py_BaseObject_RichCompare(PyObject* self, PyObject* other, int op); + extern PyObject* _Py_slot_tp_getattro(PyObject *self, PyObject *name); extern PyObject* _Py_slot_tp_getattr_hook(PyObject *self, PyObject *name); diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 558715383c82ee..5e66d58fd2cb18 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -627,6 +627,11 @@ def __dir__(self): # test that object has a __dir__() self.assertEqual(sorted([].__dir__()), dir([])) + def test___ne__(self): + self.assertFalse(None.__ne__(None)) + self.assertTrue(None.__ne__(0)) + self.assertTrue(None.__ne__("abc")) + def test_divmod(self): self.assertEqual(divmod(12, 7), (1, 5)) self.assertEqual(divmod(-12, 7), (-2, 2)) diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-07-13-19-55.gh-issue-112125.4ADN7i.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-07-13-19-55.gh-issue-112125.4ADN7i.rst new file mode 100644 index 00000000000000..52cd45029fb8c7 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-07-13-19-55.gh-issue-112125.4ADN7i.rst @@ -0,0 +1 @@ +Fix None.__ne__(None) returning NotImplemented instead of False diff --git a/Objects/object.c b/Objects/object.c index d145674cb3ba34..cdb7a08a7828fb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2026,7 +2026,7 @@ PyTypeObject _PyNone_Type = { 0, /*tp_doc */ 0, /*tp_traverse */ 0, /*tp_clear */ - 0, /*tp_richcompare */ + _Py_BaseObject_RichCompare, /*tp_richcompare */ 0, /*tp_weaklistoffset */ 0, /*tp_iter */ 0, /*tp_iternext */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index aa00e04ad5e11b..08f5f47d586729 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5625,6 +5625,12 @@ object_richcompare(PyObject *self, PyObject *other, int op) return res; } +PyObject* +_Py_BaseObject_RichCompare(PyObject* self, PyObject* other, int op) +{ + return object_richcompare(self, other, op); +} + static PyObject * object_get_class(PyObject *self, void *closure) { From 7576534f4a230cc8f9b0f13ef2e521eeaa9e9ead Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Thu, 7 Dec 2023 07:24:11 -0600 Subject: [PATCH 142/442] bpo-42519: Remove outdated sentence in comment (#112822) Update objimpl.h --- Include/objimpl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/objimpl.h b/Include/objimpl.h index 967e2776767756..ff5fa7a8c1d3d8 100644 --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -35,7 +35,7 @@ Functions and macros for modules that implement new object types. fields, this also fills in the ob_size field. - PyObject_Free(op) releases the memory allocated for an object. It does not - run a destructor -- it only frees the memory. PyObject_Free is identical. + run a destructor -- it only frees the memory. - PyObject_Init(op, typeobj) and PyObject_InitVar(op, typeobj, n) don't allocate memory. Instead of a 'type' parameter, they take a pointer to a From 9f67042f28bf886a9bf30fed6795d26cff255f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Thu, 7 Dec 2023 14:29:15 +0100 Subject: [PATCH 143/442] gh-110190: Temporarily skip new test introduced in gh-112604 on PPC64LE (#112818) --- Lib/test/test_ctypes/test_structures.py | 98 +++++++++++++------------ 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py index 771205ffb32ecc..57ae9240b3c165 100644 --- a/Lib/test/test_ctypes/test_structures.py +++ b/Lib/test/test_ctypes/test_structures.py @@ -1,4 +1,5 @@ import _ctypes_test +import platform import struct import sys import unittest @@ -494,51 +495,6 @@ class Test3B(Test3A): ('more_data', c_float * 2), ] - class Test3C1(Structure): - _fields_ = [ - ("data", c_double * 4) - ] - - class DataType4(Array): - _type_ = c_double - _length_ = 4 - - class Test3C2(Structure): - _fields_ = [ - ("data", DataType4) - ] - - class Test3C3(Structure): - _fields_ = [ - ("x", c_double), - ("y", c_double), - ("z", c_double), - ("t", c_double) - ] - - class Test3D1(Structure): - _fields_ = [ - ("data", c_double * 5) - ] - - class DataType5(Array): - _type_ = c_double - _length_ = 5 - - class Test3D2(Structure): - _fields_ = [ - ("data", DataType5) - ] - - class Test3D3(Structure): - _fields_ = [ - ("x", c_double), - ("y", c_double), - ("z", c_double), - ("t", c_double), - ("u", c_double) - ] - # Load the shared library dll = CDLL(_ctypes_test.__file__) @@ -587,6 +543,58 @@ class Test3D3(Structure): self.assertAlmostEqual(s.more_data[0], -3.0, places=6) self.assertAlmostEqual(s.more_data[1], -2.0, places=6) + @unittest.skipIf( + 'ppc64le' in platform.uname().machine, + "gh-110190: currently fails on ppc64le", + ) + def test_array_in_struct_registers(self): + dll = CDLL(_ctypes_test.__file__) + + class Test3C1(Structure): + _fields_ = [ + ("data", c_double * 4) + ] + + class DataType4(Array): + _type_ = c_double + _length_ = 4 + + class Test3C2(Structure): + _fields_ = [ + ("data", DataType4) + ] + + class Test3C3(Structure): + _fields_ = [ + ("x", c_double), + ("y", c_double), + ("z", c_double), + ("t", c_double) + ] + + class Test3D1(Structure): + _fields_ = [ + ("data", c_double * 5) + ] + + class DataType5(Array): + _type_ = c_double + _length_ = 5 + + class Test3D2(Structure): + _fields_ = [ + ("data", DataType5) + ] + + class Test3D3(Structure): + _fields_ = [ + ("x", c_double), + ("y", c_double), + ("z", c_double), + ("t", c_double), + ("u", c_double) + ] + # Tests for struct Test3C expected = (1.0, 2.0, 3.0, 4.0) func = dll._testfunc_array_in_struct_set_defaults_3C From 2d76be251d0aee89f76e6fa5a63fa1ad3f2b76cf Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 7 Dec 2023 08:47:55 -0500 Subject: [PATCH 144/442] gh-111962: Make dtoa thread-safe in `--disable-gil` builds. (#112049) This updates `dtoa.c` to avoid using the Bigint free-list in --disable-gil builds and to pre-computes the needed powers of 5 during interpreter initialization. * gh-111962: Make dtoa thread-safe in `--disable-gil` builds. This avoids using the Bigint free-list in `--disable-gil` builds and pre-computes the needed powers of 5 during interpreter initialization. * Fix size of cached powers of 5 array. We need the powers of 5 up to 5**512 because we only jump straight to underflow when the exponent is less than -512 (or larger than 308). * Rename Py_NOGIL to Py_GIL_DISABLED * Changes from review * Fix assertion placement --- Include/internal/pycore_dtoa.h | 16 ++++--- Python/dtoa.c | 78 +++++++++++++++++++++++----------- Python/pylifecycle.c | 6 +++ 3 files changed, 70 insertions(+), 30 deletions(-) diff --git a/Include/internal/pycore_dtoa.h b/Include/internal/pycore_dtoa.h index ac62a4d300720a..c5cfdf4ce8f823 100644 --- a/Include/internal/pycore_dtoa.h +++ b/Include/internal/pycore_dtoa.h @@ -35,6 +35,9 @@ struct _dtoa_state { /* The size of the Bigint freelist */ #define Bigint_Kmax 7 +/* The size of the cached powers of 5 array */ +#define Bigint_Pow5size 8 + #ifndef PRIVATE_MEM #define PRIVATE_MEM 2304 #endif @@ -42,9 +45,10 @@ struct _dtoa_state { ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) struct _dtoa_state { - /* p5s is a linked list of powers of 5 of the form 5**(2**i), i >= 2 */ + // p5s is an array of powers of 5 of the form: + // 5**(2**(i+2)) for 0 <= i < Bigint_Pow5size + struct Bigint *p5s[Bigint_Pow5size]; // XXX This should be freed during runtime fini. - struct Bigint *p5s; struct Bigint *freelist[Bigint_Kmax+1]; double preallocated[Bigint_PREALLOC_SIZE]; double *preallocated_next; @@ -57,9 +61,6 @@ struct _dtoa_state { #endif // !Py_USING_MEMORY_DEBUGGER -/* These functions are used by modules compiled as C extension like math: - they must be exported. */ - extern double _Py_dg_strtod(const char *str, char **ptr); extern char* _Py_dg_dtoa(double d, int mode, int ndigits, int *decpt, int *sign, char **rve); @@ -67,6 +68,11 @@ extern void _Py_dg_freedtoa(char *s); #endif // _PY_SHORT_FLOAT_REPR == 1 + +extern PyStatus _PyDtoa_Init(PyInterpreterState *interp); +extern void _PyDtoa_Fini(PyInterpreterState *interp); + + #ifdef __cplusplus } #endif diff --git a/Python/dtoa.c b/Python/dtoa.c index 5dfc0e179cbc34..6e3162f80bdae1 100644 --- a/Python/dtoa.c +++ b/Python/dtoa.c @@ -309,7 +309,7 @@ BCinfo { // struct Bigint is defined in pycore_dtoa.h. typedef struct Bigint Bigint; -#ifndef Py_USING_MEMORY_DEBUGGER +#if !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER) /* Memory management: memory is allocated from, and returned to, Kmax+1 pools of memory, where pool k (0 <= k <= Kmax) is for Bigints b with b->maxwds == @@ -428,7 +428,7 @@ Bfree(Bigint *v) } } -#endif /* Py_USING_MEMORY_DEBUGGER */ +#endif /* !defined(Py_GIL_DISABLED) && !defined(Py_USING_MEMORY_DEBUGGER) */ #define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ y->wds*sizeof(Long) + 2*sizeof(int)) @@ -673,10 +673,17 @@ mult(Bigint *a, Bigint *b) static Bigint * pow5mult(Bigint *b, int k) { - Bigint *b1, *p5, *p51; + Bigint *b1, *p5, **p5s; int i; static const int p05[3] = { 5, 25, 125 }; + // For double-to-string conversion, the maximum value of k is limited by + // DBL_MAX_10_EXP (308), the maximum decimal base-10 exponent for binary64. + // For string-to-double conversion, the extreme case is constrained by our + // hardcoded exponent limit before we underflow of -512, adjusted by + // STRTOD_DIGLIM-DBL_DIG-1, giving a maximum of k=535. + assert(0 <= k && k < 1024); + if ((i = k & 3)) { b = multadd(b, p05[i-1], 0); if (b == NULL) @@ -686,18 +693,11 @@ pow5mult(Bigint *b, int k) if (!(k >>= 2)) return b; PyInterpreterState *interp = _PyInterpreterState_GET(); - p5 = interp->dtoa.p5s; - if (!p5) { - /* first time */ - p5 = i2b(625); - if (p5 == NULL) { - Bfree(b); - return NULL; - } - interp->dtoa.p5s = p5; - p5->next = 0; - } + p5s = interp->dtoa.p5s; for(;;) { + assert(p5s != interp->dtoa.p5s + Bigint_Pow5size); + p5 = *p5s; + p5s++; if (k & 1) { b1 = mult(b, p5); Bfree(b); @@ -707,17 +707,6 @@ pow5mult(Bigint *b, int k) } if (!(k >>= 1)) break; - p51 = p5->next; - if (!p51) { - p51 = mult(p5,p5); - if (p51 == NULL) { - Bfree(b); - return NULL; - } - p51->next = 0; - p5->next = p51; - } - p5 = p51; } return b; } @@ -2811,3 +2800,42 @@ _Py_dg_dtoa(double dd, int mode, int ndigits, } #endif // _PY_SHORT_FLOAT_REPR == 1 + +PyStatus +_PyDtoa_Init(PyInterpreterState *interp) +{ +#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER) + Bigint **p5s = interp->dtoa.p5s; + + // 5**4 = 625 + Bigint *p5 = i2b(625); + if (p5 == NULL) { + return PyStatus_NoMemory(); + } + p5s[0] = p5; + + // compute 5**8, 5**16, 5**32, ..., 5**512 + for (Py_ssize_t i = 1; i < Bigint_Pow5size; i++) { + p5 = mult(p5, p5); + if (p5 == NULL) { + return PyStatus_NoMemory(); + } + p5s[i] = p5; + } + +#endif + return PyStatus_Ok(); +} + +void +_PyDtoa_Fini(PyInterpreterState *interp) +{ +#if _PY_SHORT_FLOAT_REPR == 1 && !defined(Py_USING_MEMORY_DEBUGGER) + Bigint **p5s = interp->dtoa.p5s; + for (Py_ssize_t i = 0; i < Bigint_Pow5size; i++) { + Bigint *p5 = p5s[i]; + p5s[i] = NULL; + Bfree(p5); + } +#endif +} diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 95a72eb47048f2..20bfe1a0b75b29 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -820,6 +820,11 @@ pycore_interp_init(PyThreadState *tstate) return status; } + status = _PyDtoa_Init(interp); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + // The GC must be initialized before the first GC collection. status = _PyGC_Init(interp); if (_PyStatus_EXCEPTION(status)) { @@ -1776,6 +1781,7 @@ finalize_interp_clear(PyThreadState *tstate) _PyXI_Fini(tstate->interp); _PyExc_ClearExceptionGroupType(tstate->interp); _Py_clear_generic_types(tstate->interp); + _PyDtoa_Fini(tstate->interp); /* Clear interpreter state and all thread states */ _PyInterpreterState_Clear(tstate); From 21221c398f6d89b2d9295895d8a2fd71d28138fa Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Thu, 7 Dec 2023 10:01:58 -0600 Subject: [PATCH 145/442] gh-112302: Add Software Bill-of-Materials (SBOM) tracking for dependencies (#112303) --- .github/CODEOWNERS | 4 + .github/workflows/mypy.yml | 2 + Makefile.pre.in | 6 +- ...-12-06-14-06-59.gh-issue-112302.3bl20f.rst | 2 + Misc/sbom.spdx.json | 2294 +++++++++++++++++ Tools/build/generate_sbom.py | 179 ++ Tools/build/mypy.ini | 13 + 7 files changed, 2499 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Security/2023-12-06-14-06-59.gh-issue-112302.3bl20f.rst create mode 100644 Misc/sbom.spdx.json create mode 100644 Tools/build/generate_sbom.py create mode 100644 Tools/build/mypy.ini diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1925363cbeb46e..aa6d937d9cbc31 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -190,3 +190,7 @@ Doc/howto/clinic.rst @erlend-aasland # WebAssembly /Tools/wasm/ @brettcannon + +# SBOM +/Misc/sbom.spdx.json @sethmlarson +/Tools/build/generate_sbom.py @sethmlarson diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 72ae67aa02aa96..792903a90a4880 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -9,6 +9,7 @@ on: paths: - ".github/workflows/mypy.yml" - "Lib/test/libregrtest/**" + - "Tools/build/generate_sbom.py" - "Tools/cases_generator/**" - "Tools/clinic/**" - "Tools/peg_generator/**" @@ -34,6 +35,7 @@ jobs: matrix: target: [ "Lib/test/libregrtest", + "Tools/build/", "Tools/cases_generator", "Tools/clinic", "Tools/peg_generator", diff --git a/Makefile.pre.in b/Makefile.pre.in index 6ac68d59c8c47f..db85b11ef01b2c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1359,7 +1359,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-test-levenshtein regen-global-objects regen-sbom @echo @echo "Note: make regen-stdlib-module-names, make regen-limited-abi, " @echo "make regen-configure and make regen-unicodedata should be run manually" @@ -2651,6 +2651,10 @@ autoconf: regen-configure: $(srcdir)/Tools/build/regen-configure.sh +.PHONY: regen-sbom +regen-sbom: + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_sbom.py + # Create a tags file for vi tags:: ctags -w $(srcdir)/Include/*.h $(srcdir)/Include/cpython/*.h $(srcdir)/Include/internal/*.h diff --git a/Misc/NEWS.d/next/Security/2023-12-06-14-06-59.gh-issue-112302.3bl20f.rst b/Misc/NEWS.d/next/Security/2023-12-06-14-06-59.gh-issue-112302.3bl20f.rst new file mode 100644 index 00000000000000..65e4dc3762d3c0 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2023-12-06-14-06-59.gh-issue-112302.3bl20f.rst @@ -0,0 +1,2 @@ +Created a Software Bill-of-Materials document and tooling for tracking +dependencies. diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json new file mode 100644 index 00000000000000..09355640db888e --- /dev/null +++ b/Misc/sbom.spdx.json @@ -0,0 +1,2294 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "files": [ + { + "SPDXID": "SPDXRef-FILE-Modules-expat-COPYING", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "39e6f567a10e36b2e77727e98e60bbcb3eb3af0b" + }, + { + "algorithm": "SHA256", + "checksumValue": "122f2c27000472a201d337b9b31f7eb2b52d091b02857061a8880371612d9534" + } + ], + "fileName": "Modules/expat/COPYING" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-ascii.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b0235fa3cf845a7d68e8e66dd344d5e32e8951b5" + }, + { + "algorithm": "SHA256", + "checksumValue": "42f8b392c70366743eacbc60ce021389ccaa333598dd49eef6ee5c93698ca205" + } + ], + "fileName": "Modules/expat/ascii.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-asciitab.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cbb53d16ca1f35ee9c9e296116efd222ae611ed9" + }, + { + "algorithm": "SHA256", + "checksumValue": "1cc0ae749019fc0e488cd1cf245f6beaa6d4f7c55a1fc797e5aa40a408bc266b" + } + ], + "fileName": "Modules/expat/asciitab.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-expat.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ab7bb32514d170592dfb3f76e41bbdc075a4e7e0" + }, + { + "algorithm": "SHA256", + "checksumValue": "f521acdad222644365b0e81a33bcd6939a98c91b225c47582cc84bd73d96febc" + } + ], + "fileName": "Modules/expat/expat.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-expat-config.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "73627287302ee3e84347c4fe21f37a9cb828bc3b" + }, + { + "algorithm": "SHA256", + "checksumValue": "f17e59f9d95eeb05694c02508aa284d332616c22cbe2e6a802d8a0710310eaab" + } + ], + "fileName": "Modules/expat/expat_config.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-expat-external.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b70ce53fdc25ae482681ae2f6623c3c8edc9c1b7" + }, + { + "algorithm": "SHA256", + "checksumValue": "86afb425ec9999eb4f1ec9ab2fb41c58c4aa5cb9bf934b8c94264670fc5a961d" + } + ], + "fileName": "Modules/expat/expat_external.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-iasciitab.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "1b0e9014c0baa4c6254d2b5e6a67c70148309c34" + }, + { + "algorithm": "SHA256", + "checksumValue": "ad8b01e9f323cc4208bcd22241df383d7e8641fe3c8b3415aa513de82531f89f" + } + ], + "fileName": "Modules/expat/iasciitab.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-internal.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2790d37e7de2f13dccc4f4fb352cbdf9ed6abaa2" + }, + { + "algorithm": "SHA256", + "checksumValue": "d2efe5a1018449968a689f444cca432e3d5875aba6ad08ee18ca235d64f41bb9" + } + ], + "fileName": "Modules/expat/internal.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-latin1tab.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d335ecca380e331a0ea7dc33838a4decd93ec1e4" + }, + { + "algorithm": "SHA256", + "checksumValue": "eab66226da100372e01e42e1cbcd8ac2bbbb5c1b5f95d735289cc85c7a8fc2ba" + } + ], + "fileName": "Modules/expat/latin1tab.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-nametab.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cf2bc9626c945826602ba9170786e9a2a44645e4" + }, + { + "algorithm": "SHA256", + "checksumValue": "67dcf415d37a4b692a6a8bb46f990c02d83f2ef3d01a65cd61c8594a084246f2" + } + ], + "fileName": "Modules/expat/nametab.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-pyexpatns.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "baa44fe4581895d42e8d5e83d8ce6a69b1c34dbe" + }, + { + "algorithm": "SHA256", + "checksumValue": "33a7b9ac8bf4571e23272cdf644c6f9808bd44c66b149e3c41ab3870d1888609" + } + ], + "fileName": "Modules/expat/pyexpatns.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-siphash.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2b984f806f10fbfbf72d8d1b7ba2992413c15299" + }, + { + "algorithm": "SHA256", + "checksumValue": "fbce56cd680e690043bbf572188cc2d0a25dbfc0d47ac8cb98eb3de768d4e694" + } + ], + "fileName": "Modules/expat/siphash.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-utf8tab.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b77c8fcfb551553c81d6fbd94c798c8aa04ad021" + }, + { + "algorithm": "SHA256", + "checksumValue": "8cd26bd461d334d5e1caedb3af4518d401749f2fc66d56208542b29085159c18" + } + ], + "fileName": "Modules/expat/utf8tab.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-winconfig.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "e774ae6ee9391aa6ffb8f775fb74e48f4b428959" + }, + { + "algorithm": "SHA256", + "checksumValue": "3c71cea9a6174718542331971a35db317902b2433be9d8dd1cb24239b635c0cc" + } + ], + "fileName": "Modules/expat/winconfig.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmlparse.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b580e827e16baa6b035586ffcd4d90301e5a353f" + }, + { + "algorithm": "SHA256", + "checksumValue": "483518bbd69338eefc706cd7fc0b6039df2d3e347f64097989059ed6d2385a1e" + } + ], + "fileName": "Modules/expat/xmlparse.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmlrole.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "5ef21312af73deb2428be3fe97a65244608e76de" + }, + { + "algorithm": "SHA256", + "checksumValue": "6fcf8c72ac0112c1b98bd2039c632a66b4c3dc516ce7c1f981390951121ef3c0" + } + ], + "fileName": "Modules/expat/xmlrole.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmlrole.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c1a4ea6356643d0820edb9c024c20ad2aaf562dc" + }, + { + "algorithm": "SHA256", + "checksumValue": "2b5d674be6ef20c7e3f69295176d75e68c5616e4dfce0a186fdd5e2ed8315f7a" + } + ], + "fileName": "Modules/expat/xmlrole.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmltok.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "e6d66ae9fd61d7950c62c5d87693c30a707e8577" + }, + { + "algorithm": "SHA256", + "checksumValue": "1110f651bdccfa765ad3d6f3857a35887ab35fc0fe7f3f3488fde2b238b482e3" + } + ], + "fileName": "Modules/expat/xmltok.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmltok.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "9c2a544875fd08ba9c2397296c97263518a410aa" + }, + { + "algorithm": "SHA256", + "checksumValue": "4299a03828b98bfe47ec6809f6e279252954a9a911dc7e0f19551bd74e3af971" + } + ], + "fileName": "Modules/expat/xmltok.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmltok-impl.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "aa96882de8e3d1d3083124b595aa911efe44e5ad" + }, + { + "algorithm": "SHA256", + "checksumValue": "0fbcba7931707c60301305dab78d2298d96447d0a5513926d8b18135228c0818" + } + ], + "fileName": "Modules/expat/xmltok_impl.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmltok-impl.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "788332fe8040bed71172cddedb69abd848cc62f7" + }, + { + "algorithm": "SHA256", + "checksumValue": "f05ad4fe5e98429a7349ff04f57192cac58c324601f2a2e5e697ab0bc05d36d5" + } + ], + "fileName": "Modules/expat/xmltok_impl.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-xmltok-ns.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2d82d0a1201f78d478b30d108ff8fc27ee3e2672" + }, + { + "algorithm": "SHA256", + "checksumValue": "6ce6d03193279078d55280150fe91e7370370b504a6c123a79182f28341f3e90" + } + ], + "fileName": "Modules/expat/xmltok_ns.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-MD5.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "f77449b2b4eb99f1da0938633cc558baf9c444fb" + }, + { + "algorithm": "SHA256", + "checksumValue": "0f252967debca5b35362ca53951ea16ca8bb97a19a1d24f6695f44d50010859e" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_MD5.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-MD5.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c24e6779a91c840f3d65d24abbce225b608b676e" + }, + { + "algorithm": "SHA256", + "checksumValue": "9cd062e782801013e3cacaba583e44e1b5e682e217d20208d5323354d42011f1" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_MD5.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA1.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "560f6ff541b5eff480ea047b147f4212bb0db7ed" + }, + { + "algorithm": "SHA256", + "checksumValue": "0ade3ab264e912d7b4e5cdcf773db8c63e4440540d295922d74b06bcfc74c77a" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA1.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA1.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "853b77d45379146faaeac5fe899b28db386ad13c" + }, + { + "algorithm": "SHA256", + "checksumValue": "b13eb14f91582703819235ea7c8f807bb93e4f1e6b695499dc1d86021dc39e72" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA1.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA2.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "667120b6100c946cdaa442f1173c723339923071" + }, + { + "algorithm": "SHA256", + "checksumValue": "b189459b863341a3a9c5c78c0208b6554a2f2ac26e0748fbd4432a91db21fae6" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA2.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "81db38b0b920e63ec33c7109d1144c35cf091da0" + }, + { + "algorithm": "SHA256", + "checksumValue": "631c9ba19c1c2c835bb63d3f2f22b8d76fb535edfed3c254ff2a52f12af3fe61" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA3.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "9c832b98a2f2a68202d2da016fb718965d7b7602" + }, + { + "algorithm": "SHA256", + "checksumValue": "38d350d1184238966cfa821a59ae00343f362182b6c2fbea7f2651763d757fb7" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA3.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA3.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ecc766fb6f7ee85e902b593b61b41e5a728fca34" + }, + { + "algorithm": "SHA256", + "checksumValue": "bae290a94366a2460f51e8468144baaade91d9048db111e10d2e2ffddc3f98cf" + } + ], + "fileName": "Modules/_hacl/Hacl_Hash_SHA3.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-Hacl-Streaming-Types.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ab7b4d9465a2765a07f8d5bccace7182b28ed1b8" + }, + { + "algorithm": "SHA256", + "checksumValue": "26913613f3b4f8ffff0a3e211a5ebc849159094e5e11de0a31fcb95b6105b74c" + } + ], + "fileName": "Modules/_hacl/Hacl_Streaming_Types.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-FStar-UInt128-Verified.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2ea61d6a236147462045f65c20311819d74db80c" + }, + { + "algorithm": "SHA256", + "checksumValue": "2c22b4d49ba06d6a3053cdc66405bd5ae953a28fcfed1ab164e8f5e0f6e2fb8b" + } + ], + "fileName": "Modules/_hacl/include/krml/FStar_UInt128_Verified.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-FStar-UInt-8-16-32-64.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "1a647d841180ac8ca667afa968c353425e81ad0d" + }, + { + "algorithm": "SHA256", + "checksumValue": "e5d1c5854833bec7ea02e227ec35bd7b49c5fb9e0f339efa0dd83e1595f722d4" + } + ], + "fileName": "Modules/_hacl/include/krml/FStar_UInt_8_16_32_64.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-fstar-uint128-struct-endianness.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "1987119a563a8fdc5966286e274f716dbcea77ee" + }, + { + "algorithm": "SHA256", + "checksumValue": "fe57e1bc5ce3224d106e36cb8829b5399c63a68a70b0ccd0c91d82a4565c8869" + } + ], + "fileName": "Modules/_hacl/include/krml/fstar_uint128_struct_endianness.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-internal-target.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "903c9eb76b01f3a95c04c3bc841c2fb71dea5403" + }, + { + "algorithm": "SHA256", + "checksumValue": "08ec602c7f90a1540389c0cfc95769fa7fec251e7ca143ef83c0b9f7afcf89a7" + } + ], + "fileName": "Modules/_hacl/include/krml/internal/target.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-lowstar-endianness.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "964e09bd99ff2366afd6193b59863fc925e7fb05" + }, + { + "algorithm": "SHA256", + "checksumValue": "3734c7942bec9a434e16df069fa45bdcb84b130f14417bc5f7bfe8546272d9f5" + } + ], + "fileName": "Modules/_hacl/include/krml/lowstar_endianness.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-include-krml-types.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "df8e0ed74a5970d09d3cc4c6e7c6c7a4c4e5015c" + }, + { + "algorithm": "SHA256", + "checksumValue": "de7444c345caa4c47902c4380500356a3ee7e199d2aab84fd8c4960410154f3d" + } + ], + "fileName": "Modules/_hacl/include/krml/types.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-MD5.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "5dd4ee3c835a0d176a6e9fecbe9752fd1474ff41" + }, + { + "algorithm": "SHA256", + "checksumValue": "d82ef594cba44203576d67b047240316bb3c542912ebb7034afa1e07888cec56" + } + ], + "fileName": "Modules/_hacl/internal/Hacl_Hash_MD5.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA1.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "515b3082eb7c30597773e1c63ec46688f6da3634" + }, + { + "algorithm": "SHA256", + "checksumValue": "10aacf847006b8e0dfb64d5c327443f954db6718b4aec712fb3268230df6a752" + } + ], + "fileName": "Modules/_hacl/internal/Hacl_Hash_SHA1.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "a044ec12b70ba97b67e9a312827d6270452a20ca" + }, + { + "algorithm": "SHA256", + "checksumValue": "a1426b54fa7273ba5b50817c25b2b26fc85c4d1befb14092cd27dc4c99439463" + } + ], + "fileName": "Modules/_hacl/internal/Hacl_Hash_SHA2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA3.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cfb7b520c39a73cb84c541d370455f92b998781f" + }, + { + "algorithm": "SHA256", + "checksumValue": "fd41997f9e96b3c9a3337b1b51fab965a1e21b0c16f353d156f1a1fa00709fbf" + } + ], + "fileName": "Modules/_hacl/internal/Hacl_Hash_SHA3.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-hacl-python-hacl-namespaces.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "f5c7b3ed911af6c8d582e8b3714b0c36195dc994" + }, + { + "algorithm": "SHA256", + "checksumValue": "07de72398b12957e014e97b9ac197bceef12d6d6505c2bfe8b23ee17b94ec5fa" + } + ], + "fileName": "Modules/_hacl/python_hacl_namespaces.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2-config.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ff5e3ae2360adf7279a9c54d12a1d32e16a1f223" + }, + { + "algorithm": "SHA256", + "checksumValue": "1eb919e885244e43cdf7b2104ad30dc9271513478c0026f6bfb4bad6e2f0ab42" + } + ], + "fileName": "Modules/_blake2/impl/blake2-config.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2-impl.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "28b947b43bdc680b9f4335712bb2a5f2d5d32623" + }, + { + "algorithm": "SHA256", + "checksumValue": "4277092643b289f1d36d32cf0fd2efc30ead8bdd99342e5da3b3609dd8ea7d86" + } + ], + "fileName": "Modules/_blake2/impl/blake2-impl.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "caa3da7953109d0d2961e3b686d2d285c484b901" + }, + { + "algorithm": "SHA256", + "checksumValue": "2f6c9d0ecf70be474f2853b52394993625a32960e0a64eae147ef97a3a5c1460" + } + ], + "fileName": "Modules/_blake2/impl/blake2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2b-load-sse2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "029a98f87a178936d9e5211c7798b3e0fc622f94" + }, + { + "algorithm": "SHA256", + "checksumValue": "b392a6e7b43813a05609e994db5fc3552c5912bd482efc781daa0778eb56ab4e" + } + ], + "fileName": "Modules/_blake2/impl/blake2b-load-sse2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2b-load-sse41.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fb466dd72344170d09e311e5ea12de99ce071357" + }, + { + "algorithm": "SHA256", + "checksumValue": "cc3072c92164142bf2f9dda4e6c08db61be68ec15a95442415e861090d08f6a2" + } + ], + "fileName": "Modules/_blake2/impl/blake2b-load-sse41.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2b-ref.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4c0d79128cf891a95b1f668031d55c0c6d2e0270" + }, + { + "algorithm": "SHA256", + "checksumValue": "07b257d44e9cc2d95d4911629c92138feafd16d63fef0a5fa7b38914dfd82349" + } + ], + "fileName": "Modules/_blake2/impl/blake2b-ref.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2b-round.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4c7418e2026417c9c6736fcd305a31f23e05a661" + }, + { + "algorithm": "SHA256", + "checksumValue": "fa34a60c2d198a0585033f43fd4003f4ba279c9ebcabdf5d6650def0e6d1e914" + } + ], + "fileName": "Modules/_blake2/impl/blake2b-round.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2b.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "6fa074693aa7305018dfa8db48010a8ef1050ad4" + }, + { + "algorithm": "SHA256", + "checksumValue": "c8c6dd861ac193d4a0e836242ff44900f83423f86d2c2940c8c4c1e41fbd5812" + } + ], + "fileName": "Modules/_blake2/impl/blake2b.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-sse2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ad3f79b6cbe3fd812722114a0d5d08064e69e4d0" + }, + { + "algorithm": "SHA256", + "checksumValue": "57f1ac6c09f4a50d95811529062220eab4f29cec3805bc6081dec00426c6df62" + } + ], + "fileName": "Modules/_blake2/impl/blake2s-load-sse2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-sse41.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "51c32d79f419f3d2eb9875cd9a7f5c0d7892f8a8" + }, + { + "algorithm": "SHA256", + "checksumValue": "ecc9e09adcbe098629eafd305596bed8d7004be1d83f326995def42bbde93b23" + } + ], + "fileName": "Modules/_blake2/impl/blake2s-load-sse41.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-xop.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2749a7ba0104b765d4f56f13faf70b6eb89cf203" + }, + { + "algorithm": "SHA256", + "checksumValue": "8bc95595cec4c50f5d70f2b330d3798de07cc784e8890791b3328890e602d5c5" + } + ], + "fileName": "Modules/_blake2/impl/blake2s-load-xop.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s-ref.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "883fcfe85f9063819f21b1100296d1f9eb55bac1" + }, + { + "algorithm": "SHA256", + "checksumValue": "9715c00d0f11587a139b07fa26678e6d26e44d3d4910b96158d158da2b022bfb" + } + ], + "fileName": "Modules/_blake2/impl/blake2s-ref.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s-round.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "5d9f69adda40ed163b287b9ed4cedb35b88f2daa" + }, + { + "algorithm": "SHA256", + "checksumValue": "65d90111c89c43bb18a9e1d1a4fdbd9f85bebd1ff00129335b85995d0f30ee8b" + } + ], + "fileName": "Modules/_blake2/impl/blake2s-round.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-blake2-impl-blake2s.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d2691353fa54ac6ffcd7c0a294984dc9d7968ef7" + }, + { + "algorithm": "SHA256", + "checksumValue": "cfd7948c9fd50e9f9c62f8a93b20a254d1d510a862d1092af4f187b7c1a859a3" + } + ], + "fileName": "Modules/_blake2/impl/blake2s.c" + }, + { + "SPDXID": "SPDXRef-FILE-Lib-ctypes-macholib-init-.py", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0fbc026a9771d9675e7094790b5b945334d3cb53" + }, + { + "algorithm": "SHA256", + "checksumValue": "1e77c01eec8f167ed10b754f153c0c743c8e5196ae9c81dffc08f129ab56dbfd" + } + ], + "fileName": "Lib/ctypes/macholib/__init__.py" + }, + { + "SPDXID": "SPDXRef-FILE-Lib-ctypes-macholib-dyld.py", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4a78ebd73ce4167c722689781a15fe0b4578e967" + }, + { + "algorithm": "SHA256", + "checksumValue": "eb8e7b17f1533bc3e86e23e8695f7a5e4b7a99ef1b1575d10af54f389161b655" + } + ], + "fileName": "Lib/ctypes/macholib/dyld.py" + }, + { + "SPDXID": "SPDXRef-FILE-Lib-ctypes-macholib-dylib.py", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "f339420cc01bd01f8d0da19b6102f099075e8bcd" + }, + { + "algorithm": "SHA256", + "checksumValue": "f19ee056b18165cc6735efab0b4ca3508be9405b9646c38113316c15e8278a6f" + } + ], + "fileName": "Lib/ctypes/macholib/dylib.py" + }, + { + "SPDXID": "SPDXRef-FILE-Lib-ctypes-macholib-framework.py", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0b219f58467d7f193fa1de0c1b118485840d855b" + }, + { + "algorithm": "SHA256", + "checksumValue": "302439e40d9cbdd61b8b7cffd0b7e1278a6811b635044ee366a36e0d991f62da" + } + ], + "fileName": "Lib/ctypes/macholib/framework.py" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-README.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "bda6e0bd6121f7069b420bdc0bc7c49414d948d1" + }, + { + "algorithm": "SHA256", + "checksumValue": "89926cd0fe6cfb33a2b5b7416c101e9b5d42b0d639d348e0871acf6ffc8258a3" + } + ], + "fileName": "Modules/_decimal/libmpdec/README.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-basearith.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "33757ce2ec0c93c1b5e03c45a495563a00e498ae" + }, + { + "algorithm": "SHA256", + "checksumValue": "ad498362c31a5b99ab19fce320ac540cf14c5c4ec09478f0ad3858da1428113d" + } + ], + "fileName": "Modules/_decimal/libmpdec/basearith.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-basearith.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "bf03919412c068e6969e7ac48850f91bfcd3b2b1" + }, + { + "algorithm": "SHA256", + "checksumValue": "2eaac88a71b9bcf3144396c12dcfeced573e0e550a0050d75b9ed3903248596d" + } + ], + "fileName": "Modules/_decimal/libmpdec/basearith.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-bench.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c925b7f26754ae182aaa461d51802e8b6a2bb5e9" + }, + { + "algorithm": "SHA256", + "checksumValue": "007e38542ec8d9d8805fe243b5390d79211b9360e2797a20079e833e68ad9e45" + } + ], + "fileName": "Modules/_decimal/libmpdec/bench.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-bench-full.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cb22686269685a53a17afdea9ed984714e399d9d" + }, + { + "algorithm": "SHA256", + "checksumValue": "1b9e892d4b268deea835ec8906f20a1e5d25e037b2e698edcd34315613f3608c" + } + ], + "fileName": "Modules/_decimal/libmpdec/bench_full.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-bits.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fc91c2450cdf1e785d1347411662294c3945eb27" + }, + { + "algorithm": "SHA256", + "checksumValue": "ce7741e58ea761a24250c0bfa10058cec8c4fd220dca70a41de3927a2e4f5376" + } + ], + "fileName": "Modules/_decimal/libmpdec/bits.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-constants.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "7187c18916b0a546ec19b4fc4bec43d0d9fb5fc2" + }, + { + "algorithm": "SHA256", + "checksumValue": "cd430b8657cf8a616916e02f9bd5ca044d5fc19e69333f5d427e1fdb90b0864b" + } + ], + "fileName": "Modules/_decimal/libmpdec/constants.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-constants.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "af9cbd016fb0ef0b30ced49c0aa4ce2ca3c20125" + }, + { + "algorithm": "SHA256", + "checksumValue": "19dc46df04abb7ee08e9a403f87c8aac8d4a077efcce314c597f8b73e22884f2" + } + ], + "fileName": "Modules/_decimal/libmpdec/constants.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-context.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "666162870230bebd3f2383020d908806fd03909e" + }, + { + "algorithm": "SHA256", + "checksumValue": "9a265d366f31894aad78bca7fcdc1457bc4a3aa3887ca231b7d78e41f79541c0" + } + ], + "fileName": "Modules/_decimal/libmpdec/context.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-convolute.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0545547a8b37b922fbe2574fbad8fc3bf16f1d33" + }, + { + "algorithm": "SHA256", + "checksumValue": "66fe27b9bb37039cad5be32b105ed509e5aefa15c1957a9058af8ee23cddc97a" + } + ], + "fileName": "Modules/_decimal/libmpdec/convolute.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-convolute.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "05ff0936c5bb08f40d460f5843004a1cc0751d9b" + }, + { + "algorithm": "SHA256", + "checksumValue": "c00d17450c2b8e1d7f1eb8a084f7e6a68f257a453f8701600e860bf357c531d7" + } + ], + "fileName": "Modules/_decimal/libmpdec/convolute.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-crt.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "fe8176849bc99a306332ba25caa4e91bfa3c6f7d" + }, + { + "algorithm": "SHA256", + "checksumValue": "1f4e65c44864c3e911a6e91f33adec76765293e90553459e3ebce35a58898dba" + } + ], + "fileName": "Modules/_decimal/libmpdec/crt.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-crt.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "1930b9e0910014b3479aec4e940f02118d9e4a08" + }, + { + "algorithm": "SHA256", + "checksumValue": "7d31f1d0dd73b62964dab0f7a1724473bf87f1f95d8febf0b40c15430ae9a47c" + } + ], + "fileName": "Modules/_decimal/libmpdec/crt.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-difradix2.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "415c51e7d7f517b6366bec2a809610d0d38ada14" + }, + { + "algorithm": "SHA256", + "checksumValue": "0a9fef8a374f55277e9f6000b7277bb037b9763c32b156c29950422b057498bd" + } + ], + "fileName": "Modules/_decimal/libmpdec/difradix2.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-difradix2.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d8a998c3bee4c3d9059ba7bf9ae6a8b64649c2ba" + }, + { + "algorithm": "SHA256", + "checksumValue": "5c6766496224de657400995b58b64db3e7084004bf00daebdd7e08d0c5995243" + } + ], + "fileName": "Modules/_decimal/libmpdec/difradix2.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-README.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "158f6ad18edf348efa4fdd7cf61114c77c1d22e9" + }, + { + "algorithm": "SHA256", + "checksumValue": "7b0da2758097a2688f06b3c7ca46b2ebc8329addbd28bb4f5fe95626cc81f8a9" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/README.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-compare.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ef80ba26847287fb351ab0df0a78b5f08ba0b5b7" + }, + { + "algorithm": "SHA256", + "checksumValue": "452666ee4eb10a8cf0a926cb3bcf5e95b5c361fa129dbdfe27b654e6d640417e" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/compare.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-div.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "6ca3a369b3d1e140fdc93c4fdbedb724f7daf969" + }, + { + "algorithm": "SHA256", + "checksumValue": "6d369f5a24d0bb1e7cb6a4f8b0e97a273260e7668c8a540a8fcc92e039f7af2e" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/div.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-divmod.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "3872a28b4f77e07e1760256067ea338a8dd183f8" + }, + { + "algorithm": "SHA256", + "checksumValue": "5db54bae75ac3d7fa12f1bb0f7ce1bf797df86a81030e8c3ce44d3b1f9b958b7" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/divmod.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-multiply.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "25dbc94fd4ee5dec21061d2d40dd5d0f88849cb1" + }, + { + "algorithm": "SHA256", + "checksumValue": "22ed39b18fa740a27aacfd29a7bb40066be24500ba49b9b1f24e2af1e039fcd9" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/multiply.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-pow.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "13d3b7657dc2dc5000fea428f57963d520792ef7" + }, + { + "algorithm": "SHA256", + "checksumValue": "cd8c037649b3d4d6897c9acd2f92f3f9d5390433061d5e48623a5d526a3f4f9c" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/pow.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-powmod.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "1f7e6c3d3e38df52bbcec0f5a180a8f328679618" + }, + { + "algorithm": "SHA256", + "checksumValue": "e29614b43abf1856b656a84d6b67c22cc5dc7af8cbae8ddc7acf17022220ee12" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/powmod.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-shift.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "0bd9ce89c7987d1109eb7b0c8f1f9a1298e1422e" + }, + { + "algorithm": "SHA256", + "checksumValue": "203f2dbf11d115580cb3c7c524ac6ccca2a7b31d89545db1b6263381b5de2b6a" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/shift.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-sqrt.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b401ba0814e17c9164c0df26e01cc0a355382f46" + }, + { + "algorithm": "SHA256", + "checksumValue": "f3dc2ce321833bbd4b3d1d9ea6fa2e0bcc1bfe1e39abb8d55be53e46c33949db" + } + ], + "fileName": "Modules/_decimal/libmpdec/examples/sqrt.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-fnt.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "060615ddef089a5a8f879a57e4968d920972a0e2" + }, + { + "algorithm": "SHA256", + "checksumValue": "a9f923524d53a9445769f27405375ec3d95fa804bb11db5ee249ae047f11cfce" + } + ], + "fileName": "Modules/_decimal/libmpdec/fnt.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-fnt.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b205043ebeaf065b16505a299342a992654f19b0" + }, + { + "algorithm": "SHA256", + "checksumValue": "3b03e69adf78fde68c8f87d33595d557237581d33fc067e1039eed9e9f2cc44c" + } + ], + "fileName": "Modules/_decimal/libmpdec/fnt.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-fourstep.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "702c27599b43280c94906235d7e1a74193ba701b" + }, + { + "algorithm": "SHA256", + "checksumValue": "cf2e69b946ec14b087e523c0ff606553070d13c23e851fb0ba1df51a728017e6" + } + ], + "fileName": "Modules/_decimal/libmpdec/fourstep.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-fourstep.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "ee5291c265ef1f5ae373bc243a4d96975eb3e7b5" + }, + { + "algorithm": "SHA256", + "checksumValue": "dbaced03b52d0f880c377b86c943bcb36f24d557c99a5e9732df3ad5debb5917" + } + ], + "fileName": "Modules/_decimal/libmpdec/fourstep.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-io.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "12402bcf7f0161adb83f78163f41cc10a5e5de5f" + }, + { + "algorithm": "SHA256", + "checksumValue": "cba044c76b6bc3ae6cfa49df1121cad7552140157b9e61e11cbb6580cc5d74cf" + } + ], + "fileName": "Modules/_decimal/libmpdec/io.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-io.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "28c653cd40b1ce46575e41f5dbfda5f6dd0db4d1" + }, + { + "algorithm": "SHA256", + "checksumValue": "259eab89fe27914e0e39e61199094a357ac60d86b2aab613c909040ff64a4a0c" + } + ], + "fileName": "Modules/_decimal/libmpdec/io.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-REFERENCES.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "218d1d7bedb335cd2c31eae89a15873c3139e13f" + }, + { + "algorithm": "SHA256", + "checksumValue": "a57e8bed93ded481ef264166aec2c49d1a7f3252f29a873ee41fff053cfd9c20" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/REFERENCES.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-bignum.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "f67eab2431336cf6eeafb30cdafd7e54c251def3" + }, + { + "algorithm": "SHA256", + "checksumValue": "dc34aa122c208ce79e3fc6baee8628094ffaf6a662862dd5647836241f6ebd79" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/bignum.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-fnt.py", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "a58cfbcd8ea57d66ddfd11fb5a170138c8bbfb3a" + }, + { + "algorithm": "SHA256", + "checksumValue": "122de20eebf87274af2d02072251a94500e7df2d5ef29e81aeabeda991c079e3" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/fnt.py" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-matrix-transform.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "9a947f6b660150cbd457c4458da2956a36c5824d" + }, + { + "algorithm": "SHA256", + "checksumValue": "592659e7192e3a939b797f5bc7455455834a285f5d8b643ccd780b5114914f73" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/matrix-transform.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-mulmod-64.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "69fe9afb8353b5a2b57917469c51c64ac518169d" + }, + { + "algorithm": "SHA256", + "checksumValue": "229a80ca940c594a32e3345412370cbc097043fe59c66a6153cbcf01e7837266" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/mulmod-64.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-mulmod-ppro.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "720d468a1f51098036c7a0c869810fff22ed9b79" + }, + { + "algorithm": "SHA256", + "checksumValue": "f3549fc73f697a087267c7b042e30a409e191cbba69a2c0902685e507fbae9f7" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/mulmod-ppro.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-six-step.txt", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "6815ec3a39baebebe7b3f51d45d10c180a659f17" + }, + { + "algorithm": "SHA256", + "checksumValue": "bf15f73910a173c98fca9db56122b6cc71983668fa8b934c46ca21a57398ec54" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/six-step.txt" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-umodarith.lisp", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "c91ac4438e661ce78f86e981257546e5adff39ae" + }, + { + "algorithm": "SHA256", + "checksumValue": "783a1b4b9b7143677b0c3d30ffaf28aa0cb01956409031fa38ed8011970bdee0" + } + ], + "fileName": "Modules/_decimal/libmpdec/literature/umodarith.lisp" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-mpalloc.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "7e8dfb4b7a801b48c501969b001153203b14679e" + }, + { + "algorithm": "SHA256", + "checksumValue": "5ba2f4c80302e71fb216aa247c858e0bf6c8cfabffe7980ac17d4d023c0fef2b" + } + ], + "fileName": "Modules/_decimal/libmpdec/mpalloc.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-mpalloc.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "bccb6a6ae76fd7f6c8a9102a78958bcad7862950" + }, + { + "algorithm": "SHA256", + "checksumValue": "f7412521de38afb837fcabc2b1d48b971b86bfaa55f3f40d58ff9e46e92debd3" + } + ], + "fileName": "Modules/_decimal/libmpdec/mpalloc.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-mpdecimal.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "f4539afb1ace58c52d18ffd0cc7704f53ca55182" + }, + { + "algorithm": "SHA256", + "checksumValue": "4f89b8095e408a18deff79cfb605299e615bae747898eb105d8936064f7fb626" + } + ], + "fileName": "Modules/_decimal/libmpdec/mpdecimal.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-mpdecimal.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4b80e25ac49b7e1ea0d1e84967ee32a3d111fc4c" + }, + { + "algorithm": "SHA256", + "checksumValue": "ea0b9c6b296c13aed6ecaa50b463e39a9c1bdc059b84f50507fd8247b2e660f9" + } + ], + "fileName": "Modules/_decimal/libmpdec/mpdecimal.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-mpsignal.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "5c7305a6db0fddf64c6d97e29d3b0c402e3d5d6e" + }, + { + "algorithm": "SHA256", + "checksumValue": "653171cf2549719478417db7e9800fa0f9d99c02dec6da6876329ccf2c07b93f" + } + ], + "fileName": "Modules/_decimal/libmpdec/mpsignal.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-numbertheory.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d736b874c43777ca021dde5289ea718893f39219" + }, + { + "algorithm": "SHA256", + "checksumValue": "bdbf2e246f341a3ba3f6f9d8759e7cb222eb9b15f9ed1e7c9f6a59cbb9f8bc91" + } + ], + "fileName": "Modules/_decimal/libmpdec/numbertheory.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-numbertheory.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d341508d8c6dd4c4cbd8b99afc8029945f9bbe0d" + }, + { + "algorithm": "SHA256", + "checksumValue": "2f7d5b40af508fa6ac86f5d62101fa3bf683c63b24aa87c9548e3fdd13abc57b" + } + ], + "fileName": "Modules/_decimal/libmpdec/numbertheory.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-sixstep.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cbd05d68bb3940d0d7d0818b14cc03b090a4dd74" + }, + { + "algorithm": "SHA256", + "checksumValue": "7602aaf98ec9525bc4b3cab9631615e1be2efd9af894002ef4e3f5ec63924fcf" + } + ], + "fileName": "Modules/_decimal/libmpdec/sixstep.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-sixstep.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4c059463ec4b4522562dab24760fc64c172c9eee" + }, + { + "algorithm": "SHA256", + "checksumValue": "a191366348b3d3dd49b9090ec5c77dbd77bb3a523c01ff32adafa137e5097ce7" + } + ], + "fileName": "Modules/_decimal/libmpdec/sixstep.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-transpose.c", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cc5593ac9fdb60480cc23fc9d6f27d85670bd35f" + }, + { + "algorithm": "SHA256", + "checksumValue": "2d12fcae512143a9376c8a0d4c1ba3008e420e024497a7e7ec64c6bec23fcddc" + } + ], + "fileName": "Modules/_decimal/libmpdec/transpose.c" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-transpose.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "2f616425756b6cbdf7d189744870b98b613455bd" + }, + { + "algorithm": "SHA256", + "checksumValue": "fafeb2b901b2b41bf0df00be7d99b84df1a78e3cc1e582e09cbfc3b6d44d4abe" + } + ], + "fileName": "Modules/_decimal/libmpdec/transpose.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-typearith.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b1e9341e173cc8e219ad4aa45fad36d92cce10d3" + }, + { + "algorithm": "SHA256", + "checksumValue": "25e0a0703b51744277834e6b2398d7b7d2c17f92bf30f8b6f949e0486ae2b346" + } + ], + "fileName": "Modules/_decimal/libmpdec/typearith.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-umodarith.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "46f6483fce136cd3cc2f7516ee119a487d86333e" + }, + { + "algorithm": "SHA256", + "checksumValue": "bfe1ddb2ca92906456b80745adcbe02c83cadac3ef69caa21bc09b7292cc152b" + } + ], + "fileName": "Modules/_decimal/libmpdec/umodarith.h" + }, + { + "SPDXID": "SPDXRef-FILE-Modules-decimal-libmpdec-vcdiv64.asm", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "d0cc1052fcba08b773d935b0ae2dc6b80d0f2f68" + }, + { + "algorithm": "SHA256", + "checksumValue": "aacc3e47ea8f41e8840c6c67f64ec96d54696a16889903098fa1aab56949a00f" + } + ], + "fileName": "Modules/_decimal/libmpdec/vcdiv64.asm" + }, + { + "SPDXID": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.1-py3-none-any.whl", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "4b2baddc0673f73017e531648a9ee27e47925e7a" + }, + { + "algorithm": "SHA256", + "checksumValue": "55eb67bb6171d37447e82213be585b75fe2b12b359e993773aca4de9247a052b" + } + ], + "fileName": "Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl" + } + ], + "packages": [ + { + "SPDXID": "SPDXRef-PACKAGE-expat", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6b902ab103843592be5e99504f846ec109c1abb692e85347587f237a4ffa1033" + } + ], + "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.5.0:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "MIT", + "name": "expat", + "originator": "Organization: Expat development team", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2.5.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-hacl-star", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "c23ac158b238c368389dc86bfc315263e5c0e57785da74144aea2cab9a3d51a2" + } + ], + "downloadLocation": "https://github.com/hacl-star/hacl-star/archive/521af282fdf6d60227335120f18ae9309a4b8e8c.zip", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:hacl-star:hacl-star:521af282fdf6d60227335120f18ae9309a4b8e8c:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "Apache-2.0", + "name": "hacl-star", + "originator": "Organization: HACL* Developers", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "521af282fdf6d60227335120f18ae9309a4b8e8c" + }, + { + "SPDXID": "SPDXRef-PACKAGE-libb2", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "53626fddce753c454a3fea581cbbc7fe9bbcf0bc70416d48fdbbf5d87ef6c72e" + } + ], + "downloadLocation": "https://github.com/BLAKE2/libb2/releases/download/v0.98.1/libb2-0.98.1.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:blake2:libb2:0.98.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "CC0-1.0", + "name": "libb2", + "originator": "Organization: BLAKE2 - fast secure hashing", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "0.98.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-macholib", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "c76f268f5054024e962f2515a0e522baf85313064f6740d80375afc850787a38" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/ec/57/f0a712efc3ed982cf4038a3cee172057303b9be914c32c93b2fbec27f785/macholib-1.0.tar.gz", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/macholib@1.0", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "macholib", + "originator": "Person: Ronald Oussoren (ronaldoussoren@mac.com)", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-mpdecimal", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "9f9cd4c041f99b5c49ffb7b59d9f12d95b683d88585608aa56a6307667b2b21f" + } + ], + "downloadLocation": "https://www.bytereef.org/software/mpdecimal/releases/mpdecimal-2.5.1.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:bytereef:mpdecimal:2.5.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "BSD-2-Clause", + "name": "mpdecimal", + "originator": "Organization: bytereef.org", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "2.5.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-pip", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be" + } + ], + "downloadLocation": "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:pypa:pip:23.2.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": "pkg:pypi/pip@23.2.1", + "referenceType": "purl" + } + ], + "licenseConcluded": "MIT", + "name": "pip", + "originator": "Organization: Python Packaging Authority", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "23.2.1" + } + ], + "relationships": [ + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-COPYING", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-ascii.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-asciitab.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat-config.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat-external.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-iasciitab.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-internal.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-latin1tab.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-nametab.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-pyexpatns.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-siphash.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-utf8tab.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-winconfig.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmlparse.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmlrole.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmlrole.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmltok.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmltok.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmltok-impl.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmltok-impl.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-xmltok-ns.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-MD5.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-MD5.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA1.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA1.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA2.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA3.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Hash-SHA3.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-Hacl-Streaming-Types.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-FStar-UInt128-Verified.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-FStar-UInt-8-16-32-64.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-fstar-uint128-struct-endianness.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-internal-target.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-lowstar-endianness.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-include-krml-types.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-MD5.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA1.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-internal-Hacl-Hash-SHA3.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-hacl-python-hacl-namespaces.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-hacl-star" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2-config.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2-impl.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2b-load-sse2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2b-load-sse41.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2b-ref.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2b-round.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2b.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-sse2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-sse41.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s-load-xop.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s-ref.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s-round.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-blake2-impl-blake2s.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-libb2" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Lib-ctypes-macholib-init-.py", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-macholib" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Lib-ctypes-macholib-dyld.py", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-macholib" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Lib-ctypes-macholib-dylib.py", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-macholib" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Lib-ctypes-macholib-framework.py", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-macholib" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-README.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-basearith.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-basearith.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-bench.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-bench-full.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-bits.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-constants.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-constants.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-context.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-convolute.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-convolute.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-crt.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-crt.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-difradix2.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-difradix2.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-README.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-compare.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-div.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-divmod.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-multiply.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-pow.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-powmod.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-shift.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-examples-sqrt.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-fnt.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-fnt.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-fourstep.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-fourstep.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-io.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-io.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-REFERENCES.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-bignum.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-fnt.py", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-matrix-transform.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-mulmod-64.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-mulmod-ppro.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-six-step.txt", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-literature-umodarith.lisp", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-mpalloc.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-mpalloc.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-mpdecimal.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-mpdecimal.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-mpsignal.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-numbertheory.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-numbertheory.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-sixstep.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-sixstep.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-transpose.c", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-transpose.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-typearith.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-umodarith.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-decimal-libmpdec-vcdiv64.asm", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-mpdecimal" + }, + { + "relatedSpdxElement": "SPDXRef-FILE-Lib-ensurepip-bundled-pip-23.3.1-py3-none-any.whl", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-pip" + } + ], + "spdxVersion": "SPDX-2.3" +} \ No newline at end of file diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py new file mode 100644 index 00000000000000..0089db81af9b9d --- /dev/null +++ b/Tools/build/generate_sbom.py @@ -0,0 +1,179 @@ +"""Tool for generating Software Bill of Materials (SBOM) for Python's dependencies""" + +import re +import hashlib +import json +import glob +import pathlib +import subprocess +import typing + +# Before adding a new entry to this list, double check that +# the license expression is a valid SPDX license expression: +# See: https://spdx.org/licenses +ALLOWED_LICENSE_EXPRESSIONS = { + "MIT", + "CC0-1.0", + "Apache-2.0", + "BSD-2-Clause", +} + +# Properties which are required for our purposes. +REQUIRED_PROPERTIES_PACKAGE = frozenset([ + "SPDXID", + "name", + "versionInfo", + "downloadLocation", + "checksums", + "licenseConcluded", + "externalRefs", + "originator", + "primaryPackagePurpose", +]) + + +class PackageFiles(typing.NamedTuple): + """Structure for describing the files of a package""" + include: list[str] + exclude: list[str] | None = None + + +# SBOMS don't have a method to specify the sources of files +# so we need to do that external to the SBOM itself. Add new +# values to 'exclude' if we create new files within tracked +# directories that aren't sourced from third-party packages. +PACKAGE_TO_FILES = { + "mpdecimal": PackageFiles( + include=["Modules/_decimal/libmpdec/**"] + ), + "expat": PackageFiles( + include=["Modules/expat/**"] + ), + "pip": PackageFiles( + include=["Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl"] + ), + "macholib": PackageFiles( + include=["Lib/ctypes/macholib/**"], + exclude=[ + "Lib/ctypes/macholib/README.ctypes", + "Lib/ctypes/macholib/fetch_macholib", + "Lib/ctypes/macholib/fetch_macholib.bat", + ], + ), + "libb2": PackageFiles( + include=["Modules/_blake2/impl/**"] + ), + "hacl-star": PackageFiles( + include=["Modules/_hacl/**"], + exclude=[ + "Modules/_hacl/refresh.sh", + "Modules/_hacl/README.md", + "Modules/_hacl/python_hacl_namespace.h", + ] + ), +} + + +def spdx_id(value: str) -> str: + """Encode a value into characters that are valid in an SPDX ID""" + return re.sub(r"[^a-zA-Z0-9.\-]+", "-", value) + + +def filter_gitignored_paths(paths: list[str]) -> list[str]: + """ + Filter out paths excluded by the gitignore file. + The output of 'git check-ignore --non-matching --verbose' looks + like this for non-matching (included) files: + + '::' + + And looks like this for matching (excluded) files: + + '.gitignore:9:*.a Tools/lib.a' + """ + # Filter out files in gitignore. + # Non-matching files show up as '::' + git_check_ignore_proc = subprocess.run( + ["git", "check-ignore", "--verbose", "--non-matching", *paths], + check=False, + stdout=subprocess.PIPE, + ) + # 1 means matches, 0 means no matches. + assert git_check_ignore_proc.returncode in (0, 1) + + # Return the list of paths sorted + git_check_ignore_lines = git_check_ignore_proc.stdout.decode().splitlines() + return sorted([line.split()[-1] for line in git_check_ignore_lines if line.startswith("::")]) + + +def main() -> None: + root_dir = pathlib.Path(__file__).parent.parent.parent + sbom_path = root_dir / "Misc/sbom.spdx.json" + sbom_data = json.loads(sbom_path.read_bytes()) + + # Make a bunch of assertions about the SBOM data to ensure it's consistent. + assert {package["name"] for package in sbom_data["packages"]} == set(PACKAGE_TO_FILES) + for package in sbom_data["packages"]: + + # Properties and ID must be properly formed. + assert set(package.keys()) == REQUIRED_PROPERTIES_PACKAGE + assert package["SPDXID"] == spdx_id(f"SPDXRef-PACKAGE-{package['name']}") + + # Version must be in the download and external references. + version = package["versionInfo"] + assert version in package["downloadLocation"] + assert all(version in ref["referenceLocator"] for ref in package["externalRefs"]) + + # License must be on the approved list for SPDX. + assert package["licenseConcluded"] in ALLOWED_LICENSE_EXPRESSIONS, package["licenseConcluded"] + + # 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): + + # Find all the paths and then filter them through .gitignore. + paths = glob.glob(include, root_dir=root_dir, recursive=True) + paths = filter_gitignored_paths(paths) + assert paths, include # Make sure that every value returns something! + + for path in paths: + # Skip directories and excluded files + if not (root_dir / path).is_file() or path in exclude: + continue + + # SPDX requires SHA1 to be used for files, but we provide SHA256 too. + data = (root_dir / path).read_bytes() + checksum_sha1 = hashlib.sha1(data).hexdigest() + checksum_sha256 = hashlib.sha256(data).hexdigest() + + file_spdx_id = spdx_id(f"SPDXRef-FILE-{path}") + sbom_files.append({ + "SPDXID": file_spdx_id, + "fileName": path, + "checksums": [ + {"algorithm": "SHA1", "checksumValue": checksum_sha1}, + {"algorithm": "SHA256", "checksumValue": checksum_sha256}, + ], + }) + + # Tie each file back to its respective package. + sbom_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)) + + +if __name__ == "__main__": + main() diff --git a/Tools/build/mypy.ini b/Tools/build/mypy.ini new file mode 100644 index 00000000000000..cf1dac7fde5ac5 --- /dev/null +++ b/Tools/build/mypy.ini @@ -0,0 +1,13 @@ +[mypy] +files = Tools/build/generate_sbom.py +pretty = True + +# Make sure Python can still be built +# using Python 3.10 for `PYTHON_FOR_REGEN`... +python_version = 3.10 + +# ...And be strict: +strict = True +strict_concatenate = True +enable_error_code = ignore-without-code,redundant-expr,truthy-bool,possibly-undefined +warn_unreachable = True From 81c16cd94ec38d61aa478b9a452436dc3b1b524d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20L=C3=B8vborg?= Date: Thu, 7 Dec 2023 17:04:06 +0100 Subject: [PATCH 146/442] gh-91133: tempfile.TemporaryDirectory: fix symlink bug in cleanup (GH-99930) Co-authored-by: Serhiy Storchaka --- Lib/tempfile.py | 27 +++-- Lib/test/test_tempfile.py | 111 +++++++++++++++++- ...2-12-01-16-57-44.gh-issue-91133.LKMVCV.rst | 2 + 3 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 55403ad1faf46d..9a5e7d01c2379b 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -270,6 +270,22 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type): raise FileExistsError(_errno.EEXIST, "No usable temporary file name found") +def _dont_follow_symlinks(func, path, *args): + # Pass follow_symlinks=False, unless not supported on this platform. + if func in _os.supports_follow_symlinks: + func(path, *args, follow_symlinks=False) + elif _os.name == 'nt' or not _os.path.islink(path): + func(path, *args) + +def _resetperms(path): + try: + chflags = _os.chflags + except AttributeError: + pass + else: + _dont_follow_symlinks(chflags, path, 0) + _dont_follow_symlinks(_os.chmod, path, 0o700) + # User visible interfaces. @@ -876,17 +892,10 @@ def __init__(self, suffix=None, prefix=None, dir=None, def _rmtree(cls, name, ignore_errors=False): def onexc(func, path, exc): if isinstance(exc, PermissionError): - def resetperms(path): - try: - _os.chflags(path, 0) - except AttributeError: - pass - _os.chmod(path, 0o700) - try: if path != name: - resetperms(_os.path.dirname(path)) - resetperms(path) + _resetperms(_os.path.dirname(path)) + _resetperms(path) try: _os.unlink(path) diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index f4aef887799ed4..2729bec7a21c71 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1673,6 +1673,103 @@ def test_cleanup_with_symlink_to_a_directory(self): "were deleted") d2.cleanup() + @os_helper.skip_unless_symlink + def test_cleanup_with_symlink_modes(self): + # cleanup() should not follow symlinks when fixing mode bits (#91133) + with self.do_create(recurse=0) as d2: + file1 = os.path.join(d2, 'file1') + open(file1, 'wb').close() + dir1 = os.path.join(d2, 'dir1') + os.mkdir(dir1) + for mode in range(8): + mode <<= 6 + with self.subTest(mode=format(mode, '03o')): + def test(target, target_is_directory): + d1 = self.do_create(recurse=0) + symlink = os.path.join(d1.name, 'symlink') + os.symlink(target, symlink, + target_is_directory=target_is_directory) + try: + os.chmod(symlink, mode, follow_symlinks=False) + except NotImplementedError: + pass + try: + os.chmod(symlink, mode) + except FileNotFoundError: + pass + os.chmod(d1.name, mode) + d1.cleanup() + self.assertFalse(os.path.exists(d1.name)) + + with self.subTest('nonexisting file'): + test('nonexisting', target_is_directory=False) + with self.subTest('nonexisting dir'): + test('nonexisting', target_is_directory=True) + + with self.subTest('existing file'): + os.chmod(file1, mode) + old_mode = os.stat(file1).st_mode + test(file1, target_is_directory=False) + new_mode = os.stat(file1).st_mode + self.assertEqual(new_mode, old_mode, + '%03o != %03o' % (new_mode, old_mode)) + + with self.subTest('existing dir'): + os.chmod(dir1, mode) + old_mode = os.stat(dir1).st_mode + test(dir1, target_is_directory=True) + new_mode = os.stat(dir1).st_mode + self.assertEqual(new_mode, old_mode, + '%03o != %03o' % (new_mode, old_mode)) + + @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') + @os_helper.skip_unless_symlink + def test_cleanup_with_symlink_flags(self): + # cleanup() should not follow symlinks when fixing flags (#91133) + flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK + self.check_flags(flags) + + with self.do_create(recurse=0) as d2: + file1 = os.path.join(d2, 'file1') + open(file1, 'wb').close() + dir1 = os.path.join(d2, 'dir1') + os.mkdir(dir1) + def test(target, target_is_directory): + d1 = self.do_create(recurse=0) + symlink = os.path.join(d1.name, 'symlink') + os.symlink(target, symlink, + target_is_directory=target_is_directory) + try: + os.chflags(symlink, flags, follow_symlinks=False) + except NotImplementedError: + pass + try: + os.chflags(symlink, flags) + except FileNotFoundError: + pass + os.chflags(d1.name, flags) + d1.cleanup() + self.assertFalse(os.path.exists(d1.name)) + + with self.subTest('nonexisting file'): + test('nonexisting', target_is_directory=False) + with self.subTest('nonexisting dir'): + test('nonexisting', target_is_directory=True) + + with self.subTest('existing file'): + os.chflags(file1, flags) + old_flags = os.stat(file1).st_flags + test(file1, target_is_directory=False) + new_flags = os.stat(file1).st_flags + self.assertEqual(new_flags, old_flags) + + with self.subTest('existing dir'): + os.chflags(dir1, flags) + old_flags = os.stat(dir1).st_flags + test(dir1, target_is_directory=True) + new_flags = os.stat(dir1).st_flags + self.assertEqual(new_flags, old_flags) + @support.cpython_only def test_del_on_collection(self): # A TemporaryDirectory is deleted when garbage collected @@ -1845,10 +1942,7 @@ def test_modes(self): d.cleanup() self.assertFalse(os.path.exists(d.name)) - @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') - def test_flags(self): - flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK - + def check_flags(self, flags): # skip the test if these flags are not supported (ex: FreeBSD 13) filename = os_helper.TESTFN try: @@ -1857,13 +1951,18 @@ def test_flags(self): os.chflags(filename, flags) except OSError as exc: # "OSError: [Errno 45] Operation not supported" - self.skipTest(f"chflags() doesn't support " - f"UF_IMMUTABLE|UF_NOUNLINK: {exc}") + self.skipTest(f"chflags() doesn't support flags " + f"{flags:#b}: {exc}") else: os.chflags(filename, 0) finally: os_helper.unlink(filename) + @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags') + def test_flags(self): + flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK + self.check_flags(flags) + d = self.do_create(recurse=3, dirs=2, files=2) with d: # Change files and directories flags recursively. diff --git a/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst new file mode 100644 index 00000000000000..7991048fc48e03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-12-01-16-57-44.gh-issue-91133.LKMVCV.rst @@ -0,0 +1,2 @@ +Fix a bug in :class:`tempfile.TemporaryDirectory` cleanup, which now no longer +dereferences symlinks when working around file system permission errors. From ba18893555bbf69b1da262aaf85d65e4b67e8955 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Dec 2023 18:32:10 +0200 Subject: [PATCH 147/442] gh-87319: Simplify TemporaryDirectory cleanup using os.path.isjunction() (GH-112791) --- Lib/tempfile.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 9a5e7d01c2379b..4d99f91e1f53b7 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -41,7 +41,6 @@ import io as _io import os as _os import shutil as _shutil -import stat as _stat import errno as _errno from random import Random as _Random import sys as _sys @@ -909,18 +908,7 @@ def onexc(func, path, exc): # raise NotADirectoryError and mask the PermissionError. # So we must re-raise the current PermissionError if # path is not a directory. - try: - st = _os.lstat(path) - except OSError: - if ignore_errors: - return - raise - if (_stat.S_ISLNK(st.st_mode) or - not _stat.S_ISDIR(st.st_mode) or - (hasattr(st, 'st_file_attributes') and - st.st_file_attributes & _stat.FILE_ATTRIBUTE_REPARSE_POINT and - st.st_reparse_tag == _stat.IO_REPARSE_TAG_MOUNT_POINT) - ): + if not _os.path.isdir(path) or _os.path.isjunction(path): if ignore_errors: return raise From b2923a61a10dc2717f4662b590cc9f6d181c6983 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 7 Dec 2023 19:21:36 +0200 Subject: [PATCH 148/442] gh-79325: Fix recursion error in TemporaryDirectory cleanup on Windows (GH-112762) --- Lib/tempfile.py | 10 ++++++++-- Lib/test/test_tempfile.py | 11 +++++++++++ .../2023-12-05-18-57-53.gh-issue-79325.P2vMVK.rst | 2 ++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-05-18-57-53.gh-issue-79325.P2vMVK.rst diff --git a/Lib/tempfile.py b/Lib/tempfile.py index 4d99f91e1f53b7..cbfc172a789b25 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -888,9 +888,14 @@ def __init__(self, suffix=None, prefix=None, dir=None, ignore_errors=self._ignore_cleanup_errors, delete=self._delete) @classmethod - def _rmtree(cls, name, ignore_errors=False): + def _rmtree(cls, name, ignore_errors=False, repeated=False): def onexc(func, path, exc): if isinstance(exc, PermissionError): + if repeated and path == name: + if ignore_errors: + return + raise + try: if path != name: _resetperms(_os.path.dirname(path)) @@ -912,7 +917,8 @@ def onexc(func, path, exc): if ignore_errors: return raise - cls._rmtree(path, ignore_errors=ignore_errors) + cls._rmtree(path, ignore_errors=ignore_errors, + repeated=(path == name)) except FileNotFoundError: pass elif isinstance(exc, FileNotFoundError): diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index 2729bec7a21c71..b64b6a4f2baeb5 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -1651,6 +1651,17 @@ def test_explicit_cleanup_correct_error(self): with self.assertRaises(PermissionError): temp_dir.cleanup() + @unittest.skipUnless(os.name == "nt", "Only on Windows.") + def test_cleanup_with_used_directory(self): + with tempfile.TemporaryDirectory() as working_dir: + temp_dir = self.do_create(dir=working_dir) + subdir = os.path.join(temp_dir.name, "subdir") + os.mkdir(subdir) + with os_helper.change_cwd(subdir): + # Previously raised RecursionError on some OSes + # (e.g. Windows). See bpo-35144. + with self.assertRaises(PermissionError): + temp_dir.cleanup() @os_helper.skip_unless_symlink def test_cleanup_with_symlink_to_a_directory(self): diff --git a/Misc/NEWS.d/next/Library/2023-12-05-18-57-53.gh-issue-79325.P2vMVK.rst b/Misc/NEWS.d/next/Library/2023-12-05-18-57-53.gh-issue-79325.P2vMVK.rst new file mode 100644 index 00000000000000..f3c32d27b5fe66 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-05-18-57-53.gh-issue-79325.P2vMVK.rst @@ -0,0 +1,2 @@ +Fix an infinite recursion error in :func:`tempfile.TemporaryDirectory` +cleanup on Windows. From a955fd68d6451bd42199110c978e99b3d2959db2 Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 8 Dec 2023 01:26:29 +0800 Subject: [PATCH 149/442] gh-112278: Disable WMI queries on Windows after they time out (GH-112658) --- Lib/platform.py | 32 +++++++++++-------- ...-12-03-19-22-37.gh-issue-112278.FiloCE.rst | 2 ++ PC/_wmimodule.cpp | 25 +++++++++++++-- 3 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-03-19-22-37.gh-issue-112278.FiloCE.rst diff --git a/Lib/platform.py b/Lib/platform.py index 7bb222088d5061..75aa55510858fd 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -118,6 +118,10 @@ import sys import functools import itertools +try: + import _wmi +except ImportError: + _wmi = None ### Globals & Constants @@ -312,24 +316,26 @@ def _syscmd_ver(system='', release='', version='', version = _norm_version(version) return system, release, version -try: - import _wmi -except ImportError: - def _wmi_query(*keys): + +def _wmi_query(table, *keys): + global _wmi + if not _wmi: raise OSError("not supported") -else: - def _wmi_query(table, *keys): - table = { - "OS": "Win32_OperatingSystem", - "CPU": "Win32_Processor", - }[table] + table = { + "OS": "Win32_OperatingSystem", + "CPU": "Win32_Processor", + }[table] + try: data = _wmi.exec_query("SELECT {} FROM {}".format( ",".join(keys), table, )).split("\0") - split_data = (i.partition("=") for i in data) - dict_data = {i[0]: i[2] for i in split_data} - return (dict_data[k] for k in keys) + except OSError: + _wmi = None + raise OSError("not supported") + split_data = (i.partition("=") for i in data) + dict_data = {i[0]: i[2] for i in split_data} + return (dict_data[k] for k in keys) _WIN32_CLIENT_RELEASES = [ diff --git a/Misc/NEWS.d/next/Windows/2023-12-03-19-22-37.gh-issue-112278.FiloCE.rst b/Misc/NEWS.d/next/Windows/2023-12-03-19-22-37.gh-issue-112278.FiloCE.rst new file mode 100644 index 00000000000000..0350d105d97375 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-03-19-22-37.gh-issue-112278.FiloCE.rst @@ -0,0 +1,2 @@ +Reduce the time cost for some functions in :mod:`platform` on Windows if +current user has no permission to the WMI. diff --git a/PC/_wmimodule.cpp b/PC/_wmimodule.cpp index fdf09ec6ec6f63..215350acfb0d8e 100644 --- a/PC/_wmimodule.cpp +++ b/PC/_wmimodule.cpp @@ -44,6 +44,7 @@ struct _query_data { LPCWSTR query; HANDLE writePipe; HANDLE readPipe; + HANDLE connectEvent; }; @@ -86,6 +87,9 @@ _query_thread(LPVOID param) NULL, NULL, 0, NULL, 0, 0, &services ); } + if (!SetEvent(data->connectEvent)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } if (SUCCEEDED(hr)) { hr = CoSetProxyBlanket( services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, @@ -231,7 +235,8 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) Py_BEGIN_ALLOW_THREADS - if (!CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) { + data.connectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!data.connectEvent || !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) { err = GetLastError(); } else { hThread = CreateThread(NULL, 0, _query_thread, (LPVOID*)&data, 0, NULL); @@ -243,6 +248,21 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) } } + // gh-112278: If current user doesn't have permission to query the WMI, the + // function IWbemLocator::ConnectServer will hang for 5 seconds, and there + // is no way to specify the timeout. So we use an Event object to simulate + // a timeout. + switch (WaitForSingleObject(data.connectEvent, 100)) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + err = WAIT_TIMEOUT; + break; + default: + err = GetLastError(); + break; + } + while (!err) { if (ReadFile( data.readPipe, @@ -265,7 +285,7 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) } // Allow the thread some time to clean up - switch (WaitForSingleObject(hThread, 1000)) { + switch (WaitForSingleObject(hThread, 100)) { case WAIT_OBJECT_0: // Thread ended cleanly if (!GetExitCodeThread(hThread, (LPDWORD)&err)) { @@ -286,6 +306,7 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) } CloseHandle(hThread); + CloseHandle(data.connectEvent); hThread = NULL; Py_END_ALLOW_THREADS From bf0beae6a05f3266606a21e22a4d803abbb8d731 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 7 Dec 2023 19:41:27 +0100 Subject: [PATCH 150/442] gh-110017: Disable test_signal.test_stress_modifying_handlers on macOS (#112834) Test test_stress_modifying_handlers in test_signal can crash the interpreter due to a bug in macOS. Filed as FB13453490 with Apple. --- Lib/test/test_signal.py | 1 + .../next/macOS/2023-12-07-15-53-16.gh-issue-110017.UMYzMR.rst | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-07-15-53-16.gh-issue-110017.UMYzMR.rst diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index f2ae28c38dd72d..acb7e9d4c6074d 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1318,6 +1318,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.skipUnless(hasattr(signal, "SIGUSR1"), "test needs SIGUSR1") @threading_helper.requires_working_threading() diff --git a/Misc/NEWS.d/next/macOS/2023-12-07-15-53-16.gh-issue-110017.UMYzMR.rst b/Misc/NEWS.d/next/macOS/2023-12-07-15-53-16.gh-issue-110017.UMYzMR.rst new file mode 100644 index 00000000000000..eab1746f1ae3f7 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-07-15-53-16.gh-issue-110017.UMYzMR.rst @@ -0,0 +1,2 @@ +Disable a signal handling stress test on macOS due to a bug in macOS +(FB13453490). From db460735af7503984d1b7d878069722db44b11e8 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 7 Dec 2023 14:11:45 -0500 Subject: [PATCH 151/442] gh-112538: Add internal-only _PyThreadStateImpl "wrapper" for PyThreadState (gh-112560) Every PyThreadState instance is now actually a _PyThreadStateImpl. It is safe to cast from `PyThreadState*` to `_PyThreadStateImpl*` and back. The _PyThreadStateImpl will contain fields that we do not want to expose in the public C API. --- Include/internal/pycore_interp.h | 5 +++-- Include/internal/pycore_runtime_init.h | 7 ++++++- Include/internal/pycore_tstate.h | 26 ++++++++++++++++++++++++ Makefile.pre.in | 1 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 +++ Python/pystate.c | 28 +++++++++++++------------- 7 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 Include/internal/pycore_tstate.h diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 498db8becf114c..2a683196eeced3 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -29,6 +29,7 @@ extern "C" { #include "pycore_list.h" // struct _Py_list_state #include "pycore_object_state.h" // struct _py_object_state #include "pycore_obmalloc.h" // struct _obmalloc_state +#include "pycore_tstate.h" // _PyThreadStateImpl #include "pycore_tuple.h" // struct _Py_tuple_state #include "pycore_typeobject.h" // struct types_state #include "pycore_unicodeobject.h" // struct _Py_unicode_state @@ -210,8 +211,8 @@ struct _is { struct _Py_interp_cached_objects cached_objects; struct _Py_interp_static_objects static_objects; - /* the initial PyInterpreterState.threads.head */ - PyThreadState _initial_thread; + /* the initial PyInterpreterState.threads.head */ + _PyThreadStateImpl _initial_thread; Py_ssize_t _interactive_src_count; }; diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index fa5d8114abf0d7..d324a94278839c 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -186,7 +186,12 @@ extern PyTypeObject _PyExc_MemoryError; }, \ }, \ }, \ - ._initial_thread = _PyThreadState_INIT, \ + ._initial_thread = _PyThreadStateImpl_INIT, \ + } + +#define _PyThreadStateImpl_INIT \ + { \ + .base = _PyThreadState_INIT, \ } #define _PyThreadState_INIT \ diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h new file mode 100644 index 00000000000000..17f3e865641773 --- /dev/null +++ b/Include/internal/pycore_tstate.h @@ -0,0 +1,26 @@ +#ifndef Py_INTERNAL_TSTATE_H +#define Py_INTERNAL_TSTATE_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + + +// Every PyThreadState is actually allocated as a _PyThreadStateImpl. The +// PyThreadState fields are exposed as part of the C API, although most fields +// are intended to be private. The _PyThreadStateImpl fields not exposed. +typedef struct _PyThreadStateImpl { + // semi-public fields are in PyThreadState. + PyThreadState base; + + // TODO: add private fields here +} _PyThreadStateImpl; + + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_TSTATE_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in index db85b11ef01b2c..f57894a2118e74 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1874,6 +1874,7 @@ PYTHON_HEADERS= \ $(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 \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index be1b98dba02fc5..278f1f5622543c 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -285,6 +285,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 4f0da8f35998b7..c9b34c64fbf75f 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -780,6 +780,9 @@ Include\internal + + Include\internal + Include\internal diff --git a/Python/pystate.c b/Python/pystate.c index 6196b15da0117a..c75991667869cf 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1353,20 +1353,19 @@ allocate_chunk(int size_in_bytes, _PyStackChunk* previous) return res; } -static PyThreadState * +static _PyThreadStateImpl * alloc_threadstate(void) { - return PyMem_RawCalloc(1, sizeof(PyThreadState)); + return PyMem_RawCalloc(1, sizeof(_PyThreadStateImpl)); } static void -free_threadstate(PyThreadState *tstate) +free_threadstate(_PyThreadStateImpl *tstate) { // The initial thread state of the interpreter is allocated // as part of the interpreter state so should not be freed. - if (tstate == &tstate->interp->_initial_thread) { + if (tstate == &tstate->base.interp->_initial_thread) { // Restore to _PyThreadState_INIT. - tstate = &tstate->interp->_initial_thread; memcpy(tstate, &initial._main_interpreter._initial_thread, sizeof(*tstate)); @@ -1385,9 +1384,10 @@ free_threadstate(PyThreadState *tstate) */ static void -init_threadstate(PyThreadState *tstate, +init_threadstate(_PyThreadStateImpl *_tstate, PyInterpreterState *interp, uint64_t id, int whence) { + PyThreadState *tstate = (PyThreadState *)_tstate; if (tstate->_status.initialized) { Py_FatalError("thread state already initialized"); } @@ -1444,13 +1444,13 @@ add_threadstate(PyInterpreterState *interp, PyThreadState *tstate, static PyThreadState * new_threadstate(PyInterpreterState *interp, int whence) { - PyThreadState *tstate; + _PyThreadStateImpl *tstate; _PyRuntimeState *runtime = interp->runtime; // We don't need to allocate a thread state for the main interpreter // (the common case), but doing it later for the other case revealed a // reentrancy problem (deadlock). So for now we always allocate before // taking the interpreters lock. See GH-96071. - PyThreadState *new_tstate = alloc_threadstate(); + _PyThreadStateImpl *new_tstate = alloc_threadstate(); int used_newtstate; if (new_tstate == NULL) { return NULL; @@ -1482,14 +1482,14 @@ new_threadstate(PyInterpreterState *interp, int whence) } init_threadstate(tstate, interp, id, whence); - add_threadstate(interp, tstate, old_head); + add_threadstate(interp, (PyThreadState *)tstate, old_head); HEAD_UNLOCK(runtime); if (!used_newtstate) { // Must be called with lock unlocked to avoid re-entrancy deadlock. PyMem_RawFree(new_tstate); } - return tstate; + return (PyThreadState *)tstate; } PyThreadState * @@ -1678,7 +1678,7 @@ zapthreads(PyInterpreterState *interp) while ((tstate = interp->threads.head) != NULL) { tstate_verify_not_active(tstate); tstate_delete_common(tstate); - free_threadstate(tstate); + free_threadstate((_PyThreadStateImpl *)tstate); } } @@ -1689,7 +1689,7 @@ PyThreadState_Delete(PyThreadState *tstate) _Py_EnsureTstateNotNULL(tstate); tstate_verify_not_active(tstate); tstate_delete_common(tstate); - free_threadstate(tstate); + free_threadstate((_PyThreadStateImpl *)tstate); } @@ -1701,7 +1701,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate) tstate_delete_common(tstate); current_fast_clear(tstate->interp->runtime); _PyEval_ReleaseLock(tstate->interp, NULL); - free_threadstate(tstate); + free_threadstate((_PyThreadStateImpl *)tstate); } void @@ -1751,7 +1751,7 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate) for (p = list; p; p = next) { next = p->next; PyThreadState_Clear(p); - free_threadstate(p); + free_threadstate((_PyThreadStateImpl *)p); } } From cf6110ba1337cb67e5867d86e7c0e8d923a5bc8d Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 7 Dec 2023 14:33:40 -0500 Subject: [PATCH 152/442] gh-111924: Use PyMutex for Runtime-global Locks. (gh-112207) This replaces some usages of PyThread_type_lock with PyMutex, which does not require memory allocation to initialize. This simplifies some of the runtime initialization and is also one step towards avoiding changing the default raw memory allocator during initialize/finalization, which can be non-thread-safe in some circumstances. --- Include/internal/pycore_atexit.h | 5 +- Include/internal/pycore_ceval.h | 3 +- Include/internal/pycore_ceval_state.h | 3 +- Include/internal/pycore_crossinterp.h | 3 +- Include/internal/pycore_import.h | 3 +- Include/internal/pycore_lock.h | 17 ++++ Include/internal/pycore_pymem.h | 5 +- Include/internal/pycore_pystate.h | 4 +- Include/internal/pycore_runtime.h | 4 +- Include/internal/pycore_unicodeobject.h | 3 +- Objects/obmalloc.c | 61 ++++-------- Objects/unicodeobject.c | 4 +- Python/ceval_gil.c | 37 ++------ Python/crossinterp.c | 29 +----- Python/import.c | 10 +- Python/pylifecycle.c | 14 +-- Python/pystate.c | 121 +++--------------------- Python/sysmodule.c | 12 +-- 18 files changed, 97 insertions(+), 241 deletions(-) diff --git a/Include/internal/pycore_atexit.h b/Include/internal/pycore_atexit.h index 3966df70e2616f..4dcda8f517c787 100644 --- a/Include/internal/pycore_atexit.h +++ b/Include/internal/pycore_atexit.h @@ -1,5 +1,8 @@ #ifndef Py_INTERNAL_ATEXIT_H #define Py_INTERNAL_ATEXIT_H + +#include "pycore_lock.h" // PyMutex + #ifdef __cplusplus extern "C" { #endif @@ -15,7 +18,7 @@ extern "C" { typedef void (*atexit_callbackfunc)(void); struct _atexit_runtime_state { - PyThread_type_lock mutex; + PyMutex mutex; #define NEXITFUNCS 32 atexit_callbackfunc callbacks[NEXITFUNCS]; int ncallbacks; diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 3f7ac922bdf451..64fb4034669e19 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -41,8 +41,7 @@ PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState *); #endif extern void _Py_FinishPendingCalls(PyThreadState *tstate); -extern void _PyEval_InitState(PyInterpreterState *, PyThread_type_lock); -extern void _PyEval_FiniState(struct _ceval_state *ceval); +extern void _PyEval_InitState(PyInterpreterState *); extern void _PyEval_SignalReceived(PyInterpreterState *interp); // bitwise flags: diff --git a/Include/internal/pycore_ceval_state.h b/Include/internal/pycore_ceval_state.h index 072bbcda0c3c82..28738980eb49be 100644 --- a/Include/internal/pycore_ceval_state.h +++ b/Include/internal/pycore_ceval_state.h @@ -8,6 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_lock.h" // PyMutex #include "pycore_gil.h" // struct _gil_runtime_state @@ -15,7 +16,7 @@ typedef int (*_Py_pending_call_func)(void *); struct _pending_calls { int busy; - PyThread_type_lock lock; + PyMutex mutex; /* Request for running pending calls. */ int32_t calls_to_do; #define NPENDINGCALLS 32 diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index ec9dac96292f35..2e6d09a49f95d3 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -8,6 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_lock.h" // PyMutex #include "pycore_pyerrors.h" @@ -128,7 +129,7 @@ struct _xidregitem { struct _xidregistry { int global; /* builtin types or heap types */ int initialized; - PyThread_type_lock mutex; + PyMutex mutex; struct _xidregitem *head; }; diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 117e46bb86285d..c84f87a831bf38 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -9,6 +9,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_lock.h" // PyMutex #include "pycore_hashtable.h" // _Py_hashtable_t #include "pycore_time.h" // _PyTime_t @@ -47,7 +48,7 @@ struct _import_runtime_state { Py_ssize_t last_module_index; struct { /* A lock to guard the cache. */ - PyThread_type_lock mutex; + PyMutex mutex; /* The actual cache of (filename, name, PyModuleDef) for modules. Only legacy (single-phase init) extension modules are added and only if they support multiple initialization (m_size >- 0) diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index f135cbbc3754fb..03ad1c9fd584b5 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -92,6 +92,13 @@ PyMutex_IsLocked(PyMutex *m) return (_Py_atomic_load_uint8(&m->v) & _Py_LOCKED) != 0; } +// Re-initializes the mutex after a fork to the unlocked state. +static inline void +_PyMutex_at_fork_reinit(PyMutex *m) +{ + memset(m, 0, sizeof(*m)); +} + typedef enum _PyLockFlags { // Do not detach/release the GIL when waiting on the lock. _Py_LOCK_DONT_DETACH = 0, @@ -108,6 +115,16 @@ typedef enum _PyLockFlags { extern PyLockStatus _PyMutex_LockTimed(PyMutex *m, _PyTime_t timeout_ns, _PyLockFlags flags); +// Lock a mutex with aditional options. See _PyLockFlags for details. +static inline void +PyMutex_LockFlags(PyMutex *m, _PyLockFlags flags) +{ + uint8_t expected = _Py_UNLOCKED; + if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) { + _PyMutex_LockTimed(m, -1, flags); + } +} + // Unlock a mutex, returns 0 if the mutex is not locked (used for improved // error messages). extern int _PyMutex_TryUnlock(PyMutex *m); diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 6b5113714dbeb2..8631ca34a5e616 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -1,5 +1,8 @@ #ifndef Py_INTERNAL_PYMEM_H #define Py_INTERNAL_PYMEM_H + +#include "pycore_lock.h" // PyMutex + #ifdef __cplusplus extern "C" { #endif @@ -30,7 +33,7 @@ typedef struct { } debug_alloc_api_t; struct _pymem_allocators { - PyThread_type_lock mutex; + PyMutex mutex; struct { PyMemAllocatorEx raw; PyMemAllocatorEx mem; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 7fa952e371d7b4..c031a38cd6bfa3 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -220,9 +220,9 @@ PyAPI_FUNC(int) _PyState_AddModule( extern int _PyOS_InterruptOccurred(PyThreadState *tstate); #define HEAD_LOCK(runtime) \ - PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK) + PyMutex_LockFlags(&(runtime)->interpreters.mutex, _Py_LOCK_DONT_DETACH) #define HEAD_UNLOCK(runtime) \ - PyThread_release_lock((runtime)->interpreters.mutex) + PyMutex_Unlock(&(runtime)->interpreters.mutex) // Get the configuration of the current interpreter. // The caller must hold the GIL. diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 36743723f8afd8..e3348296ea61b7 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -173,7 +173,7 @@ typedef struct pyruntimestate { unsigned long _finalizing_id; struct pyinterpreters { - PyThread_type_lock mutex; + PyMutex mutex; /* The linked list of interpreters, newest first. */ PyInterpreterState *head; /* The runtime's initial interpreter, which has a special role @@ -234,7 +234,7 @@ typedef struct pyruntimestate { Py_OpenCodeHookFunction open_code_hook; void *open_code_userdata; struct { - PyThread_type_lock mutex; + PyMutex mutex; _Py_AuditHookEntry *head; } audit_hooks; diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index a0d00af92e0f5d..7ee540154b23d8 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -8,6 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_lock.h" // PyMutex #include "pycore_fileutils.h" // _Py_error_handler #include "pycore_identifier.h" // _Py_Identifier #include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI @@ -277,7 +278,7 @@ extern PyTypeObject _PyUnicodeASCIIIter_Type; /* --- Other API ---------------------------------------------------------- */ struct _Py_unicode_runtime_ids { - PyThread_type_lock lock; + PyMutex mutex; // next_index value must be preserved when Py_Initialize()/Py_Finalize() // is called multiple times: see _PyUnicode_FromId() implementation. Py_ssize_t next_index; diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 2761c774209786..b737c030957564 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -329,13 +329,9 @@ int _PyMem_SetDefaultAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *old_alloc) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must be initializing. */ - return set_default_allocator_unlocked(domain, pydebug, old_alloc); - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); int res = set_default_allocator_unlocked(domain, pydebug, old_alloc); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); return res; } @@ -467,9 +463,9 @@ set_up_allocators_unlocked(PyMemAllocatorName allocator) int _PyMem_SetupAllocators(PyMemAllocatorName allocator) { - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); int res = set_up_allocators_unlocked(allocator); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); return res; } @@ -554,9 +550,9 @@ get_current_allocator_name_unlocked(void) const char* _PyMem_GetCurrentAllocatorName(void) { - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); const char *name = get_current_allocator_name_unlocked(); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); return name; } @@ -653,14 +649,9 @@ set_up_debug_hooks_unlocked(void) void PyMem_SetupDebugHooks(void) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must not be completely initialized yet. */ - set_up_debug_hooks_unlocked(); - return; - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); set_up_debug_hooks_unlocked(); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); } static void @@ -696,53 +687,33 @@ set_allocator_unlocked(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator) void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must not be completely initialized yet. */ - get_allocator_unlocked(domain, allocator); - return; - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); get_allocator_unlocked(domain, allocator); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); } void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must not be completely initialized yet. */ - set_allocator_unlocked(domain, allocator); - return; - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); set_allocator_unlocked(domain, allocator); - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); } void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must not be completely initialized yet. */ - *allocator = _PyObject_Arena; - return; - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); *allocator = _PyObject_Arena; - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); } void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator) { - if (ALLOCATORS_MUTEX == NULL) { - /* The runtime must not be completely initialized yet. */ - _PyObject_Arena = *allocator; - return; - } - PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK); + PyMutex_Lock(&ALLOCATORS_MUTEX); _PyObject_Arena = *allocator; - PyThread_release_lock(ALLOCATORS_MUTEX); + PyMutex_Unlock(&ALLOCATORS_MUTEX); } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 10022e23c04abf..836e14fd5d5dea 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1904,7 +1904,7 @@ _PyUnicode_FromId(_Py_Identifier *id) if (index < 0) { struct _Py_unicode_runtime_ids *rt_ids = &interp->runtime->unicode_state.ids; - PyThread_acquire_lock(rt_ids->lock, WAIT_LOCK); + PyMutex_Lock(&rt_ids->mutex); // Check again to detect concurrent access. Another thread can have // initialized the index while this thread waited for the lock. index = _Py_atomic_load_ssize(&id->index); @@ -1914,7 +1914,7 @@ _PyUnicode_FromId(_Py_Identifier *id) rt_ids->next_index++; _Py_atomic_store_ssize(&id->index, index); } - PyThread_release_lock(rt_ids->lock); + PyMutex_Unlock(&rt_ids->mutex); } assert(index >= 0); diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 92c4b2fee9f863..636e4db898f2d9 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -589,9 +589,7 @@ _PyEval_ReInitThreads(PyThreadState *tstate) take_gil(tstate); struct _pending_calls *pending = &tstate->interp->ceval.pending; - if (_PyThread_at_fork_reinit(&pending->lock) < 0) { - return _PyStatus_ERR("Can't reinitialize pending calls lock"); - } + _PyMutex_at_fork_reinit(&pending->mutex); /* Destroy all threads except the current one */ _PyThreadState_DeleteExcept(tstate); @@ -720,13 +718,10 @@ _PyEval_AddPendingCall(PyInterpreterState *interp, assert(_Py_IsMainInterpreter(interp)); pending = &_PyRuntime.ceval.pending_mainthread; } - /* Ensure that _PyEval_InitState() was called - and that _PyEval_FiniState() is not called yet. */ - assert(pending->lock != NULL); - PyThread_acquire_lock(pending->lock, WAIT_LOCK); + PyMutex_Lock(&pending->mutex); int result = _push_pending_call(pending, func, arg, flags); - PyThread_release_lock(pending->lock); + PyMutex_Unlock(&pending->mutex); /* signal main loop */ SIGNAL_PENDING_CALLS(interp); @@ -768,9 +763,9 @@ _make_pending_calls(struct _pending_calls *pending) int flags = 0; /* pop one item off the queue while holding the lock */ - PyThread_acquire_lock(pending->lock, WAIT_LOCK); + PyMutex_Lock(&pending->mutex); _pop_pending_call(pending, &func, &arg, &flags); - PyThread_release_lock(pending->lock); + PyMutex_Unlock(&pending->mutex); /* having released the lock, perform the callback */ if (func == NULL) { @@ -795,7 +790,7 @@ make_pending_calls(PyInterpreterState *interp) /* Only one thread (per interpreter) may run the pending calls at once. In the same way, we don't do recursive pending calls. */ - PyThread_acquire_lock(pending->lock, WAIT_LOCK); + PyMutex_Lock(&pending->mutex); if (pending->busy) { /* A pending call was added after another thread was already handling the pending calls (and had already "unsignaled"). @@ -807,11 +802,11 @@ make_pending_calls(PyInterpreterState *interp) care of any remaining pending calls. Until then, though, all the interpreter's threads will be tripping the eval breaker every time it's checked. */ - PyThread_release_lock(pending->lock); + PyMutex_Unlock(&pending->mutex); return 0; } pending->busy = 1; - PyThread_release_lock(pending->lock); + PyMutex_Unlock(&pending->mutex); /* unsignal before starting to call callbacks, so that any callback added in-between re-signals */ @@ -892,23 +887,9 @@ Py_MakePendingCalls(void) } void -_PyEval_InitState(PyInterpreterState *interp, PyThread_type_lock pending_lock) +_PyEval_InitState(PyInterpreterState *interp) { _gil_initialize(&interp->_gil); - - struct _pending_calls *pending = &interp->ceval.pending; - assert(pending->lock == NULL); - pending->lock = pending_lock; -} - -void -_PyEval_FiniState(struct _ceval_state *ceval) -{ - struct _pending_calls *pending = &ceval->pending; - if (pending->lock != NULL) { - PyThread_free_lock(pending->lock); - pending->lock = NULL; - } } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 21b96ef05ed799..f74fee38648266 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -456,16 +456,17 @@ _xidregistry_clear(struct _xidregistry *xidregistry) static void _xidregistry_lock(struct _xidregistry *registry) { - if (registry->mutex != NULL) { - PyThread_acquire_lock(registry->mutex, WAIT_LOCK); + if (registry->global) { + PyMutex_Lock(®istry->mutex); } + // else: Within an interpreter we rely on the GIL instead of a separate lock. } static void _xidregistry_unlock(struct _xidregistry *registry) { - if (registry->mutex != NULL) { - PyThread_release_lock(registry->mutex); + if (registry->global) { + PyMutex_Unlock(®istry->mutex); } } @@ -874,19 +875,10 @@ _xidregistry_init(struct _xidregistry *registry) registry->initialized = 1; if (registry->global) { - // We manage the mutex lifecycle in pystate.c. - assert(registry->mutex != NULL); - // Registering the builtins is cheap so we don't bother doing it lazily. assert(registry->head == NULL); _register_builtins_for_crossinterpreter_data(registry); } - else { - // Within an interpreter we rely on the GIL instead of a separate lock. - assert(registry->mutex == NULL); - - // There's nothing else to initialize. - } } static void @@ -898,17 +890,6 @@ _xidregistry_fini(struct _xidregistry *registry) registry->initialized = 0; _xidregistry_clear(registry); - - if (registry->global) { - // We manage the mutex lifecycle in pystate.c. - assert(registry->mutex != NULL); - } - else { - // There's nothing else to finalize. - - // Within an interpreter we rely on the GIL instead of a separate lock. - assert(registry->mutex == NULL); - } } diff --git a/Python/import.c b/Python/import.c index ef81f46a4d65c1..2dd95d8364a0be 100644 --- a/Python/import.c +++ b/Python/import.c @@ -418,11 +418,7 @@ remove_module(PyThreadState *tstate, PyObject *name) Py_ssize_t _PyImport_GetNextModuleIndex(void) { - PyThread_acquire_lock(EXTENSIONS.mutex, WAIT_LOCK); - LAST_MODULE_INDEX++; - Py_ssize_t index = LAST_MODULE_INDEX; - PyThread_release_lock(EXTENSIONS.mutex); - return index; + return _Py_atomic_add_ssize(&LAST_MODULE_INDEX, 1) + 1; } static const char * @@ -882,13 +878,13 @@ gets even messier. static inline void extensions_lock_acquire(void) { - PyThread_acquire_lock(_PyRuntime.imports.extensions.mutex, WAIT_LOCK); + PyMutex_Lock(&_PyRuntime.imports.extensions.mutex); } static inline void extensions_lock_release(void) { - PyThread_release_lock(_PyRuntime.imports.extensions.mutex); + PyMutex_Unlock(&_PyRuntime.imports.extensions.mutex); } /* Magic for extension modules (built-in as well as dynamically diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 20bfe1a0b75b29..45a119fcca7f2c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -3056,13 +3056,13 @@ wait_for_thread_shutdown(PyThreadState *tstate) int Py_AtExit(void (*func)(void)) { struct _atexit_runtime_state *state = &_PyRuntime.atexit; - PyThread_acquire_lock(state->mutex, WAIT_LOCK); + PyMutex_Lock(&state->mutex); if (state->ncallbacks >= NEXITFUNCS) { - PyThread_release_lock(state->mutex); + PyMutex_Unlock(&state->mutex); return -1; } state->callbacks[state->ncallbacks++] = func; - PyThread_release_lock(state->mutex); + PyMutex_Unlock(&state->mutex); return 0; } @@ -3072,18 +3072,18 @@ call_ll_exitfuncs(_PyRuntimeState *runtime) atexit_callbackfunc exitfunc; struct _atexit_runtime_state *state = &runtime->atexit; - PyThread_acquire_lock(state->mutex, WAIT_LOCK); + PyMutex_Lock(&state->mutex); while (state->ncallbacks > 0) { /* pop last function from the list */ state->ncallbacks--; exitfunc = state->callbacks[state->ncallbacks]; state->callbacks[state->ncallbacks] = NULL; - PyThread_release_lock(state->mutex); + PyMutex_Unlock(&state->mutex); exitfunc(); - PyThread_acquire_lock(state->mutex, WAIT_LOCK); + PyMutex_Lock(&state->mutex); } - PyThread_release_lock(state->mutex); + PyMutex_Unlock(&state->mutex); fflush(stdout); fflush(stderr); diff --git a/Python/pystate.c b/Python/pystate.c index c75991667869cf..1a7c0c968504d1 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -379,49 +379,23 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS static const _PyRuntimeState initial = _PyRuntimeState_INIT(_PyRuntime); _Py_COMP_DIAG_POP -#define NUMLOCKS 8 #define LOCKS_INIT(runtime) \ { \ &(runtime)->interpreters.mutex, \ &(runtime)->xi.registry.mutex, \ - &(runtime)->unicode_state.ids.lock, \ + &(runtime)->unicode_state.ids.mutex, \ &(runtime)->imports.extensions.mutex, \ - &(runtime)->ceval.pending_mainthread.lock, \ + &(runtime)->ceval.pending_mainthread.mutex, \ &(runtime)->atexit.mutex, \ &(runtime)->audit_hooks.mutex, \ &(runtime)->allocators.mutex, \ } -static int -alloc_for_runtime(PyThread_type_lock locks[NUMLOCKS]) -{ - /* Force default allocator, since _PyRuntimeState_Fini() must - use the same allocator than this function. */ - PyMemAllocatorEx old_alloc; - _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - for (int i = 0; i < NUMLOCKS; i++) { - PyThread_type_lock lock = PyThread_allocate_lock(); - if (lock == NULL) { - for (int j = 0; j < i; j++) { - PyThread_free_lock(locks[j]); - locks[j] = NULL; - } - break; - } - locks[i] = lock; - } - - PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - return 0; -} - static void init_runtime(_PyRuntimeState *runtime, void *open_code_hook, void *open_code_userdata, _Py_AuditHookEntry *audit_hook_head, - Py_ssize_t unicode_next_index, - PyThread_type_lock locks[NUMLOCKS]) + Py_ssize_t unicode_next_index) { assert(!runtime->preinitializing); assert(!runtime->preinitialized); @@ -435,12 +409,6 @@ init_runtime(_PyRuntimeState *runtime, PyPreConfig_InitPythonConfig(&runtime->preconfig); - PyThread_type_lock *lockptrs[NUMLOCKS] = LOCKS_INIT(runtime); - for (int i = 0; i < NUMLOCKS; i++) { - assert(locks[i] != NULL); - *lockptrs[i] = locks[i]; - } - // Set it to the ID of the main thread of the main interpreter. runtime->main_thread = PyThread_get_thread_ident(); @@ -466,11 +434,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) // is called multiple times. Py_ssize_t unicode_next_index = runtime->unicode_state.ids.next_index; - PyThread_type_lock locks[NUMLOCKS]; - if (alloc_for_runtime(locks) != 0) { - return _PyStatus_NO_MEMORY(); - } - if (runtime->_initialized) { // Py_Initialize() must be running again. // Reset to _PyRuntimeState_INIT. @@ -489,7 +452,7 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) } init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head, - unicode_next_index, locks); + unicode_next_index); return _PyStatus_OK(); } @@ -509,23 +472,6 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime) if (PyThread_tss_is_created(&runtime->trashTSSkey)) { PyThread_tss_delete(&runtime->trashTSSkey); } - - /* Force the allocator used by _PyRuntimeState_Init(). */ - PyMemAllocatorEx old_alloc; - _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); -#define FREE_LOCK(LOCK) \ - if (LOCK != NULL) { \ - PyThread_free_lock(LOCK); \ - LOCK = NULL; \ - } - - PyThread_type_lock *lockptrs[NUMLOCKS] = LOCKS_INIT(runtime); - for (int i = 0; i < NUMLOCKS; i++) { - FREE_LOCK(*lockptrs[i]); - } - -#undef FREE_LOCK - PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); } #ifdef HAVE_FORK @@ -537,28 +483,19 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime) // This was initially set in _PyRuntimeState_Init(). runtime->main_thread = PyThread_get_thread_ident(); - /* Force default allocator, since _PyRuntimeState_Fini() must - use the same allocator than this function. */ - PyMemAllocatorEx old_alloc; - _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - PyThread_type_lock *lockptrs[NUMLOCKS] = LOCKS_INIT(runtime); - int reinit_err = 0; - for (int i = 0; i < NUMLOCKS; i++) { - reinit_err += _PyThread_at_fork_reinit(lockptrs[i]); - } - - PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - // Clears the parking lot. Any waiting threads are dead. This must be // called before releasing any locks that use the parking lot. _PyParkingLot_AfterFork(); + // Re-initialize global locks + PyMutex *locks[] = LOCKS_INIT(runtime); + for (size_t i = 0; i < Py_ARRAY_LENGTH(locks); i++) { + _PyMutex_at_fork_reinit(locks[i]); + } + /* bpo-42540: id_mutex is freed by _PyInterpreterState_Delete, which does * not force the default allocator. */ - reinit_err += _PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex); - - if (reinit_err < 0) { + if (_PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex) < 0) { return _PyStatus_ERR("Failed to reinitialize runtime locks"); } @@ -594,24 +531,6 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime) { struct pyinterpreters *interpreters = &runtime->interpreters; interpreters->next_id = 0; - - /* Py_Finalize() calls _PyRuntimeState_Fini() which clears the mutex. - Create a new mutex if needed. */ - if (interpreters->mutex == NULL) { - /* Force default allocator, since _PyRuntimeState_Fini() must - use the same allocator than this function. */ - PyMemAllocatorEx old_alloc; - _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - interpreters->mutex = PyThread_allocate_lock(); - - PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - if (interpreters->mutex == NULL) { - return _PyStatus_ERR("Can't initialize threads for interpreter"); - } - } - return _PyStatus_OK(); } @@ -654,8 +573,7 @@ free_interpreter(PyInterpreterState *interp) static PyStatus init_interpreter(PyInterpreterState *interp, _PyRuntimeState *runtime, int64_t id, - PyInterpreterState *next, - PyThread_type_lock pending_lock) + PyInterpreterState *next) { if (interp->_initialized) { return _PyStatus_ERR("interpreter already initialized"); @@ -684,7 +602,7 @@ init_interpreter(PyInterpreterState *interp, return status; } - _PyEval_InitState(interp, pending_lock); + _PyEval_InitState(interp); _PyGC_InitState(&interp->gc); PyConfig_InitPythonConfig(&interp->config); _PyType_InitCache(interp); @@ -730,11 +648,6 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp) } } - PyThread_type_lock pending_lock = PyThread_allocate_lock(); - if (pending_lock == NULL) { - return _PyStatus_NO_MEMORY(); - } - /* We completely serialize creation of multiple interpreters, since it simplifies things here and blocking concurrent calls isn't a problem. Regardless, we must fully block subinterpreter creation until @@ -781,11 +694,10 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp) interpreters->head = interp; status = init_interpreter(interp, runtime, - id, old_head, pending_lock); + id, old_head); if (_PyStatus_EXCEPTION(status)) { goto error; } - pending_lock = NULL; HEAD_UNLOCK(runtime); @@ -796,9 +708,6 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp) error: HEAD_UNLOCK(runtime); - if (pending_lock != NULL) { - PyThread_free_lock(pending_lock); - } if (interp != NULL) { free_interpreter(interp); } @@ -1003,8 +912,6 @@ PyInterpreterState_Delete(PyInterpreterState *interp) zapthreads(interp); - _PyEval_FiniState(&interp->ceval); - // XXX These two calls should be done at the end of clear_interpreter(), // but currently some objects get decref'ed after that. #ifdef Py_REF_DEBUG diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 46878c7c9687f5..57dc4a1226ce75 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -451,15 +451,9 @@ PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData) e->hookCFunction = (Py_AuditHookFunction)hook; e->userData = userData; - if (runtime->audit_hooks.mutex == NULL) { - /* The runtime must not be initialized yet. */ - add_audit_hook_entry_unlocked(runtime, e); - } - else { - PyThread_acquire_lock(runtime->audit_hooks.mutex, WAIT_LOCK); - add_audit_hook_entry_unlocked(runtime, e); - PyThread_release_lock(runtime->audit_hooks.mutex); - } + PyMutex_Lock(&runtime->audit_hooks.mutex); + add_audit_hook_entry_unlocked(runtime, e); + PyMutex_Unlock(&runtime->audit_hooks.mutex); return 0; } From 64d8b4c7099a6097a7f7340c575679c5622fcd5c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 7 Dec 2023 13:22:15 -0700 Subject: [PATCH 153/442] gh-112826: Add a "What's New" Entry About _thread._is_main_interpreter (gh-112853) As of gh-112661, the threading module expects the _thread module to have a _is_main_interpreter(), which is used in the internal threading._shutdown(). This change causes a problem for anyone that replaces the _thread module with a custom one (only if they don't provide _is_main_interpreter()). They need to be sure to add it for 3.13+, thus this PR is adding a note in "What's New". This also forward-ports the "What's New" entry from 3.12 (gh-112850). Note that we do not also forward-port the fix in that PR. The fix is there only due to a regression from 3.12.0. There is no regression in 3.13+. --- Doc/whatsnew/3.12.rst | 9 +++++++++ Doc/whatsnew/3.13.rst | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 07d22a4a5fb773..8551b35438e2c3 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1895,6 +1895,15 @@ Changes in the Python API * Mixing tabs and spaces as indentation in the same file is not supported anymore and will raise a :exc:`TabError`. +* 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 + main interpreter. + + Any library or application that provides a custom ``_thread`` module + should provide ``_is_main_interpreter()``. + (See :gh:`112826`.) + Build Changes ============= diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 9adf7a3893bd70..4401deb0768c11 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1082,6 +1082,16 @@ Changes in the Python API retrieve a username, instead of :exc:`ImportError` on non-Unix platforms or :exc:`KeyError` on Unix platforms where the password database is empty. +* 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 + main interpreter. + + Any library or application that provides a custom ``_thread`` module + must provide ``_is_main_interpreter()``, just like the module's + other "private" attributes. + (See :gh:`112826`.) + Build Changes ============= From 2c3906bc4b7ee62bf9d122a6fdd98b6ae330643f Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 7 Dec 2023 20:57:30 +0000 Subject: [PATCH 154/442] gh-101100: Silence Sphinx warnings when `ntpath` or `posixpath` are referenced (#112833) --- Doc/conf.py | 4 ++++ Doc/library/pathlib.rst | 2 +- Doc/tools/.nitignore | 1 - Doc/whatsnew/3.7.rst | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 323d443588ceb6..f2d36fdc70430c 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -166,6 +166,10 @@ # Deprecated function that was never documented: ('py:func', 'getargspec'), ('py:func', 'inspect.getargspec'), + # Undocumented modules that users shouldn't have to worry about + # (implementation details of `os.path`): + ('py:mod', 'ntpath'), + ('py:mod', 'posixpath'), ] # Temporary undocumented names. diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 62d4ed5e3f46b9..43200e269f56f4 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -306,7 +306,7 @@ Pure paths provide the following methods and properties: .. attribute:: PurePath.pathmod The implementation of the :mod:`os.path` module used for low-level path - operations: either ``posixpath`` or ``ntpath``. + operations: either :mod:`posixpath` or :mod:`ntpath`. .. versionadded:: 3.13 diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index ada1fc5fafc9c9..8a033f019372f7 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -72,7 +72,6 @@ Doc/library/multiprocessing.rst Doc/library/multiprocessing.shared_memory.rst Doc/library/numbers.rst Doc/library/optparse.rst -Doc/library/os.path.rst Doc/library/os.rst Doc/library/pickle.rst Doc/library/pickletools.rst diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 99f280af84ab01..7a74f9c1685c31 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -2144,9 +2144,9 @@ The following features and APIs have been removed from Python 3.7: * Removed support of the *exclude* argument in :meth:`tarfile.TarFile.add`. It was deprecated in Python 2.7 and 3.2. Use the *filter* argument instead. -* The ``splitunc()`` function in the :mod:`ntpath` module was deprecated in - Python 3.1, and has now been removed. Use the :func:`~os.path.splitdrive` - function instead. +* The :func:`!ntpath.splitunc` function was deprecated in + Python 3.1, and has now been removed. Use :func:`~os.path.splitdrive` + instead. * :func:`collections.namedtuple` no longer supports the *verbose* parameter or ``_source`` attribute which showed the generated source code for the From 28b2b7407c25d448ff5d8836efabbe7c02316568 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Thu, 7 Dec 2023 21:45:40 +0000 Subject: [PATCH 155/442] GH-112675: Move path joining tests into `test_posixpath` and `test_ntpath` (#112676) In `test_pathlib`, the `check_drive_root_parts` test methods evaluated both joining and parsing/normalisation of paths. This dates from a time when pathlib implemented both functions itself, but nowadays path joining is done with `posixpath.join()` and `ntpath.join()`. This commit moves the joining-related test cases into `test_posixpath` and `test_ntpath`. --- Lib/test/test_ntpath.py | 11 +++ Lib/test/test_pathlib.py | 159 +++++++++++++++---------------------- Lib/test/test_posixpath.py | 32 +++++--- 3 files changed, 96 insertions(+), 106 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 3e710d1c6dabe4..bf990ed36fbcae 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -256,6 +256,7 @@ def test_join(self): tester('ntpath.join("a", "b", "c")', 'a\\b\\c') tester('ntpath.join("a\\", "b", "c")', 'a\\b\\c') tester('ntpath.join("a", "b\\", "c")', 'a\\b\\c') + tester('ntpath.join("a", "b", "c\\")', 'a\\b\\c\\') tester('ntpath.join("a", "b", "\\c")', '\\c') tester('ntpath.join("d:\\", "\\pleep")', 'd:\\pleep') tester('ntpath.join("d:\\", "a", "b")', 'd:\\a\\b') @@ -313,6 +314,16 @@ def test_join(self): tester("ntpath.join('\\\\computer\\', 'share')", '\\\\computer\\share') tester("ntpath.join('\\\\computer\\share\\', 'a')", '\\\\computer\\share\\a') tester("ntpath.join('\\\\computer\\share\\a\\', 'b')", '\\\\computer\\share\\a\\b') + # Second part is anchored, so that the first part is ignored. + tester("ntpath.join('a', 'Z:b', 'c')", 'Z:b\\c') + tester("ntpath.join('a', 'Z:\\b', 'c')", 'Z:\\b\\c') + tester("ntpath.join('a', '\\\\b\\c', 'd')", '\\\\b\\c\\d') + # Second part has a root but not drive. + tester("ntpath.join('a', '\\b', 'c')", '\\b\\c') + tester("ntpath.join('Z:/a', '/b', 'c')", 'Z:\\b\\c') + tester("ntpath.join('//?/Z:/a', '/b', 'c')", '\\\\?\\Z:\\b\\c') + tester("ntpath.join('D:a', './c:b')", 'D:a\\.\\c:b') + tester("ntpath.join('D:/a', './c:b')", 'D:\\a\\.\\c:b') def test_normpath(self): tester("ntpath.normpath('A//////././//.//B')", r'A\B') diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 1b10d6c2f0cb19..ea922143e36e48 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -160,45 +160,30 @@ def with_segments(self, *pathsegments): for parent in p.parents: self.assertEqual(42, parent.session_id) - def _get_drive_root_parts(self, parts): - path = self.cls(*parts) - return path.drive, path.root, path.parts - - def _check_drive_root_parts(self, arg, *expected): + def _check_parse_path(self, raw_path, *expected): sep = self.pathmod.sep - actual = self._get_drive_root_parts([x.replace('/', sep) for x in arg]) + actual = self.cls._parse_path(raw_path.replace('/', sep)) self.assertEqual(actual, expected) if altsep := self.pathmod.altsep: - actual = self._get_drive_root_parts([x.replace('/', altsep) for x in arg]) + actual = self.cls._parse_path(raw_path.replace('/', altsep)) self.assertEqual(actual, expected) - def test_drive_root_parts_common(self): - check = self._check_drive_root_parts + def test_parse_path_common(self): + check = self._check_parse_path sep = self.pathmod.sep - # Unanchored parts. - check((), '', '', ()) - check(('a',), '', '', ('a',)) - check(('a/',), '', '', ('a',)) - check(('a', 'b'), '', '', ('a', 'b')) - # Expansion. - check(('a/b',), '', '', ('a', 'b')) - check(('a/b/',), '', '', ('a', 'b')) - check(('a', 'b/c', 'd'), '', '', ('a', 'b', 'c', 'd')) - # Collapsing and stripping excess slashes. - check(('a', 'b//c', 'd'), '', '', ('a', 'b', 'c', 'd')) - check(('a', 'b/c/', 'd'), '', '', ('a', 'b', 'c', 'd')) - # Eliminating standalone dots. - check(('.',), '', '', ()) - check(('.', '.', 'b'), '', '', ('b',)) - check(('a', '.', 'b'), '', '', ('a', 'b')) - check(('a', '.', '.'), '', '', ('a',)) - # The first part is anchored. - check(('/a/b',), '', sep, (sep, 'a', 'b')) - check(('/a', 'b'), '', sep, (sep, 'a', 'b')) - check(('/a/', 'b'), '', sep, (sep, 'a', 'b')) - # Ignoring parts before an anchored part. - check(('a', '/b', 'c'), '', sep, (sep, 'b', 'c')) - check(('a', '/b', '/c'), '', sep, (sep, 'c')) + check('', '', '', []) + check('a', '', '', ['a']) + check('a/', '', '', ['a']) + check('a/b', '', '', ['a', 'b']) + check('a/b/', '', '', ['a', 'b']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('.', '', '', []) + check('././b', '', '', ['b']) + check('a/./b', '', '', ['a', 'b']) + check('a/./.', '', '', ['a']) + check('/a/b', '', sep, ['a', 'b']) def test_join_common(self): P = self.cls @@ -792,17 +777,17 @@ def test_repr_roundtrips(self): class PurePosixPathTest(PurePathTest): cls = pathlib.PurePosixPath - def test_drive_root_parts(self): - check = self._check_drive_root_parts + def test_parse_path(self): + check = self._check_parse_path # Collapsing of excess leading slashes, except for the double-slash # special case. - check(('//a', 'b'), '', '//', ('//', 'a', 'b')) - check(('///a', 'b'), '', '/', ('/', 'a', 'b')) - check(('////a', 'b'), '', '/', ('/', 'a', 'b')) + check('//a/b', '', '//', ['a', 'b']) + check('///a/b', '', '/', ['a', 'b']) + check('////a/b', '', '/', ['a', 'b']) # Paths which look like NT paths aren't treated specially. - check(('c:a',), '', '', ('c:a',)) - check(('c:\\a',), '', '', ('c:\\a',)) - check(('\\a',), '', '', ('\\a',)) + check('c:a', '', '', ['c:a',]) + check('c:\\a', '', '', ['c:\\a',]) + check('\\a', '', '', ['\\a',]) def test_root(self): P = self.cls @@ -900,67 +885,53 @@ class PureWindowsPathTest(PurePathTest): ], }) - def test_drive_root_parts(self): - check = self._check_drive_root_parts + def test_parse_path(self): + check = self._check_parse_path # First part is anchored. - check(('c:',), 'c:', '', ('c:',)) - check(('c:/',), 'c:', '\\', ('c:\\',)) - check(('/',), '', '\\', ('\\',)) - check(('c:a',), 'c:', '', ('c:', 'a')) - check(('c:/a',), 'c:', '\\', ('c:\\', 'a')) - check(('/a',), '', '\\', ('\\', 'a')) - # UNC paths. - check(('//',), '\\\\', '', ('\\\\',)) - check(('//a',), '\\\\a', '', ('\\\\a',)) - check(('//a/',), '\\\\a\\', '', ('\\\\a\\',)) - check(('//a/b',), '\\\\a\\b', '\\', ('\\\\a\\b\\',)) - check(('//a/b/',), '\\\\a\\b', '\\', ('\\\\a\\b\\',)) - check(('//a/b/c',), '\\\\a\\b', '\\', ('\\\\a\\b\\', 'c')) - # Second part is anchored, so that the first part is ignored. - check(('a', 'Z:b', 'c'), 'Z:', '', ('Z:', 'b', 'c')) - check(('a', 'Z:/b', 'c'), 'Z:', '\\', ('Z:\\', 'b', 'c')) + check('c:', 'c:', '', []) + check('c:/', 'c:', '\\', []) + check('/', '', '\\', []) + check('c:a', 'c:', '', ['a']) + check('c:/a', 'c:', '\\', ['a']) + check('/a', '', '\\', ['a']) # UNC paths. - check(('a', '//b/c', 'd'), '\\\\b\\c', '\\', ('\\\\b\\c\\', 'd')) + check('//', '\\\\', '', []) + check('//a', '\\\\a', '', []) + check('//a/', '\\\\a\\', '', []) + check('//a/b', '\\\\a\\b', '\\', []) + check('//a/b/', '\\\\a\\b', '\\', []) + check('//a/b/c', '\\\\a\\b', '\\', ['c']) # Collapsing and stripping excess slashes. - check(('a', 'Z://b//c/', 'd/'), 'Z:', '\\', ('Z:\\', 'b', 'c', 'd')) + check('Z://b//c/d/', 'Z:', '\\', ['b', 'c', 'd']) # UNC paths. - check(('a', '//b/c//', 'd'), '\\\\b\\c', '\\', ('\\\\b\\c\\', 'd')) + check('//b/c//d', '\\\\b\\c', '\\', ['d']) # Extended paths. - check(('//./c:',), '\\\\.\\c:', '', ('\\\\.\\c:',)) - check(('//?/c:/',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\',)) - check(('//?/c:/a',), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'a')) - check(('//?/c:/a', '/b'), '\\\\?\\c:', '\\', ('\\\\?\\c:\\', 'b')) + check('//./c:', '\\\\.\\c:', '', []) + check('//?/c:/', '\\\\?\\c:', '\\', []) + check('//?/c:/a', '\\\\?\\c:', '\\', ['a']) # Extended UNC paths (format is "\\?\UNC\server\share"). - check(('//?',), '\\\\?', '', ('\\\\?',)) - check(('//?/',), '\\\\?\\', '', ('\\\\?\\',)) - check(('//?/UNC',), '\\\\?\\UNC', '', ('\\\\?\\UNC',)) - check(('//?/UNC/',), '\\\\?\\UNC\\', '', ('\\\\?\\UNC\\',)) - check(('//?/UNC/b',), '\\\\?\\UNC\\b', '', ('\\\\?\\UNC\\b',)) - check(('//?/UNC/b/',), '\\\\?\\UNC\\b\\', '', ('\\\\?\\UNC\\b\\',)) - check(('//?/UNC/b/c',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',)) - check(('//?/UNC/b/c/',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\',)) - check(('//?/UNC/b/c/d',), '\\\\?\\UNC\\b\\c', '\\', ('\\\\?\\UNC\\b\\c\\', 'd')) + check('//?', '\\\\?', '', []) + check('//?/', '\\\\?\\', '', []) + check('//?/UNC', '\\\\?\\UNC', '', []) + check('//?/UNC/', '\\\\?\\UNC\\', '', []) + check('//?/UNC/b', '\\\\?\\UNC\\b', '', []) + check('//?/UNC/b/', '\\\\?\\UNC\\b\\', '', []) + check('//?/UNC/b/c', '\\\\?\\UNC\\b\\c', '\\', []) + check('//?/UNC/b/c/', '\\\\?\\UNC\\b\\c', '\\', []) + check('//?/UNC/b/c/d', '\\\\?\\UNC\\b\\c', '\\', ['d']) # UNC device paths - check(('//./BootPartition/',), '\\\\.\\BootPartition', '\\', ('\\\\.\\BootPartition\\',)) - check(('//?/BootPartition/',), '\\\\?\\BootPartition', '\\', ('\\\\?\\BootPartition\\',)) - check(('//./PhysicalDrive0',), '\\\\.\\PhysicalDrive0', '', ('\\\\.\\PhysicalDrive0',)) - check(('//?/Volume{}/',), '\\\\?\\Volume{}', '\\', ('\\\\?\\Volume{}\\',)) - check(('//./nul',), '\\\\.\\nul', '', ('\\\\.\\nul',)) - # Second part has a root but not drive. - check(('a', '/b', 'c'), '', '\\', ('\\', 'b', 'c')) - check(('Z:/a', '/b', 'c'), 'Z:', '\\', ('Z:\\', 'b', 'c')) - check(('//?/Z:/a', '/b', 'c'), '\\\\?\\Z:', '\\', ('\\\\?\\Z:\\', 'b', 'c')) - # Joining with the same drive => the first path is appended to if - # the second path is relative. - check(('c:/a/b', 'c:x/y'), 'c:', '\\', ('c:\\', 'a', 'b', 'x', 'y')) - check(('c:/a/b', 'c:/x/y'), 'c:', '\\', ('c:\\', 'x', 'y')) + check('//./BootPartition/', '\\\\.\\BootPartition', '\\', []) + check('//?/BootPartition/', '\\\\?\\BootPartition', '\\', []) + check('//./PhysicalDrive0', '\\\\.\\PhysicalDrive0', '', []) + check('//?/Volume{}/', '\\\\?\\Volume{}', '\\', []) + check('//./nul', '\\\\.\\nul', '', []) # Paths to files with NTFS alternate data streams - check(('./c:s',), '', '', ('c:s',)) - check(('cc:s',), '', '', ('cc:s',)) - check(('C:c:s',), 'C:', '', ('C:', 'c:s')) - check(('C:/c:s',), 'C:', '\\', ('C:\\', 'c:s')) - check(('D:a', './c:b'), 'D:', '', ('D:', 'a', 'c:b')) - check(('D:/a', './c:b'), 'D:', '\\', ('D:\\', 'a', 'c:b')) + check('./c:s', '', '', ['c:s']) + check('cc:s', '', '', ['cc:s']) + check('C:c:s', 'C:', '', ['c:s']) + check('C:/c:s', 'C:', '\\', ['c:s']) + check('D:a/c:b', 'D:', '', ['a', 'c:b']) + check('D:/a/c:b', 'D:', '\\', ['a', 'c:b']) def test_str(self): p = self.cls('a/b/c') diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 9be4640f970aef..86ce1b1d41ba61 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -47,18 +47,26 @@ def tearDown(self): safe_rmdir(os_helper.TESTFN + suffix) def test_join(self): - self.assertEqual(posixpath.join("/foo", "bar", "/bar", "baz"), - "/bar/baz") - self.assertEqual(posixpath.join("/foo", "bar", "baz"), "/foo/bar/baz") - self.assertEqual(posixpath.join("/foo/", "bar/", "baz/"), - "/foo/bar/baz/") - - self.assertEqual(posixpath.join(b"/foo", b"bar", b"/bar", b"baz"), - b"/bar/baz") - self.assertEqual(posixpath.join(b"/foo", b"bar", b"baz"), - b"/foo/bar/baz") - self.assertEqual(posixpath.join(b"/foo/", b"bar/", b"baz/"), - b"/foo/bar/baz/") + fn = posixpath.join + self.assertEqual(fn("/foo", "bar", "/bar", "baz"), "/bar/baz") + self.assertEqual(fn("/foo", "bar", "baz"), "/foo/bar/baz") + self.assertEqual(fn("/foo/", "bar/", "baz/"), "/foo/bar/baz/") + + self.assertEqual(fn(b"/foo", b"bar", b"/bar", b"baz"), b"/bar/baz") + self.assertEqual(fn(b"/foo", b"bar", b"baz"), b"/foo/bar/baz") + self.assertEqual(fn(b"/foo/", b"bar/", b"baz/"), b"/foo/bar/baz/") + + self.assertEqual(fn("a", "b"), "a/b") + self.assertEqual(fn("a", "b/"), "a/b/") + self.assertEqual(fn("a/", "b"), "a/b") + self.assertEqual(fn("a/", "b/"), "a/b/") + self.assertEqual(fn("a", "b/c", "d"), "a/b/c/d") + self.assertEqual(fn("a", "b//c", "d"), "a/b//c/d") + self.assertEqual(fn("a", "b/c/", "d"), "a/b/c/d") + self.assertEqual(fn("/a", "b"), "/a/b") + self.assertEqual(fn("/a/", "b"), "/a/b") + self.assertEqual(fn("a", "/b", "c"), "/b/c") + self.assertEqual(fn("a", "/b", "/c"), "/c") def test_split(self): self.assertEqual(posixpath.split("/foo/bar"), ("/foo", "bar")) From 4ac1e8fb25c5c0e1da61784281ab878db671761b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 8 Dec 2023 10:18:15 +0200 Subject: [PATCH 156/442] Add a versionchanged directive for gh-94692 (GH-112846) Co-authored-by: Alex Waygood --- Doc/library/shutil.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index d30d289710b129..f61ef8b0ecc7ba 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -346,6 +346,8 @@ Directory and files operations .. versionchanged:: 3.13 :func:`!rmtree` now ignores :exc:`FileNotFoundError` exceptions for all but the top-level path. + Exceptions other than :exc:`OSError` and subclasses of :exc:`!OSError` + are now always propagated to the caller. .. attribute:: rmtree.avoids_symlink_attacks From 15a80b15af9a0b0ebe6bd538a1919712ce7d4ef9 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Fri, 8 Dec 2023 10:09:34 +0100 Subject: [PATCH 157/442] gh-110820: Make sure processor specific defines are correct for Universal 2 build on macOS (#112828) * gh-110820: Make sure processor specific defines are correct for Universal 2 build on macOS A number of processor specific defines are different for x86-64 and arm64, and need to be adjusted in pymacconfig.h. * remove debug stuf --- Include/pymacconfig.h | 11 ++++++++++- .../2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst diff --git a/Include/pymacconfig.h b/Include/pymacconfig.h index 806e41955efd7f..615abe103ca038 100644 --- a/Include/pymacconfig.h +++ b/Include/pymacconfig.h @@ -7,7 +7,9 @@ #define PY_MACCONFIG_H #ifdef __APPLE__ +#undef ALIGNOF_MAX_ALIGN_T #undef SIZEOF_LONG +#undef SIZEOF_LONG_DOUBLE #undef SIZEOF_PTHREAD_T #undef SIZEOF_SIZE_T #undef SIZEOF_TIME_T @@ -20,6 +22,7 @@ #undef DOUBLE_IS_BIG_ENDIAN_IEEE754 #undef DOUBLE_IS_LITTLE_ENDIAN_IEEE754 #undef HAVE_GCC_ASM_FOR_X87 +#undef HAVE_GCC_ASM_FOR_X64 #undef VA_LIST_IS_ARRAY #if defined(__LP64__) && defined(__x86_64__) @@ -74,8 +77,14 @@ # define DOUBLE_IS_LITTLE_ENDIAN_IEEE754 #endif -#ifdef __i386__ +#if defined(__i386__) || defined(__x86_64__) # define HAVE_GCC_ASM_FOR_X87 +# define ALIGNOF_MAX_ALIGN_T 16 +# define HAVE_GCC_ASM_FOR_X64 1 +# define SIZEOF_LONG_DOUBLE 16 +#else +# define ALIGNOF_MAX_ALIGN_T 8 +# define SIZEOF_LONG_DOUBLE 8 #endif #endif // __APPLE__ diff --git a/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst b/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst new file mode 100644 index 00000000000000..0badace7928745 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst @@ -0,0 +1,3 @@ +Make sure the preprocessor definitions for ``ALIGNOF_MAX_ALIGN_T``, +``SIZEOF_LONG_DOUBLE`` and ``HAVE_GCC_ASM_FOR_X64`` are correct for +Universal 2 builds on macOS. From aefdebdef16b8e9e6c1c2a0a54d6cb25bd8e28dc Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 8 Dec 2023 11:48:30 +0000 Subject: [PATCH 158/442] GH-111485: Factor out opcode ID generator from the main cases generator. (GH-112831) --- Include/opcode_ids.h | 9 +- Makefile.pre.in | 3 +- Tools/cases_generator/cwriter.py | 7 +- Tools/cases_generator/generate_cases.py | 47 +----- Tools/cases_generator/generators_common.py | 19 +++ Tools/cases_generator/opcode_id_generator.py | 153 +++++++++++++++++++ Tools/cases_generator/tier1_generator.py | 37 ++--- 7 files changed, 203 insertions(+), 72 deletions(-) create mode 100644 Tools/cases_generator/generators_common.py create mode 100644 Tools/cases_generator/opcode_id_generator.py diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index ba25bd459c1bcd..47f809e345f61c 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -1,6 +1,6 @@ -// This file is generated by Tools/cases_generator/generate_cases.py +// This file is generated by Tools/cases_generator/opcode_id_generator.py // from: -// Python/bytecodes.c +// ['./Python/bytecodes.c'] // Do not edit! #ifndef Py_OPCODE_IDS_H @@ -55,7 +55,6 @@ extern "C" { #define UNARY_NEGATIVE 42 #define UNARY_NOT 43 #define WITH_EXCEPT_START 44 -#define HAVE_ARGUMENT 45 #define BINARY_OP 45 #define BUILD_CONST_KEY_MAP 46 #define BUILD_LIST 47 @@ -200,7 +199,6 @@ extern "C" { #define UNPACK_SEQUENCE_LIST 216 #define UNPACK_SEQUENCE_TUPLE 217 #define UNPACK_SEQUENCE_TWO_TUPLE 218 -#define MIN_INSTRUMENTED_OPCODE 236 #define INSTRUMENTED_RESUME 236 #define INSTRUMENTED_END_FOR 237 #define INSTRUMENTED_END_SEND 238 @@ -233,6 +231,9 @@ extern "C" { #define SETUP_WITH 266 #define STORE_FAST_MAYBE_NULL 267 +#define HAVE_ARGUMENT 45 +#define MIN_INSTRUMENTED_OPCODE 236 + #ifdef __cplusplus } #endif diff --git a/Makefile.pre.in b/Makefile.pre.in index f57894a2118e74..6ca11f080dcc3f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1587,13 +1587,14 @@ regen-cases: $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/generate_cases.py \ $(CASESFLAG) \ - -n $(srcdir)/Include/opcode_ids.h.new \ -t $(srcdir)/Python/opcode_targets.h.new \ -m $(srcdir)/Include/internal/pycore_opcode_metadata.h.new \ -e $(srcdir)/Python/executor_cases.c.h.new \ -p $(srcdir)/Lib/_opcode_metadata.py.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) \ + $(srcdir)/Tools/cases_generator/opcode_id_generator.py -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py index 0b7edd03fd9e47..34e39855a9b40a 100644 --- a/Tools/cases_generator/cwriter.py +++ b/Tools/cases_generator/cwriter.py @@ -48,8 +48,13 @@ def maybe_indent(self, txt: str) -> None: if offset <= self.indents[-1] or offset > 40: offset = self.indents[-1] + 4 self.indents.append(offset) - elif "{" in txt or is_label(txt): + if is_label(txt): self.indents.append(self.indents[-1] + 4) + elif "{" in txt: + if 'extern "C"' in txt: + self.indents.append(self.indents[-1]) + else: + self.indents.append(self.indents[-1] + 4) def emit_text(self, txt: str) -> None: self.out.write(txt) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 4b7f028970bd0c..d0fdc4a0aeb7b0 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -101,13 +101,6 @@ arg_parser.add_argument( "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT ) -arg_parser.add_argument( - "-n", - "--opcode_ids_h", - type=str, - help="Header file with opcode number definitions", - default=DEFAULT_OPCODE_IDS_H_OUTPUT, -) arg_parser.add_argument( "-t", "--opcode_targets_h", @@ -334,42 +327,8 @@ def map_op(op: int, name: str) -> None: self.opmap = opmap self.markers = markers - def write_opcode_ids( - self, opcode_ids_h_filename: str, opcode_targets_filename: str - ) -> None: - """Write header file that defined the opcode IDs""" - - with open(opcode_ids_h_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 0) - - self.write_provenance_header() - - self.out.emit("") - self.out.emit("#ifndef Py_OPCODE_IDS_H") - self.out.emit("#define Py_OPCODE_IDS_H") - self.out.emit("#ifdef __cplusplus") - self.out.emit('extern "C" {') - self.out.emit("#endif") - self.out.emit("") - self.out.emit("/* Instruction opcodes for compiled code */") - - def define(name: str, opcode: int) -> None: - self.out.emit(f"#define {name:<38} {opcode:>3}") - - all_pairs: list[tuple[int, int, str]] = [] - # the second item in the tuple sorts the markers before the ops - all_pairs.extend((i, 1, name) for (name, i) in self.markers.items()) - all_pairs.extend((i, 2, name) for (name, i) in self.opmap.items()) - for i, _, name in sorted(all_pairs): - assert name is not None - define(name, i) - - self.out.emit("") - self.out.emit("#ifdef __cplusplus") - self.out.emit("}") - self.out.emit("#endif") - self.out.emit("#endif /* !Py_OPCODE_IDS_H */") + def write_opcode_targets(self, opcode_targets_filename: str) -> None: + """Write header file that defines the jump target table""" with open(opcode_targets_filename, "w") as f: # Create formatter @@ -885,7 +844,7 @@ def main() -> None: # These raise OSError if output can't be written a.assign_opcode_ids() - a.write_opcode_ids(args.opcode_ids_h, args.opcode_targets_h) + a.write_opcode_targets(args.opcode_targets_h) a.write_metadata(args.metadata, args.pymetadata) a.write_executor_instructions(args.executor_cases, args.emit_line_directives) a.write_abstract_interpreter_instructions( diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py new file mode 100644 index 00000000000000..76900d1efffd5d --- /dev/null +++ b/Tools/cases_generator/generators_common.py @@ -0,0 +1,19 @@ +from pathlib import Path +from typing import TextIO + +ROOT = Path(__file__).parent.parent.parent +DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute() + + +def root_relative_path(filename: str) -> str: + return Path(filename).relative_to(ROOT).as_posix() + + +def write_header(generator: str, source: str, outfile: TextIO) -> None: + outfile.write( + f"""// This file is generated by {root_relative_path(generator)} +// from: +// {source} +// Do not edit! +""" + ) diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py new file mode 100644 index 00000000000000..a1f6f62156ebd3 --- /dev/null +++ b/Tools/cases_generator/opcode_id_generator.py @@ -0,0 +1,153 @@ +"""Generate the list of opcode IDs. +Reads the instruction definitions from bytecodes.c. +Writes the IDs to opcode._ids.h by default. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, +) +from cwriter import CWriter +from typing import TextIO + + +DEFAULT_OUTPUT = ROOT / "Include/opcode_ids.h" + + +def generate_opcode_header(filenames: str, analysis: Analysis, outfile: TextIO) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 0, False) + out.emit("\n") + instmap: dict[str, int] = {} + + # 0 is reserved for cache entries. This helps debugging. + instmap["CACHE"] = 0 + + # 17 is reserved as it is the initial value for the specializing counter. + # This helps catch cases where we attempt to execute a cache. + instmap["RESERVED"] = 17 + + # 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py + instmap["RESUME"] = 149 + instmap["INSTRUMENTED_LINE"] = 254 + + instrumented = [ + name for name in analysis.instructions if name.startswith("INSTRUMENTED") + ] + + # Special case: this instruction is implemented in ceval.c + # rather than bytecodes.c, so we need to add it explicitly + # here (at least until we add something to bytecodes.c to + # declare external instructions). + instrumented.append("INSTRUMENTED_LINE") + + specialized: set[str] = set() + no_arg: list[str] = [] + has_arg: list[str] = [] + + for family in analysis.families.values(): + specialized.update(inst.name for inst in family.members) + + for inst in analysis.instructions.values(): + name = inst.name + if name in specialized: + continue + if name in instrumented: + continue + if inst.properties.oparg: + has_arg.append(name) + else: + no_arg.append(name) + + # Specialized ops appear in their own section + # Instrumented opcodes are at the end of the valid range + min_internal = 150 + min_instrumented = 254 - (len(instrumented) - 1) + assert min_internal + len(specialized) < min_instrumented + + next_opcode = 1 + + def add_instruction(name: str) -> None: + nonlocal next_opcode + if name in instmap: + return # Pre-defined name + while next_opcode in instmap.values(): + next_opcode += 1 + instmap[name] = next_opcode + next_opcode += 1 + + for name in sorted(no_arg): + add_instruction(name) + for name in sorted(has_arg): + add_instruction(name) + # For compatibility + next_opcode = min_internal + for name in sorted(specialized): + add_instruction(name) + next_opcode = min_instrumented + for name in instrumented: + add_instruction(name) + + for op, name in enumerate(sorted(analysis.pseudos), 256): + instmap[name] = op + + assert 255 not in instmap.values() + + out.emit( + """#ifndef Py_OPCODE_IDS_H +#define Py_OPCODE_IDS_H +#ifdef __cplusplus +extern "C" { +#endif + +/* Instruction opcodes for compiled code */ +""" + ) + + def write_define(name: str, op: int) -> None: + out.emit(f"#define {name:<38} {op:>3}\n") + + for op, name in sorted([(op, name) for (name, op) in instmap.items()]): + write_define(name, op) + + out.emit("\n") + write_define("HAVE_ARGUMENT", len(no_arg)) + write_define("MIN_INSTRUMENTED_OPCODE", min_instrumented) + + out.emit("\n") + out.emit("#ifdef __cplusplus\n") + out.emit("}\n") + out.emit("#endif\n") + out.emit("#endif /* !Py_OPCODE_IDS_H */\n") + + +arg_parser = argparse.ArgumentParser( + description="Generate the header file with all opcode IDs.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_opcode_header(args.input, data, outfile) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index eba926435d2415..9787403b3bbc47 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -17,33 +17,18 @@ StackItem, analysis_error, ) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, +) from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token from stack import StackOffset -HERE = os.path.dirname(__file__) -ROOT = os.path.join(HERE, "../..") -THIS = os.path.relpath(__file__, ROOT).replace(os.path.sep, "/") - -DEFAULT_INPUT = os.path.relpath(os.path.join(ROOT, "Python/bytecodes.c")) -DEFAULT_OUTPUT = os.path.relpath(os.path.join(ROOT, "Python/generated_cases.c.h")) - - -def write_header(filename: str, outfile: TextIO) -> None: - outfile.write( - f"""// This file is generated by {THIS} -// from: -// {filename} -// Do not edit! - -#ifdef TIER_TWO - #error "This file is for Tier 1 only" -#endif -#define TIER_ONE 1 -""" - ) +DEFAULT_OUTPUT = ROOT / "Python/generated_cases.c.h" FOOTER = "#undef TIER_ONE\n" @@ -351,7 +336,15 @@ def uses_this(inst: Instruction) -> bool: def generate_tier1( filenames: str, analysis: Analysis, outfile: TextIO, lines: bool ) -> None: - write_header(filenames, outfile) + write_header(__file__, filenames, outfile) + outfile.write( + """ +#ifdef TIER_TWO + #error "This file is for Tier 1 only" +#endif +#define TIER_ONE 1 +""" + ) out = CWriter(outfile, 2, lines) out.emit("\n") for name, inst in sorted(analysis.instructions.items()): From 3cdcc2edf81c7be4c88d4f273947ce29f916f49a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 8 Dec 2023 12:31:11 +0000 Subject: [PATCH 159/442] gh-101100: Fix Sphinx nitpicks in `library/shelve.rst` (#112836) --- Doc/library/shelve.rst | 9 +++++---- Doc/tools/.nitignore | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/shelve.rst b/Doc/library/shelve.rst index 219219af6fd87f..88802d717d7383 100644 --- a/Doc/library/shelve.rst +++ b/Doc/library/shelve.rst @@ -149,13 +149,14 @@ Restrictions .. class:: BsdDbShelf(dict, protocol=None, writeback=False, keyencoding='utf-8') - A subclass of :class:`Shelf` which exposes :meth:`first`, :meth:`!next`, - :meth:`previous`, :meth:`last` and :meth:`set_location` which are available - in the third-party :mod:`bsddb` module from `pybsddb + A subclass of :class:`Shelf` which exposes :meth:`!first`, :meth:`!next`, + :meth:`!previous`, :meth:`!last` and :meth:`!set_location` methods. + These are available + in the third-party :mod:`!bsddb` module from `pybsddb `_ but not in other database modules. The *dict* object passed to the constructor must support those methods. This is generally accomplished by calling one of - :func:`bsddb.hashopen`, :func:`bsddb.btopen` or :func:`bsddb.rnopen`. The + :func:`!bsddb.hashopen`, :func:`!bsddb.btopen` or :func:`!bsddb.rnopen`. The optional *protocol*, *writeback*, and *keyencoding* parameters have the same interpretation as for the :class:`Shelf` class. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8a033f019372f7..18dddb26867837 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -86,7 +86,6 @@ Doc/library/readline.rst Doc/library/resource.rst Doc/library/rlcompleter.rst Doc/library/select.rst -Doc/library/shelve.rst Doc/library/signal.rst Doc/library/smtplib.rst Doc/library/socket.rst From e4c087603397a1314253b861d35f8314fba8ae92 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 8 Dec 2023 13:17:57 +0000 Subject: [PATCH 160/442] gh-101100: Fix Sphinx nits in `library/contextlib.rst` (#112870) --- Doc/library/contextlib.rst | 14 +++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index f6ebbfacfba509..aab319cbe7405e 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -106,8 +106,8 @@ Functions and classes provided: This function is a :term:`decorator` that can be used to define a factory function for :keyword:`async with` statement asynchronous context managers, - without needing to create a class or separate :meth:`__aenter__` and - :meth:`__aexit__` methods. It must be applied to an :term:`asynchronous + without needing to create a class or separate :meth:`~object.__aenter__` and + :meth:`~object.__aexit__` methods. It must be applied to an :term:`asynchronous generator` function. A simple example:: @@ -616,12 +616,12 @@ Functions and classes provided: asynchronous context managers, as well as having coroutines for cleanup logic. - The :meth:`close` method is not implemented, :meth:`aclose` must be used + The :meth:`~ExitStack.close` method is not implemented; :meth:`aclose` must be used instead. .. coroutinemethod:: enter_async_context(cm) - Similar to :meth:`enter_context` but expects an asynchronous context + Similar to :meth:`ExitStack.enter_context` but expects an asynchronous context manager. .. versionchanged:: 3.11 @@ -630,16 +630,16 @@ Functions and classes provided: .. method:: push_async_exit(exit) - Similar to :meth:`push` but expects either an asynchronous context manager + Similar to :meth:`ExitStack.push` but expects either an asynchronous context manager or a coroutine function. .. method:: push_async_callback(callback, /, *args, **kwds) - Similar to :meth:`callback` but expects a coroutine function. + Similar to :meth:`ExitStack.callback` but expects a coroutine function. .. coroutinemethod:: aclose() - Similar to :meth:`close` but properly handles awaitables. + Similar to :meth:`ExitStack.close` but properly handles awaitables. Continuing the example for :func:`asynccontextmanager`:: diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 18dddb26867837..5ef68cc089d436 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -39,7 +39,6 @@ Doc/library/collections.abc.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst Doc/library/configparser.rst -Doc/library/contextlib.rst Doc/library/csv.rst Doc/library/datetime.rst Doc/library/dbm.rst From ed21d0c1f4bd17b392e24bfd83e652723dad4ddf Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 8 Dec 2023 13:18:53 +0000 Subject: [PATCH 161/442] gh-101100: Improve documentation for attributes on instance methods (#112832) --- Doc/library/inspect.rst | 2 +- Doc/library/stdtypes.rst | 23 ++++++----- Doc/reference/datamodel.rst | 77 +++++++++++++++++++++++++------------ Doc/tutorial/classes.rst | 6 ++- Doc/whatsnew/2.6.rst | 4 +- Doc/whatsnew/2.7.rst | 5 ++- 6 files changed, 76 insertions(+), 41 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 0138557f5fd84c..8381e508139fbd 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -492,7 +492,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Methods implemented via descriptors that also pass one of the other tests return ``False`` from the :func:`ismethoddescriptor` test, simply because the other tests promise more -- you can, e.g., count on having the - :ref:`__func__ ` attribute (etc) when an object passes + :attr:`~method.__func__` attribute (etc) when an object passes :func:`ismethod`. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 44c13bd9474ea1..1265b5b12e492d 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5328,25 +5328,30 @@ Methods .. index:: pair: object; method Methods are functions that are called using the attribute notation. There are -two flavors: built-in methods (such as :meth:`append` on lists) and class -instance methods. Built-in methods are described with the types that support -them. +two flavors: :ref:`built-in methods ` (such as :meth:`append` +on lists) and :ref:`class instance method `. +Built-in methods are described with the types that support them. If you access a method (a function defined in a class namespace) through an instance, you get a special object: a :dfn:`bound method` (also called -:dfn:`instance method`) object. When called, it will add the ``self`` argument +:ref:`instance method `) object. When called, it will add +the ``self`` argument to the argument list. Bound methods have two special read-only attributes: -``m.__self__`` is the object on which the method operates, and ``m.__func__`` is +:attr:`m.__self__ ` is the object on which the method +operates, and :attr:`m.__func__ ` is the function implementing the method. Calling ``m(arg-1, arg-2, ..., arg-n)`` is completely equivalent to calling ``m.__func__(m.__self__, arg-1, arg-2, ..., arg-n)``. -Like function objects, bound method objects support getting arbitrary +Like :ref:`function objects `, bound method objects support +getting arbitrary attributes. However, since method attributes are actually stored on the -underlying function object (``meth.__func__``), setting method attributes on +underlying function object (:attr:`method.__func__`), setting method attributes on bound methods is disallowed. Attempting to set an attribute on a method results in an :exc:`AttributeError` being raised. In order to set a method -attribute, you need to explicitly set it on the underlying function object:: +attribute, you need to explicitly set it on the underlying function object: + +.. doctest:: >>> class C: ... def method(self): @@ -5361,7 +5366,7 @@ attribute, you need to explicitly set it on the underlying function object:: >>> c.method.whoami 'my name is method' -See :ref:`types` for more information. +See :ref:`instance-methods` for more information. .. index:: object; code, code object diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 3bcc170faa087a..27d379a8b70f31 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -519,6 +519,8 @@ These are the types to which the function call operation (see section :ref:`calls`) can be applied: +.. _user-defined-funcs: + User-defined functions ^^^^^^^^^^^^^^^^^^^^^^ @@ -654,43 +656,64 @@ callable object (normally a user-defined function). single: __name__ (method attribute) single: __module__ (method attribute) -Special read-only attributes: :attr:`__self__` is the class instance object, -:attr:`__func__` is the function object; :attr:`__doc__` is the method's -documentation (same as ``__func__.__doc__``); :attr:`~definition.__name__` is the -method name (same as ``__func__.__name__``); :attr:`__module__` is the -name of the module the method was defined in, or ``None`` if unavailable. +Special read-only attributes: + +.. list-table:: + + * - .. attribute:: method.__self__ + - Refers to the class instance object to which the method is + :ref:`bound ` + + * - .. attribute:: method.__func__ + - Refers to the original function object + + * - .. attribute:: method.__doc__ + - The method's documentation (same as :attr:`!method.__func__.__doc__`). + A :class:`string ` if the original function had a docstring, else + ``None``. + + * - .. attribute:: method.__name__ + - The name of the method (same as :attr:`!method.__func__.__name__`) + + * - .. attribute:: method.__module__ + - The name of the module the method was defined in, or ``None`` if + unavailable. Methods also support accessing (but not setting) the arbitrary function -attributes on the underlying function object. +attributes on the underlying :ref:`function object `. User-defined method objects may be created when getting an attribute of a class (perhaps via an instance of that class), if that attribute is a -user-defined function object or a class method object. +user-defined :ref:`function object ` or a +:class:`classmethod` object. + +.. _method-binding: When an instance method object is created by retrieving a user-defined -function object from a class via one of its instances, its -:attr:`__self__` attribute is the instance, and the method object is said -to be bound. The new method's :attr:`__func__` attribute is the original -function object. - -When an instance method object is created by retrieving a class method -object from a class or instance, its :attr:`__self__` attribute is the -class itself, and its :attr:`__func__` attribute is the function object +:ref:`function object ` from a class via one of its +instances, its :attr:`~method.__self__` attribute is the instance, and the +method object is said to be *bound*. The new method's :attr:`~method.__func__` +attribute is the original function object. + +When an instance method object is created by retrieving a :class:`classmethod` +object from a class or instance, its :attr:`~method.__self__` attribute is the +class itself, and its :attr:`~method.__func__` attribute is the function object underlying the class method. When an instance method object is called, the underlying function -(:attr:`__func__`) is called, inserting the class instance -(:attr:`__self__`) in front of the argument list. For instance, when +(:attr:`~method.__func__`) is called, inserting the class instance +(:attr:`~method.__self__`) in front of the argument list. For instance, when :class:`!C` is a class which contains a definition for a function :meth:`!f`, and ``x`` is an instance of :class:`!C`, calling ``x.f(1)`` is equivalent to calling ``C.f(x, 1)``. -When an instance method object is derived from a class method object, the -"class instance" stored in :attr:`__self__` will actually be the class +When an instance method object is derived from a :class:`classmethod` object, the +"class instance" stored in :attr:`~method.__self__` will actually be the class itself, so that calling either ``x.f(1)`` or ``C.f(1)`` is equivalent to calling ``f(C,1)`` where ``f`` is the underlying function. -Note that the transformation from function object to instance method +Note that the transformation from :ref:`function object ` +to instance method object happens each time the attribute is retrieved from the instance. In some cases, a fruitful optimization is to assign the attribute to a local variable and call that local variable. Also notice that this @@ -774,6 +797,8 @@ set to ``None`` (but see the next item); :attr:`__module__` is the name of the module the function was defined in or ``None`` if unavailable. +.. _builtin-methods: + Built-in methods ^^^^^^^^^^^^^^^^ @@ -785,8 +810,9 @@ Built-in methods This is really a different disguise of a built-in function, this time containing an object passed to the C function as an implicit extra argument. An example of a built-in method is ``alist.append()``, assuming *alist* is a list object. In -this case, the special read-only attribute :attr:`__self__` is set to the object -denoted by *alist*. +this case, the special read-only attribute :attr:`!__self__` is set to the object +denoted by *alist*. (The attribute has the same semantics as it does with +:attr:`other instance methods `.) Classes @@ -901,8 +927,9 @@ https://www.python.org/download/releases/2.3/mro/. When a class attribute reference (for class :class:`!C`, say) would yield a class method object, it is transformed into an instance method object whose -:attr:`__self__` attribute is :class:`!C`. When it would yield a static -method object, it is transformed into the object wrapped by the static method +:attr:`~method.__self__` attribute is :class:`!C`. +When it would yield a :class:`staticmethod` object, +it is transformed into the object wrapped by the static method object. See section :ref:`descriptors` for another way in which attributes retrieved from a class may differ from those actually contained in its :attr:`~object.__dict__`. @@ -970,7 +997,7 @@ in which attribute references are searched. When an attribute is not found there, and the instance's class has an attribute by that name, the search continues with the class attributes. If a class attribute is found that is a user-defined function object, it is transformed into an instance method -object whose :attr:`__self__` attribute is the instance. Static method and +object whose :attr:`~method.__self__` attribute is the instance. Static method and class method objects are also transformed; see above under "Classes". See section :ref:`descriptors` for another way in which attributes of a class retrieved via its instances may differ from the objects actually stored in diff --git a/Doc/tutorial/classes.rst b/Doc/tutorial/classes.rst index 7b92e1a51b6e67..3bf138ca225ee5 100644 --- a/Doc/tutorial/classes.rst +++ b/Doc/tutorial/classes.rst @@ -769,8 +769,10 @@ data from a string buffer instead, and pass it as an argument. or arithmetic operators, and assigning such a "pseudo-file" to sys.stdin will not cause the interpreter to read further input from it.) -Instance method objects have attributes, too: ``m.__self__`` is the instance -object with the method :meth:`!m`, and ``m.__func__`` is the function object +:ref:`Instance method objects ` have attributes, too: +:attr:`m.__self__ ` is the instance +object with the method :meth:`!m`, and :attr:`m.__func__ ` is +the :ref:`function object ` corresponding to the method. diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 8bdbb0fa352ed1..e8c1709c42abac 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1678,8 +1678,8 @@ Some smaller changes made to the core Python language are: * Instance method objects have new attributes for the object and function comprising the method; the new synonym for :attr:`!im_self` is - :ref:`__self__ `, and :attr:`!im_func` is also available as - :ref:`__func__ `. + :attr:`~method.__self__`, and :attr:`!im_func` is also available as + :attr:`~method.__func__`. The old names are still supported in Python 2.6, but are gone in 3.0. * An obscure change: when you use the :func:`locals` function inside a diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 162dd74637479a..cf6d26859bb6a2 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -858,9 +858,10 @@ Some smaller changes made to the core Python language are: .. XXX bytearray doesn't seem to be documented -* When using ``@classmethod`` and ``@staticmethod`` to wrap +* When using :class:`@classmethod ` and + :class:`@staticmethod ` to wrap methods as class or static methods, the wrapper object now - exposes the wrapped function as their :ref:`__func__ ` + exposes the wrapped function as their :attr:`~method.__func__` attribute. (Contributed by Amaury Forgeot d'Arc, after a suggestion by George Sakkis; :issue:`5982`.) From e6ac25429fa5034ce7c1f04e34ec705ed6e9f522 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Fri, 8 Dec 2023 07:46:19 -0600 Subject: [PATCH 162/442] gh-112302: Annotate SBOM file as generated in .gitattributes (#112854) Annotate SBOM file as generated in .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 8c37dbbb631022..acfd62411542f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,6 +84,7 @@ Lib/keyword.py generated Lib/test/levenshtein_examples.json generated Lib/test/test_stable_abi_ctypes.py generated Lib/token.py generated +Misc/sbom.spdx.json generated Objects/typeslots.inc generated PC/python3dll.c generated Parser/parser.c generated From c744dbe9ac0e88e395a7464f20a4fd4184a0a222 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Fri, 8 Dec 2023 14:28:07 +0000 Subject: [PATCH 163/442] gh-112535: Update _Py_ThreadId() to support s390/s390x (gh-112751) --- Include/object.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Include/object.h b/Include/object.h index 85abd30b5ad7d6..bd576b0bd43211 100644 --- a/Include/object.h +++ b/Include/object.h @@ -279,6 +279,10 @@ _Py_ThreadId(void) __asm__ ("" : "=r" (tp)); tid = tp; #endif +#elif defined(__s390__) && defined(__GNUC__) + // Both GCC and Clang have supported __builtin_thread_pointer + // for s390 from long time ago. + tid = (uintptr_t)__builtin_thread_pointer(); #else # error "define _Py_ThreadId for this platform" #endif From 4d1eea59bd26d329417cc2252f1c91b52d0f4a28 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 8 Dec 2023 11:31:32 -0500 Subject: [PATCH 164/442] gh-112779: Check 1-byte atomics in configure (gh-112819) --- configure | 18 ++++++++++++++---- configure.ac | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/configure b/configure index 8894122a11e86e..5f880d6d8edd96 100755 --- a/configure +++ b/configure @@ -27885,6 +27885,9 @@ printf "%s\n" "$TEST_MODULES" >&6; } # libatomic __atomic_fetch_or_8(), or not, depending on the C compiler and the # compiler flags. # +# gh-112779: On RISC-V, GCC 12 and earlier require libatomic support for 1-byte +# and 2-byte operations, but not for 8-byte operations. +# # Avoid #include or #include . The header # requires header which is only written below by AC_OUTPUT below. # If the check is done after AC_OUTPUT, modifying LIBS has no effect @@ -27924,12 +27927,19 @@ typedef intptr_t Py_ssize_t; int main() { - uint64_t byte; - _Py_atomic_store_uint64(&byte, 2); - if (_Py_atomic_or_uint64(&byte, 8) != 2) { + uint64_t value; + _Py_atomic_store_uint64(&value, 2); + if (_Py_atomic_or_uint64(&value, 8) != 2) { + return 1; // error + } + if (_Py_atomic_load_uint64(&value) != 10) { + return 1; // error + } + uint8_t byte = 0xb8; + if (_Py_atomic_or_uint8(&byte, 0x2d) != 0xb8) { return 1; // error } - if (_Py_atomic_load_uint64(&byte) != 10) { + if (_Py_atomic_load_uint8(&byte) != 0xbd) { return 1; // error } return 0; // all good diff --git a/configure.ac b/configure.ac index 1512e6d9e8c42a..c07d7ce6bdc918 100644 --- a/configure.ac +++ b/configure.ac @@ -7023,6 +7023,9 @@ AC_SUBST([TEST_MODULES]) # libatomic __atomic_fetch_or_8(), or not, depending on the C compiler and the # compiler flags. # +# gh-112779: On RISC-V, GCC 12 and earlier require libatomic support for 1-byte +# and 2-byte operations, but not for 8-byte operations. +# # Avoid #include or #include . The header # requires header which is only written below by AC_OUTPUT below. # If the check is done after AC_OUTPUT, modifying LIBS has no effect @@ -7052,12 +7055,19 @@ typedef intptr_t Py_ssize_t; int main() { - uint64_t byte; - _Py_atomic_store_uint64(&byte, 2); - if (_Py_atomic_or_uint64(&byte, 8) != 2) { + uint64_t value; + _Py_atomic_store_uint64(&value, 2); + if (_Py_atomic_or_uint64(&value, 8) != 2) { + return 1; // error + } + if (_Py_atomic_load_uint64(&value) != 10) { + return 1; // error + } + uint8_t byte = 0xb8; + if (_Py_atomic_or_uint8(&byte, 0x2d) != 0xb8) { return 1; // error } - if (_Py_atomic_load_uint64(&byte) != 10) { + if (_Py_atomic_load_uint8(&byte) != 0xbd) { return 1; // error } return 0; // all good From 5a0137ca34deb6e1e2e890a52cb4b22d645c166b Mon Sep 17 00:00:00 2001 From: AN Long Date: Sat, 9 Dec 2023 00:52:22 +0800 Subject: [PATCH 165/442] gh-112278: In _wmi, treat initialization timeout separately from connection timeout (GH-112878) --- PC/_wmimodule.cpp | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/PC/_wmimodule.cpp b/PC/_wmimodule.cpp index 215350acfb0d8e..5ab6dcb032550b 100644 --- a/PC/_wmimodule.cpp +++ b/PC/_wmimodule.cpp @@ -44,6 +44,7 @@ struct _query_data { LPCWSTR query; HANDLE writePipe; HANDLE readPipe; + HANDLE initEvent; HANDLE connectEvent; }; @@ -81,13 +82,16 @@ _query_thread(LPVOID param) IID_IWbemLocator, (LPVOID *)&locator ); } + if (SUCCEEDED(hr) && !SetEvent(data->initEvent)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } if (SUCCEEDED(hr)) { hr = locator->ConnectServer( bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &services ); } - if (!SetEvent(data->connectEvent)) { + if (SUCCEEDED(hr) && !SetEvent(data->connectEvent)) { hr = HRESULT_FROM_WIN32(GetLastError()); } if (SUCCEEDED(hr)) { @@ -193,6 +197,24 @@ _query_thread(LPVOID param) } +static DWORD +wait_event(HANDLE event, DWORD timeout) +{ + DWORD err = 0; + switch (WaitForSingleObject(event, timeout)) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + err = WAIT_TIMEOUT; + break; + default: + err = GetLastError(); + break; + } + return err; +} + + /*[clinic input] _wmi.exec_query @@ -235,8 +257,11 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) Py_BEGIN_ALLOW_THREADS + data.initEvent = CreateEvent(NULL, TRUE, FALSE, NULL); data.connectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!data.connectEvent || !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) { + if (!data.initEvent || !data.connectEvent || + !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) + { err = GetLastError(); } else { hThread = CreateThread(NULL, 0, _query_thread, (LPVOID*)&data, 0, NULL); @@ -251,16 +276,12 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) // gh-112278: If current user doesn't have permission to query the WMI, the // function IWbemLocator::ConnectServer will hang for 5 seconds, and there // is no way to specify the timeout. So we use an Event object to simulate - // a timeout. - switch (WaitForSingleObject(data.connectEvent, 100)) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - err = WAIT_TIMEOUT; - break; - default: - err = GetLastError(); - break; + // a timeout. The initEvent will be set after COM initialization, it will + // take a longer time when first initialized. The connectEvent will be set + // after connected to WMI. + err = wait_event(data.initEvent, 1000); + if (!err) { + err = wait_event(data.connectEvent, 100); } while (!err) { @@ -306,6 +327,7 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) } CloseHandle(hThread); + CloseHandle(data.initEvent); CloseHandle(data.connectEvent); hThread = NULL; From 76929fdeebc5f89655a7a535c19fdcece9728a7d Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 8 Dec 2023 17:39:04 +0000 Subject: [PATCH 166/442] GH-110109: Add `pathlib._PurePathBase` (#110670) Add private `pathlib._PurePathBase` class: a private superclass of both `PurePath` and `_PathBase`. Unlike `PurePath`, it does not define any of these special methods: `__fspath__`, `__bytes__`, `__reduce__`, `__hash__`, `__eq__`, `__lt__`, `__le__`, `__gt__`, `__ge__`. Its initializer and path joining methods accept only strings, not os.PathLike objects more broadly. This is important for supporting *virtual paths*: user subclasses of `_PathBase` that provide access to archive files, FTP servers, etc. In these classes, the above methods should be implemented by users only as appropriate, with due consideration for the hash/equality of any backing objects, such as file objects or sockets. --- Lib/pathlib.py | 83 +++++++++++-------- Lib/test/test_pathlib.py | 82 ++++++++++++++---- ...-10-11-02-34-01.gh-issue-110109.RFCmHs.rst | 3 + 3 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index c48cff307083a8..87d1f6b58ec52e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -198,14 +198,13 @@ def __repr__(self): return "<{}.parents>".format(type(self._path).__name__) -class PurePath: - """Base class for manipulating paths without I/O. +class _PurePathBase: + """Base class for pure path objects. - PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. + This class *does not* provide several magic methods that are defined in + its subclass PurePath. They are: __fspath__, __bytes__, __reduce__, + __hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path + joining methods accept only strings, not os.PathLike objects more broadly. """ __slots__ = ( @@ -227,22 +226,6 @@ class PurePath: # for the first time. It's used to implement `_str_normcase` '_str', - # The `_str_normcase_cached` slot stores the string path with - # normalized case. It is set when the `_str_normcase` property is - # accessed for the first time. It's used to implement `__eq__()` - # `__hash__()`, and `_parts_normcase` - '_str_normcase_cached', - - # The `_parts_normcase_cached` slot stores the case-normalized - # string path after splitting on path separators. It's set when the - # `_parts_normcase` property is accessed for the first time. It's used - # to implement comparison methods like `__lt__()`. - '_parts_normcase_cached', - - # The `_hash` slot stores the hash of the case-normalized string - # path. It's set when `__hash__()` is called for the first time. - '_hash', - # The '_resolving' slot stores a boolean indicating whether the path # is being processed by `_PathBase.resolve()`. This prevents duplicate # work from occurring when `resolve()` calls `stat()` or `readlink()`. @@ -250,6 +233,10 @@ class PurePath: ) pathmod = os.path + def __init__(self, *paths): + self._raw_paths = paths + self._resolving = False + def with_segments(self, *pathsegments): """Construct a new path object from any number of path-like objects. Subclasses may override this method to customize how new path objects @@ -444,7 +431,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, PurePath): + elif not isinstance(other, _PurePathBase): other = self.with_segments(other) for step, path in enumerate(chain([other], other.parents)): if path == self or path in self.parents: @@ -468,7 +455,7 @@ def is_relative_to(self, other, /, *_deprecated): warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, PurePath): + elif not isinstance(other, _PurePathBase): other = self.with_segments(other) return other == self or other in self.parents @@ -487,7 +474,7 @@ def joinpath(self, *pathsegments): paths) or a totally different path (if one of the arguments is anchored). """ - return self.with_segments(self, *pathsegments) + return self.with_segments(*self._raw_paths, *pathsegments) def __truediv__(self, key): try: @@ -497,7 +484,7 @@ def __truediv__(self, key): def __rtruediv__(self, key): try: - return self.with_segments(key, self) + return self.with_segments(key, *self._raw_paths) except TypeError: return NotImplemented @@ -555,7 +542,7 @@ def match(self, path_pattern, *, case_sensitive=None): """ Return True if this path matches the given pattern. """ - if not isinstance(path_pattern, PurePath): + 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) @@ -570,6 +557,35 @@ def match(self, path_pattern, *, case_sensitive=None): match = _compile_pattern(pattern_str, sep, case_sensitive) return match(str(self)) is not None + +class PurePath(_PurePathBase): + """Base class for manipulating paths without I/O. + + PurePath represents a filesystem path and offers operations which + don't imply any actual filesystem I/O. Depending on your system, + instantiating a PurePath will return either a PurePosixPath or a + PureWindowsPath object. You can also instantiate either of these classes + directly, regardless of your system. + """ + + __slots__ = ( + # The `_str_normcase_cached` slot stores the string path with + # normalized case. It is set when the `_str_normcase` property is + # accessed for the first time. It's used to implement `__eq__()` + # `__hash__()`, and `_parts_normcase` + '_str_normcase_cached', + + # The `_parts_normcase_cached` slot stores the case-normalized + # string path after splitting on path separators. It's set when the + # `_parts_normcase` property is accessed for the first time. It's used + # to implement comparison methods like `__lt__()`. + '_parts_normcase_cached', + + # The `_hash` slot stores the hash of the case-normalized string + # path. It's set when `__hash__()` is called for the first time. + '_hash', + ) + def __new__(cls, *args, **kwargs): """Construct a PurePath from one or several strings and or existing PurePath objects. The strings and path objects are combined so as @@ -600,8 +616,7 @@ def __init__(self, *args): "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") paths.append(path) - self._raw_paths = paths - self._resolving = False + super().__init__(*paths) def __reduce__(self): # Using the parts tuple helps share interned path parts @@ -719,7 +734,7 @@ class PureWindowsPath(PurePath): # Filesystem-accessing classes -class _PathBase(PurePath): +class _PathBase(_PurePathBase): """Base class for concrete path objects. This class provides dummy implementations for many methods that derived @@ -733,8 +748,6 @@ class _PathBase(PurePath): such as paths in archive files or on remote storage systems. """ __slots__ = () - __bytes__ = None - __fspath__ = None # virtual paths have no local file system representation @classmethod def _unsupported(cls, method_name): @@ -1341,7 +1354,7 @@ def as_uri(self): self._unsupported("as_uri") -class Path(_PathBase): +class Path(_PathBase, PurePath): """PurePath subclass that can make system calls. Path represents a filesystem path but unlike PurePath, also offers @@ -1351,8 +1364,6 @@ class Path(_PathBase): but cannot instantiate a WindowsPath on a POSIX system or vice versa. """ __slots__ = () - __bytes__ = PurePath.__bytes__ - __fspath__ = PurePath.__fspath__ as_uri = PurePath.as_uri def __init__(self, *args, **kwargs): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index ea922143e36e48..d35516a5c8fd9b 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -49,8 +49,35 @@ def test_is_notimplemented(self): # Tests for the pure classes. # -class PurePathTest(unittest.TestCase): - cls = pathlib.PurePath + +class PurePathBaseTest(unittest.TestCase): + cls = pathlib._PurePathBase + + def test_magic_methods(self): + P = self.cls + self.assertFalse(hasattr(P, '__fspath__')) + self.assertFalse(hasattr(P, '__bytes__')) + self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__hash__, object.__hash__) + self.assertIs(P.__eq__, object.__eq__) + self.assertIs(P.__lt__, object.__lt__) + self.assertIs(P.__le__, object.__le__) + self.assertIs(P.__gt__, object.__gt__) + self.assertIs(P.__ge__, object.__ge__) + + +class DummyPurePath(pathlib._PurePathBase): + def __eq__(self, other): + if not isinstance(other, DummyPurePath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + +class DummyPurePathTest(unittest.TestCase): + cls = DummyPurePath # Keys are canonical paths, values are list of tuples of arguments # supposed to produce equal paths. @@ -82,12 +109,6 @@ def test_constructor_common(self): P('/a', 'b', 'c') P('a/b/c') P('/a/b/c') - P(FakePath("a/b/c")) - self.assertEqual(P(P('a')), P('a')) - self.assertEqual(P(P('a'), 'b'), P('a/b')) - self.assertEqual(P(P('a'), P('b')), P('a/b')) - self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) - self.assertEqual(P(P('./a:b')), P('./a:b')) def test_concrete_class(self): if self.cls is pathlib.PurePath: @@ -193,8 +214,6 @@ def test_join_common(self): self.assertIs(type(pp), type(p)) pp = p.joinpath('c', 'd') self.assertEqual(pp, P('a/b/c/d')) - pp = p.joinpath(P('c')) - self.assertEqual(pp, P('a/b/c')) pp = p.joinpath('/c') self.assertEqual(pp, P('/c')) @@ -211,8 +230,6 @@ def test_div_common(self): self.assertEqual(pp, P('a/b/c/d')) pp = 'c' / p / 'd' self.assertEqual(pp, P('c/a/b/d')) - pp = p / P('c') - self.assertEqual(pp, P('a/b/c')) pp = p/ '/c' self.assertEqual(pp, P('/c')) @@ -678,6 +695,29 @@ def test_is_relative_to_common(self): self.assertFalse(p.is_relative_to('')) self.assertFalse(p.is_relative_to(P('a'))) + +class PurePathTest(DummyPurePathTest): + cls = pathlib.PurePath + + def test_constructor_nested(self): + P = self.cls + P(FakePath("a/b/c")) + self.assertEqual(P(P('a')), P('a')) + self.assertEqual(P(P('a'), 'b'), P('a/b')) + self.assertEqual(P(P('a'), P('b')), P('a/b')) + self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) + self.assertEqual(P(P('./a:b')), P('./a:b')) + + def test_join_nested(self): + P = self.cls + p = P('a/b').joinpath(P('c')) + self.assertEqual(p, P('a/b/c')) + + def test_div_nested(self): + P = self.cls + p = P('a/b') / P('c') + self.assertEqual(p, P('a/b/c')) + def test_pickling_common(self): P = self.cls p = P('/a/b') @@ -1545,7 +1585,7 @@ class cls(pathlib.PurePath): # Tests for the virtual classes. # -class PathBaseTest(PurePathTest): +class PathBaseTest(PurePathBaseTest): cls = pathlib._PathBase def test_unsupported_operation(self): @@ -1636,6 +1676,14 @@ class DummyPath(pathlib._PathBase): _directories = {} _symlinks = {} + def __eq__(self, other): + if not isinstance(other, DummyPath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + def stat(self, *, follow_symlinks=True): if follow_symlinks: path = str(self.resolve()) @@ -1707,7 +1755,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False): self.mkdir(mode, parents=False, exist_ok=exist_ok) -class DummyPathTest(unittest.TestCase): +class DummyPathTest(DummyPurePathTest): """Tests for PathBase methods that use stat(), open() and iterdir().""" cls = DummyPath @@ -2014,7 +2062,7 @@ def _check(path, glob, expected): def test_rglob_common(self): def _check(glob, expected): - self.assertEqual(sorted(glob), sorted(P(BASE, q) for q in expected)) + self.assertEqual(set(glob), {P(BASE, q) for q in expected}) P = self.cls p = P(BASE) it = p.rglob("fileA") @@ -2198,7 +2246,7 @@ def test_glob_above_recursion_limit(self): # directory_depth > recursion_limit directory_depth = recursion_limit + 10 base = self.cls(BASE, 'deep') - path = self.cls(base, *(['d'] * directory_depth)) + path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) with set_recursion_limit(recursion_limit): @@ -2741,7 +2789,7 @@ def test_walk_above_recursion_limit(self): # directory_depth > recursion_limit directory_depth = recursion_limit + 10 base = self.cls(BASE, 'deep') - path = self.cls(base, *(['d'] * directory_depth)) + path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) with set_recursion_limit(recursion_limit): diff --git a/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst b/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst new file mode 100644 index 00000000000000..4f12d128f49fb3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst @@ -0,0 +1,3 @@ +Add private ``pathlib._PurePathBase`` class: a base class for +:class:`pathlib.PurePath` that omits certain magic methods. It may be made +public (along with ``_PathBase``) in future. From f3bff4ee9d3e4276949e5cde81180195b95bacb9 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 8 Dec 2023 12:05:56 -0600 Subject: [PATCH 167/442] gh-112540: Support zero inputs in geometric_mean() (gh-112880) --- Lib/statistics.py | 30 ++++++++++++++----- Lib/test/test_statistics.py | 12 ++++++-- ...-12-08-11-17-17.gh-issue-112540.Pm5egX.rst | 2 ++ 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst diff --git a/Lib/statistics.py b/Lib/statistics.py index 4da06889c6db46..83aaedb04515e0 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -527,8 +527,10 @@ def count(iterable): def geometric_mean(data): """Convert data to floats and compute the geometric mean. - Raises a StatisticsError if the input dataset is empty, - if it contains a zero, or if it contains a negative value. + Raises a StatisticsError if the input dataset is empty + or if it contains a negative value. + + Returns zero if the product of inputs is zero. No special efforts are made to achieve exact results. (However, this may change in the future.) @@ -536,11 +538,25 @@ def geometric_mean(data): >>> round(geometric_mean([54, 24, 36]), 9) 36.0 """ - try: - return exp(fmean(map(log, data))) - except ValueError: - raise StatisticsError('geometric mean requires a non-empty dataset ' - 'containing positive numbers') from None + n = 0 + found_zero = False + def count_positive(iterable): + nonlocal n, found_zero + for n, x in enumerate(iterable, start=1): + if x > 0.0 or math.isnan(x): + yield x + elif x == 0.0: + found_zero = True + else: + raise StatisticsError('No negative inputs allowed', x) + total = fsum(map(log, count_positive(data))) + if not n: + raise StatisticsError('Must have a non-empty dataset') + if math.isnan(total): + return math.nan + if found_zero: + return math.nan if total == math.inf else 0.0 + return exp(total / n) def harmonic_mean(data, weights=None): diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index b24fc3c3d077fe..bf2c254c9ee7d9 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2302,10 +2302,12 @@ def test_error_cases(self): StatisticsError = statistics.StatisticsError with self.assertRaises(StatisticsError): geometric_mean([]) # empty input - with self.assertRaises(StatisticsError): - geometric_mean([3.5, 0.0, 5.25]) # zero input with self.assertRaises(StatisticsError): geometric_mean([3.5, -4.0, 5.25]) # negative input + with self.assertRaises(StatisticsError): + geometric_mean([0.0, -4.0, 5.25]) # negative input with zero + with self.assertRaises(StatisticsError): + geometric_mean([3.5, -math.inf, 5.25]) # negative infinity with self.assertRaises(StatisticsError): geometric_mean(iter([])) # empty iterator with self.assertRaises(TypeError): @@ -2328,6 +2330,12 @@ def test_special_values(self): with self.assertRaises(ValueError): geometric_mean([Inf, -Inf]) + # Cases with zero + self.assertEqual(geometric_mean([3, 0.0, 5]), 0.0) # Any zero gives a zero + self.assertEqual(geometric_mean([3, -0.0, 5]), 0.0) # Negative zero allowed + self.assertTrue(math.isnan(geometric_mean([0, NaN]))) # NaN beats zero + self.assertTrue(math.isnan(geometric_mean([0, Inf]))) # Because 0.0 * Inf -> NaN + def test_mixed_int_and_float(self): # Regression test for b.p.o. issue #28327 geometric_mean = statistics.geometric_mean diff --git a/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst b/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst new file mode 100644 index 00000000000000..263b13d1762bf1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst @@ -0,0 +1,2 @@ +The statistics.geometric_mean() function now returns zero for datasets +containing a zero. Formerly, it would raise an exception. From ed8720ace4f73e49f149a1fdd548063ee05f42d5 Mon Sep 17 00:00:00 2001 From: Taylor Packard <3.t.packard@gmail.com> Date: Fri, 8 Dec 2023 13:13:17 -0500 Subject: [PATCH 168/442] gh-112758: Updated pathlib documentation for PurePath.match (#112814) --- Doc/library/pathlib.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 43200e269f56f4..60791725c2323d 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -595,6 +595,9 @@ Pure paths provide the following methods and properties: >>> PurePath('a/b.py').match(pattern) True + .. versionchanged:: 3.12 + Accepts an object implementing the :class:`os.PathLike` interface. + As with other methods, case-sensitivity follows platform defaults:: >>> PurePosixPath('b.py').match('*.PY') From 10e9bb13b8dcaa414645b9bd10718d8f7179e82b Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Fri, 8 Dec 2023 16:18:35 -0800 Subject: [PATCH 169/442] gh-112334: Regression test that vfork is used when expected. (#112734) Regression test that vfork is used when expected by subprocess. This is written integration test style, it uses strace if it is present and appears to work to find out what system call actually gets used in different scenarios. Test coverage is added for the default behavior and that of each of the specific arguments that must disable the use of vfork. obviously not an entire test matrix, but it covers the most important aspects. If there are ever issues with this test being flaky or failing on new platforms, rather than try and adapt it for all possible platforms, feel free to narrow the range it gets tested on when appropriate. That is not likely to reduce coverage. --- .github/workflows/posix-deps-apt.sh | 1 + Lib/test/support/script_helper.py | 15 +++ Lib/test/test_subprocess.py | 98 ++++++++++++++++--- ...-12-04-15-56-11.gh-issue-112334.FFc9Ti.rst | 2 + 4 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2023-12-04-15-56-11.gh-issue-112334.FFc9Ti.rst diff --git a/.github/workflows/posix-deps-apt.sh b/.github/workflows/posix-deps-apt.sh index bbae378f7b994e..0800401f4cd113 100755 --- a/.github/workflows/posix-deps-apt.sh +++ b/.github/workflows/posix-deps-apt.sh @@ -21,6 +21,7 @@ apt-get -yq install \ libssl-dev \ lzma \ lzma-dev \ + strace \ tk-dev \ uuid-dev \ xvfb \ diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index 7dffe79a0da04e..759020c33aa700 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -92,13 +92,28 @@ def fail(self, cmd_line): # Executing the interpreter in a subprocess @support.requires_subprocess() def run_python_until_end(*args, **env_vars): + """Used to implement assert_python_*. + + *args are the command line flags to pass to the python interpreter. + **env_vars keyword arguments are environment variables to set on the process. + + If __run_using_command= is supplied, it must be a list of + command line arguments to prepend to the command line used. + Useful when you want to run another command that should launch the + python interpreter via its own arguments. ["/bin/echo", "--"] for + example could print the unquoted python command line instead of + run it. + """ env_required = interpreter_requires_environment() + run_using_command = env_vars.pop('__run_using_command', None) cwd = env_vars.pop('__cwd', None) if '__isolated' in env_vars: isolated = env_vars.pop('__isolated') else: isolated = not env_vars and not env_required cmd_line = [sys.executable, '-X', 'faulthandler'] + if run_using_command: + cmd_line = run_using_command + cmd_line if isolated: # isolated mode: ignore Python environment variables, ignore user # site-packages, and don't add the current directory to sys.path diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 319bc0d2638563..5eeea54fd55f1a 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1561,21 +1561,6 @@ def test_class_getitems(self): self.assertIsInstance(subprocess.Popen[bytes], types.GenericAlias) self.assertIsInstance(subprocess.CompletedProcess[str], types.GenericAlias) - @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), - "vfork() not enabled by configure.") - @mock.patch("subprocess._fork_exec") - def test__use_vfork(self, mock_fork_exec): - self.assertTrue(subprocess._USE_VFORK) # The default value regardless. - mock_fork_exec.side_effect = RuntimeError("just testing args") - with self.assertRaises(RuntimeError): - subprocess.run([sys.executable, "-c", "pass"]) - mock_fork_exec.assert_called_once() - self.assertTrue(mock_fork_exec.call_args.args[-1]) - with mock.patch.object(subprocess, '_USE_VFORK', False): - with self.assertRaises(RuntimeError): - subprocess.run([sys.executable, "-c", "pass"]) - self.assertFalse(mock_fork_exec.call_args_list[-1].args[-1]) - class RunFuncTestCase(BaseTestCase): def run_python(self, code, **kwargs): @@ -3360,6 +3345,89 @@ def exit_handler(): self.assertEqual(out, b'') self.assertIn(b"preexec_fn not supported at interpreter shutdown", err) + @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), + "vfork() not enabled by configure.") + @mock.patch("subprocess._fork_exec") + def test__use_vfork(self, mock_fork_exec): + self.assertTrue(subprocess._USE_VFORK) # The default value regardless. + mock_fork_exec.side_effect = RuntimeError("just testing args") + with self.assertRaises(RuntimeError): + subprocess.run([sys.executable, "-c", "pass"]) + mock_fork_exec.assert_called_once() + # NOTE: These assertions are *ugly* as they require the last arg + # to remain the have_vfork boolean. We really need to refactor away + # from the giant "wall of args" internal C extension API. + self.assertTrue(mock_fork_exec.call_args.args[-1]) + with mock.patch.object(subprocess, '_USE_VFORK', False): + with self.assertRaises(RuntimeError): + subprocess.run([sys.executable, "-c", "pass"]) + self.assertFalse(mock_fork_exec.call_args_list[-1].args[-1]) + + @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), + "vfork() not enabled by configure.") + @unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.") + def test_vfork_used_when_expected(self): + # This is a performance regression test to ensure we default to using + # vfork() when possible. + strace_binary = "/usr/bin/strace" + # The only system calls we are interested in. + strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group" + true_binary = "/bin/true" + strace_command = [strace_binary, strace_filter] + + try: + does_strace_work_process = subprocess.run( + strace_command + [true_binary], + stderr=subprocess.PIPE, + stdout=subprocess.DEVNULL, + ) + rc = does_strace_work_process.returncode + stderr = does_strace_work_process.stderr + except OSError: + rc = -1 + stderr = "" + if rc or (b"+++ exited with 0 +++" not in stderr): + self.skipTest("strace not found or not working as expected.") + + with self.subTest(name="default_is_vfork"): + vfork_result = assert_python_ok( + "-c", + textwrap.dedent(f"""\ + import subprocess + subprocess.check_call([{true_binary!r}])"""), + __run_using_command=strace_command, + ) + # Match both vfork() and clone(..., flags=...|CLONE_VFORK|...) + self.assertRegex(vfork_result.err, br"(?i)vfork") + # Do NOT check that fork() or other clones did not happen. + # If the OS denys the vfork it'll fallback to plain fork(). + + # Test that each individual thing that would disable the use of vfork + # actually disables it. + for sub_name, preamble, sp_kwarg, expect_permission_error in ( + ("!use_vfork", "subprocess._USE_VFORK = False", "", False), + ("preexec", "", "preexec_fn=lambda: None", False), + ("setgid", "", f"group={os.getgid()}", True), + ("setuid", "", f"user={os.getuid()}", True), + ("setgroups", "", "extra_groups=[]", True), + ): + with self.subTest(name=sub_name): + non_vfork_result = assert_python_ok( + "-c", + textwrap.dedent(f"""\ + import subprocess + {preamble} + try: + subprocess.check_call( + [{true_binary!r}], **dict({sp_kwarg})) + except PermissionError: + if not {expect_permission_error}: + raise"""), + __run_using_command=strace_command, + ) + # Ensure neither vfork() or clone(..., flags=...|CLONE_VFORK|...). + self.assertNotRegex(non_vfork_result.err, br"(?i)vfork") + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): diff --git a/Misc/NEWS.d/next/Tests/2023-12-04-15-56-11.gh-issue-112334.FFc9Ti.rst b/Misc/NEWS.d/next/Tests/2023-12-04-15-56-11.gh-issue-112334.FFc9Ti.rst new file mode 100644 index 00000000000000..aeaad6e5055522 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-12-04-15-56-11.gh-issue-112334.FFc9Ti.rst @@ -0,0 +1,2 @@ +Adds a regression test to verify that ``vfork()`` is used when expected by +:mod:`subprocess` on vfork enabled POSIX systems (Linux). From c98c40227e8cd976a08ff0f6dc386b5d33f62f84 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 9 Dec 2023 10:03:02 +0000 Subject: [PATCH 170/442] gh-112720: Move instruction formatting from the dis.Instruction class to a new class dis.InstructionFormatter. Add the ArgResolver class. (#112722) --- Lib/dis.py | 422 ++++++++++++++++++++++--------------------- Lib/test/test_dis.py | 15 +- 2 files changed, 232 insertions(+), 205 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 8d3885d2526b70..efa935c5a6a0b6 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -336,93 +336,6 @@ class Instruction(_Instruction): covered by this instruction """ - @staticmethod - def _get_argval_argrepr(op, arg, offset, co_consts, names, varname_from_oparg, - labels_map): - get_name = None if names is None else names.__getitem__ - argval = None - argrepr = '' - deop = _deoptop(op) - if arg is not None: - # Set argval to the dereferenced value of the argument when - # available, and argrepr to the string representation of argval. - # _disassemble_bytes needs the string repr of the - # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. - argval = arg - if deop in hasconst: - argval, argrepr = _get_const_info(deop, arg, co_consts) - elif deop in hasname: - if deop == LOAD_GLOBAL: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL" - elif deop == LOAD_ATTR: - argval, argrepr = _get_name_info(arg//2, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - elif deop == LOAD_SUPER_ATTR: - argval, argrepr = _get_name_info(arg//4, get_name) - if (arg & 1) and argrepr: - argrepr = f"{argrepr} + NULL|self" - else: - argval, argrepr = _get_name_info(arg, get_name) - elif deop in hasjabs: - argval = arg*2 - argrepr = f"to L{labels_map[argval]}" - elif deop in hasjrel: - signed_arg = -arg if _is_backward_jump(deop) else arg - argval = offset + 2 + signed_arg*2 - caches = _get_cache_size(_all_opname[deop]) - argval += 2 * caches - if deop == ENTER_EXECUTOR: - argval += 2 - argrepr = f"to L{labels_map[argval]}" - elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): - arg1 = arg >> 4 - arg2 = arg & 15 - val1, argrepr1 = _get_name_info(arg1, varname_from_oparg) - val2, argrepr2 = _get_name_info(arg2, varname_from_oparg) - argrepr = argrepr1 + ", " + argrepr2 - argval = val1, val2 - elif deop in haslocal or deop in hasfree: - argval, argrepr = _get_name_info(arg, varname_from_oparg) - elif deop in hascompare: - argval = cmp_op[arg >> 5] - argrepr = argval - if arg & 16: - argrepr = f"bool({argrepr})" - elif deop == CONVERT_VALUE: - argval = (None, str, repr, ascii)[arg] - argrepr = ('', 'str', 'repr', 'ascii')[arg] - elif deop == SET_FUNCTION_ATTRIBUTE: - argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) - if arg & (1<' marker arrow as part of the line *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field + + *line_offset* the line number (within the code unit) """ + self.file = file + self.lineno_width = lineno_width + self.offset_width = offset_width + self.label_width = label_width + + + def print_instruction(self, instr, mark_as_current=False): + """Format instruction details for inclusion in disassembly output.""" + lineno_width = self.lineno_width + offset_width = self.offset_width + label_width = self.label_width + + new_source_line = (lineno_width > 0 and + instr.starts_line and + instr.offset > 0) + if new_source_line: + print(file=self.file) + fields = [] # Column: Source code line number if lineno_width: - if self.starts_line: - lineno_fmt = "%%%dd" if self.line_number is not None else "%%%ds" + if instr.starts_line: + lineno_fmt = "%%%dd" if instr.line_number is not None else "%%%ds" lineno_fmt = lineno_fmt % lineno_width - lineno = self.line_number if self.line_number is not None else '--' + lineno = _NO_LINENO if instr.line_number is None else instr.line_number fields.append(lineno_fmt % lineno) else: fields.append(' ' * lineno_width) # Column: Label - if self.label is not None: - lbl = f"L{self.label}:" + if instr.label is not None: + lbl = f"L{instr.label}:" fields.append(f"{lbl:>{label_width}}") else: fields.append(' ' * label_width) # Column: Instruction offset from start of code sequence if offset_width > 0: - fields.append(f"{repr(self.offset):>{offset_width}} ") + fields.append(f"{repr(instr.offset):>{offset_width}} ") # Column: Current instruction indicator if mark_as_current: fields.append('-->') else: fields.append(' ') # Column: Opcode name - fields.append(self.opname.ljust(_OPNAME_WIDTH)) + fields.append(instr.opname.ljust(_OPNAME_WIDTH)) # Column: Opcode argument - if self.arg is not None: - arg = repr(self.arg) + if instr.arg is not None: + arg = repr(instr.arg) # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into # the space reserved for oparg. This results in fewer misaligned opargs # in the disassembly output. - opname_excess = max(0, len(self.opname) - _OPNAME_WIDTH) - fields.append(repr(self.arg).rjust(_OPARG_WIDTH - opname_excess)) + opname_excess = max(0, len(instr.opname) - _OPNAME_WIDTH) + fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess)) # Column: Opcode argument details - if self.argrepr: - fields.append('(' + self.argrepr + ')') - return ' '.join(fields).rstrip() + if instr.argrepr: + fields.append('(' + instr.argrepr + ')') + print(' '.join(fields).rstrip(), file=self.file) + + def print_exception_table(self, exception_entries): + file = self.file + if exception_entries: + print("ExceptionTable:", file=file) + for entry in exception_entries: + lasti = " lasti" if entry.lasti else "" + start = entry.start_label + end = entry.end_label + target = entry.target_label + print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) + + +class ArgResolver: + def __init__(self, co_consts, names, varname_from_oparg, labels_map): + self.co_consts = co_consts + self.names = names + self.varname_from_oparg = varname_from_oparg + self.labels_map = labels_map + + def get_argval_argrepr(self, op, arg, offset): + get_name = None if self.names is None else self.names.__getitem__ + argval = None + argrepr = '' + deop = _deoptop(op) + if arg is not None: + # Set argval to the dereferenced value of the argument when + # available, and argrepr to the string representation of argval. + # _disassemble_bytes needs the string repr of the + # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. + argval = arg + if deop in hasconst: + argval, argrepr = _get_const_info(deop, arg, self.co_consts) + elif deop in hasname: + if deop == LOAD_GLOBAL: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL" + elif deop == LOAD_ATTR: + argval, argrepr = _get_name_info(arg//2, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + elif deop == LOAD_SUPER_ATTR: + argval, argrepr = _get_name_info(arg//4, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + NULL|self" + else: + argval, argrepr = _get_name_info(arg, get_name) + elif deop in hasjabs: + argval = arg*2 + argrepr = f"to L{self.labels_map[argval]}" + elif deop in hasjrel: + signed_arg = -arg if _is_backward_jump(deop) else arg + argval = offset + 2 + signed_arg*2 + caches = _get_cache_size(_all_opname[deop]) + argval += 2 * caches + if deop == ENTER_EXECUTOR: + argval += 2 + argrepr = f"to L{self.labels_map[argval]}" + elif deop in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST): + arg1 = arg >> 4 + arg2 = arg & 15 + val1, argrepr1 = _get_name_info(arg1, self.varname_from_oparg) + val2, argrepr2 = _get_name_info(arg2, self.varname_from_oparg) + argrepr = argrepr1 + ", " + argrepr2 + argval = val1, val2 + elif deop in haslocal or deop in hasfree: + argval, argrepr = _get_name_info(arg, self.varname_from_oparg) + elif deop in hascompare: + argval = cmp_op[arg >> 5] + argrepr = argval + if arg & 16: + argrepr = f"bool({argrepr})" + elif deop == CONVERT_VALUE: + argval = (None, str, repr, ascii)[arg] + argrepr = ('', 'str', 'repr', 'ascii')[arg] + elif deop == SET_FUNCTION_ATTRIBUTE: + argrepr = ', '.join(s for i, s in enumerate(FUNCTION_ATTR_FLAGS) + if arg & (1< 0 - else: - show_lineno = False - if show_lineno: - maxlineno = max(linestarts_ints) + line_offset - if maxlineno >= 1000: - lineno_width = len(str(maxlineno)) - else: - lineno_width = 3 + offset_width = len(str(max(len(code) - 2, 9999))) if show_offsets else 0 - if lineno_width < len(str(None)) and None in linestarts.values(): - lineno_width = len(str(None)) - else: - lineno_width = 0 - if show_offsets: - maxoffset = len(code) - 2 - if maxoffset >= 10000: - offset_width = len(str(maxoffset)) - else: - offset_width = 4 - else: - offset_width = 0 - - label_width = -1 - for instr in _get_instructions_bytes(code, varname_from_oparg, names, - co_consts, linestarts, - line_offset=line_offset, - exception_entries=exception_entries, - co_positions=co_positions, - show_caches=show_caches, - original_code=original_code): - new_source_line = (show_lineno and - instr.starts_line and - instr.offset > 0) - if new_source_line: - print(file=file) + labels_map = _make_labels_map(original_code or code, exception_entries) + label_width = 4 + len(str(len(labels_map))) + + formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), + offset_width=offset_width, + label_width=label_width, + line_offset=line_offset) + + arg_resolver = ArgResolver(co_consts, names, varname_from_oparg, labels_map) + instrs = _get_instructions_bytes(code, linestarts=linestarts, + line_offset=line_offset, + co_positions=co_positions, + show_caches=show_caches, + original_code=original_code, + labels_map=labels_map, + arg_resolver=arg_resolver) + + print_instructions(instrs, exception_entries, formatter, + show_caches=show_caches, lasti=lasti) + + +def print_instructions(instrs, exception_entries, formatter, show_caches=False, lasti=-1): + for instr in instrs: if show_caches: is_current_instr = instr.offset == lasti else: # Each CACHE takes 2 bytes is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) - label_width = getattr(instr, 'label_width', label_width) - assert label_width >= 0 - print(instr._disassemble(lineno_width, is_current_instr, offset_width, label_width), - file=file) - if exception_entries: - print("ExceptionTable:", file=file) - for entry in exception_entries: - lasti = " lasti" if entry.lasti else "" - start = entry.start_label - end = entry.end_label - target = entry.target_label - print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file) + formatter.print_instruction(instr, is_current_instr) + formatter.print_exception_table(exception_entries) def _disassemble_str(source, **kwargs): """Compile the source string, then disassemble the code object.""" @@ -927,15 +944,18 @@ def __init__(self, x, *, first_line=None, current_offset=None, show_caches=False def __iter__(self): co = self.codeobj + original_code = co.co_code + labels_map = _make_labels_map(original_code, self.exception_entries) + arg_resolver = ArgResolver(co.co_consts, co.co_names, co._varname_from_oparg, + labels_map) return _get_instructions_bytes(_get_code_array(co, self.adaptive), - co._varname_from_oparg, - co.co_names, co.co_consts, - self._linestarts, + linestarts=self._linestarts, line_offset=self._line_offset, - exception_entries=self.exception_entries, co_positions=co.co_positions(), show_caches=self.show_caches, - original_code=co.co_code) + original_code=original_code, + labels_map=labels_map, + arg_resolver=arg_resolver) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 349790ecd7d075..0ea4dc4566a4a4 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1785,6 +1785,12 @@ def __init__(self, *args): super().__init__(*args) self.maxDiff = None + def test_instruction_str(self): + # smoke test for __str__ + instrs = dis.get_instructions(simple) + for instr in instrs: + str(instr) + def test_default_first_line(self): actual = dis.get_instructions(simple) self.assertInstructionsEqual(list(actual), expected_opinfo_simple) @@ -1955,15 +1961,16 @@ def test_jump_target(self): self.assertEqual(10 + 2 + 1*2 + 100*2, instruction.jump_target) def test_argval_argrepr(self): - def f(*args): - return dis.Instruction._get_argval_argrepr( - *args, labels_map={24: 1}) + def f(opcode, oparg, offset, *init_args): + arg_resolver = dis.ArgResolver(*init_args) + return arg_resolver.get_argval_argrepr(opcode, oparg, offset) offset = 42 co_consts = (0, 1, 2, 3) names = {1: 'a', 2: 'b'} varname_from_oparg = lambda i : names[i] - args = (offset, co_consts, names, varname_from_oparg) + labels_map = {24: 1} + args = (offset, co_consts, names, varname_from_oparg, labels_map) self.assertEqual(f(opcode.opmap["POP_TOP"], None, *args), (None, '')) self.assertEqual(f(opcode.opmap["LOAD_CONST"], 1, *args), (1, '1')) self.assertEqual(f(opcode.opmap["LOAD_GLOBAL"], 2, *args), ('a', 'a')) From a98e7a8112f5d77fd647e70c4cf4264b2fd12288 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 9 Dec 2023 15:07:40 +0000 Subject: [PATCH 171/442] GH-110109: Move pathlib ABCs to new `pathlib._abc` module. (#112881) Move `_PurePathBase` and `_PathBase` to a new `pathlib._abc` module, and drop the underscores from the class names. Tests are mostly left alone in this commit, but they'll be similarly split in a subsequent commit. The `pathlib._abc` module will be published as an independent PyPI package (similar to how `zipfile._path` is published as `zipp`), to be refined and stabilised prior to its possible addition to the standard library. --- Lib/pathlib/__init__.py | 507 +++++++++++++++++++++++++++ Lib/{pathlib.py => pathlib/_abc.py} | 514 +--------------------------- Lib/test/test_pathlib.py | 8 +- 3 files changed, 518 insertions(+), 511 deletions(-) create mode 100644 Lib/pathlib/__init__.py rename Lib/{pathlib.py => pathlib/_abc.py} (70%) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py new file mode 100644 index 00000000000000..f4668ab3270e51 --- /dev/null +++ b/Lib/pathlib/__init__.py @@ -0,0 +1,507 @@ +"""Object-oriented filesystem paths. + +This module provides classes to represent abstract paths and concrete +paths with operations that have semantics appropriate for different +operating systems. +""" + +import io +import ntpath +import os +import posixpath + +try: + import pwd +except ImportError: + pwd = None +try: + import grp +except ImportError: + grp = None + +from . import _abc + + +__all__ = [ + "UnsupportedOperation", + "PurePath", "PurePosixPath", "PureWindowsPath", + "Path", "PosixPath", "WindowsPath", + ] + + +UnsupportedOperation = _abc.UnsupportedOperation + + +class PurePath(_abc.PurePathBase): + """Base class for manipulating paths without I/O. + + PurePath represents a filesystem path and offers operations which + don't imply any actual filesystem I/O. Depending on your system, + instantiating a PurePath will return either a PurePosixPath or a + PureWindowsPath object. You can also instantiate either of these classes + directly, regardless of your system. + """ + + __slots__ = ( + # The `_str_normcase_cached` slot stores the string path with + # normalized case. It is set when the `_str_normcase` property is + # accessed for the first time. It's used to implement `__eq__()` + # `__hash__()`, and `_parts_normcase` + '_str_normcase_cached', + + # The `_parts_normcase_cached` slot stores the case-normalized + # string path after splitting on path separators. It's set when the + # `_parts_normcase` property is accessed for the first time. It's used + # to implement comparison methods like `__lt__()`. + '_parts_normcase_cached', + + # The `_hash` slot stores the hash of the case-normalized string + # path. It's set when `__hash__()` is called for the first time. + '_hash', + ) + + def __new__(cls, *args, **kwargs): + """Construct a PurePath from one or several strings and or existing + PurePath objects. The strings and path objects are combined so as + to yield a canonicalized path, which is incorporated into the + new PurePath object. + """ + if cls is PurePath: + cls = PureWindowsPath if os.name == 'nt' else PurePosixPath + return object.__new__(cls) + + def __init__(self, *args): + paths = [] + for arg in args: + if isinstance(arg, PurePath): + if arg.pathmod is ntpath and self.pathmod is posixpath: + # GH-103631: Convert separators for backwards compatibility. + paths.extend(path.replace('\\', '/') for path in arg._raw_paths) + else: + paths.extend(arg._raw_paths) + else: + try: + path = os.fspath(arg) + except TypeError: + path = arg + if not isinstance(path, str): + raise TypeError( + "argument should be a str or an os.PathLike " + "object where __fspath__ returns a str, " + f"not {type(path).__name__!r}") + paths.append(path) + super().__init__(*paths) + + def __reduce__(self): + # Using the parts tuple helps share interned path parts + # when pickling related paths. + return (self.__class__, self.parts) + + def __fspath__(self): + return str(self) + + def __bytes__(self): + """Return the bytes representation of the path. This is only + recommended to use under Unix.""" + return os.fsencode(self) + + @property + def _str_normcase(self): + # String with normalized case, for hashing and equality checks + try: + return self._str_normcase_cached + except AttributeError: + if _abc._is_case_sensitive(self.pathmod): + self._str_normcase_cached = str(self) + else: + self._str_normcase_cached = str(self).lower() + return self._str_normcase_cached + + def __hash__(self): + try: + return self._hash + except AttributeError: + self._hash = hash(self._str_normcase) + return self._hash + + def __eq__(self, other): + if not isinstance(other, PurePath): + return NotImplemented + return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod + + @property + def _parts_normcase(self): + # Cached parts with normalized case, for comparisons. + try: + return self._parts_normcase_cached + except AttributeError: + self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep) + return self._parts_normcase_cached + + def __lt__(self, other): + if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: + return NotImplemented + return self._parts_normcase < other._parts_normcase + + def __le__(self, other): + if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: + return NotImplemented + return self._parts_normcase <= other._parts_normcase + + def __gt__(self, other): + if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: + return NotImplemented + return self._parts_normcase > other._parts_normcase + + def __ge__(self, other): + if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: + return NotImplemented + return self._parts_normcase >= other._parts_normcase + + def as_uri(self): + """Return the path as a URI.""" + if not self.is_absolute(): + raise ValueError("relative path can't be expressed as a file URI") + + drive = self.drive + if len(drive) == 2 and drive[1] == ':': + # It's a path on a local drive => 'file:///c:/a/b' + prefix = 'file:///' + drive + path = self.as_posix()[2:] + elif drive: + # It's a path on a network drive => 'file://host/share/a/b' + prefix = 'file:' + path = self.as_posix() + else: + # It's a posix path => 'file:///etc/hosts' + prefix = 'file://' + path = str(self) + from urllib.parse import quote_from_bytes + return prefix + quote_from_bytes(os.fsencode(path)) + + +# Subclassing os.PathLike makes isinstance() checks slower, +# which in turn makes Path construction slower. Register instead! +os.PathLike.register(PurePath) + + +class PurePosixPath(PurePath): + """PurePath subclass for non-Windows systems. + + On a POSIX system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + pathmod = posixpath + __slots__ = () + + +class PureWindowsPath(PurePath): + """PurePath subclass for Windows systems. + + On a Windows system, instantiating a PurePath should return this object. + However, you can also instantiate it directly on any system. + """ + pathmod = ntpath + __slots__ = () + + +class Path(_abc.PathBase, PurePath): + """PurePath subclass that can make system calls. + + Path represents a filesystem path but unlike PurePath, also offers + methods to do system calls on path objects. Depending on your system, + instantiating a Path will return either a PosixPath or a WindowsPath + object. You can also instantiate a PosixPath or WindowsPath directly, + but cannot instantiate a WindowsPath on a POSIX system or vice versa. + """ + __slots__ = () + 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 __init__(self, *args, **kwargs): + if kwargs: + import warnings + msg = ("support for supplying keyword arguments to pathlib.PurePath " + "is deprecated and scheduled for removal in Python {remove}") + warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14)) + super().__init__(*args) + + def __new__(cls, *args, **kwargs): + if cls is Path: + cls = WindowsPath if os.name == 'nt' else PosixPath + return object.__new__(cls) + + def stat(self, *, follow_symlinks=True): + """ + Return the result of the stat() system call on this path, like + os.stat() does. + """ + return os.stat(self, follow_symlinks=follow_symlinks) + + def is_mount(self): + """ + Check if this path is a mount point + """ + return os.path.ismount(self) + + def is_junction(self): + """ + Whether this path is a junction. + """ + return os.path.isjunction(self) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + """ + Open the file pointed by this path and return a file object, as + the built-in open() function does. + """ + if "b" not in mode: + encoding = io.text_encoding(encoding) + return io.open(self, mode, buffering, encoding, errors, newline) + + def iterdir(self): + """Yield path objects of the directory contents. + + The children are yielded in arbitrary order, and the + special entries '.' and '..' are not included. + """ + return (self._make_child_relpath(name) for name in os.listdir(self)) + + def _scandir(self): + return os.scandir(self) + + def absolute(self): + """Return an absolute version of this path + No normalization or symlink resolution is performed. + + Use resolve() to resolve symlinks and remove '..' segments. + """ + if self.is_absolute(): + return self + if self.root: + drive = os.path.splitroot(os.getcwd())[0] + return self._from_parsed_parts(drive, self.root, self._tail) + if self.drive: + # There is a CWD on each drive-letter drive. + cwd = os.path.abspath(self.drive) + else: + cwd = os.getcwd() + if not self._tail: + # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). + # We pass only one argument to with_segments() to avoid the cost + # of joining, and we exploit the fact that getcwd() returns a + # fully-normalized string by storing it in _str. This is used to + # implement Path.cwd(). + result = self.with_segments(cwd) + result._str = cwd + return result + drive, root, rel = os.path.splitroot(cwd) + if not rel: + return self._from_parsed_parts(drive, root, self._tail) + tail = rel.split(self.pathmod.sep) + tail.extend(self._tail) + return self._from_parsed_parts(drive, root, tail) + + def resolve(self, strict=False): + """ + Make the path absolute, resolving all symlinks on the way and also + normalizing it. + """ + + return self.with_segments(os.path.realpath(self, strict=strict)) + + if pwd: + def owner(self, *, follow_symlinks=True): + """ + Return the login name of the file owner. + """ + uid = self.stat(follow_symlinks=follow_symlinks).st_uid + return pwd.getpwuid(uid).pw_name + + if grp: + def group(self, *, follow_symlinks=True): + """ + Return the group name of the file gid. + """ + gid = self.stat(follow_symlinks=follow_symlinks).st_gid + return grp.getgrgid(gid).gr_name + + if hasattr(os, "readlink"): + def readlink(self): + """ + Return the path to which the symbolic link points. + """ + return self.with_segments(os.readlink(self)) + + def touch(self, mode=0o666, exist_ok=True): + """ + Create this file with the given access mode, if it doesn't exist. + """ + + if exist_ok: + # First try to bump modification time + # Implementation note: GNU touch uses the UTIME_NOW option of + # the utimensat() / futimens() functions. + try: + os.utime(self, None) + except OSError: + # Avoid exception chaining + pass + else: + return + flags = os.O_CREAT | os.O_WRONLY + if not exist_ok: + flags |= os.O_EXCL + fd = os.open(self, flags, mode) + os.close(fd) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + """ + Create a new directory at this given path. + """ + try: + os.mkdir(self, mode) + except FileNotFoundError: + if not parents or self.parent == self: + raise + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + except OSError: + # Cannot rely on checking for EEXIST, since the operating system + # could give priority to other errors like EACCES or EROFS + if not exist_ok or not self.is_dir(): + raise + + def chmod(self, mode, *, follow_symlinks=True): + """ + Change the permissions of the path, like os.chmod(). + """ + os.chmod(self, mode, follow_symlinks=follow_symlinks) + + def unlink(self, missing_ok=False): + """ + Remove this file or link. + If the path is a directory, use rmdir() instead. + """ + try: + os.unlink(self) + except FileNotFoundError: + if not missing_ok: + raise + + def rmdir(self): + """ + Remove this directory. The directory must be empty. + """ + os.rmdir(self) + + def rename(self, target): + """ + Rename this path to the target path. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + os.rename(self, target) + return self.with_segments(target) + + def replace(self, target): + """ + Rename this path to the target path, overwriting if that path exists. + + The target path may be absolute or relative. Relative paths are + interpreted relative to the current working directory, *not* the + directory of the Path object. + + Returns the new Path instance pointing to the target path. + """ + os.replace(self, target) + return self.with_segments(target) + + if hasattr(os, "symlink"): + 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. + """ + os.symlink(target, self, target_is_directory) + + if hasattr(os, "link"): + def hardlink_to(self, target): + """ + Make this path a hard link pointing to the same file as *target*. + + Note the order of arguments (self, target) is the reverse of os.link's. + """ + os.link(target, self) + + def expanduser(self): + """ Return a new path with expanded ~ and ~user constructs + (as returned by os.path.expanduser) + """ + if (not (self.drive or self.root) and + self._tail and self._tail[0][:1] == '~'): + homedir = os.path.expanduser(self._tail[0]) + if homedir[:1] == "~": + raise RuntimeError("Could not determine home directory.") + drv, root, tail = self._parse_path(homedir) + return self._from_parsed_parts(drv, root, tail + self._tail[1:]) + + return self + + @classmethod + def from_uri(cls, uri): + """Return a new path from the given 'file' URI.""" + if not uri.startswith('file:'): + raise ValueError(f"URI does not start with 'file:': {uri!r}") + path = uri[5:] + if path[:3] == '///': + # Remove empty authority + path = path[2:] + elif path[:12] == '//localhost/': + # Remove 'localhost' authority + path = path[11:] + if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'): + # Remove slash before DOS device/UNC path + path = path[1:] + if path[1:2] == '|': + # Replace bar with colon in DOS drive + path = path[:1] + ':' + path[2:] + from urllib.parse import unquote_to_bytes + path = cls(os.fsdecode(unquote_to_bytes(path))) + if not path.is_absolute(): + raise ValueError(f"URI is not absolute: {uri!r}") + return path + + +class PosixPath(Path, PurePosixPath): + """Path subclass for non-Windows systems. + + On a POSIX system, instantiating a Path should return this object. + """ + __slots__ = () + + if os.name == 'nt': + def __new__(cls, *args, **kwargs): + raise UnsupportedOperation( + f"cannot instantiate {cls.__name__!r} on your system") + +class WindowsPath(Path, PureWindowsPath): + """Path subclass for Windows systems. + + On a Windows system, instantiating a Path should return this object. + """ + __slots__ = () + + if os.name != 'nt': + def __new__(cls, *args, **kwargs): + raise UnsupportedOperation( + f"cannot instantiate {cls.__name__!r} on your system") diff --git a/Lib/pathlib.py b/Lib/pathlib/_abc.py similarity index 70% rename from Lib/pathlib.py rename to Lib/pathlib/_abc.py index 87d1f6b58ec52e..4808d0e61f7038 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib/_abc.py @@ -1,10 +1,3 @@ -"""Object-oriented filesystem paths. - -This module provides classes to represent abstract paths and concrete -paths with operations that have semantics appropriate for different -operating systems. -""" - import functools import io import ntpath @@ -17,27 +10,11 @@ from itertools import chain from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO -try: - import pwd -except ImportError: - pwd = None -try: - import grp -except ImportError: - grp = None - - -__all__ = [ - "UnsupportedOperation", - "PurePath", "PurePosixPath", "PureWindowsPath", - "Path", "PosixPath", "WindowsPath", - ] - # # Internals # -# Maximum number of symlinks to follow in _PathBase.resolve() +# Maximum number of symlinks to follow in PathBase.resolve() _MAX_SYMLINKS = 40 # Reference for Windows paths can be found at @@ -158,10 +135,6 @@ def _select_unique(paths): yielded.clear() -# -# Public API -# - class UnsupportedOperation(NotImplementedError): """An exception that is raised when an unsupported operation is called on a path object. @@ -198,7 +171,7 @@ def __repr__(self): return "<{}.parents>".format(type(self._path).__name__) -class _PurePathBase: +class PurePathBase: """Base class for pure path objects. This class *does not* provide several magic methods that are defined in @@ -227,7 +200,7 @@ class _PurePathBase: '_str', # The '_resolving' slot stores a boolean indicating whether the path - # is being processed by `_PathBase.resolve()`. This prevents duplicate + # is being processed by `PathBase.resolve()`. This prevents duplicate # work from occurring when `resolve()` calls `stat()` or `readlink()`. '_resolving', ) @@ -431,7 +404,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, _PurePathBase): + elif not isinstance(other, PurePathBase): other = self.with_segments(other) for step, path in enumerate(chain([other], other.parents)): if path == self or path in self.parents: @@ -455,7 +428,7 @@ def is_relative_to(self, other, /, *_deprecated): warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, _PurePathBase): + elif not isinstance(other, PurePathBase): other = self.with_segments(other) return other == self or other in self.parents @@ -542,7 +515,7 @@ def match(self, path_pattern, *, case_sensitive=None): """ Return True if this path matches the given pattern. """ - if not isinstance(path_pattern, _PurePathBase): + 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) @@ -558,183 +531,8 @@ def match(self, path_pattern, *, case_sensitive=None): return match(str(self)) is not None -class PurePath(_PurePathBase): - """Base class for manipulating paths without I/O. - - PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. - """ - - __slots__ = ( - # The `_str_normcase_cached` slot stores the string path with - # normalized case. It is set when the `_str_normcase` property is - # accessed for the first time. It's used to implement `__eq__()` - # `__hash__()`, and `_parts_normcase` - '_str_normcase_cached', - - # The `_parts_normcase_cached` slot stores the case-normalized - # string path after splitting on path separators. It's set when the - # `_parts_normcase` property is accessed for the first time. It's used - # to implement comparison methods like `__lt__()`. - '_parts_normcase_cached', - - # The `_hash` slot stores the hash of the case-normalized string - # path. It's set when `__hash__()` is called for the first time. - '_hash', - ) - - def __new__(cls, *args, **kwargs): - """Construct a PurePath from one or several strings and or existing - PurePath objects. The strings and path objects are combined so as - to yield a canonicalized path, which is incorporated into the - new PurePath object. - """ - if cls is PurePath: - cls = PureWindowsPath if os.name == 'nt' else PurePosixPath - return object.__new__(cls) - - def __init__(self, *args): - paths = [] - for arg in args: - if isinstance(arg, PurePath): - if arg.pathmod is ntpath and self.pathmod is posixpath: - # GH-103631: Convert separators for backwards compatibility. - paths.extend(path.replace('\\', '/') for path in arg._raw_paths) - else: - paths.extend(arg._raw_paths) - else: - try: - path = os.fspath(arg) - except TypeError: - path = arg - if not isinstance(path, str): - raise TypeError( - "argument should be a str or an os.PathLike " - "object where __fspath__ returns a str, " - f"not {type(path).__name__!r}") - paths.append(path) - super().__init__(*paths) - - def __reduce__(self): - # Using the parts tuple helps share interned path parts - # when pickling related paths. - return (self.__class__, self.parts) - - def __fspath__(self): - return str(self) - - def __bytes__(self): - """Return the bytes representation of the path. This is only - recommended to use under Unix.""" - return os.fsencode(self) - - @property - def _str_normcase(self): - # String with normalized case, for hashing and equality checks - try: - return self._str_normcase_cached - except AttributeError: - if _is_case_sensitive(self.pathmod): - self._str_normcase_cached = str(self) - else: - self._str_normcase_cached = str(self).lower() - return self._str_normcase_cached - - def __hash__(self): - try: - return self._hash - except AttributeError: - self._hash = hash(self._str_normcase) - return self._hash - - def __eq__(self, other): - if not isinstance(other, PurePath): - return NotImplemented - return self._str_normcase == other._str_normcase and self.pathmod is other.pathmod - - @property - def _parts_normcase(self): - # Cached parts with normalized case, for comparisons. - try: - return self._parts_normcase_cached - except AttributeError: - self._parts_normcase_cached = self._str_normcase.split(self.pathmod.sep) - return self._parts_normcase_cached - - def __lt__(self, other): - if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: - return NotImplemented - return self._parts_normcase < other._parts_normcase - - def __le__(self, other): - if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: - return NotImplemented - return self._parts_normcase <= other._parts_normcase - - def __gt__(self, other): - if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: - return NotImplemented - return self._parts_normcase > other._parts_normcase - - def __ge__(self, other): - if not isinstance(other, PurePath) or self.pathmod is not other.pathmod: - return NotImplemented - return self._parts_normcase >= other._parts_normcase - - def as_uri(self): - """Return the path as a URI.""" - if not self.is_absolute(): - raise ValueError("relative path can't be expressed as a file URI") - - drive = self.drive - if len(drive) == 2 and drive[1] == ':': - # It's a path on a local drive => 'file:///c:/a/b' - prefix = 'file:///' + drive - path = self.as_posix()[2:] - elif drive: - # It's a path on a network drive => 'file://host/share/a/b' - prefix = 'file:' - path = self.as_posix() - else: - # It's a posix path => 'file:///etc/hosts' - prefix = 'file://' - path = str(self) - from urllib.parse import quote_from_bytes - return prefix + quote_from_bytes(os.fsencode(path)) - - -# Subclassing os.PathLike makes isinstance() checks slower, -# which in turn makes Path construction slower. Register instead! -os.PathLike.register(PurePath) - - -class PurePosixPath(PurePath): - """PurePath subclass for non-Windows systems. - - On a POSIX system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. - """ - pathmod = posixpath - __slots__ = () - - -class PureWindowsPath(PurePath): - """PurePath subclass for Windows systems. - - On a Windows system, instantiating a PurePath should return this object. - However, you can also instantiate it directly on any system. - """ - pathmod = ntpath - __slots__ = () - - -# Filesystem-accessing classes - -class _PathBase(_PurePathBase): +class PathBase(PurePathBase): """Base class for concrete path objects. This class provides dummy implementations for many methods that derived @@ -752,8 +550,6 @@ class _PathBase(_PurePathBase): @classmethod def _unsupported(cls, method_name): msg = f"{cls.__name__}.{method_name}() is unsupported" - if issubclass(cls, Path): - msg += " on this system" raise UnsupportedOperation(msg) def stat(self, *, follow_symlinks=True): @@ -1352,299 +1148,3 @@ def from_uri(cls, uri): def as_uri(self): """Return the path as a URI.""" self._unsupported("as_uri") - - -class Path(_PathBase, PurePath): - """PurePath subclass that can make system calls. - - Path represents a filesystem path but unlike PurePath, also offers - methods to do system calls on path objects. Depending on your system, - instantiating a Path will return either a PosixPath or a WindowsPath - object. You can also instantiate a PosixPath or WindowsPath directly, - but cannot instantiate a WindowsPath on a POSIX system or vice versa. - """ - __slots__ = () - as_uri = PurePath.as_uri - - def __init__(self, *args, **kwargs): - if kwargs: - msg = ("support for supplying keyword arguments to pathlib.PurePath " - "is deprecated and scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath(**kwargs)", msg, remove=(3, 14)) - super().__init__(*args) - - def __new__(cls, *args, **kwargs): - if cls is Path: - cls = WindowsPath if os.name == 'nt' else PosixPath - return object.__new__(cls) - - def stat(self, *, follow_symlinks=True): - """ - Return the result of the stat() system call on this path, like - os.stat() does. - """ - return os.stat(self, follow_symlinks=follow_symlinks) - - def is_mount(self): - """ - Check if this path is a mount point - """ - return os.path.ismount(self) - - def is_junction(self): - """ - Whether this path is a junction. - """ - return os.path.isjunction(self) - - def open(self, mode='r', buffering=-1, encoding=None, - errors=None, newline=None): - """ - Open the file pointed by this path and return a file object, as - the built-in open() function does. - """ - if "b" not in mode: - encoding = io.text_encoding(encoding) - return io.open(self, mode, buffering, encoding, errors, newline) - - def iterdir(self): - """Yield path objects of the directory contents. - - The children are yielded in arbitrary order, and the - special entries '.' and '..' are not included. - """ - return (self._make_child_relpath(name) for name in os.listdir(self)) - - def _scandir(self): - return os.scandir(self) - - def absolute(self): - """Return an absolute version of this path - No normalization or symlink resolution is performed. - - Use resolve() to resolve symlinks and remove '..' segments. - """ - if self.is_absolute(): - return self - if self.root: - drive = os.path.splitroot(os.getcwd())[0] - return self._from_parsed_parts(drive, self.root, self._tail) - if self.drive: - # There is a CWD on each drive-letter drive. - cwd = os.path.abspath(self.drive) - else: - cwd = os.getcwd() - if not self._tail: - # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). - # We pass only one argument to with_segments() to avoid the cost - # of joining, and we exploit the fact that getcwd() returns a - # fully-normalized string by storing it in _str. This is used to - # implement Path.cwd(). - result = self.with_segments(cwd) - result._str = cwd - return result - drive, root, rel = os.path.splitroot(cwd) - if not rel: - return self._from_parsed_parts(drive, root, self._tail) - tail = rel.split(self.pathmod.sep) - tail.extend(self._tail) - return self._from_parsed_parts(drive, root, tail) - - def resolve(self, strict=False): - """ - Make the path absolute, resolving all symlinks on the way and also - normalizing it. - """ - - return self.with_segments(os.path.realpath(self, strict=strict)) - - if pwd: - def owner(self, *, follow_symlinks=True): - """ - Return the login name of the file owner. - """ - uid = self.stat(follow_symlinks=follow_symlinks).st_uid - return pwd.getpwuid(uid).pw_name - - if grp: - def group(self, *, follow_symlinks=True): - """ - Return the group name of the file gid. - """ - gid = self.stat(follow_symlinks=follow_symlinks).st_gid - return grp.getgrgid(gid).gr_name - - if hasattr(os, "readlink"): - def readlink(self): - """ - Return the path to which the symbolic link points. - """ - return self.with_segments(os.readlink(self)) - - def touch(self, mode=0o666, exist_ok=True): - """ - Create this file with the given access mode, if it doesn't exist. - """ - - if exist_ok: - # First try to bump modification time - # Implementation note: GNU touch uses the UTIME_NOW option of - # the utimensat() / futimens() functions. - try: - os.utime(self, None) - except OSError: - # Avoid exception chaining - pass - else: - return - flags = os.O_CREAT | os.O_WRONLY - if not exist_ok: - flags |= os.O_EXCL - fd = os.open(self, flags, mode) - os.close(fd) - - def mkdir(self, mode=0o777, parents=False, exist_ok=False): - """ - Create a new directory at this given path. - """ - try: - os.mkdir(self, mode) - except FileNotFoundError: - if not parents or self.parent == self: - raise - self.parent.mkdir(parents=True, exist_ok=True) - self.mkdir(mode, parents=False, exist_ok=exist_ok) - except OSError: - # Cannot rely on checking for EEXIST, since the operating system - # could give priority to other errors like EACCES or EROFS - if not exist_ok or not self.is_dir(): - raise - - def chmod(self, mode, *, follow_symlinks=True): - """ - Change the permissions of the path, like os.chmod(). - """ - os.chmod(self, mode, follow_symlinks=follow_symlinks) - - def unlink(self, missing_ok=False): - """ - Remove this file or link. - If the path is a directory, use rmdir() instead. - """ - try: - os.unlink(self) - except FileNotFoundError: - if not missing_ok: - raise - - def rmdir(self): - """ - Remove this directory. The directory must be empty. - """ - os.rmdir(self) - - def rename(self, target): - """ - Rename this path to the target path. - - The target path may be absolute or relative. Relative paths are - interpreted relative to the current working directory, *not* the - directory of the Path object. - - Returns the new Path instance pointing to the target path. - """ - os.rename(self, target) - return self.with_segments(target) - - def replace(self, target): - """ - Rename this path to the target path, overwriting if that path exists. - - The target path may be absolute or relative. Relative paths are - interpreted relative to the current working directory, *not* the - directory of the Path object. - - Returns the new Path instance pointing to the target path. - """ - os.replace(self, target) - return self.with_segments(target) - - if hasattr(os, "symlink"): - 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. - """ - os.symlink(target, self, target_is_directory) - - if hasattr(os, "link"): - def hardlink_to(self, target): - """ - Make this path a hard link pointing to the same file as *target*. - - Note the order of arguments (self, target) is the reverse of os.link's. - """ - os.link(target, self) - - def expanduser(self): - """ Return a new path with expanded ~ and ~user constructs - (as returned by os.path.expanduser) - """ - if (not (self.drive or self.root) and - self._tail and self._tail[0][:1] == '~'): - homedir = os.path.expanduser(self._tail[0]) - if homedir[:1] == "~": - raise RuntimeError("Could not determine home directory.") - drv, root, tail = self._parse_path(homedir) - return self._from_parsed_parts(drv, root, tail + self._tail[1:]) - - return self - - @classmethod - def from_uri(cls, uri): - """Return a new path from the given 'file' URI.""" - if not uri.startswith('file:'): - raise ValueError(f"URI does not start with 'file:': {uri!r}") - path = uri[5:] - if path[:3] == '///': - # Remove empty authority - path = path[2:] - elif path[:12] == '//localhost/': - # Remove 'localhost' authority - path = path[11:] - if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'): - # Remove slash before DOS device/UNC path - path = path[1:] - if path[1:2] == '|': - # Replace bar with colon in DOS drive - path = path[:1] + ':' + path[2:] - from urllib.parse import unquote_to_bytes - path = cls(os.fsdecode(unquote_to_bytes(path))) - if not path.is_absolute(): - raise ValueError(f"URI is not absolute: {uri!r}") - return path - - -class PosixPath(Path, PurePosixPath): - """Path subclass for non-Windows systems. - - On a POSIX system, instantiating a Path should return this object. - """ - __slots__ = () - - if os.name == 'nt': - def __new__(cls, *args, **kwargs): - raise UnsupportedOperation( - f"cannot instantiate {cls.__name__!r} on your system") - -class WindowsPath(Path, PureWindowsPath): - """Path subclass for Windows systems. - - On a Windows system, instantiating a Path should return this object. - """ - __slots__ = () - - if os.name != 'nt': - def __new__(cls, *args, **kwargs): - raise UnsupportedOperation( - f"cannot instantiate {cls.__name__!r} on your system") diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index d35516a5c8fd9b..a1acba406ea37c 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -51,7 +51,7 @@ def test_is_notimplemented(self): class PurePathBaseTest(unittest.TestCase): - cls = pathlib._PurePathBase + cls = pathlib._abc.PurePathBase def test_magic_methods(self): P = self.cls @@ -66,7 +66,7 @@ def test_magic_methods(self): self.assertIs(P.__ge__, object.__ge__) -class DummyPurePath(pathlib._PurePathBase): +class DummyPurePath(pathlib._abc.PurePathBase): def __eq__(self, other): if not isinstance(other, DummyPurePath): return NotImplemented @@ -1586,7 +1586,7 @@ class cls(pathlib.PurePath): # class PathBaseTest(PurePathBaseTest): - cls = pathlib._PathBase + cls = pathlib._abc.PathBase def test_unsupported_operation(self): P = self.cls @@ -1667,7 +1667,7 @@ def close(self): super().close() -class DummyPath(pathlib._PathBase): +class DummyPath(pathlib._abc.PathBase): """ Simple implementation of PathBase that keeps files and directories in memory. From c1652d6d6201e5407b94afc297115a584b5a0955 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 9 Dec 2023 18:05:33 +0000 Subject: [PATCH 172/442] gh-110109: Fix installed buildbots now `pathlib` is a package (#112901) --- Makefile.pre.in | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.pre.in b/Makefile.pre.in index 6ca11f080dcc3f..42a7545be7e666 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2164,6 +2164,7 @@ LIBSUBDIRS= asyncio \ json \ logging \ multiprocessing multiprocessing/dummy \ + pathlib \ pydoc_data \ re \ site-packages \ From 890ce430d94b0b2bccc92a8472b1e1030b4faeb8 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Sat, 9 Dec 2023 13:50:48 -0800 Subject: [PATCH 173/442] gh-112867: fix for WITH_PYMALLOC_RADIX_TREE=0 (GH-112885) The _obmalloc_usage structure is only defined if the obmalloc radix tree is enabled. --- Include/internal/pycore_obmalloc.h | 2 ++ .../next/Build/2023-12-08-11-33-37.gh-issue-112867.ZzDfXQ.rst | 1 + 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Build/2023-12-08-11-33-37.gh-issue-112867.ZzDfXQ.rst diff --git a/Include/internal/pycore_obmalloc.h b/Include/internal/pycore_obmalloc.h index b0dbf53d4e3d15..17572dba65487d 100644 --- a/Include/internal/pycore_obmalloc.h +++ b/Include/internal/pycore_obmalloc.h @@ -665,7 +665,9 @@ struct _obmalloc_global_state { struct _obmalloc_state { struct _obmalloc_pools pools; struct _obmalloc_mgmt mgmt; +#if WITH_PYMALLOC_RADIX_TREE struct _obmalloc_usage usage; +#endif }; diff --git a/Misc/NEWS.d/next/Build/2023-12-08-11-33-37.gh-issue-112867.ZzDfXQ.rst b/Misc/NEWS.d/next/Build/2023-12-08-11-33-37.gh-issue-112867.ZzDfXQ.rst new file mode 100644 index 00000000000000..a36814854882bb --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-12-08-11-33-37.gh-issue-112867.ZzDfXQ.rst @@ -0,0 +1 @@ +Fix the build for the case that WITH_PYMALLOC_RADIX_TREE=0 set. From 54410e6bd9460c9899b17132f34fd1a68b0cbdfe Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 9 Dec 2023 22:08:01 +0000 Subject: [PATCH 174/442] gh-101100: Fix Sphinx nitpicks in `library/tempfile.rst` (#112886) Co-authored-by: Hugo van Kemenade --- Doc/library/tempfile.rst | 52 ++++++++++++++++++++++------------------ Doc/tools/.nitignore | 1 - 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Doc/library/tempfile.rst b/Doc/library/tempfile.rst index b68a78e8267bcc..9add8500c7788c 100644 --- a/Doc/library/tempfile.rst +++ b/Doc/library/tempfile.rst @@ -18,7 +18,7 @@ This module creates temporary files and directories. It works on all supported platforms. :class:`TemporaryFile`, :class:`NamedTemporaryFile`, :class:`TemporaryDirectory`, and :class:`SpooledTemporaryFile` are high-level interfaces which provide automatic cleanup and can be used as -context managers. :func:`mkstemp` and +:term:`context managers `. :func:`mkstemp` and :func:`mkdtemp` are lower-level functions which require manual cleanup. All the user-callable functions and constructors take additional arguments which @@ -41,7 +41,7 @@ The module defines the following user-callable items: this; your code should not rely on a temporary file created using this function having or not having a visible name in the file system. - The resulting object can be used as a context manager (see + The resulting object can be used as a :term:`context manager` (see :ref:`tempfile-examples`). On completion of the context or destruction of the file object the temporary file will be removed from the filesystem. @@ -87,9 +87,9 @@ The module defines the following user-callable items: determine whether and how the named file should be automatically deleted. The returned object is always a :term:`file-like object` whose :attr:`!file` - attribute is the underlying true file object. This :term:`file-like object` + attribute is the underlying true file object. This file-like object can be used in a :keyword:`with` statement, just like a normal file. The - name of the temporary file can be retrieved from the :attr:`name` attribute + name of the temporary file can be retrieved from the :attr:`!name` attribute of the returned file-like object. On Unix, unlike with the :func:`TemporaryFile`, the directory entry does not get unlinked immediately after the file creation. @@ -151,18 +151,20 @@ The module defines the following user-callable items: contents are written to disk and operation proceeds as with :func:`TemporaryFile`. - The resulting file has one additional method, :func:`rollover`, which - causes the file to roll over to an on-disk file regardless of its size. + .. method:: SpooledTemporaryFile.rollover - The returned object is a file-like object whose :attr:`_file` attribute + The resulting file has one additional method, :meth:`!rollover`, which + causes the file to roll over to an on-disk file regardless of its size. + + The returned object is a file-like object whose :attr:`!_file` attribute is either an :class:`io.BytesIO` or :class:`io.TextIOWrapper` object (depending on whether binary or text *mode* was specified) or a true file - object, depending on whether :func:`rollover` has been called. This + object, depending on whether :meth:`rollover` has been called. This file-like object can be used in a :keyword:`with` statement, just like a normal file. .. versionchanged:: 3.3 - the truncate method now accepts a ``size`` argument. + the truncate method now accepts a *size* argument. .. versionchanged:: 3.8 Added *errors* parameter. @@ -176,24 +178,28 @@ The module defines the following user-callable items: .. class:: TemporaryDirectory(suffix=None, prefix=None, dir=None, ignore_cleanup_errors=False, *, delete=True) This class securely creates a temporary directory using the same rules as :func:`mkdtemp`. - The resulting object can be used as a context manager (see + The resulting object can be used as a :term:`context manager` (see :ref:`tempfile-examples`). On completion of the context or destruction of the temporary directory object, the newly created temporary directory and all its contents are removed from the filesystem. - The directory name can be retrieved from the :attr:`name` attribute of the - returned object. When the returned object is used as a context manager, the - :attr:`name` will be assigned to the target of the :keyword:`!as` clause in - the :keyword:`with` statement, if there is one. - - The directory can be explicitly cleaned up by calling the - :func:`cleanup` method. If *ignore_cleanup_errors* is true, any unhandled - exceptions during explicit or implicit cleanup (such as a - :exc:`PermissionError` removing open files on Windows) will be ignored, - and the remaining removable items deleted on a "best-effort" basis. - Otherwise, errors will be raised in whatever context cleanup occurs - (the :func:`cleanup` call, exiting the context manager, when the object - is garbage-collected or during interpreter shutdown). + .. attribute:: TemporaryDirectory.name + + The directory name can be retrieved from the :attr:`!name` attribute of the + returned object. When the returned object is used as a :term:`context manager`, the + :attr:`!name` will be assigned to the target of the :keyword:`!as` clause in + the :keyword:`with` statement, if there is one. + + .. method:: TemporaryDirectory.cleanup + + The directory can be explicitly cleaned up by calling the + :meth:`!cleanup` method. If *ignore_cleanup_errors* is true, any unhandled + exceptions during explicit or implicit cleanup (such as a + :exc:`PermissionError` removing open files on Windows) will be ignored, + and the remaining removable items deleted on a "best-effort" basis. + Otherwise, errors will be raised in whatever context cleanup occurs + (the :meth:`!cleanup` call, exiting the context manager, when the object + is garbage-collected or during interpreter shutdown). The *delete* parameter can be used to disable cleanup of the directory tree upon exiting the context. While it may seem unusual for a context manager diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 5ef68cc089d436..7c48b723dc6acb 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -94,7 +94,6 @@ Doc/library/string.rst Doc/library/subprocess.rst Doc/library/syslog.rst Doc/library/tarfile.rst -Doc/library/tempfile.rst Doc/library/termios.rst Doc/library/test.rst Doc/library/tkinter.rst From 96f64a2b1b4e3d4902848c63e42717a9c5539e03 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 9 Dec 2023 22:43:53 +0000 Subject: [PATCH 175/442] gh-101100: Improve documentation of `TracebackType` attributes (#112884) --- Doc/library/stdtypes.rst | 5 ++-- Doc/library/traceback.rst | 6 ++-- Doc/library/types.rst | 7 ++--- Doc/reference/datamodel.rst | 60 ++++++++++++++++++++++++------------- Doc/whatsnew/3.5.rst | 3 +- Doc/whatsnew/3.7.rst | 4 +-- 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 1265b5b12e492d..399238cb5d6c11 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5461,8 +5461,9 @@ It is written as ``NotImplemented``. Internal Objects ---------------- -See :ref:`types` for this information. It describes stack frame objects, -traceback objects, and slice objects. +See :ref:`types` for this information. It describes +:ref:`stack frame objects `, +:ref:`traceback objects `, and slice objects. .. _specialattrs: diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 2d5ea19b2cb892..14cade690fc773 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -16,7 +16,8 @@ interpreter. .. index:: pair: object; traceback -The module uses traceback objects --- these are objects of type :class:`types.TracebackType`, +The module uses :ref:`traceback objects ` --- these are +objects of type :class:`types.TracebackType`, which are assigned to the ``__traceback__`` field of :class:`BaseException` instances. .. seealso:: @@ -212,7 +213,8 @@ The module defines the following functions: .. function:: walk_tb(tb) - Walk a traceback following ``tb_next`` yielding the frame and line number + Walk a traceback following :attr:`~traceback.tb_next` yielding the frame and + line number for each frame. This helper is used with :meth:`StackSummary.extract`. .. versionadded:: 3.5 diff --git a/Doc/library/types.rst b/Doc/library/types.rst index 22766462822af9..8ce67cf77253c3 100644 --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -378,11 +378,8 @@ Standard names are defined for the following types: .. data:: FrameType - The type of frame objects such as found in ``tb.tb_frame`` if ``tb`` is a - traceback object. - - See :ref:`the language reference ` for details of the - available attributes and operations. + The type of :ref:`frame objects ` such as found in + :attr:`tb.tb_frame ` if ``tb`` is a traceback object. .. data:: GetSetDescriptorType diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 27d379a8b70f31..4db3f6b16e7c00 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1244,8 +1244,9 @@ Frame objects .. index:: pair: object; frame -Frame objects represent execution frames. They may occur in traceback objects -(see below), and are also passed to registered trace functions. +Frame objects represent execution frames. They may occur in +:ref:`traceback objects `, +and are also passed to registered trace functions. .. index:: single: f_back (frame attribute) @@ -1357,26 +1358,30 @@ Traceback objects single: sys.exception single: sys.last_traceback -Traceback objects represent a stack trace of an exception. A traceback object +Traceback objects represent the stack trace of an :ref:`exception `. +A traceback object is implicitly created when an exception occurs, and may also be explicitly created by calling :class:`types.TracebackType`. +.. versionchanged:: 3.7 + Traceback objects can now be explicitly instantiated from Python code. + For implicitly created tracebacks, when the search for an exception handler unwinds the execution stack, at each unwound level a traceback object is inserted in front of the current traceback. When an exception handler is entered, the stack trace is made available to the program. (See section :ref:`try`.) It is accessible as the third item of the -tuple returned by ``sys.exc_info()``, and as the ``__traceback__`` attribute +tuple returned by :func:`sys.exc_info`, and as the ``__traceback__`` attribute of the caught exception. When the program contains no suitable handler, the stack trace is written (nicely formatted) to the standard error stream; if the interpreter is interactive, it is also made available to the user -as ``sys.last_traceback``. +as :data:`sys.last_traceback`. For explicitly created tracebacks, it is up to the creator of the traceback -to determine how the ``tb_next`` attributes should be linked to form a -full stack trace. +to determine how the :attr:`~traceback.tb_next` attributes should be linked to +form a full stack trace. .. index:: single: tb_frame (traceback attribute) @@ -1385,27 +1390,40 @@ full stack trace. pair: statement; try Special read-only attributes: -:attr:`tb_frame` points to the execution frame of the current level; -:attr:`tb_lineno` gives the line number where the exception occurred; -:attr:`tb_lasti` indicates the precise instruction. + +.. list-table:: + + * - .. attribute:: traceback.tb_frame + - Points to the execution :ref:`frame ` of the current + level. + + Accessing this attribute raises an + :ref:`auditing event ` ``object.__getattr__`` with arguments + ``obj`` and ``"tb_frame"``. + + * - .. attribute:: traceback.tb_lineno + - Gives the line number where the exception occurred + + * - .. attribute:: traceback.tb_lasti + - Indicates the "precise instruction". + The line number and last instruction in the traceback may differ from the -line number of its frame object if the exception occurred in a +line number of its :ref:`frame object ` if the exception +occurred in a :keyword:`try` statement with no matching except clause or with a -finally clause. - -Accessing ``tb_frame`` raises an :ref:`auditing event ` -``object.__getattr__`` with arguments ``obj`` and ``"tb_frame"``. +:keyword:`finally` clause. .. index:: single: tb_next (traceback attribute) -Special writable attribute: :attr:`tb_next` is the next level in the stack -trace (towards the frame where the exception occurred), or ``None`` if -there is no next level. +.. attribute:: traceback.tb_next -.. versionchanged:: 3.7 - Traceback objects can now be explicitly instantiated from Python code, - and the ``tb_next`` attribute of existing instances can be updated. + The special writable attribute :attr:`!tb_next` is the next level in the + stack trace (towards the frame where the exception occurred), or ``None`` if + there is no next level. + + .. versionchanged:: 3.7 + This attribute is now writable Slice objects diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index ae6affcab664c6..a32866094ffeb5 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -1947,7 +1947,8 @@ traceback --------- New :func:`~traceback.walk_stack` and :func:`~traceback.walk_tb` -functions to conveniently traverse frame and traceback objects. +functions to conveniently traverse frame and +:ref:`traceback objects `. (Contributed by Robert Collins in :issue:`17911`.) New lightweight classes: :class:`~traceback.TracebackException`, diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 7a74f9c1685c31..616e51571388a8 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -525,8 +525,8 @@ Other Language Changes * In order to better support dynamic creation of stack traces, :class:`types.TracebackType` can now be instantiated from Python code, and - the ``tb_next`` attribute on :ref:`tracebacks ` is now - writable. + the :attr:`~traceback.tb_next` attribute on + :ref:`tracebacks ` is now writable. (Contributed by Nathaniel J. Smith in :issue:`30579`.) * When using the :option:`-m` switch, ``sys.path[0]`` is now eagerly expanded From 23df46a1dde82bc5a51578d9443024cf85827b95 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 10 Dec 2023 00:06:27 +0000 Subject: [PATCH 176/442] GH-112906: Fix performance regression in pathlib path initialisation (#112907) This was caused by 76929fdeeb, specifically its use of `super()` and its packing/unpacking `*args`. Co-authored-by: Alex Waygood --- Lib/pathlib/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index f4668ab3270e51..b020d2db350da8 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -90,7 +90,9 @@ def __init__(self, *args): "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") paths.append(path) - super().__init__(*paths) + # Avoid calling super().__init__, as an optimisation + self._raw_paths = paths + self._resolving = False def __reduce__(self): # Using the parts tuple helps share interned path parts From ca1bde894305a1b88fdbb191426547f35b7e9200 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 9 Dec 2023 21:29:40 -0500 Subject: [PATCH 177/442] IDLE: Tweak iomenu.IOBinding.maybesave (#112914) Add docstring, use f-string, simplify code. --- Lib/idlelib/iomenu.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py index 667623ec71ac98..464126e2df0668 100644 --- a/Lib/idlelib/iomenu.py +++ b/Lib/idlelib/iomenu.py @@ -7,7 +7,7 @@ from tkinter import filedialog from tkinter import messagebox -from tkinter.simpledialog import askstring +from tkinter.simpledialog import askstring # loadfile encoding. from idlelib.config import idleConf from idlelib.util import py_extensions @@ -180,24 +180,25 @@ def loadfile(self, filename): return True def maybesave(self): + """Return 'yes', 'no', 'cancel' as appropriate. + + Tkinter messagebox.askyesnocancel converts these tk responses + to True, False, None. Convert back, as now expected elsewhere. + """ if self.get_saved(): return "yes" - message = "Do you want to save %s before closing?" % ( - self.filename or "this untitled document") + message = ("Do you want to save " + f"{self.filename or 'this untitled document'}" + " before closing?") confirm = messagebox.askyesnocancel( title="Save On Close", message=message, default=messagebox.YES, parent=self.text) if confirm: - reply = "yes" self.save(None) - if not self.get_saved(): - reply = "cancel" - elif confirm is None: - reply = "cancel" - else: - reply = "no" + reply = "yes" if self.get_saved() else "cancel" + else: reply = "cancel" if confirm is None else "no" self.text.focus_set() return reply From 5bf7580d72259d7d64f5ee8cfc2df677de5310a4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 10 Dec 2023 10:39:51 +0200 Subject: [PATCH 178/442] Docs: Use 'f-strings' as header (#112888) --- Doc/reference/lexical_analysis.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst index 3e07d16068a627..0adfb0365934e4 100644 --- a/Doc/reference/lexical_analysis.rst +++ b/Doc/reference/lexical_analysis.rst @@ -708,10 +708,12 @@ and formatted string literals may be concatenated with plain string literals. single: ! (exclamation); in formatted string literal single: : (colon); in formatted string literal single: = (equals); for help in debugging using string literals + .. _f-strings: +.. _formatted-string-literals: -Formatted string literals -------------------------- +f-strings +--------- .. versionadded:: 3.6 From dd2ebdf89ff144e89db180bd552c50615f712cb2 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Sun, 10 Dec 2023 12:38:49 +0100 Subject: [PATCH 179/442] gh-109980: Fix test_tarfile_vs_tar on macOS (#112905) On recentish macOS versions the system tar command includes system metadata (ACLs, extended attributes and resource forks) in the tar archive, which shutil.make_archive will not do. This can cause spurious test failures. --- Lib/test/test_shutil.py | 11 +++++++++++ .../2023-12-09-21-27-46.gh-issue-109980.y--500.rst | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2023-12-09-21-27-46.gh-issue-109980.y--500.rst diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index d7061b2f9d8724..b29d316352f219 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1670,6 +1670,17 @@ def test_tarfile_vs_tar(self): # now create another tarball using `tar` tarball2 = os.path.join(root_dir, 'archive2.tar') tar_cmd = ['tar', '-cf', 'archive2.tar', base_dir] + if sys.platform == 'darwin': + # macOS tar can include extended attributes, + # ACLs and other mac specific metadata into the + # archive (an recentish version of the OS). + # + # This feature can be disabled with the + # '--no-mac-metadata' option on macOS 11 or + # later. + import platform + if int(platform.mac_ver()[0].split('.')[0]) >= 11: + tar_cmd.insert(1, '--no-mac-metadata') subprocess.check_call(tar_cmd, cwd=root_dir, stdout=subprocess.DEVNULL) diff --git a/Misc/NEWS.d/next/Tests/2023-12-09-21-27-46.gh-issue-109980.y--500.rst b/Misc/NEWS.d/next/Tests/2023-12-09-21-27-46.gh-issue-109980.y--500.rst new file mode 100644 index 00000000000000..c475a33919db98 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-12-09-21-27-46.gh-issue-109980.y--500.rst @@ -0,0 +1,2 @@ +Fix ``test_tarfile_vs_tar`` in ``test_shutil`` for macOS, where system tar +can include more information in the archive than :mod:`shutil.make_archive`. From 7595d47722ae359e6642506646640a3f86816cef Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 10 Dec 2023 14:53:26 +0200 Subject: [PATCH 180/442] gh-101100: Fix Sphinx warning in library/http.cookies.rst (#112908) Co-authored-by: Alex Waygood --- Doc/library/http.cookies.rst | 42 +++++++++++++++++++----------------- Doc/tools/.nitignore | 1 - 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Doc/library/http.cookies.rst b/Doc/library/http.cookies.rst index a2c1eb00d8b33d..e91972fe621a48 100644 --- a/Doc/library/http.cookies.rst +++ b/Doc/library/http.cookies.rst @@ -18,16 +18,17 @@ cookie value. The module formerly strictly applied the parsing rules described in the :rfc:`2109` and :rfc:`2068` specifications. It has since been discovered that -MSIE 3.0x doesn't follow the character rules outlined in those specs and also -many current day browsers and servers have relaxed parsing rules when comes to -Cookie handling. As a result, the parsing rules used are a bit less strict. +MSIE 3.0x didn't follow the character rules outlined in those specs; many +current-day browsers and servers have also relaxed parsing rules when it comes +to cookie handling. As a result, this module now uses parsing rules that are a +bit less strict than they once were. The character set, :data:`string.ascii_letters`, :data:`string.digits` and ``!#$%&'*+-.^_`|~:`` denote the set of valid characters allowed by this module -in Cookie name (as :attr:`~Morsel.key`). +in a cookie name (as :attr:`~Morsel.key`). .. versionchanged:: 3.3 - Allowed ':' as a valid Cookie name character. + Allowed ':' as a valid cookie name character. .. note:: @@ -54,9 +55,10 @@ in Cookie name (as :attr:`~Morsel.key`). .. class:: SimpleCookie([input]) - This class derives from :class:`BaseCookie` and overrides :meth:`value_decode` - and :meth:`value_encode`. SimpleCookie supports strings as cookie values. - When setting the value, SimpleCookie calls the builtin :func:`str()` to convert + This class derives from :class:`BaseCookie` and overrides :meth:`~BaseCookie.value_decode` + and :meth:`~BaseCookie.value_encode`. :class:`!SimpleCookie` supports + strings as cookie values. When setting the value, :class:`!SimpleCookie` + calls the builtin :func:`str` to convert the value to a string. Values received from HTTP are kept as strings. .. seealso:: @@ -129,17 +131,17 @@ Morsel Objects Abstract a key/value pair, which has some :rfc:`2109` attributes. Morsels are dictionary-like objects, whose set of keys is constant --- the valid - :rfc:`2109` attributes, which are - - * ``expires`` - * ``path`` - * ``comment`` - * ``domain`` - * ``max-age`` - * ``secure`` - * ``version`` - * ``httponly`` - * ``samesite`` + :rfc:`2109` attributes, which are: + + .. attribute:: expires + path + comment + domain + max-age + secure + version + httponly + samesite The attribute :attr:`httponly` specifies that the cookie is only transferred in HTTP requests, and is not accessible through JavaScript. This is intended @@ -152,7 +154,7 @@ Morsel Objects The keys are case-insensitive and their default value is ``''``. .. versionchanged:: 3.5 - :meth:`~Morsel.__eq__` now takes :attr:`~Morsel.key` and :attr:`~Morsel.value` + :meth:`!__eq__` now takes :attr:`~Morsel.key` and :attr:`~Morsel.value` into account. .. versionchanged:: 3.7 diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 7c48b723dc6acb..511648ab6991c6 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -57,7 +57,6 @@ Doc/library/ftplib.rst Doc/library/functools.rst Doc/library/http.client.rst Doc/library/http.cookiejar.rst -Doc/library/http.cookies.rst Doc/library/http.server.rst Doc/library/importlib.rst Doc/library/inspect.rst From 9d02d3451a61521c65db6f93596ece2f572f1f3e Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sun, 10 Dec 2023 18:21:20 +0300 Subject: [PATCH 181/442] gh-110686: Test pattern matching with `runtime_checkable` protocols (#110687) --- Lib/test/test_patma.py | 155 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index dedbc828784184..298e78ccee3875 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -2760,6 +2760,132 @@ def test_patma_255(self): self.assertEqual(y, 1) self.assertIs(z, x) + def test_patma_runtime_checkable_protocol(self): + # Runtime-checkable protocol + from typing import Protocol, runtime_checkable + + @runtime_checkable + class P(Protocol): + x: int + y: int + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class B(A): ... + + for cls in (A, B): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P() as p: + self.assertIsInstance(p, cls) + self.assertEqual(p.x, 1) + self.assertEqual(p.y, 2) + w = 1 + self.assertEqual(w, 1) + + q = 0 + match inst: + case P(x=x, y=y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + q = 1 + self.assertEqual(q, 1) + + + def test_patma_generic_protocol(self): + # Runtime-checkable generic protocol + from typing import Generic, TypeVar, Protocol, runtime_checkable + + T = TypeVar('T') # not using PEP695 to be able to backport changes + + @runtime_checkable + class P(Protocol[T]): + a: T + b: T + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class G(Generic[T]): + def __init__(self, x: T, y: T): + self.x = x + self.y = y + + for cls in (A, G): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P(): + w = 1 + self.assertEqual(w, 0) + + def test_patma_protocol_with_match_args(self): + # Runtime-checkable protocol with `__match_args__` + from typing import Protocol, runtime_checkable + + # Used to fail before + # https://github.com/python/cpython/issues/110682 + @runtime_checkable + class P(Protocol): + __match_args__ = ('x', 'y') + x: int + y: int + + class A: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class B(A): ... + + for cls in (A, B): + with self.subTest(cls=cls.__name__): + inst = cls(1, 2) + w = 0 + match inst: + case P() as p: + self.assertIsInstance(p, cls) + self.assertEqual(p.x, 1) + self.assertEqual(p.y, 2) + w = 1 + self.assertEqual(w, 1) + + q = 0 + match inst: + case P(x=x, y=y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + q = 1 + self.assertEqual(q, 1) + + j = 0 + match inst: + case P(x=1, y=2): + j = 1 + self.assertEqual(j, 1) + + g = 0 + match inst: + case P(x, y): + self.assertEqual(x, 1) + self.assertEqual(y, 2) + g = 1 + self.assertEqual(g, 1) + + h = 0 + match inst: + case P(1, 2): + h = 1 + self.assertEqual(h, 1) + class TestSyntaxErrors(unittest.TestCase): @@ -3198,6 +3324,35 @@ def test_class_pattern_not_type(self): w = 0 self.assertIsNone(w) + def test_regular_protocol(self): + from typing import Protocol + class P(Protocol): ... + msg = ( + 'Instance and class checks can only be used ' + 'with @runtime_checkable protocols' + ) + w = None + with self.assertRaisesRegex(TypeError, msg): + match 1: + case P(): + w = 0 + self.assertIsNone(w) + + def test_positional_patterns_with_regular_protocol(self): + from typing import Protocol + class P(Protocol): + x: int # no `__match_args__` + y: int + class A: + x = 1 + y = 2 + w = None + with self.assertRaises(TypeError): + match A(): + case P(x, y): + w = 0 + self.assertIsNone(w) + class TestValueErrors(unittest.TestCase): From 1f9cd3c1e5410e45ade4362713229fa445ea6962 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 10 Dec 2023 07:30:02 -0800 Subject: [PATCH 182/442] Argument Clinic: fix bare "type" in annotations (#112915) Bare "type" in annotations should be equivalent to "type[Any]"; see https://discuss.python.org/t/inconsistencies-between-type-and-type/37404 and python/mypy#16366. It's better to use "type[object]", which is safer and unambiguous. --- Tools/clinic/clinic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 6c76f66a81abd4..816ce0e6efed61 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -3184,7 +3184,7 @@ def closure(f: CConverterClassT) -> CConverterClassT: class CConverterAutoRegister(type): def __init__( - cls, name: str, bases: tuple[type, ...], classdict: dict[str, Any] + cls, name: str, bases: tuple[type[object], ...], classdict: dict[str, Any] ) -> None: converter_cls = cast(type["CConverter"], cls) add_c_converter(converter_cls) @@ -3217,7 +3217,7 @@ class CConverter(metaclass=CConverterAutoRegister): # If not None, default must be isinstance() of this type. # (You can also specify a tuple of types.) - default_type: bltns.type[Any] | tuple[bltns.type[Any], ...] | None = None + default_type: bltns.type[object] | tuple[bltns.type[object], ...] | None = None # "default" converted into a C value, as a string. # Or None if there is no default. @@ -3683,7 +3683,7 @@ def add_include(self, name: str, reason: str, ReturnConverterDict = dict[str, ReturnConverterType] return_converters: ReturnConverterDict = {} -TypeSet = set[bltns.type[Any]] +TypeSet = set[bltns.type[object]] class bool_converter(CConverter): @@ -4347,7 +4347,7 @@ class buffer: pass class rwbuffer: pass class robuffer: pass -StrConverterKeyType = tuple[frozenset[type], bool, bool] +StrConverterKeyType = tuple[frozenset[type[object]], bool, bool] def str_converter_key( types: TypeSet, encoding: bool | str | None, zeroes: bool @@ -4846,7 +4846,7 @@ class CReturnConverterAutoRegister(type): def __init__( cls: ReturnConverterType, name: str, - bases: tuple[type, ...], + bases: tuple[type[object], ...], classdict: dict[str, Any] ) -> None: add_c_return_converter(cls) From eb27c9a99edb6bf3be1c93579d885edd0f403901 Mon Sep 17 00:00:00 2001 From: Brad Larsen Date: Sun, 10 Dec 2023 12:16:15 -0500 Subject: [PATCH 183/442] Add a fuzzer for `Py_CompileStringExFlags` (#111721) --- .../dictionaries/fuzz_pycompile.dict | 165 ++++++++++++++++++ .../fuzz_pycompile_corpus/input1.py | 7 + .../fuzz_pycompile_corpus/input2.py | 5 + .../fuzz_pycompile_corpus/input3.py | 6 + .../fuzz_pycompile_corpus/input4.py | 3 + .../fuzz_pycompile_corpus/input5.py | 7 + .../fuzz_pycompile_corpus/input6.py | 8 + Modules/_xxtestfuzz/fuzz_tests.txt | 1 + Modules/_xxtestfuzz/fuzzer.c | 60 +++++++ Tools/c-analyzer/cpython/ignored.tsv | 3 + 10 files changed, 265 insertions(+) create mode 100644 Modules/_xxtestfuzz/dictionaries/fuzz_pycompile.dict create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input1.py create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input2.py create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input3.py create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input4.py create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input5.py create mode 100644 Modules/_xxtestfuzz/fuzz_pycompile_corpus/input6.py diff --git a/Modules/_xxtestfuzz/dictionaries/fuzz_pycompile.dict b/Modules/_xxtestfuzz/dictionaries/fuzz_pycompile.dict new file mode 100644 index 00000000000000..c6a44d946284ef --- /dev/null +++ b/Modules/_xxtestfuzz/dictionaries/fuzz_pycompile.dict @@ -0,0 +1,165 @@ +# bits of syntax +"( " +") " +"[ " +"] " +": " +", " +"; " +"{ " +"} " + +# operators +"+ " +"- " +"* " +"** " +"/ " +"// " +"| " +"& " +"< " +"> " +"= " +". " +"% " +"` " +"^ " +"~ " +"@ " +"== " +"!= " +"<> " +"<< " +"<= " +">= " +">> " +"+= " +"-= " +"*= " +"** " +"/= " +"//= " +"|= " +"%= " +"&= " +"^= " +"<<= " +">>= " +"**= " +":= " +"@= " + +# whitespace +" " +":\\n " + +# type signatures and functions +"-> " +": List[int]" +": Dict[int, str]" + +"# type:" +"# type: List[int]" +"# type: Dict[int, str]" + +", *" +", /" +", *args" +", **kwargs" +", x=42" + + +# literals +"0x0a" +"0b0000" +"42" +"0o70" +"42j" +"42.01" +"-5" +"+42e-3" +"0_0_0" +"1e1_0" +".1_4" + +"{}" + +# variable names +"x" +"y" + +# strings +"r'x'" + +"b'x'" + +"rb\"x\"" + +"br\"x\"" + +"f'{x + 5}'" +"f\"{x + 5}\"" + +"'''" +"\"\"\"" + +"\\u" +"\\x" + +# keywords +"def " +"del " +"pass " +"break " +"continue " +"return " +"raise " +"from " +"import " +".. " +"... " +"__future__ " +"as " +"global " +"nonlocal " +"assert " +"print " +"if " +"elif " +"else: " +"while " +"try: " +"except " +"finally: " +"with " +"lambda " +"or " +"and " +"not " +"None " +"__peg_parser__" +"True " +"False " +"yield " +"async " +"await " +"for " +"in " +"is " +"class " + +# shebangs and encodings +"#!" +"# coding:" +"# coding=" +"# coding: latin-1" +"# coding=latin-1" +"# coding: utf-8" +"# coding=utf-8" +"# coding: ascii" +"# coding=ascii" +"# coding: cp860" +"# coding=cp860" +"# coding: gbk" +"# coding=gbk" diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input1.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input1.py new file mode 100644 index 00000000000000..c43994dda29eed --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input1.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +def test() -> None: + x: list[int] = [] + x: dict[int, str] = {} + x: set[bytes] = {} + print(5 + 42 * 3, x) diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input2.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input2.py new file mode 100644 index 00000000000000..7be326e95be0eb --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input2.py @@ -0,0 +1,5 @@ +class Foo(metaclass=42): + __slots__ = ['x'] + pass + +foo = Foo() diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input3.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input3.py new file mode 100644 index 00000000000000..9bc3a45ebe75da --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input3.py @@ -0,0 +1,6 @@ +def evens(): + i = 0 + while True: + i += 1 + if i % 2 == 0: + yield i diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input4.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input4.py new file mode 100644 index 00000000000000..490de90fb97b39 --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input4.py @@ -0,0 +1,3 @@ +async def hello(name: str): + await name + print(name) diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input5.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input5.py new file mode 100644 index 00000000000000..4cfcfe590ebc95 --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input5.py @@ -0,0 +1,7 @@ +try: + eval('importer exporter... really long matches') +except SyntaxError: + print("nothing to see here") +finally: + print("all done here") + raise diff --git a/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input6.py b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input6.py new file mode 100644 index 00000000000000..d8e59ade503a8c --- /dev/null +++ b/Modules/_xxtestfuzz/fuzz_pycompile_corpus/input6.py @@ -0,0 +1,8 @@ +"""Some module docstring""" +import sys + +def main(): + print("Hello world!", file=sys.stderr) + +if __name__ == '__main__': + main() diff --git a/Modules/_xxtestfuzz/fuzz_tests.txt b/Modules/_xxtestfuzz/fuzz_tests.txt index 40aa22110e7d27..ea6f982eefc9da 100644 --- a/Modules/_xxtestfuzz/fuzz_tests.txt +++ b/Modules/_xxtestfuzz/fuzz_tests.txt @@ -8,3 +8,4 @@ fuzz_csv_reader fuzz_struct_unpack fuzz_ast_literal_eval fuzz_elementtree_parsewhole +fuzz_pycompile diff --git a/Modules/_xxtestfuzz/fuzzer.c b/Modules/_xxtestfuzz/fuzzer.c index 77d29ce773a04b..e133b4d3c44480 100644 --- a/Modules/_xxtestfuzz/fuzzer.c +++ b/Modules/_xxtestfuzz/fuzzer.c @@ -501,6 +501,63 @@ static int fuzz_elementtree_parsewhole(const char* data, size_t size) { return 0; } +#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]); + +static const int optimize_vals[] = {-1, 0, 1, 2}; +const size_t NUM_OPTIMIZE_VALS = sizeof(optimize_vals) / sizeof(optimize_vals[0]); + +/* Fuzz `PyCompileStringExFlags` using a variety of input parameters. + * That function is essentially behind the `compile` builtin */ +static int fuzz_pycompile(const char* data, size_t size) { + // Ignore overly-large inputs, and account for a NUL terminator + if (size > MAX_PYCOMPILE_TEST_SIZE - 1) { + return 0; + } + + // Need 2 bytes for parameter selection + if (size < 2) { + return 0; + } + + // Use first byte to determine element of `start_vals` to use + unsigned char start_idx = (unsigned char) data[0]; + int start = start_vals[start_idx % NUM_START_VALS]; + + // Use second byte to determine element of `optimize_vals` to use + unsigned char optimize_idx = (unsigned char) data[1]; + int optimize = optimize_vals[optimize_idx % NUM_OPTIMIZE_VALS]; + + // 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.) + pycompile_scratch[size - 2] = '\0'; + + // XXX: instead of always using NULL for the `flags` value to + // `Py_CompileStringExFlags`, there are many flags that conditionally + // change parser behavior: + // + // #define PyCF_TYPE_COMMENTS 0x1000 + // #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000 + // #define PyCF_ONLY_AST 0x0400 + // + // It would be good to test various combinations of these, too. + PyCompilerFlags *flags = NULL; + + PyObject *result = Py_CompileStringExFlags(pycompile_scratch, "", start, flags, optimize); + if (result == NULL) { + /* compilation failed, most likely from a syntax error */ + PyErr_Clear(); + } else { + Py_DECREF(result); + } + + return 0; +} + /* Run fuzzer and abort on failure. */ static int _run_fuzz(const uint8_t *data, size_t size, int(*fuzzer)(const char* , size_t)) { int rv = fuzzer((const char*) data, size); @@ -642,6 +699,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { } rv |= _run_fuzz(data, size, fuzz_elementtree_parsewhole); +#endif +#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_pycompile) + rv |= _run_fuzz(data, size, fuzz_pycompile); #endif return rv; } diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index d59e0ddcdfde4e..ff6e1ef4f993ba 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -599,6 +599,9 @@ Modules/_xxtestfuzz/fuzzer.c - re_error_exception - Modules/_xxtestfuzz/fuzzer.c - struct_error - Modules/_xxtestfuzz/fuzzer.c - struct_unpack_method - Modules/_xxtestfuzz/fuzzer.c - xmlparser_type - +Modules/_xxtestfuzz/fuzzer.c - pycompile_scratch - +Modules/_xxtestfuzz/fuzzer.c - start_vals - +Modules/_xxtestfuzz/fuzzer.c - optimize_vals - Modules/_xxtestfuzz/fuzzer.c LLVMFuzzerTestOneInput CSV_READER_INITIALIZED - Modules/_xxtestfuzz/fuzzer.c LLVMFuzzerTestOneInput JSON_LOADS_INITIALIZED - Modules/_xxtestfuzz/fuzzer.c LLVMFuzzerTestOneInput SRE_COMPILE_INITIALIZED - From 42a86df3a376a77a94ffe6b4011a82cf51dc336a Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Mon, 11 Dec 2023 02:43:17 +0900 Subject: [PATCH 184/442] Doc: c-api: fix order of PyMemberDef fields (#112879) --- Doc/c-api/structures.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index 528813c255c0a5..7d82f7839dfcd7 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -419,15 +419,15 @@ Accessing attributes of extension types The string should be static, no copy is made of it. - .. c:member:: Py_ssize_t offset - - The offset in bytes that the member is located on the type’s object struct. - .. c:member:: int type The type of the member in the C struct. See :ref:`PyMemberDef-types` for the possible values. + .. c:member:: Py_ssize_t offset + + The offset in bytes that the member is located on the type’s object struct. + .. c:member:: int flags Zero or more of the :ref:`PyMemberDef-flags`, combined using bitwise OR. From 9cdf05bc28c5cd8b000b9541a907028819b3d63e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 10 Dec 2023 19:17:07 +0000 Subject: [PATCH 185/442] GH-101986: Support translation for Limited/Unstable API & Stable ABI (#107680) Co-authored-by: Ezio Melotti Co-authored-by: Hugo van Kemenade --- Doc/tools/extensions/c_annotations.py | 20 ++++++++++---------- Doc/tools/templates/dummy.html | 10 ++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py index 3551bfa4c0f133..42c2f10e0be260 100644 --- a/Doc/tools/extensions/c_annotations.py +++ b/Doc/tools/extensions/c_annotations.py @@ -126,7 +126,7 @@ def add_annotations(self, app, doctree): f"Object type mismatch in limited API annotation " f"for {name}: {record['role']!r} != {objtype!r}") stable_added = record['added'] - message = ' Part of the ' + message = sphinx_gettext(' Part of the ') emph_node = nodes.emphasis(message, message, classes=['stableabi']) ref_node = addnodes.pending_xref( @@ -134,40 +134,40 @@ def add_annotations(self, app, doctree): reftype='ref', refexplicit="False") struct_abi_kind = record['struct_abi_kind'] if struct_abi_kind in {'opaque', 'members'}: - ref_node += nodes.Text('Limited API') + ref_node += nodes.Text(sphinx_gettext('Limited API')) else: - ref_node += nodes.Text('Stable ABI') + ref_node += nodes.Text(sphinx_gettext('Stable ABI')) emph_node += ref_node if struct_abi_kind == 'opaque': - emph_node += nodes.Text(' (as an opaque struct)') + emph_node += nodes.Text(sphinx_gettext(' (as an opaque struct)')) elif struct_abi_kind == 'full-abi': - emph_node += nodes.Text(' (including all members)') + emph_node += nodes.Text(sphinx_gettext(' (including all members)')) if record['ifdef_note']: emph_node += nodes.Text(' ' + record['ifdef_note']) if stable_added == '3.2': # Stable ABI was introduced in 3.2. pass else: - emph_node += nodes.Text(f' since version {stable_added}') + emph_node += nodes.Text(sphinx_gettext(' since version %s') % stable_added) emph_node += nodes.Text('.') if struct_abi_kind == 'members': emph_node += nodes.Text( - ' (Only some members are part of the stable ABI.)') + sphinx_gettext(' (Only some members are part of the stable ABI.)')) node.insert(0, emph_node) # Unstable API annotation. if name.startswith('PyUnstable'): warn_node = nodes.admonition( classes=['unstable-c-api', 'warning']) - message = 'This is ' + message = sphinx_gettext('This is ') emph_node = nodes.emphasis(message, message) ref_node = addnodes.pending_xref( 'Unstable API', refdomain="std", reftarget='unstable-c-api', reftype='ref', refexplicit="False") - ref_node += nodes.Text('Unstable API') + ref_node += nodes.Text(sphinx_gettext('Unstable API')) emph_node += ref_node - emph_node += nodes.Text('. It may change without warning in minor releases.') + emph_node += nodes.Text(sphinx_gettext('. It may change without warning in minor releases.')) warn_node += emph_node node.insert(0, warn_node) diff --git a/Doc/tools/templates/dummy.html b/Doc/tools/templates/dummy.html index bab4aaeb4604b8..3a0acab8836b11 100644 --- a/Doc/tools/templates/dummy.html +++ b/Doc/tools/templates/dummy.html @@ -9,6 +9,16 @@ In extensions/c_annotations.py: +{% trans %} Part of the {% endtrans %} +{% trans %}Limited API{% endtrans %} +{% trans %}Stable ABI{% endtrans %} +{% trans %} (as an opaque struct){% endtrans %} +{% trans %} (including all members){% endtrans %} +{% trans %} since version %s{% endtrans %} +{% trans %} (Only some members are part of the stable ABI.){% endtrans %} +{% trans %}This is {% endtrans %} +{% trans %}Unstable API{% endtrans %} +{% trans %}. It may change without warning in minor releases.{% endtrans %} {% trans %}Return value: Always NULL.{% endtrans %} {% trans %}Return value: New reference.{% endtrans %} {% trans %}Return value: Borrowed reference.{% endtrans %} From 4c5b9c107a1d158b245f21a1839a2bec97d05383 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 11 Dec 2023 10:00:42 +0000 Subject: [PATCH 186/442] gh-101100: Improve documentation on function attributes (#112933) Co-authored-by: Hugo van Kemenade --- Doc/c-api/function.rst | 23 ++-- Doc/howto/annotations.rst | 3 +- Doc/howto/descriptor.rst | 8 +- Doc/library/inspect.rst | 5 +- Doc/library/stdtypes.rst | 6 +- Doc/library/xmlrpc.server.rst | 8 +- Doc/reference/compound_stmts.rst | 7 +- Doc/reference/datamodel.rst | 197 +++++++++++++++++-------------- Doc/whatsnew/2.1.rst | 3 +- Doc/whatsnew/2.4.rst | 3 +- Doc/whatsnew/3.0.rst | 15 +-- Doc/whatsnew/3.13.rst | 2 +- Doc/whatsnew/3.2.rst | 5 +- Doc/whatsnew/3.4.rst | 4 +- 14 files changed, 159 insertions(+), 130 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 0a18e63c7e7a2c..75a05b4197c460 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -34,18 +34,20 @@ There are a few functions specific to Python functions. Return a new function object associated with the code object *code*. *globals* must be a dictionary with the global variables accessible to the function. - The function's docstring and name are retrieved from the code object. *__module__* + The function's docstring and name are retrieved from the code object. + :func:`~function.__module__` is retrieved from *globals*. The argument defaults, annotations and closure are - set to ``NULL``. *__qualname__* is set to the same value as the code object's - :attr:`~codeobject.co_qualname` field. + set to ``NULL``. :attr:`~function.__qualname__` is set to the same value as + the code object's :attr:`~codeobject.co_qualname` field. .. c:function:: PyObject* PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname) As :c:func:`PyFunction_New`, but also allows setting the function object's - ``__qualname__`` attribute. *qualname* should be a unicode object or ``NULL``; - if ``NULL``, the ``__qualname__`` attribute is set to the same value as the - code object's :attr:`~codeobject.co_qualname` field. + :attr:`~function.__qualname__` attribute. + *qualname* should be a unicode object or ``NULL``; + if ``NULL``, the :attr:`!__qualname__` attribute is set to the same value as + the code object's :attr:`~codeobject.co_qualname` field. .. versionadded:: 3.3 @@ -62,11 +64,12 @@ There are a few functions specific to Python functions. .. c:function:: PyObject* PyFunction_GetModule(PyObject *op) - Return a :term:`borrowed reference` to the *__module__* attribute of the - function object *op*. It can be *NULL*. + Return a :term:`borrowed reference` to the :attr:`~function.__module__` + attribute of the :ref:`function object ` *op*. + It can be *NULL*. - This is normally a string containing the module name, but can be set to any - other object by Python code. + This is normally a :class:`string ` containing the module name, + but can be set to any other object by Python code. .. c:function:: PyObject* PyFunction_GetDefaults(PyObject *op) diff --git a/Doc/howto/annotations.rst b/Doc/howto/annotations.rst index 1134686c947d66..be8c7e6c827f57 100644 --- a/Doc/howto/annotations.rst +++ b/Doc/howto/annotations.rst @@ -153,7 +153,8 @@ on an arbitrary object ``o``: unwrap it by accessing either ``o.__wrapped__`` or ``o.func`` as appropriate, until you have found the root unwrapped function. * If ``o`` is a callable (but not a class), use - ``o.__globals__`` as the globals when calling :func:`eval`. + :attr:`o.__globals__ ` as the globals when calling + :func:`eval`. However, not all string values used as annotations can be successfully turned into Python values by :func:`eval`. diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst index f732aaea729d40..87274a5133d1cf 100644 --- a/Doc/howto/descriptor.rst +++ b/Doc/howto/descriptor.rst @@ -1342,7 +1342,8 @@ Using the non-data descriptor protocol, a pure Python version of The :func:`functools.update_wrapper` call adds a ``__wrapped__`` attribute that refers to the underlying function. Also it carries forward the attributes necessary to make the wrapper look like the wrapped -function: ``__name__``, ``__qualname__``, ``__doc__``, and ``__annotations__``. +function: :attr:`~function.__name__`, :attr:`~function.__qualname__`, +:attr:`~function.__doc__`, and :attr:`~function.__annotations__`. .. testcode:: :hide: @@ -1522,8 +1523,9 @@ Using the non-data descriptor protocol, a pure Python version of The :func:`functools.update_wrapper` call in ``ClassMethod`` adds a ``__wrapped__`` attribute that refers to the underlying function. Also it carries forward the attributes necessary to make the wrapper look -like the wrapped function: ``__name__``, ``__qualname__``, ``__doc__``, -and ``__annotations__``. +like the wrapped function: :attr:`~function.__name__`, +:attr:`~function.__qualname__`, :attr:`~function.__doc__`, +and :attr:`~function.__annotations__`. Member objects and __slots__ diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 8381e508139fbd..f8b3e39c4f54f0 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1225,9 +1225,10 @@ Classes and functions * If ``obj`` is a class, ``globals`` defaults to ``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults to the ``obj`` class namespace. - * If ``obj`` is a callable, ``globals`` defaults to ``obj.__globals__``, + * If ``obj`` is a callable, ``globals`` defaults to + :attr:`obj.__globals__ `, although if ``obj`` is a wrapped function (using - ``functools.update_wrapper()``) it is first unwrapped. + :func:`functools.update_wrapper`) it is first unwrapped. Calling ``get_annotations`` is best practice for accessing the annotations dict of any object. See :ref:`annotations-howto` for diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 399238cb5d6c11..9028ff5c134fa9 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5384,10 +5384,10 @@ Code objects are used by the implementation to represent "pseudo-compiled" executable Python code such as a function body. They differ from function objects because they don't contain a reference to their global execution environment. Code objects are returned by the built-in :func:`compile` function -and can be extracted from function objects through their :attr:`__code__` -attribute. See also the :mod:`code` module. +and can be extracted from function objects through their +:attr:`~function.__code__` attribute. See also the :mod:`code` module. -Accessing ``__code__`` raises an :ref:`auditing event ` +Accessing :attr:`~function.__code__` raises an :ref:`auditing event ` ``object.__getattr__`` with arguments ``obj`` and ``"__code__"``. .. index:: diff --git a/Doc/library/xmlrpc.server.rst b/Doc/library/xmlrpc.server.rst index 016369d2b89d2c..ca1ea455f0acfc 100644 --- a/Doc/library/xmlrpc.server.rst +++ b/Doc/library/xmlrpc.server.rst @@ -84,12 +84,12 @@ alone XML-RPC servers. Register a function that can respond to XML-RPC requests. If *name* is given, it will be the method name associated with *function*, otherwise - ``function.__name__`` will be used. *name* is a string, and may contain + :attr:`function.__name__` will be used. *name* is a string, and may contain characters not legal in Python identifiers, including the period character. This method can also be used as a decorator. When used as a decorator, *name* can only be given as a keyword argument to register *function* under - *name*. If no *name* is given, ``function.__name__`` will be used. + *name*. If no *name* is given, :attr:`function.__name__` will be used. .. versionchanged:: 3.7 :meth:`register_function` can be used as a decorator. @@ -298,12 +298,12 @@ requests sent to Python CGI scripts. Register a function that can respond to XML-RPC requests. If *name* is given, it will be the method name associated with *function*, otherwise - ``function.__name__`` will be used. *name* is a string, and may contain + :attr:`function.__name__` will be used. *name* is a string, and may contain characters not legal in Python identifiers, including the period character. This method can also be used as a decorator. When used as a decorator, *name* can only be given as a keyword argument to register *function* under - *name*. If no *name* is given, ``function.__name__`` will be used. + *name*. If no *name* is given, :attr:`function.__name__` will be used. .. versionchanged:: 3.7 :meth:`register_function` can be used as a decorator. diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 8f6481339837a0..7a735095bdecb2 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1261,7 +1261,8 @@ except that the original function is not temporarily bound to the name ``func``. A list of :ref:`type parameters ` may be given in square brackets between the function's name and the opening parenthesis for its parameter list. This indicates to static type checkers that the function is generic. At runtime, -the type parameters can be retrieved from the function's ``__type_params__`` +the type parameters can be retrieved from the function's +:attr:`~function.__type_params__` attribute. See :ref:`generic-functions` for more. .. versionchanged:: 3.12 @@ -1868,8 +1869,8 @@ like ``TYPE_PARAMS_OF_ListOrSet`` are not actually bound at runtime. are mappings. .. [#] A string literal appearing as the first statement in the function body is - transformed into the function's ``__doc__`` attribute and therefore the - function's :term:`docstring`. + transformed into the function's :attr:`~function.__doc__` attribute and + therefore the function's :term:`docstring`. .. [#] A string literal appearing as the first statement in the class body is transformed into the namespace's ``__doc__`` item and therefore the class's diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 4db3f6b16e7c00..de79d72a05c68a 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -534,9 +534,34 @@ section :ref:`function`). It should be called with an argument list containing the same number of items as the function's formal parameter list. -Special attributes: +Special read-only attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. index:: + single: __closure__ (function attribute) + single: __globals__ (function attribute) + pair: global; namespace + +.. list-table:: + :header-rows: 1 -.. tabularcolumns:: |l|L|l| + * - Attribute + - Meaning + + * - .. attribute:: function.__globals__ + - A reference to the :class:`dictionary ` that holds the function's + :ref:`global variables ` -- the global namespace of the module + in which the function was defined. + + * - .. attribute:: function.__closure__ + - ``None`` or a :class:`tuple` of cells that contain bindings for the + function's free variables. + + A cell object has the attribute ``cell_contents``. + This can be used to get the value of the cell, as well as set the value. + +Special writable attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. index:: single: __doc__ (function attribute) @@ -544,96 +569,78 @@ Special attributes: single: __module__ (function attribute) single: __dict__ (function attribute) single: __defaults__ (function attribute) - single: __closure__ (function attribute) single: __code__ (function attribute) - single: __globals__ (function attribute) single: __annotations__ (function attribute) single: __kwdefaults__ (function attribute) single: __type_params__ (function attribute) - pair: global; namespace -+-------------------------+-------------------------------+-----------+ -| Attribute | Meaning | | -+=========================+===============================+===========+ -| :attr:`__doc__` | The function's documentation | Writable | -| | string, or ``None`` if | | -| | unavailable; not inherited by | | -| | subclasses. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`~definition.\ | The function's name. | Writable | -| __name__` | | | -+-------------------------+-------------------------------+-----------+ -| :attr:`~definition.\ | The function's | Writable | -| __qualname__` | :term:`qualified name`. | | -| | | | -| | .. versionadded:: 3.3 | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__module__` | The name of the module the | Writable | -| | function was defined in, or | | -| | ``None`` if unavailable. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__defaults__` | A tuple containing default | Writable | -| | argument values for those | | -| | arguments that have defaults, | | -| | or ``None`` if no arguments | | -| | have a default value. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__code__` | The code object representing | Writable | -| | the compiled function body. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__globals__` | A reference to the dictionary | Read-only | -| | that holds the function's | | -| | global variables --- the | | -| | global namespace of the | | -| | module in which the function | | -| | was defined. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`~object.__dict__`| The namespace supporting | Writable | -| | arbitrary function | | -| | attributes. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__closure__` | ``None`` or a tuple of cells | Read-only | -| | that contain bindings for the | | -| | function's free variables. | | -| | See below for information on | | -| | the ``cell_contents`` | | -| | attribute. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__annotations__` | A dict containing annotations | Writable | -| | of parameters. The keys of | | -| | the dict are the parameter | | -| | names, and ``'return'`` for | | -| | the return annotation, if | | -| | provided. For more | | -| | information on working with | | -| | this attribute, see | | -| | :ref:`annotations-howto`. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__kwdefaults__` | A dict containing defaults | Writable | -| | for keyword-only parameters. | | -+-------------------------+-------------------------------+-----------+ -| :attr:`__type_params__` | A tuple containing the | Writable | -| | :ref:`type parameters | | -| | ` of a | | -| | :ref:`generic function | | -| | `. | | -+-------------------------+-------------------------------+-----------+ - -Most of the attributes labelled "Writable" check the type of the assigned value. +Most of these attributes check the type of the assigned value: + +.. list-table:: + :header-rows: 1 + + * - Attribute + - Meaning + + * - .. attribute:: function.__doc__ + - The function's documentation string, or ``None`` if unavailable. + Not inherited by subclasses. + + * - .. attribute:: function.__name__ + - The function's name. + See also: :attr:`__name__ attributes `. + + * - .. attribute:: function.__qualname__ + - The function's :term:`qualified name`. + See also: :attr:`__qualname__ attributes `. + + .. versionadded:: 3.3 + + * - .. attribute:: function.__module__ + - The name of the module the function was defined in, + or ``None`` if unavailable. + + * - .. attribute:: function.__defaults__ + - A :class:`tuple` containing default parameter values + for those parameters that have defaults, + or ``None`` if no parameters have a default value. + + * - .. attribute:: function.__code__ + - The :ref:`code object ` representing + the compiled function body. + + * - .. attribute:: function.__dict__ + - The namespace supporting arbitrary function attributes. + See also: :attr:`__dict__ attributes `. + + * - .. attribute:: function.__annotations__ + - A :class:`dictionary ` containing annotations of parameters. + The keys of the dictionary are the parameter names, + and ``'return'`` for the return annotation, if provided. + See also: :ref:`annotations-howto`. + + * - .. attribute:: function.__kwdefaults__ + - A :class:`dictionary ` containing defaults for keyword-only + parameters. + + * - .. attribute:: function.__type_params__ + - A :class:`tuple` containing the :ref:`type parameters ` of + a :ref:`generic function `. Function objects also support getting and setting arbitrary attributes, which can be used, for example, to attach metadata to functions. Regular attribute -dot-notation is used to get and set such attributes. *Note that the current -implementation only supports function attributes on user-defined functions. -Function attributes on built-in functions may be supported in the future.* +dot-notation is used to get and set such attributes. -A cell object has the attribute ``cell_contents``. This can be used to get -the value of the cell, as well as set the value. +.. impl-detail:: + + CPython's current implementation only supports function attributes + on user-defined functions. Function attributes on + :ref:`built-in functions ` may be supported in the + future. Additional information about a function's definition can be retrieved from its -code object; see the description of internal types below. The -:data:`cell ` type can be accessed in the :mod:`types` -module. +:ref:`code object ` +(accessible via the :attr:`~function.__code__` attribute). .. _instance-methods: @@ -665,15 +672,17 @@ Special read-only attributes: :ref:`bound ` * - .. attribute:: method.__func__ - - Refers to the original function object + - Refers to the original :ref:`function object ` * - .. attribute:: method.__doc__ - - The method's documentation (same as :attr:`!method.__func__.__doc__`). + - The method's documentation + (same as :attr:`method.__func__.__doc__ `). A :class:`string ` if the original function had a docstring, else ``None``. * - .. attribute:: method.__name__ - - The name of the method (same as :attr:`!method.__func__.__name__`) + - The name of the method + (same as :attr:`method.__func__.__name__ `) * - .. attribute:: method.__module__ - The name of the module the method was defined in, or ``None`` if @@ -779,6 +788,8 @@ is raised and the asynchronous iterator will have reached the end of the set of values to be yielded. +.. _builtin-functions: + Built-in functions ^^^^^^^^^^^^^^^^^^ @@ -791,10 +802,14 @@ A built-in function object is a wrapper around a C function. Examples of built-in functions are :func:`len` and :func:`math.sin` (:mod:`math` is a standard built-in module). The number and type of the arguments are determined by the C function. Special read-only attributes: -:attr:`__doc__` is the function's documentation string, or ``None`` if -unavailable; :attr:`~definition.__name__` is the function's name; :attr:`__self__` is -set to ``None`` (but see the next item); :attr:`__module__` is the name of -the module the function was defined in or ``None`` if unavailable. + +* :attr:`!__doc__` is the function's documentation string, or ``None`` if + unavailable. See :attr:`function.__doc__`. +* :attr:`!__name__` is the function's name. See :attr:`function.__name__`. +* :attr:`!__self__` is set to ``None`` (but see the next item). +* :attr:`!__module__` is the name of + the module the function was defined in or ``None`` if unavailable. + See :attr:`function.__module__`. .. _builtin-methods: @@ -844,7 +859,8 @@ the :ref:`import system ` as invoked either by the :keyword:`import` statement, or by calling functions such as :func:`importlib.import_module` and built-in :func:`__import__`. A module object has a namespace implemented by a -dictionary object (this is the dictionary referenced by the ``__globals__`` +:class:`dictionary ` object (this is the dictionary referenced by the +:attr:`~function.__globals__` attribute of functions defined in the module). Attribute references are translated to lookups in this dictionary, e.g., ``m.x`` is equivalent to ``m.__dict__["x"]``. A module object does not contain the code object used @@ -1891,7 +1907,8 @@ access (use of, assignment to, or deletion of ``x.name``) for class instances. .. note:: This method may still be bypassed when looking up special methods as the - result of implicit invocation via language syntax or built-in functions. + result of implicit invocation via language syntax or + :ref:`built-in functions `. See :ref:`special-lookup`. .. audit-event:: object.__getattr__ obj,name object.__getattribute__ diff --git a/Doc/whatsnew/2.1.rst b/Doc/whatsnew/2.1.rst index f0e1ded75a9d27..6d2d3cc02b8768 100644 --- a/Doc/whatsnew/2.1.rst +++ b/Doc/whatsnew/2.1.rst @@ -424,7 +424,8 @@ PEP 232: Function Attributes In Python 2.1, functions can now have arbitrary information attached to them. People were often using docstrings to hold information about functions and -methods, because the ``__doc__`` attribute was the only way of attaching any +methods, because the :attr:`~function.__doc__` attribute was the only way of +attaching any information to a function. For example, in the Zope web application server, functions are marked as safe for public access by having a docstring, and in John Aycock's SPARK parsing framework, docstrings hold parts of the BNF grammar diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index cab321c3e54d18..a7b0c6e5d76a6f 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -324,7 +324,8 @@ function, as previously described. In other words, ``@A @B @C(args)`` becomes:: Getting this right can be slightly brain-bending, but it's not too difficult. -A small related change makes the :attr:`func_name` attribute of functions +A small related change makes the :attr:`func_name ` +attribute of functions writable. This attribute is used to display function names in tracebacks, so decorators should change the name of any new function that's constructed and returned. diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst index b0c2529e780213..5953b32c6aaa18 100644 --- a/Doc/whatsnew/3.0.rst +++ b/Doc/whatsnew/3.0.rst @@ -779,14 +779,15 @@ Operators And Special Methods * Removed support for :attr:`__members__` and :attr:`__methods__`. -* The function attributes named :attr:`func_X` have been renamed to - use the :data:`__X__` form, freeing up these names in the function +* The function attributes named :attr:`!func_X` have been renamed to + use the :attr:`!__X__` form, freeing up these names in the function attribute namespace for user-defined attributes. To wit, - :attr:`func_closure`, :attr:`func_code`, :attr:`func_defaults`, - :attr:`func_dict`, :attr:`func_doc`, :attr:`func_globals`, - :attr:`func_name` were renamed to :attr:`__closure__`, - :attr:`__code__`, :attr:`__defaults__`, :attr:`~object.__dict__`, - :attr:`__doc__`, :attr:`__globals__`, :attr:`~definition.__name__`, + :attr:`!func_closure`, :attr:`!func_code`, :attr:`!func_defaults`, + :attr:`!func_dict`, :attr:`!func_doc`, :attr:`!func_globals`, + :attr:`!func_name` were renamed to :attr:`~function.__closure__`, + :attr:`~function.__code__`, :attr:`~function.__defaults__`, + :attr:`~function.__dict__`, :attr:`~function.__doc__`, + :attr:`~function.__globals__`, :attr:`~function.__name__`, respectively. * :meth:`!__nonzero__` is now :meth:`~object.__bool__`. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4401deb0768c11..00f396846e29bd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -483,7 +483,7 @@ Deprecated (as has always been the case for an executing frame). (Contributed by Irit Katriel in :gh:`79932`.) -* Assignment to a function's ``__code__`` attribute where the new code +* Assignment to a function's :attr:`~function.__code__` attribute where the new code object's type does not match the function's type, is deprecated. The different types are: plain function, generator, async generator and coroutine. diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index df32b76b6d7b03..5ef76968e9d86b 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -791,8 +791,9 @@ functools * The :func:`functools.wraps` decorator now adds a :attr:`__wrapped__` attribute pointing to the original callable function. This allows wrapped functions to - be introspected. It also copies :attr:`__annotations__` if defined. And now - it also gracefully skips over missing attributes such as :attr:`__doc__` which + be introspected. It also copies :attr:`~function.__annotations__` if + defined. And now it also gracefully skips over missing attributes such as + :attr:`~function.__doc__` which might not be defined for the wrapped callable. In the above example, the cache can be removed by recovering the original diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 2ddab76814369e..9f9cf7fafdc19e 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -2405,8 +2405,8 @@ Changes in the Python API storage). (:issue:`17094`.) * Parameter names in ``__annotations__`` dicts are now mangled properly, - similarly to ``__kwdefaults__``. (Contributed by Yury Selivanov in - :issue:`20625`.) + similarly to :attr:`~function.__kwdefaults__`. + (Contributed by Yury Selivanov in :issue:`20625`.) * :attr:`hashlib.hash.name` now always returns the identifier in lower case. Previously some builtin hashes had uppercase names, but now that it is a From a135a6d2c6d503b186695f01efa7eed65611b04e Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 11 Dec 2023 11:44:22 +0000 Subject: [PATCH 187/442] gh-112943: Correctly compute end offsets for multiline tokens in the tokenize module (#112949) --- Lib/test/test_tokenize.py | 10 ++++++++++ ...023-12-11-00-50-00.gh-issue-112943.RHNZie.rst | 2 ++ Parser/pegen.c | 16 +++++++++++----- Parser/pegen.h | 1 + Python/Python-tokenize.c | 2 +- 5 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-11-00-50-00.gh-issue-112943.RHNZie.rst diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 290f4608c5e739..21e8637a7ca905 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -615,6 +615,16 @@ def test_string(self): OP '}' (3, 0) (3, 1) FSTRING_MIDDLE '__' (3, 1) (3, 3) FSTRING_END "'" (3, 3) (3, 4) + """) + + self.check_tokenize("""\ + '''Autorzy, którzy tą jednostkę mają wpisani jako AKTUALNA -- czyli + aktualni pracownicy, obecni pracownicy''' +""", """\ + INDENT ' ' (1, 0) (1, 4) + STRING "'''Autorzy, którzy tą jednostkę mają wpisani jako AKTUALNA -- czyli\\n aktualni pracownicy, obecni pracownicy'''" (1, 4) (2, 45) + NEWLINE '\\n' (2, 45) (2, 46) + DEDENT '' (3, 0) (3, 0) """) def test_function(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-11-00-50-00.gh-issue-112943.RHNZie.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-11-00-50-00.gh-issue-112943.RHNZie.rst new file mode 100644 index 00000000000000..4bc2fe7c26d904 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-11-00-50-00.gh-issue-112943.RHNZie.rst @@ -0,0 +1,2 @@ +Correctly compute end column offsets for multiline tokens in the +:mod:`tokenize` module. Patch by Pablo Galindo diff --git a/Parser/pegen.c b/Parser/pegen.c index 0c60394e4f199b..7766253a76066f 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -19,12 +19,8 @@ _PyPegen_interactive_exit(Parser *p) } Py_ssize_t -_PyPegen_byte_offset_to_character_offset(PyObject *line, Py_ssize_t col_offset) +_PyPegen_byte_offset_to_character_offset_raw(const char* str, Py_ssize_t col_offset) { - const char *str = PyUnicode_AsUTF8(line); - if (!str) { - return -1; - } Py_ssize_t len = strlen(str); if (col_offset > len + 1) { col_offset = len + 1; @@ -39,6 +35,16 @@ _PyPegen_byte_offset_to_character_offset(PyObject *line, Py_ssize_t col_offset) return size; } +Py_ssize_t +_PyPegen_byte_offset_to_character_offset(PyObject *line, Py_ssize_t col_offset) +{ + const char *str = PyUnicode_AsUTF8(line); + if (!str) { + return -1; + } + return _PyPegen_byte_offset_to_character_offset_raw(str, col_offset); +} + // Here, mark is the start of the node, while p->mark is the end. // If node==NULL, they should be the same. int diff --git a/Parser/pegen.h b/Parser/pegen.h index 424f80acd7be3b..57b45a54c36c57 100644 --- a/Parser/pegen.h +++ b/Parser/pegen.h @@ -149,6 +149,7 @@ expr_ty _PyPegen_name_token(Parser *p); expr_ty _PyPegen_number_token(Parser *p); void *_PyPegen_string_token(Parser *p); Py_ssize_t _PyPegen_byte_offset_to_character_offset(PyObject *line, Py_ssize_t col_offset); +Py_ssize_t _PyPegen_byte_offset_to_character_offset_raw(const char*, Py_ssize_t col_offset); // Error handling functions and APIs typedef enum { diff --git a/Python/Python-tokenize.c b/Python/Python-tokenize.c index 364fe55d0a05e4..a7891709b3b44a 100644 --- a/Python/Python-tokenize.c +++ b/Python/Python-tokenize.c @@ -225,7 +225,7 @@ tokenizeriter_next(tokenizeriterobject *it) col_offset = _PyPegen_byte_offset_to_character_offset(line, token.start - line_start); } if (token.end != NULL && token.end >= it->tok->line_start) { - end_col_offset = _PyPegen_byte_offset_to_character_offset(line, token.end - it->tok->line_start); + end_col_offset = _PyPegen_byte_offset_to_character_offset_raw(it->tok->line_start, token.end - it->tok->line_start); } if (it->tok->tok_extra_tokens) { From 97cd45bfdbb6525457ba9d6824386f1e0eea6657 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 11 Dec 2023 06:48:52 -0500 Subject: [PATCH 188/442] Fix SyntaxWarning in test_syntax.py (GH-112944) --- Lib/test/test_syntax.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 99433df73387d0..ece1366076798c 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2336,7 +2336,7 @@ def test_error_parenthesis(self): # Examples with dencodings s = b'# coding=latin\n(aaaaaaaaaaaaaaaaa\naaaaaaaaaaa\xb5' - self._check_error(s, "'\(' was never closed") + self._check_error(s, r"'\(' was never closed") def test_error_string_literal(self): From c27e9d5d17abf468ea10081e177ba673316b1b98 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 11 Dec 2023 14:14:36 +0000 Subject: [PATCH 189/442] GH-111485: Factor out generation of uop IDs from cases generator. (GH-112877) --- .gitattributes | 1 + Include/internal/pycore_opcode_metadata.h | 97 +------- Include/internal/pycore_uop_ids.h | 268 ++++++++++++++++++++++ Makefile.pre.in | 3 + Tools/cases_generator/analyzer.py | 2 + Tools/cases_generator/generate_cases.py | 2 +- Tools/cases_generator/tier1_generator.py | 2 +- Tools/cases_generator/uop_id_generator.py | 91 ++++++++ 8 files changed, 368 insertions(+), 98 deletions(-) create mode 100644 Include/internal/pycore_uop_ids.h create mode 100644 Tools/cases_generator/uop_id_generator.py diff --git a/.gitattributes b/.gitattributes index acfd62411542f1..22afffb05abb20 100644 --- a/.gitattributes +++ b/.gitattributes @@ -76,6 +76,7 @@ Include/internal/pycore_ast_state.h generated Include/internal/pycore_opcode.h generated Include/internal/pycore_opcode_metadata.h generated Include/internal/pycore_*_generated.h generated +Include/internal/pycore_uop_ids.h generated Include/opcode.h generated Include/opcode_ids.h generated Include/token.h generated diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 774c0f99379ed6..1f460640a1e398 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -25,102 +25,7 @@ ((OP) == POP_BLOCK) || \ 0) -#define _EXIT_TRACE 300 -#define _SET_IP 301 -#define _SPECIALIZE_TO_BOOL 302 -#define _TO_BOOL 303 -#define _GUARD_BOTH_INT 304 -#define _BINARY_OP_MULTIPLY_INT 305 -#define _BINARY_OP_ADD_INT 306 -#define _BINARY_OP_SUBTRACT_INT 307 -#define _GUARD_BOTH_FLOAT 308 -#define _BINARY_OP_MULTIPLY_FLOAT 309 -#define _BINARY_OP_ADD_FLOAT 310 -#define _BINARY_OP_SUBTRACT_FLOAT 311 -#define _GUARD_BOTH_UNICODE 312 -#define _BINARY_OP_ADD_UNICODE 313 -#define _BINARY_OP_INPLACE_ADD_UNICODE 314 -#define _SPECIALIZE_BINARY_SUBSCR 315 -#define _BINARY_SUBSCR 316 -#define _SPECIALIZE_STORE_SUBSCR 317 -#define _STORE_SUBSCR 318 -#define _POP_FRAME 319 -#define _SPECIALIZE_SEND 320 -#define _SEND 321 -#define _SPECIALIZE_UNPACK_SEQUENCE 322 -#define _UNPACK_SEQUENCE 323 -#define _SPECIALIZE_STORE_ATTR 324 -#define _STORE_ATTR 325 -#define _SPECIALIZE_LOAD_GLOBAL 326 -#define _LOAD_GLOBAL 327 -#define _GUARD_GLOBALS_VERSION 328 -#define _GUARD_BUILTINS_VERSION 329 -#define _LOAD_GLOBAL_MODULE 330 -#define _LOAD_GLOBAL_BUILTINS 331 -#define _SPECIALIZE_LOAD_SUPER_ATTR 332 -#define _LOAD_SUPER_ATTR 333 -#define _SPECIALIZE_LOAD_ATTR 334 -#define _LOAD_ATTR 335 -#define _GUARD_TYPE_VERSION 336 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES 337 -#define _LOAD_ATTR_INSTANCE_VALUE 338 -#define _CHECK_ATTR_MODULE 339 -#define _LOAD_ATTR_MODULE 340 -#define _CHECK_ATTR_WITH_HINT 341 -#define _LOAD_ATTR_WITH_HINT 342 -#define _LOAD_ATTR_SLOT 343 -#define _CHECK_ATTR_CLASS 344 -#define _LOAD_ATTR_CLASS 345 -#define _GUARD_DORV_VALUES 346 -#define _STORE_ATTR_INSTANCE_VALUE 347 -#define _STORE_ATTR_SLOT 348 -#define _SPECIALIZE_COMPARE_OP 349 -#define _COMPARE_OP 350 -#define _POP_JUMP_IF_FALSE 351 -#define _POP_JUMP_IF_TRUE 352 -#define _IS_NONE 353 -#define _SPECIALIZE_FOR_ITER 354 -#define _FOR_ITER 355 -#define _FOR_ITER_TIER_TWO 356 -#define _ITER_CHECK_LIST 357 -#define _ITER_JUMP_LIST 358 -#define _GUARD_NOT_EXHAUSTED_LIST 359 -#define _ITER_NEXT_LIST 360 -#define _ITER_CHECK_TUPLE 361 -#define _ITER_JUMP_TUPLE 362 -#define _GUARD_NOT_EXHAUSTED_TUPLE 363 -#define _ITER_NEXT_TUPLE 364 -#define _ITER_CHECK_RANGE 365 -#define _ITER_JUMP_RANGE 366 -#define _GUARD_NOT_EXHAUSTED_RANGE 367 -#define _ITER_NEXT_RANGE 368 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 369 -#define _GUARD_KEYS_VERSION 370 -#define _LOAD_ATTR_METHOD_WITH_VALUES 371 -#define _LOAD_ATTR_METHOD_NO_DICT 372 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 373 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 374 -#define _CHECK_ATTR_METHOD_LAZY_DICT 375 -#define _LOAD_ATTR_METHOD_LAZY_DICT 376 -#define _SPECIALIZE_CALL 377 -#define _CALL 378 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 379 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 380 -#define _CHECK_PEP_523 381 -#define _CHECK_FUNCTION_EXACT_ARGS 382 -#define _CHECK_STACK_SPACE 383 -#define _INIT_CALL_PY_EXACT_ARGS 384 -#define _PUSH_FRAME 385 -#define _SPECIALIZE_BINARY_OP 386 -#define _BINARY_OP 387 -#define _GUARD_IS_TRUE_POP 388 -#define _GUARD_IS_FALSE_POP 389 -#define _GUARD_IS_NONE_POP 390 -#define _GUARD_IS_NOT_NONE_POP 391 -#define _JUMP_TO_TOP 392 -#define _SAVE_RETURN_OFFSET 393 -#define _INSERT 394 -#define _CHECK_VALIDITY 395 +#include "pycore_uop_ids.h" extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); #ifdef NEED_OPCODE_METADATA diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h new file mode 100644 index 00000000000000..03e089953698c9 --- /dev/null +++ b/Include/internal/pycore_uop_ids.h @@ -0,0 +1,268 @@ +// This file is generated by Tools/cases_generator/uop_id_generator.py +// from: +// ['./Python/bytecodes.c'] +// Do not edit! +#ifndef Py_CORE_UOP_IDS_H +#define Py_CORE_UOP_IDS_H +#ifdef __cplusplus +extern "C" { +#endif + +#define _EXIT_TRACE 300 +#define _SET_IP 301 +#define _NOP NOP +#define _RESUME RESUME +#define _RESUME_CHECK RESUME_CHECK +#define _INSTRUMENTED_RESUME INSTRUMENTED_RESUME +#define _LOAD_FAST_CHECK LOAD_FAST_CHECK +#define _LOAD_FAST LOAD_FAST +#define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR +#define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST +#define _LOAD_CONST LOAD_CONST +#define _STORE_FAST STORE_FAST +#define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST +#define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST +#define _POP_TOP POP_TOP +#define _PUSH_NULL PUSH_NULL +#define _INSTRUMENTED_END_FOR INSTRUMENTED_END_FOR +#define _END_SEND END_SEND +#define _INSTRUMENTED_END_SEND INSTRUMENTED_END_SEND +#define _UNARY_NEGATIVE UNARY_NEGATIVE +#define _UNARY_NOT UNARY_NOT +#define _SPECIALIZE_TO_BOOL 302 +#define _TO_BOOL 303 +#define _TO_BOOL_BOOL TO_BOOL_BOOL +#define _TO_BOOL_INT TO_BOOL_INT +#define _TO_BOOL_LIST TO_BOOL_LIST +#define _TO_BOOL_NONE TO_BOOL_NONE +#define _TO_BOOL_STR TO_BOOL_STR +#define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE +#define _UNARY_INVERT UNARY_INVERT +#define _GUARD_BOTH_INT 304 +#define _BINARY_OP_MULTIPLY_INT 305 +#define _BINARY_OP_ADD_INT 306 +#define _BINARY_OP_SUBTRACT_INT 307 +#define _GUARD_BOTH_FLOAT 308 +#define _BINARY_OP_MULTIPLY_FLOAT 309 +#define _BINARY_OP_ADD_FLOAT 310 +#define _BINARY_OP_SUBTRACT_FLOAT 311 +#define _GUARD_BOTH_UNICODE 312 +#define _BINARY_OP_ADD_UNICODE 313 +#define _BINARY_OP_INPLACE_ADD_UNICODE 314 +#define _SPECIALIZE_BINARY_SUBSCR 315 +#define _BINARY_SUBSCR 316 +#define _BINARY_SLICE BINARY_SLICE +#define _STORE_SLICE STORE_SLICE +#define _BINARY_SUBSCR_LIST_INT BINARY_SUBSCR_LIST_INT +#define _BINARY_SUBSCR_STR_INT BINARY_SUBSCR_STR_INT +#define _BINARY_SUBSCR_TUPLE_INT BINARY_SUBSCR_TUPLE_INT +#define _BINARY_SUBSCR_DICT BINARY_SUBSCR_DICT +#define _BINARY_SUBSCR_GETITEM BINARY_SUBSCR_GETITEM +#define _LIST_APPEND LIST_APPEND +#define _SET_ADD SET_ADD +#define _SPECIALIZE_STORE_SUBSCR 317 +#define _STORE_SUBSCR 318 +#define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT +#define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT +#define _DELETE_SUBSCR DELETE_SUBSCR +#define _CALL_INTRINSIC_1 CALL_INTRINSIC_1 +#define _CALL_INTRINSIC_2 CALL_INTRINSIC_2 +#define _RAISE_VARARGS RAISE_VARARGS +#define _INTERPRETER_EXIT INTERPRETER_EXIT +#define _POP_FRAME 319 +#define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE +#define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST +#define _GET_AITER GET_AITER +#define _GET_ANEXT GET_ANEXT +#define _GET_AWAITABLE GET_AWAITABLE +#define _SPECIALIZE_SEND 320 +#define _SEND 321 +#define _SEND_GEN SEND_GEN +#define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE +#define _YIELD_VALUE YIELD_VALUE +#define _POP_EXCEPT POP_EXCEPT +#define _RERAISE RERAISE +#define _END_ASYNC_FOR END_ASYNC_FOR +#define _CLEANUP_THROW CLEANUP_THROW +#define _LOAD_ASSERTION_ERROR LOAD_ASSERTION_ERROR +#define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS +#define _STORE_NAME STORE_NAME +#define _DELETE_NAME DELETE_NAME +#define _SPECIALIZE_UNPACK_SEQUENCE 322 +#define _UNPACK_SEQUENCE 323 +#define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE +#define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE +#define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST +#define _UNPACK_EX UNPACK_EX +#define _SPECIALIZE_STORE_ATTR 324 +#define _STORE_ATTR 325 +#define _DELETE_ATTR DELETE_ATTR +#define _STORE_GLOBAL STORE_GLOBAL +#define _DELETE_GLOBAL DELETE_GLOBAL +#define _LOAD_LOCALS LOAD_LOCALS +#define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS +#define _LOAD_NAME LOAD_NAME +#define _SPECIALIZE_LOAD_GLOBAL 326 +#define _LOAD_GLOBAL 327 +#define _GUARD_GLOBALS_VERSION 328 +#define _GUARD_BUILTINS_VERSION 329 +#define _LOAD_GLOBAL_MODULE 330 +#define _LOAD_GLOBAL_BUILTINS 331 +#define _DELETE_FAST DELETE_FAST +#define _MAKE_CELL MAKE_CELL +#define _DELETE_DEREF DELETE_DEREF +#define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF +#define _LOAD_DEREF LOAD_DEREF +#define _STORE_DEREF STORE_DEREF +#define _COPY_FREE_VARS COPY_FREE_VARS +#define _BUILD_STRING BUILD_STRING +#define _BUILD_TUPLE BUILD_TUPLE +#define _BUILD_LIST BUILD_LIST +#define _LIST_EXTEND LIST_EXTEND +#define _SET_UPDATE SET_UPDATE +#define _BUILD_SET BUILD_SET +#define _BUILD_MAP BUILD_MAP +#define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS +#define _BUILD_CONST_KEY_MAP BUILD_CONST_KEY_MAP +#define _DICT_UPDATE DICT_UPDATE +#define _DICT_MERGE DICT_MERGE +#define _MAP_ADD MAP_ADD +#define _INSTRUMENTED_LOAD_SUPER_ATTR INSTRUMENTED_LOAD_SUPER_ATTR +#define _SPECIALIZE_LOAD_SUPER_ATTR 332 +#define _LOAD_SUPER_ATTR 333 +#define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR +#define _LOAD_SUPER_ATTR_METHOD LOAD_SUPER_ATTR_METHOD +#define _SPECIALIZE_LOAD_ATTR 334 +#define _LOAD_ATTR 335 +#define _GUARD_TYPE_VERSION 336 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 337 +#define _LOAD_ATTR_INSTANCE_VALUE 338 +#define _CHECK_ATTR_MODULE 339 +#define _LOAD_ATTR_MODULE 340 +#define _CHECK_ATTR_WITH_HINT 341 +#define _LOAD_ATTR_WITH_HINT 342 +#define _LOAD_ATTR_SLOT 343 +#define _CHECK_ATTR_CLASS 344 +#define _LOAD_ATTR_CLASS 345 +#define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN +#define _GUARD_DORV_VALUES 346 +#define _STORE_ATTR_INSTANCE_VALUE 347 +#define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT +#define _STORE_ATTR_SLOT 348 +#define _SPECIALIZE_COMPARE_OP 349 +#define _COMPARE_OP 350 +#define _COMPARE_OP_FLOAT COMPARE_OP_FLOAT +#define _COMPARE_OP_INT COMPARE_OP_INT +#define _COMPARE_OP_STR COMPARE_OP_STR +#define _IS_OP IS_OP +#define _CONTAINS_OP CONTAINS_OP +#define _CHECK_EG_MATCH CHECK_EG_MATCH +#define _CHECK_EXC_MATCH CHECK_EXC_MATCH +#define _IMPORT_NAME IMPORT_NAME +#define _IMPORT_FROM IMPORT_FROM +#define _JUMP_FORWARD JUMP_FORWARD +#define _JUMP_BACKWARD JUMP_BACKWARD +#define _ENTER_EXECUTOR ENTER_EXECUTOR +#define _POP_JUMP_IF_FALSE 351 +#define _POP_JUMP_IF_TRUE 352 +#define _IS_NONE 353 +#define _JUMP_BACKWARD_NO_INTERRUPT JUMP_BACKWARD_NO_INTERRUPT +#define _GET_LEN GET_LEN +#define _MATCH_CLASS MATCH_CLASS +#define _MATCH_MAPPING MATCH_MAPPING +#define _MATCH_SEQUENCE MATCH_SEQUENCE +#define _MATCH_KEYS MATCH_KEYS +#define _GET_ITER GET_ITER +#define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER +#define _SPECIALIZE_FOR_ITER 354 +#define _FOR_ITER 355 +#define _FOR_ITER_TIER_TWO 356 +#define _INSTRUMENTED_FOR_ITER INSTRUMENTED_FOR_ITER +#define _ITER_CHECK_LIST 357 +#define _ITER_JUMP_LIST 358 +#define _GUARD_NOT_EXHAUSTED_LIST 359 +#define _ITER_NEXT_LIST 360 +#define _ITER_CHECK_TUPLE 361 +#define _ITER_JUMP_TUPLE 362 +#define _GUARD_NOT_EXHAUSTED_TUPLE 363 +#define _ITER_NEXT_TUPLE 364 +#define _ITER_CHECK_RANGE 365 +#define _ITER_JUMP_RANGE 366 +#define _GUARD_NOT_EXHAUSTED_RANGE 367 +#define _ITER_NEXT_RANGE 368 +#define _FOR_ITER_GEN FOR_ITER_GEN +#define _BEFORE_ASYNC_WITH BEFORE_ASYNC_WITH +#define _BEFORE_WITH BEFORE_WITH +#define _WITH_EXCEPT_START WITH_EXCEPT_START +#define _PUSH_EXC_INFO PUSH_EXC_INFO +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 369 +#define _GUARD_KEYS_VERSION 370 +#define _LOAD_ATTR_METHOD_WITH_VALUES 371 +#define _LOAD_ATTR_METHOD_NO_DICT 372 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 373 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 374 +#define _CHECK_ATTR_METHOD_LAZY_DICT 375 +#define _LOAD_ATTR_METHOD_LAZY_DICT 376 +#define _INSTRUMENTED_CALL INSTRUMENTED_CALL +#define _SPECIALIZE_CALL 377 +#define _CALL 378 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 379 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 380 +#define _CHECK_PEP_523 381 +#define _CHECK_FUNCTION_EXACT_ARGS 382 +#define _CHECK_STACK_SPACE 383 +#define _INIT_CALL_PY_EXACT_ARGS 384 +#define _PUSH_FRAME 385 +#define _CALL_PY_WITH_DEFAULTS CALL_PY_WITH_DEFAULTS +#define _CALL_TYPE_1 CALL_TYPE_1 +#define _CALL_STR_1 CALL_STR_1 +#define _CALL_TUPLE_1 CALL_TUPLE_1 +#define _CALL_ALLOC_AND_ENTER_INIT CALL_ALLOC_AND_ENTER_INIT +#define _EXIT_INIT_CHECK EXIT_INIT_CHECK +#define _CALL_BUILTIN_CLASS CALL_BUILTIN_CLASS +#define _CALL_BUILTIN_O CALL_BUILTIN_O +#define _CALL_BUILTIN_FAST CALL_BUILTIN_FAST +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS CALL_BUILTIN_FAST_WITH_KEYWORDS +#define _CALL_LEN CALL_LEN +#define _CALL_ISINSTANCE CALL_ISINSTANCE +#define _CALL_LIST_APPEND CALL_LIST_APPEND +#define _CALL_METHOD_DESCRIPTOR_O CALL_METHOD_DESCRIPTOR_O +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS +#define _CALL_METHOD_DESCRIPTOR_NOARGS CALL_METHOD_DESCRIPTOR_NOARGS +#define _CALL_METHOD_DESCRIPTOR_FAST CALL_METHOD_DESCRIPTOR_FAST +#define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW +#define _CALL_KW CALL_KW +#define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX +#define _CALL_FUNCTION_EX CALL_FUNCTION_EX +#define _MAKE_FUNCTION MAKE_FUNCTION +#define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE +#define _RETURN_GENERATOR RETURN_GENERATOR +#define _BUILD_SLICE BUILD_SLICE +#define _CONVERT_VALUE CONVERT_VALUE +#define _FORMAT_SIMPLE FORMAT_SIMPLE +#define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC +#define _COPY COPY +#define _SPECIALIZE_BINARY_OP 386 +#define _BINARY_OP 387 +#define _SWAP SWAP +#define _INSTRUMENTED_INSTRUCTION INSTRUMENTED_INSTRUCTION +#define _INSTRUMENTED_JUMP_FORWARD INSTRUMENTED_JUMP_FORWARD +#define _INSTRUMENTED_JUMP_BACKWARD INSTRUMENTED_JUMP_BACKWARD +#define _INSTRUMENTED_POP_JUMP_IF_TRUE INSTRUMENTED_POP_JUMP_IF_TRUE +#define _INSTRUMENTED_POP_JUMP_IF_FALSE INSTRUMENTED_POP_JUMP_IF_FALSE +#define _INSTRUMENTED_POP_JUMP_IF_NONE INSTRUMENTED_POP_JUMP_IF_NONE +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE INSTRUMENTED_POP_JUMP_IF_NOT_NONE +#define _GUARD_IS_TRUE_POP 388 +#define _GUARD_IS_FALSE_POP 389 +#define _GUARD_IS_NONE_POP 390 +#define _GUARD_IS_NOT_NONE_POP 391 +#define _JUMP_TO_TOP 392 +#define _SAVE_RETURN_OFFSET 393 +#define _INSERT 394 +#define _CHECK_VALIDITY 395 + +#ifdef __cplusplus +} +#endif +#endif /* !Py_OPCODE_IDS_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in index 42a7545be7e666..4317c947aeb919 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1595,10 +1595,13 @@ regen-cases: $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/opcode_id_generator.py -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) \ + $(srcdir)/Tools/cases_generator/uop_id_generator.py -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new + $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_ids.h $(srcdir)/Include/internal/pycore_uop_ids.h.new $(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 027f9861a1c0eb..945de63d209935 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -109,6 +109,7 @@ class Uop: body: list[lexer.Token] properties: Properties _size: int = -1 + implicitly_created: bool = False def dump(self, indent: str) -> None: print( @@ -328,6 +329,7 @@ def desugar_inst( assert inst.kind == "inst" name = inst.name uop = make_uop("_" + inst.name, inst) + uop.implicitly_created = True uops[inst.name] = uop add_instruction(name, [uop], instructions) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index d0fdc4a0aeb7b0..f7c362131a7a9f 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -381,7 +381,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No self.write_pseudo_instrs() self.out.emit("") - self.write_uop_items(lambda name, counter: f"#define {name} {counter}") + self.out.emit('#include "pycore_uop_ids.h"') self.write_stack_effect_functions() diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 9787403b3bbc47..56ae660a686822 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -404,7 +404,7 @@ def generate_tier1( if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: - args.input.append(DEFAULT_INPUT) + args.input.append(DEFAULT_INPUT.as_posix()) data = analyze_files(args.input) with open(args.output, "w") as outfile: generate_tier1(args.input, data, outfile, args.emit_line_directives) diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py new file mode 100644 index 00000000000000..4a96dbc171ee22 --- /dev/null +++ b/Tools/cases_generator/uop_id_generator.py @@ -0,0 +1,91 @@ +"""Generate the list of uop IDs. +Reads the instruction definitions from bytecodes.c. +Writes the IDs to pycore_uop_ids.h by default. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, +) +from cwriter import CWriter +from typing import TextIO + + +DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_ids.h" + + +def generate_uop_ids( + filenames: str, analysis: Analysis, outfile: TextIO, distinct_namespace: bool +) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 0, False) + out.emit( + """#ifndef Py_CORE_UOP_IDS_H +#define Py_CORE_UOP_IDS_H +#ifdef __cplusplus +extern "C" { +#endif + +""" + ) + + next_id = 1 if distinct_namespace else 300 + # These two are first by convention + out.emit(f"#define _EXIT_TRACE {next_id}\n") + next_id += 1 + out.emit(f"#define _SET_IP {next_id}\n") + next_id += 1 + PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP", "_CACHE", "_RESERVED", "_EXTENDED_ARG"} + + for uop in analysis.uops.values(): + if uop.name in PRE_DEFINED: + continue + if uop.implicitly_created and not distinct_namespace: + out.emit(f"#define {uop.name} {uop.name[1:]}\n") + else: + out.emit(f"#define {uop.name} {next_id}\n") + next_id += 1 + + out.emit("\n") + out.emit("#ifdef __cplusplus\n") + out.emit("}\n") + out.emit("#endif\n") + out.emit("#endif /* !Py_OPCODE_IDS_H */\n") + + +arg_parser = argparse.ArgumentParser( + description="Generate the header file with all uop IDs.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) +arg_parser.add_argument( + "-n", + "--namespace", + help="Give uops a distinct namespace", + action="store_true", +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT.as_posix()) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_uop_ids(args.input, data, outfile, args.namespace) From 3251ba8f1af535bf28e31a6832511ba19e96b262 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Mon, 11 Dec 2023 15:22:36 +0100 Subject: [PATCH 190/442] gh-112898: warn about unsaved files when quitting IDLE on macOS (#112939) * gh-112898: warn about unsaved files when quitting IDLE on macOS Implement the TK function ``::tk::mac::Quit`` on macOS to ensure that IDLE asks about saving unsaved files when quitting IDLE. Co-authored-by: Christopher Chavez chrischavez@gmx.us --- Lib/idlelib/macosx.py | 2 +- .../next/IDLE/2023-12-10-20-01-11.gh-issue-112898.98aWv2.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/IDLE/2023-12-10-20-01-11.gh-issue-112898.98aWv2.rst diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index 2ea02ec04d661a..332952f4572cbd 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -221,7 +221,7 @@ def help_dialog(event=None): # The binding above doesn't reliably work on all versions of Tk # on macOS. Adding command definition below does seem to do the # right thing for now. - root.createcommand('exit', flist.close_all_callback) + root.createcommand('::tk::mac::Quit', flist.close_all_callback) if isCarbonTk(): # for Carbon AquaTk, replace the default Tk apple menu diff --git a/Misc/NEWS.d/next/IDLE/2023-12-10-20-01-11.gh-issue-112898.98aWv2.rst b/Misc/NEWS.d/next/IDLE/2023-12-10-20-01-11.gh-issue-112898.98aWv2.rst new file mode 100644 index 00000000000000..1c20e46b1e5f7b --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2023-12-10-20-01-11.gh-issue-112898.98aWv2.rst @@ -0,0 +1 @@ +Fix processing unsaved files when quitting IDLE on macOS. From 27a5fd8cb8c88537216d7a498eba9d9177951d76 Mon Sep 17 00:00:00 2001 From: Sidney Markowitz Date: Tue, 12 Dec 2023 05:21:18 +1300 Subject: [PATCH 191/442] gh-94606: Fix error when message with Unicode surrogate not surrogateescaped string (GH-94641) Co-authored-by: Serhiy Storchaka --- Lib/email/message.py | 29 ++++++++++--------- Lib/email/utils.py | 4 +-- Lib/test/test_email/test_message.py | 29 +++++++++++++++++++ ...2-07-07-05-37-53.gh-issue-94606.hojJ54.rst | 3 ++ 4 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-07-07-05-37-53.gh-issue-94606.hojJ54.rst diff --git a/Lib/email/message.py b/Lib/email/message.py index 411118c74dabb4..fe769580fed5d0 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -289,25 +289,26 @@ def get_payload(self, i=None, decode=False): # cte might be a Header, so for now stringify it. cte = str(self.get('content-transfer-encoding', '')).lower() # payload may be bytes here. - if isinstance(payload, str): - if utils._has_surrogates(payload): - bpayload = payload.encode('ascii', 'surrogateescape') - if not decode: + if not decode: + if isinstance(payload, str) and utils._has_surrogates(payload): + try: + bpayload = payload.encode('ascii', 'surrogateescape') try: payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace') except LookupError: payload = bpayload.decode('ascii', 'replace') - elif decode: - try: - bpayload = payload.encode('ascii') - except UnicodeError: - # This won't happen for RFC compliant messages (messages - # containing only ASCII code points in the unicode input). - # If it does happen, turn the string into bytes in a way - # guaranteed not to fail. - bpayload = payload.encode('raw-unicode-escape') - if not decode: + except UnicodeEncodeError: + pass return payload + if isinstance(payload, str): + try: + bpayload = payload.encode('ascii', 'surrogateescape') + except UnicodeEncodeError: + # This won't happen for RFC compliant messages (messages + # containing only ASCII code points in the unicode input). + # If it does happen, turn the string into bytes in a way + # guaranteed not to fail. + bpayload = payload.encode('raw-unicode-escape') if cte == 'quoted-printable': return quopri.decodestring(bpayload) elif cte == 'base64': diff --git a/Lib/email/utils.py b/Lib/email/utils.py index a49a8fa986ce0c..9175f2fdb6e69e 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -44,10 +44,10 @@ escapesre = re.compile(r'[\\"]') def _has_surrogates(s): - """Return True if s contains surrogate-escaped binary data.""" + """Return True if s may contain surrogate-escaped binary data.""" # This check is based on the fact that unless there are surrogates, utf8 # (Python's default encoding) can encode any string. This is the fastest - # way to check for surrogates, see issue 11454 for timings. + # way to check for surrogates, see bpo-11454 (moved to gh-55663) for timings. try: s.encode() return False diff --git a/Lib/test/test_email/test_message.py b/Lib/test/test_email/test_message.py index d3f396f02e7a72..034f7626c1fc7c 100644 --- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -748,6 +748,35 @@ def test_iter_attachments_mutation(self): self.assertEqual(len(list(m.iter_attachments())), 2) self.assertEqual(m.get_payload(), orig) + get_payload_surrogate_params = { + + 'good_surrogateescape': ( + "String that can be encod\udcc3\udcabd with surrogateescape", + b'String that can be encod\xc3\xabd with surrogateescape' + ), + + 'string_with_utf8': ( + "String with utf-8 charactër", + b'String with utf-8 charact\xebr' + ), + + 'surrogate_and_utf8': ( + "String that cannot be ëncod\udcc3\udcabd with surrogateescape", + b'String that cannot be \xebncod\\udcc3\\udcabd with surrogateescape' + ), + + 'out_of_range_surrogate': ( + "String with \udfff cannot be encoded with surrogateescape", + b'String with \\udfff cannot be encoded with surrogateescape' + ), + } + + def get_payload_surrogate_as_gh_94606(self, msg, expected): + """test for GH issue 94606""" + m = self._str_msg(msg) + payload = m.get_payload(decode=True) + self.assertEqual(expected, payload) + class TestEmailMessage(TestEmailMessageBase, TestEmailBase): message = EmailMessage diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-07-05-37-53.gh-issue-94606.hojJ54.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-07-05-37-53.gh-issue-94606.hojJ54.rst new file mode 100644 index 00000000000000..5201ab7d842088 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-07-05-37-53.gh-issue-94606.hojJ54.rst @@ -0,0 +1,3 @@ +Fix UnicodeEncodeError when :func:`email.message.get_payload` reads a message +with a Unicode surrogate character and the message content is not well-formed for +surrogateescape encoding. Patch by Sidney Markowitz. From f4fe65e2dd7eda33c098c8af3a1974c5f7f11ab7 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Mon, 11 Dec 2023 10:43:07 -0600 Subject: [PATCH 192/442] gh-111178: Avoid calling functions from incompatible pointer types in memoryobject.c (GH-112863) * Make memory_clear() compatible with inquiry * Make memory_traverse() compatible with traverseproc * Make memory_dealloc() compatible with destructor * Make memory_repr() compatible with reprfunc * Make memory_hash() compatible with hashfunc * Make memoryiter_next() compatible with iternextfunc * Make memoryiter_traverse() compatible with traverseproc * Make memoryiter_dealloc() compatible with destructor * Make several functions compatible with getter * Make a few functions compatible with getter * Make memory_item() compatible with ssizeargfunc * Make memory_subscript() compatible with binaryfunc * Make memory_length() compatible with lenfunc * Make memory_ass_sub() compatible with objobjargproc * Make memory_releasebuf() compatible with releasebufferproc * Make memory_getbuf() compatible with getbufferproc * Make mbuf_clear() compatible with inquiry * Make mbuf_traverse() compatible with traverseproc * Make mbuf_dealloc() compatible with destructor --- Objects/memoryobject.c | 151 ++++++++++++++++++++++++----------------- 1 file changed, 90 insertions(+), 61 deletions(-) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index bcdd2ff0ceafe6..6a38952fdc1f3b 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -119,8 +119,9 @@ mbuf_release(_PyManagedBufferObject *self) } static void -mbuf_dealloc(_PyManagedBufferObject *self) +mbuf_dealloc(PyObject *_self) { + _PyManagedBufferObject *self = (_PyManagedBufferObject *)_self; assert(self->exports == 0); mbuf_release(self); if (self->flags&_Py_MANAGED_BUFFER_FREE_FORMAT) @@ -129,15 +130,17 @@ mbuf_dealloc(_PyManagedBufferObject *self) } static int -mbuf_traverse(_PyManagedBufferObject *self, visitproc visit, void *arg) +mbuf_traverse(PyObject *_self, visitproc visit, void *arg) { + _PyManagedBufferObject *self = (_PyManagedBufferObject *)_self; Py_VISIT(self->master.obj); return 0; } static int -mbuf_clear(_PyManagedBufferObject *self) +mbuf_clear(PyObject *_self) { + _PyManagedBufferObject *self = (_PyManagedBufferObject *)_self; assert(self->exports >= 0); mbuf_release(self); return 0; @@ -148,7 +151,7 @@ PyTypeObject _PyManagedBuffer_Type = { "managedbuffer", sizeof(_PyManagedBufferObject), 0, - (destructor)mbuf_dealloc, /* tp_dealloc */ + mbuf_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -165,8 +168,8 @@ PyTypeObject _PyManagedBuffer_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ - (traverseproc)mbuf_traverse, /* tp_traverse */ - (inquiry)mbuf_clear /* tp_clear */ + mbuf_traverse, /* tp_traverse */ + mbuf_clear /* tp_clear */ }; @@ -1137,8 +1140,9 @@ memoryview_release_impl(PyMemoryViewObject *self) } static void -memory_dealloc(PyMemoryViewObject *self) +memory_dealloc(PyObject *_self) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; assert(self->exports == 0); _PyObject_GC_UNTRACK(self); (void)_memory_release(self); @@ -1149,15 +1153,17 @@ memory_dealloc(PyMemoryViewObject *self) } static int -memory_traverse(PyMemoryViewObject *self, visitproc visit, void *arg) +memory_traverse(PyObject *_self, visitproc visit, void *arg) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_VISIT(self->mbuf); return 0; } static int -memory_clear(PyMemoryViewObject *self) +memory_clear(PyObject *_self) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; (void)_memory_release(self); Py_CLEAR(self->mbuf); return 0; @@ -1510,8 +1516,9 @@ memoryview_toreadonly_impl(PyMemoryViewObject *self) /**************************************************************************/ static int -memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags) +memory_getbuf(PyObject *_self, Py_buffer *view, int flags) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_buffer *base = &self->view; int baseflags = self->flags; @@ -1589,8 +1596,9 @@ memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags) } static void -memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view) +memory_releasebuf(PyObject *_self, Py_buffer *view) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; self->exports--; return; /* PyBuffer_Release() decrements view->obj after this function returns. */ @@ -1598,8 +1606,8 @@ memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view) /* Buffer methods */ static PyBufferProcs memory_as_buffer = { - (getbufferproc)memory_getbuf, /* bf_getbuffer */ - (releasebufferproc)memory_releasebuf, /* bf_releasebuffer */ + memory_getbuf, /* bf_getbuffer */ + memory_releasebuf, /* bf_releasebuffer */ }; @@ -2344,8 +2352,9 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, } static PyObject * -memory_repr(PyMemoryViewObject *self) +memory_repr(PyObject *_self) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; if (self->flags & _Py_MEMORYVIEW_RELEASED) return PyUnicode_FromFormat("", self); else @@ -2421,8 +2430,9 @@ ptr_from_tuple(const Py_buffer *view, PyObject *tup) with the type specified by view->format. Otherwise, the item is a sub-view. The function is used in memory_subscript() and memory_as_sequence. */ static PyObject * -memory_item(PyMemoryViewObject *self, Py_ssize_t index) +memory_item(PyObject *_self, Py_ssize_t index) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_buffer *view = &(self->view); const char *fmt; @@ -2546,8 +2556,9 @@ is_multiindex(PyObject *key) 0-d memoryview objects can be referenced using mv[...] or mv[()] but not with anything else. */ static PyObject * -memory_subscript(PyMemoryViewObject *self, PyObject *key) +memory_subscript(PyObject *_self, PyObject *key) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_buffer *view; view = &(self->view); @@ -2575,7 +2586,7 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key) index = PyNumber_AsSsize_t(key, PyExc_IndexError); if (index == -1 && PyErr_Occurred()) return NULL; - return memory_item(self, index); + return memory_item((PyObject *)self, index); } else if (PySlice_Check(key)) { CHECK_RESTRICTED(self); @@ -2608,8 +2619,9 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key) } static int -memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value) +memory_ass_sub(PyObject *_self, PyObject *key, PyObject *value) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_buffer *view = &(self->view); Py_buffer src; const char *fmt; @@ -2710,8 +2722,9 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value) } static Py_ssize_t -memory_length(PyMemoryViewObject *self) +memory_length(PyObject *_self) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED_INT(self); if (self->view.ndim == 0) { PyErr_SetString(PyExc_TypeError, "0-dim memory has no length"); @@ -2722,17 +2735,17 @@ memory_length(PyMemoryViewObject *self) /* As mapping */ static PyMappingMethods memory_as_mapping = { - (lenfunc)memory_length, /* mp_length */ - (binaryfunc)memory_subscript, /* mp_subscript */ - (objobjargproc)memory_ass_sub, /* mp_ass_subscript */ + memory_length, /* mp_length */ + memory_subscript, /* mp_subscript */ + memory_ass_sub, /* mp_ass_subscript */ }; /* As sequence */ static PySequenceMethods memory_as_sequence = { - (lenfunc)memory_length, /* sq_length */ + memory_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ - (ssizeargfunc)memory_item, /* sq_item */ + memory_item, /* sq_item */ }; @@ -3034,8 +3047,9 @@ memory_richcompare(PyObject *v, PyObject *w, int op) /**************************************************************************/ static Py_hash_t -memory_hash(PyMemoryViewObject *self) +memory_hash(PyObject *_self) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; if (self->hash == -1) { Py_buffer *view = &self->view; char *mem = view->buf; @@ -3112,8 +3126,9 @@ _IntTupleFromSsizet(int len, Py_ssize_t *vals) } static PyObject * -memory_obj_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_obj_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; Py_buffer *view = &self->view; CHECK_RELEASED(self); @@ -3124,78 +3139,89 @@ memory_obj_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) } static PyObject * -memory_nbytes_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_nbytes_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyLong_FromSsize_t(self->view.len); } static PyObject * -memory_format_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_format_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyUnicode_FromString(self->view.format); } static PyObject * -memory_itemsize_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_itemsize_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyLong_FromSsize_t(self->view.itemsize); } static PyObject * -memory_shape_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_shape_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.shape); } static PyObject * -memory_strides_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_strides_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.strides); } static PyObject * -memory_suboffsets_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_suboffsets_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return _IntTupleFromSsizet(self->view.ndim, self->view.suboffsets); } static PyObject * -memory_readonly_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_readonly_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyBool_FromLong(self->view.readonly); } static PyObject * -memory_ndim_get(PyMemoryViewObject *self, void *Py_UNUSED(ignored)) +memory_ndim_get(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyLong_FromLong(self->view.ndim); } static PyObject * -memory_c_contiguous(PyMemoryViewObject *self, PyObject *dummy) +memory_c_contiguous(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyBool_FromLong(MV_C_CONTIGUOUS(self->flags)); } static PyObject * -memory_f_contiguous(PyMemoryViewObject *self, PyObject *dummy) +memory_f_contiguous(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyBool_FromLong(MV_F_CONTIGUOUS(self->flags)); } static PyObject * -memory_contiguous(PyMemoryViewObject *self, PyObject *dummy) +memory_contiguous(PyObject *_self, void *Py_UNUSED(ignored)) { + PyMemoryViewObject *self = (PyMemoryViewObject *)_self; CHECK_RELEASED(self); return PyBool_FromLong(MV_ANY_CONTIGUOUS(self->flags)); } @@ -3232,18 +3258,18 @@ PyDoc_STRVAR(memory_contiguous_doc, static PyGetSetDef memory_getsetlist[] = { - {"obj", (getter)memory_obj_get, NULL, memory_obj_doc}, - {"nbytes", (getter)memory_nbytes_get, NULL, memory_nbytes_doc}, - {"readonly", (getter)memory_readonly_get, NULL, memory_readonly_doc}, - {"itemsize", (getter)memory_itemsize_get, NULL, memory_itemsize_doc}, - {"format", (getter)memory_format_get, NULL, memory_format_doc}, - {"ndim", (getter)memory_ndim_get, NULL, memory_ndim_doc}, - {"shape", (getter)memory_shape_get, NULL, memory_shape_doc}, - {"strides", (getter)memory_strides_get, NULL, memory_strides_doc}, - {"suboffsets", (getter)memory_suboffsets_get, NULL, memory_suboffsets_doc}, - {"c_contiguous", (getter)memory_c_contiguous, NULL, memory_c_contiguous_doc}, - {"f_contiguous", (getter)memory_f_contiguous, NULL, memory_f_contiguous_doc}, - {"contiguous", (getter)memory_contiguous, NULL, memory_contiguous_doc}, + {"obj", memory_obj_get, NULL, memory_obj_doc}, + {"nbytes", memory_nbytes_get, NULL, memory_nbytes_doc}, + {"readonly", memory_readonly_get, NULL, memory_readonly_doc}, + {"itemsize", memory_itemsize_get, NULL, memory_itemsize_doc}, + {"format", memory_format_get, NULL, memory_format_doc}, + {"ndim", memory_ndim_get, NULL, memory_ndim_doc}, + {"shape", memory_shape_get, NULL, memory_shape_doc}, + {"strides", memory_strides_get, NULL, memory_strides_doc}, + {"suboffsets", memory_suboffsets_get, NULL, memory_suboffsets_doc}, + {"c_contiguous", memory_c_contiguous, NULL, memory_c_contiguous_doc}, + {"f_contiguous", memory_f_contiguous, NULL, memory_f_contiguous_doc}, + {"contiguous", memory_contiguous, NULL, memory_contiguous_doc}, {NULL, NULL, NULL, NULL}, }; @@ -3276,23 +3302,26 @@ typedef struct { } memoryiterobject; static void -memoryiter_dealloc(memoryiterobject *it) +memoryiter_dealloc(PyObject *self) { + memoryiterobject *it = (memoryiterobject *)self; _PyObject_GC_UNTRACK(it); Py_XDECREF(it->it_seq); PyObject_GC_Del(it); } static int -memoryiter_traverse(memoryiterobject *it, visitproc visit, void *arg) +memoryiter_traverse(PyObject *self, visitproc visit, void *arg) { + memoryiterobject *it = (memoryiterobject *)self; Py_VISIT(it->it_seq); return 0; } static PyObject * -memoryiter_next(memoryiterobject *it) +memoryiter_next(PyObject *self) { + memoryiterobject *it = (memoryiterobject *)self; PyMemoryViewObject *seq; seq = it->it_seq; if (seq == NULL) { @@ -3347,7 +3376,7 @@ memory_iter(PyObject *seq) return NULL; } it->it_fmt = fmt; - it->it_length = memory_length(obj); + it->it_length = memory_length((PyObject *)obj); it->it_index = 0; it->it_seq = (PyMemoryViewObject*)Py_NewRef(obj); _PyObject_GC_TRACK(it); @@ -3359,12 +3388,12 @@ PyTypeObject _PyMemoryIter_Type = { .tp_name = "memory_iterator", .tp_basicsize = sizeof(memoryiterobject), // methods - .tp_dealloc = (destructor)memoryiter_dealloc, + .tp_dealloc = memoryiter_dealloc, .tp_getattro = PyObject_GenericGetAttr, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)memoryiter_traverse, + .tp_traverse = memoryiter_traverse, .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)memoryiter_next, + .tp_iternext = memoryiter_next, }; PyTypeObject PyMemoryView_Type = { @@ -3372,16 +3401,16 @@ PyTypeObject PyMemoryView_Type = { "memoryview", /* tp_name */ offsetof(PyMemoryViewObject, ob_array), /* tp_basicsize */ sizeof(Py_ssize_t), /* tp_itemsize */ - (destructor)memory_dealloc, /* tp_dealloc */ + memory_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)memory_repr, /* tp_repr */ + memory_repr, /* tp_repr */ 0, /* tp_as_number */ &memory_as_sequence, /* tp_as_sequence */ &memory_as_mapping, /* tp_as_mapping */ - (hashfunc)memory_hash, /* tp_hash */ + memory_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ @@ -3390,8 +3419,8 @@ PyTypeObject PyMemoryView_Type = { Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_SEQUENCE, /* tp_flags */ memoryview__doc__, /* tp_doc */ - (traverseproc)memory_traverse, /* tp_traverse */ - (inquiry)memory_clear, /* tp_clear */ + memory_traverse, /* tp_traverse */ + memory_clear, /* tp_clear */ memory_richcompare, /* tp_richcompare */ offsetof(PyMemoryViewObject, weakreflist),/* tp_weaklistoffset */ memory_iter, /* tp_iter */ From d7b5f102319bb0389c5248e9ecf533eae4163424 Mon Sep 17 00:00:00 2001 From: James Morris <6653392+J-M0@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:27:15 -0500 Subject: [PATCH 193/442] gh-112507: Detect Cygwin and MSYS with `uname` instead of `$OSTYPE` (GH-112508) Detect Cygwin and MSYS with `uname` instead of `$OSTYPE` `$OSTYPE` is not defined by POSIX and may not be present in other shells. `uname` is always available in any shell. --- Lib/venv/scripts/common/activate | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate index 6fdf423a1c516a..a4e0609045a9d5 100644 --- a/Lib/venv/scripts/common/activate +++ b/Lib/venv/scripts/common/activate @@ -36,14 +36,18 @@ deactivate () { deactivate nondestructive # on Windows, a path can contain colons and backslashes and has to be converted: -if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then - # transform D:\path\to\venv to /d/path/to/venv on MSYS - # and to /cygdrive/d/path/to/venv on Cygwin - export VIRTUAL_ENV=$(cygpath "__VENV_DIR__") -else - # use the path as-is - export VIRTUAL_ENV="__VENV_DIR__" -fi +case "$(uname)" in + CYGWIN*|MSYS*) + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + VIRTUAL_ENV=$(cygpath "__VENV_DIR__") + export VIRTUAL_ENV + ;; + *) + # use the path as-is + export VIRTUAL_ENV="__VENV_DIR__" + ;; +esac _OLD_VIRTUAL_PATH="$PATH" PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH" From 0738b9a338fd27ff2d4456dd9c15801a8858ffd9 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Mon, 11 Dec 2023 21:29:43 +0300 Subject: [PATCH 194/442] gh-108303: Move `double_const` to `test_import` where it belongs (#112108) --- Lib/test/test_import/__init__.py | 9 ++++++--- Lib/test/{ => test_import/data}/double_const.py | 0 2 files changed, 6 insertions(+), 3 deletions(-) rename Lib/test/{ => test_import/data}/double_const.py (100%) diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 1ecac4f37fe1c1..bbfbb57b1d8299 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -409,9 +409,12 @@ def test_case_sensitivity(self): import RAnDoM def test_double_const(self): - # Another brief digression to test the accuracy of manifest float - # constants. - from test import double_const # don't blink -- that *was* the test + # Importing double_const checks that float constants + # serialiazed by marshal as PYC files don't lose precision + # (SF bug 422177). + from test.test_import.data import double_const + unload('test.test_import.data.double_const') + from test.test_import.data import double_const def test_import(self): def test_with_extension(ext): diff --git a/Lib/test/double_const.py b/Lib/test/test_import/data/double_const.py similarity index 100% rename from Lib/test/double_const.py rename to Lib/test/test_import/data/double_const.py From d70e27f25886e3ac1aa9fcc2d44dd38b4001d8bb Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 11 Dec 2023 13:33:21 -0500 Subject: [PATCH 195/442] gh-112529: Use atomic operations for `gcstate->collecting` (#112533) * gh-112529: Use atomic operations for `gcstate->collecting` The `collecting` field in `GCState` is used to prevent overlapping garbage collections within the same interpreter. This is updated to use atomic operations in order to be thread-safe in `--disable-gil` builds. The GC code is refactored a bit to support this. More of the logic is pushed down to `gc_collect_main()` so that we can safely order the logic setting `collecting`, the selection of the generation, and the invocation of callbacks with respect to the atomic operations and the (future) stop-the-world pauses. The change uses atomic operations for both `--disable-gil` and the default build (with the GIL) to avoid extra `#ifdef` guards and ease the maintenance burden. --- Modules/gcmodule.c | 352 ++++++++++++++++++++++----------------------- 1 file changed, 168 insertions(+), 184 deletions(-) diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 8233fc56159b60..2d1f381e622226 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -74,6 +74,20 @@ module gc #define AS_GC(op) _Py_AS_GC(op) #define FROM_GC(gc) _Py_FROM_GC(gc) +// Automatically choose the generation that needs collecting. +#define GENERATION_AUTO (-1) + +typedef enum { + // GC was triggered by heap allocation + _Py_GC_REASON_HEAP, + + // GC was called during shutdown + _Py_GC_REASON_SHUTDOWN, + + // GC was called by gc.collect() or PyGC_Collect() + _Py_GC_REASON_MANUAL +} _PyGC_Reason; + static inline int gc_is_collecting(PyGC_Head *g) @@ -1194,19 +1208,122 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable, gc_list_merge(resurrected, old_generation); } + +/* 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; +} + + /* 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, - Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, - int nofail) +gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) { - GC_STAT_ADD(generation, collections, 1); -#ifdef Py_STATS - if (_Py_stats) { - _Py_stats->object_stats.object_visits = 0; - } -#endif int i; Py_ssize_t m = 0; /* # objects collected */ Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */ @@ -1223,6 +1340,36 @@ gc_collect_main(PyThreadState *tstate, int generation, 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 & DEBUG_STATS) { PySys_WriteStderr("gc: collecting generation %d...\n", generation); show_stats_each_generations(gcstate); @@ -1342,7 +1489,7 @@ gc_collect_main(PyThreadState *tstate, int generation, } if (_PyErr_Occurred(tstate)) { - if (nofail) { + if (reason == _Py_GC_REASON_SHUTDOWN) { _PyErr_Clear(tstate); } else { @@ -1351,13 +1498,6 @@ gc_collect_main(PyThreadState *tstate, int generation, } /* Update stats */ - if (n_collected) { - *n_collected = m; - } - if (n_uncollectable) { - *n_uncollectable = n; - } - struct gc_generation_stats *stats = &gcstate->generation_stats[generation]; stats->collections++; stats->collected += m; @@ -1376,134 +1516,13 @@ gc_collect_main(PyThreadState *tstate, int generation, PyDTrace_GC_DONE(n + m); } - assert(!_PyErr_Occurred(tstate)); - return n + m; -} - -/* 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; - } + if (reason != _Py_GC_REASON_SHUTDOWN) { + invoke_gc_callback(tstate, "stop", generation, m, n); } - 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)); -} - -/* Perform garbage collection of a generation and invoke - * progress callbacks. - */ -static Py_ssize_t -gc_collect_with_callback(PyThreadState *tstate, int generation) -{ assert(!_PyErr_Occurred(tstate)); - Py_ssize_t result, collected, uncollectable; - invoke_gc_callback(tstate, "start", generation, 0, 0); - result = gc_collect_main(tstate, generation, &collected, &uncollectable, 0); - invoke_gc_callback(tstate, "stop", generation, collected, uncollectable); - assert(!_PyErr_Occurred(tstate)); - return result; -} - -static Py_ssize_t -gc_collect_generations(PyThreadState *tstate) -{ - GCState *gcstate = &tstate->interp->gc; - /* 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. */ - Py_ssize_t n = 0; - 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; - n = gc_collect_with_callback(tstate, i); - break; - } - } - return n; + _Py_atomic_store_int(&gcstate->collecting, 0); + return n + m; } #include "clinic/gcmodule.c.h" @@ -1574,18 +1593,7 @@ gc_collect_impl(PyObject *module, int generation) return -1; } - GCState *gcstate = &tstate->interp->gc; - Py_ssize_t n; - if (gcstate->collecting) { - /* already collecting, don't do anything */ - n = 0; - } - else { - gcstate->collecting = 1; - n = gc_collect_with_callback(tstate, generation); - gcstate->collecting = 0; - } - return n; + return gc_collect_main(tstate, generation, _Py_GC_REASON_MANUAL); } /*[clinic input] @@ -2124,17 +2132,9 @@ PyGC_Collect(void) } Py_ssize_t n; - if (gcstate->collecting) { - /* already collecting, don't do anything */ - n = 0; - } - else { - gcstate->collecting = 1; - PyObject *exc = _PyErr_GetRaisedException(tstate); - n = gc_collect_with_callback(tstate, NUM_GENERATIONS - 1); - _PyErr_SetRaisedException(tstate, exc); - gcstate->collecting = 0; - } + PyObject *exc = _PyErr_GetRaisedException(tstate); + n = gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_MANUAL); + _PyErr_SetRaisedException(tstate, exc); return n; } @@ -2148,16 +2148,7 @@ _PyGC_CollectNoFail(PyThreadState *tstate) during interpreter shutdown (and then never finish it). See http://bugs.python.org/issue8713#msg195178 for an example. */ - GCState *gcstate = &tstate->interp->gc; - if (gcstate->collecting) { - return 0; - } - - Py_ssize_t n; - gcstate->collecting = 1; - n = gc_collect_main(tstate, NUM_GENERATIONS - 1, NULL, NULL, 1); - gcstate->collecting = 0; - return n; + return gc_collect_main(tstate, NUM_GENERATIONS - 1, _Py_GC_REASON_SHUTDOWN); } void @@ -2275,10 +2266,6 @@ PyObject_IS_GC(PyObject *obj) void _Py_ScheduleGC(PyInterpreterState *interp) { - GCState *gcstate = &interp->gc; - if (gcstate->collecting == 1) { - return; - } _Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 1); } @@ -2296,7 +2283,7 @@ _PyObject_GC_Link(PyObject *op) if (gcstate->generations[0].count > gcstate->generations[0].threshold && gcstate->enabled && gcstate->generations[0].threshold && - !gcstate->collecting && + !_Py_atomic_load_int_relaxed(&gcstate->collecting) && !_PyErr_Occurred(tstate)) { _Py_ScheduleGC(tstate->interp); @@ -2306,10 +2293,7 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { - GCState *gcstate = &tstate->interp->gc; - gcstate->collecting = 1; - gc_collect_generations(tstate); - gcstate->collecting = 0; + gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); } static PyObject * From 0066ab5bc58a036b3f448cd6f9bbdd92120e39ba Mon Sep 17 00:00:00 2001 From: colorfulappl Date: Tue, 12 Dec 2023 03:27:06 +0800 Subject: [PATCH 196/442] gh-90350: Optimize builtin functions min() and max() (GH-30286) Builtin functions min() and max() now use METH_FASTCALL --- ...3-12-11-19-53-32.gh-issue-90350.-FQy3E.rst | 1 + Python/bltinmodule.c | 81 ++++++++++--------- 2 files changed, 46 insertions(+), 36 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-11-19-53-32.gh-issue-90350.-FQy3E.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-11-19-53-32.gh-issue-90350.-FQy3E.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-11-19-53-32.gh-issue-90350.-FQy3E.rst new file mode 100644 index 00000000000000..6b7881bbd19f59 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-11-19-53-32.gh-issue-90350.-FQy3E.rst @@ -0,0 +1 @@ +Optimize builtin functions :func:`min` and :func:`max`. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 960bca01990c83..e54d5cbacdc96f 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1766,35 +1766,27 @@ builtin_locals_impl(PyObject *module) static PyObject * -min_max(PyObject *args, PyObject *kwds, int op) +min_max(PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames, int op) { - PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL; - PyObject *emptytuple, *defaultval = NULL; - static char *kwlist[] = {"key", "default", NULL}; - const char *name = op == Py_LT ? "min" : "max"; - const int positional = PyTuple_Size(args) > 1; - int ret; + PyObject *it = NULL, *item, *val, *maxitem, *maxval, *keyfunc=NULL; + PyObject *defaultval = NULL; + static const char * const keywords[] = {"key", "default", NULL}; + static _PyArg_Parser _parser_min = {"|$OO:min", keywords, 0}; + static _PyArg_Parser _parser_max = {"|$OO:max", keywords, 0}; + const char *name = (op == Py_LT) ? "min" : "max"; + _PyArg_Parser *_parser = (op == Py_LT) ? &_parser_min : &_parser_max; - if (positional) { - v = args; - } - else if (!PyArg_UnpackTuple(args, name, 1, 1, &v)) { - if (PyExceptionClass_Check(PyExc_TypeError)) { - PyErr_Format(PyExc_TypeError, "%s expected at least 1 argument, got 0", name); - } + if (nargs == 0) { + PyErr_Format(PyExc_TypeError, "%s expected at least 1 argument, got 0", name); return NULL; } - emptytuple = PyTuple_New(0); - if (emptytuple == NULL) - return NULL; - ret = PyArg_ParseTupleAndKeywords(emptytuple, kwds, - (op == Py_LT) ? "|$OO:min" : "|$OO:max", - kwlist, &keyfunc, &defaultval); - Py_DECREF(emptytuple); - if (!ret) + if (kwnames != NULL && !_PyArg_ParseStackAndKeywords(args + nargs, 0, kwnames, _parser, + &keyfunc, &defaultval)) { return NULL; + } + const int positional = nargs > 1; // False iff nargs == 1 if (positional && defaultval != NULL) { PyErr_Format(PyExc_TypeError, "Cannot specify a default for %s() with multiple " @@ -1802,9 +1794,11 @@ min_max(PyObject *args, PyObject *kwds, int op) return NULL; } - it = PyObject_GetIter(v); - if (it == NULL) { - return NULL; + if (!positional) { + it = PyObject_GetIter(args[0]); + if (it == NULL) { + return NULL; + } } if (keyfunc == Py_None) { @@ -1813,7 +1807,24 @@ min_max(PyObject *args, PyObject *kwds, int op) maxitem = NULL; /* the result */ maxval = NULL; /* the value associated with the result */ - while (( item = PyIter_Next(it) )) { + while (1) { + if (it == NULL) { + if (nargs-- <= 0) { + break; + } + item = *args++; + Py_INCREF(item); + } + else { + item = PyIter_Next(it); + if (item == NULL) { + if (PyErr_Occurred()) { + goto Fail_it; + } + break; + } + } + /* get the value from the key function */ if (keyfunc != NULL) { val = PyObject_CallOneArg(keyfunc, item); @@ -1847,8 +1858,6 @@ min_max(PyObject *args, PyObject *kwds, int op) } } } - if (PyErr_Occurred()) - goto Fail_it; if (maxval == NULL) { assert(maxitem == NULL); if (defaultval != NULL) { @@ -1860,7 +1869,7 @@ min_max(PyObject *args, PyObject *kwds, int op) } else Py_DECREF(maxval); - Py_DECREF(it); + Py_XDECREF(it); return maxitem; Fail_it_item_and_val: @@ -1870,15 +1879,15 @@ min_max(PyObject *args, PyObject *kwds, int op) Fail_it: Py_XDECREF(maxval); Py_XDECREF(maxitem); - Py_DECREF(it); + Py_XDECREF(it); return NULL; } /* AC: cannot convert yet, waiting for *args support */ static PyObject * -builtin_min(PyObject *self, PyObject *args, PyObject *kwds) +builtin_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return min_max(args, kwds, Py_LT); + return min_max(args, nargs, kwnames, Py_LT); } PyDoc_STRVAR(min_doc, @@ -1893,9 +1902,9 @@ With two or more positional arguments, return the smallest argument."); /* AC: cannot convert yet, waiting for *args support */ static PyObject * -builtin_max(PyObject *self, PyObject *args, PyObject *kwds) +builtin_max(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { - return min_max(args, kwds, Py_GT); + return min_max(args, nargs, kwnames, Py_GT); } PyDoc_STRVAR(max_doc, @@ -3054,8 +3063,8 @@ static PyMethodDef builtin_methods[] = { BUILTIN_AITER_METHODDEF BUILTIN_LEN_METHODDEF BUILTIN_LOCALS_METHODDEF - {"max", _PyCFunction_CAST(builtin_max), METH_VARARGS | METH_KEYWORDS, max_doc}, - {"min", _PyCFunction_CAST(builtin_min), METH_VARARGS | METH_KEYWORDS, min_doc}, + {"max", _PyCFunction_CAST(builtin_max), METH_FASTCALL | METH_KEYWORDS, max_doc}, + {"min", _PyCFunction_CAST(builtin_min), METH_FASTCALL | METH_KEYWORDS, min_doc}, {"next", _PyCFunction_CAST(builtin_next), METH_FASTCALL, next_doc}, BUILTIN_ANEXT_METHODDEF BUILTIN_OCT_METHODDEF From a01022af23b27a9bfb4fadbcdb60b1ddf24a7220 Mon Sep 17 00:00:00 2001 From: achhina Date: Mon, 11 Dec 2023 15:45:08 -0500 Subject: [PATCH 197/442] GH-83162: Rename re.error for better clarity. (#101677) Renamed re.error for clarity, and kept re.error for backward compatibility. Updated idlelib files at TJR's request. --------- Co-authored-by: Matthias Bussonnier Co-authored-by: Hugo van Kemenade Co-authored-by: Terry Jan Reedy --- Doc/library/re.rst | 8 +++- Doc/whatsnew/3.13.rst | 5 ++ Lib/idlelib/replace.py | 2 +- Lib/idlelib/searchengine.py | 2 +- Lib/pstats.py | 2 +- Lib/re/__init__.py | 7 +-- Lib/re/_compiler.py | 6 +-- Lib/re/_constants.py | 5 +- Lib/test/test_re.py | 47 ++++++++++--------- ...3-02-08-00-43-29.gh-issue-83162.ufdI9F.rst | 3 ++ 10 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-02-08-00-43-29.gh-issue-83162.ufdI9F.rst diff --git a/Doc/library/re.rst b/Doc/library/re.rst index 251ec8ca0021a6..302f7224de4a7a 100644 --- a/Doc/library/re.rst +++ b/Doc/library/re.rst @@ -1093,12 +1093,12 @@ Functions Exceptions ^^^^^^^^^^ -.. exception:: error(msg, pattern=None, pos=None) +.. exception:: PatternError(msg, pattern=None, pos=None) Exception raised when a string passed to one of the functions here is not a valid regular expression (for example, it might contain unmatched parentheses) or when some other error occurs during compilation or matching. It is never an - error if a string contains no match for a pattern. The error instance has + error if a string contains no match for a pattern. The ``PatternError`` instance has the following additional attributes: .. attribute:: msg @@ -1124,6 +1124,10 @@ Exceptions .. versionchanged:: 3.5 Added additional attributes. + .. versionchanged:: 3.13 + ``PatternError`` was originally named ``error``; the latter is kept as an alias for + backward compatibility. + .. _re-objects: Regular Expression Objects diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 00f396846e29bd..d599ba9ae6fac8 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -298,6 +298,11 @@ pdb command line option or :envvar:`PYTHONSAFEPATH` environment variable). (Contributed by Tian Gao and Christian Walther in :gh:`111762`.) +re +-- +* Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity. + :exc:`!re.error` is kept for backward compatibility. + sqlite3 ------- diff --git a/Lib/idlelib/replace.py b/Lib/idlelib/replace.py index a29ca591427491..7997f24f1b0fa6 100644 --- a/Lib/idlelib/replace.py +++ b/Lib/idlelib/replace.py @@ -120,7 +120,7 @@ def _replace_expand(self, m, repl): if self.engine.isre(): try: new = m.expand(repl) - except re.error: + except re.PatternError: self.engine.report_error(repl, 'Invalid Replace Expression') new = None else: diff --git a/Lib/idlelib/searchengine.py b/Lib/idlelib/searchengine.py index 0684142f43644a..ceb38cfaef900b 100644 --- a/Lib/idlelib/searchengine.py +++ b/Lib/idlelib/searchengine.py @@ -84,7 +84,7 @@ def getprog(self): flags = flags | re.IGNORECASE try: prog = re.compile(pat, flags) - except re.error as e: + except re.PatternError as e: self.report_error(pat, e.msg, e.pos) return None return prog diff --git a/Lib/pstats.py b/Lib/pstats.py index 51bcca84188740..2f054bb4011e7f 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -329,7 +329,7 @@ def eval_print_amount(self, sel, list, msg): if isinstance(sel, str): try: rex = re.compile(sel) - except re.error: + except re.PatternError: msg += " \n" % sel return new_list, msg new_list = [] diff --git a/Lib/re/__init__.py b/Lib/re/__init__.py index 428d1b0d5fbd87..7e8abbf6ffe155 100644 --- a/Lib/re/__init__.py +++ b/Lib/re/__init__.py @@ -117,7 +117,8 @@ U UNICODE For compatibility only. Ignored for string patterns (it is the default), and forbidden for bytes patterns. -This module also defines an exception 'error'. +This module also defines exception 'PatternError', aliased to 'error' for +backward compatibility. """ @@ -133,7 +134,7 @@ "findall", "finditer", "compile", "purge", "escape", "error", "Pattern", "Match", "A", "I", "L", "M", "S", "X", "U", "ASCII", "IGNORECASE", "LOCALE", "MULTILINE", "DOTALL", "VERBOSE", - "UNICODE", "NOFLAG", "RegexFlag", + "UNICODE", "NOFLAG", "RegexFlag", "PatternError" ] __version__ = "2.2.1" @@ -155,7 +156,7 @@ class RegexFlag: _numeric_repr_ = hex # sre exception -error = _compiler.error +PatternError = error = _compiler.PatternError # -------------------------------------------------------------------- # public interface diff --git a/Lib/re/_compiler.py b/Lib/re/_compiler.py index f87712d6d6f9f8..7b888f877eb3dc 100644 --- a/Lib/re/_compiler.py +++ b/Lib/re/_compiler.py @@ -150,7 +150,7 @@ def _compile(code, pattern, flags): if lo > MAXCODE: raise error("looks too much behind") if lo != hi: - raise error("look-behind requires fixed-width pattern") + raise PatternError("look-behind requires fixed-width pattern") emit(lo) # look behind _compile(code, av[1], flags) emit(SUCCESS) @@ -209,7 +209,7 @@ def _compile(code, pattern, flags): else: code[skipyes] = _len(code) - skipyes + 1 else: - raise error("internal: unsupported operand type %r" % (op,)) + raise PatternError(f"internal: unsupported operand type {op!r}") def _compile_charset(charset, flags, code): # compile charset subprogram @@ -235,7 +235,7 @@ def _compile_charset(charset, flags, code): else: emit(av) else: - raise error("internal: unsupported set operator %r" % (op,)) + raise PatternError(f"internal: unsupported set operator {op!r}") emit(FAILURE) def _optimize_charset(charset, iscased=None, fixup=None, fixes=None): diff --git a/Lib/re/_constants.py b/Lib/re/_constants.py index d8e483ac4f23b4..9c3c294ba448b4 100644 --- a/Lib/re/_constants.py +++ b/Lib/re/_constants.py @@ -20,7 +20,7 @@ # SRE standard exception (access as sre.error) # should this really be here? -class error(Exception): +class PatternError(Exception): """Exception raised for invalid regular expressions. Attributes: @@ -53,6 +53,9 @@ def __init__(self, msg, pattern=None, pos=None): super().__init__(msg) +# Backward compatibility after renaming in 3.13 +error = PatternError + class _NamedIntConstant(int): def __new__(cls, value, name): self = super(_NamedIntConstant, cls).__new__(cls, value) diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 1eca22f45378df..993a7d6e264a1f 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -47,7 +47,7 @@ def recurse(actual, expect): recurse(actual, expect) def checkPatternError(self, pattern, errmsg, pos=None): - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile(pattern) with self.subTest(pattern=pattern): err = cm.exception @@ -56,7 +56,7 @@ def checkPatternError(self, pattern, errmsg, pos=None): self.assertEqual(err.pos, pos) def checkTemplateError(self, pattern, repl, string, errmsg, pos=None): - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.sub(pattern, repl, string) with self.subTest(pattern=pattern, repl=repl): err = cm.exception @@ -64,6 +64,9 @@ def checkTemplateError(self, pattern, repl, string, errmsg, pos=None): if pos is not None: self.assertEqual(err.pos, pos) + def test_error_is_PatternError_alias(self): + assert re.error is re.PatternError + def test_keep_buffer(self): # See bug 14212 b = bytearray(b'x') @@ -154,7 +157,7 @@ def test_basic_re_sub(self): (chr(9)+chr(10)+chr(11)+chr(13)+chr(12)+chr(7)+chr(8))) for c in 'cdehijklmopqsuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': with self.subTest(c): - with self.assertRaises(re.error): + with self.assertRaises(re.PatternError): self.assertEqual(re.sub('a', '\\' + c, 'a'), '\\' + c) self.assertEqual(re.sub(r'^\s*', 'X', 'test'), 'Xtest') @@ -836,10 +839,10 @@ def test_other_escapes(self): re.purge() # for warnings for c in 'ceghijklmopqyzCEFGHIJKLMNOPQRTVXY': with self.subTest(c): - self.assertRaises(re.error, re.compile, '\\%c' % c) + self.assertRaises(re.PatternError, re.compile, '\\%c' % c) for c in 'ceghijklmopqyzABCEFGHIJKLMNOPQRTVXYZ': with self.subTest(c): - self.assertRaises(re.error, re.compile, '[\\%c]' % c) + self.assertRaises(re.PatternError, re.compile, '[\\%c]' % c) def test_named_unicode_escapes(self): # test individual Unicode named escapes @@ -970,14 +973,14 @@ def test_lookbehind(self): self.assertIsNone(re.match(r'(?:(a)|(x))b(?<=(?(1)c|x))c', 'abc')) self.assertTrue(re.match(r'(?:(a)|(x))b(?<=(?(1)b|x))c', 'abc')) # Group used before defined. - self.assertRaises(re.error, re.compile, r'(a)b(?<=(?(2)b|x))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(?(2)b|x))(c)') self.assertIsNone(re.match(r'(a)b(?<=(?(1)c|x))(c)', 'abc')) self.assertTrue(re.match(r'(a)b(?<=(?(1)b|x))(c)', 'abc')) # Group defined in the same lookbehind pattern - self.assertRaises(re.error, re.compile, r'(a)b(?<=(.)\2)(c)') - self.assertRaises(re.error, re.compile, r'(a)b(?<=(?P.)(?P=a))(c)') - self.assertRaises(re.error, re.compile, r'(a)b(?<=(a)(?(2)b|x))(c)') - self.assertRaises(re.error, re.compile, r'(a)b(?<=(.)(?<=\2))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(.)\2)(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(?P.)(?P=a))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(a)(?(2)b|x))(c)') + self.assertRaises(re.PatternError, re.compile, r'(a)b(?<=(.)(?<=\2))(c)') def test_ignore_case(self): self.assertEqual(re.match("abc", "ABC", re.I).group(0), "ABC") @@ -1318,8 +1321,8 @@ def test_sre_byte_literals(self): self.assertTrue(re.match((r"\x%02x" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"\x%02x0" % i).encode(), bytes([i])+b"0")) self.assertTrue(re.match((r"\x%02xz" % i).encode(), bytes([i])+b"z")) - self.assertRaises(re.error, re.compile, br"\u1234") - self.assertRaises(re.error, re.compile, br"\U00012345") + self.assertRaises(re.PatternError, re.compile, br"\u1234") + self.assertRaises(re.PatternError, re.compile, br"\U00012345") self.assertTrue(re.match(br"\0", b"\000")) self.assertTrue(re.match(br"\08", b"\0008")) self.assertTrue(re.match(br"\01", b"\001")) @@ -1341,8 +1344,8 @@ def test_sre_byte_class_literals(self): self.assertTrue(re.match((r"[\x%02x]" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"[\x%02x0]" % i).encode(), bytes([i]))) self.assertTrue(re.match((r"[\x%02xz]" % i).encode(), bytes([i]))) - self.assertRaises(re.error, re.compile, br"[\u1234]") - self.assertRaises(re.error, re.compile, br"[\U00012345]") + self.assertRaises(re.PatternError, re.compile, br"[\u1234]") + self.assertRaises(re.PatternError, re.compile, br"[\U00012345]") self.checkPatternError(br"[\567]", r'octal escape value \567 outside of ' r'range 0-0o377', 1) @@ -1675,11 +1678,11 @@ def test_ascii_and_unicode_flag(self): self.assertIsNone(pat.match(b'\xe0')) # Incompatibilities self.assertRaises(ValueError, re.compile, br'\w', re.UNICODE) - self.assertRaises(re.error, re.compile, br'(?u)\w') + self.assertRaises(re.PatternError, re.compile, br'(?u)\w') self.assertRaises(ValueError, re.compile, r'\w', re.UNICODE | re.ASCII) self.assertRaises(ValueError, re.compile, r'(?u)\w', re.ASCII) self.assertRaises(ValueError, re.compile, r'(?a)\w', re.UNICODE) - self.assertRaises(re.error, re.compile, r'(?au)\w') + self.assertRaises(re.PatternError, re.compile, r'(?au)\w') def test_locale_flag(self): enc = locale.getpreferredencoding() @@ -1720,11 +1723,11 @@ def test_locale_flag(self): self.assertIsNone(pat.match(bletter)) # Incompatibilities self.assertRaises(ValueError, re.compile, '', re.LOCALE) - self.assertRaises(re.error, re.compile, '(?L)') + self.assertRaises(re.PatternError, re.compile, '(?L)') self.assertRaises(ValueError, re.compile, b'', re.LOCALE | re.ASCII) self.assertRaises(ValueError, re.compile, b'(?L)', re.ASCII) self.assertRaises(ValueError, re.compile, b'(?a)', re.LOCALE) - self.assertRaises(re.error, re.compile, b'(?aL)') + self.assertRaises(re.PatternError, re.compile, b'(?aL)') def test_scoped_flags(self): self.assertTrue(re.match(r'(?i:a)b', 'Ab')) @@ -2060,7 +2063,7 @@ def test_locale_compiled(self): self.assertIsNone(p4.match(b'\xc5\xc5')) def test_error(self): - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile('(\u20ac))') err = cm.exception self.assertIsInstance(err.pattern, str) @@ -2072,14 +2075,14 @@ def test_error(self): self.assertIn(' at position 3', str(err)) self.assertNotIn(' at position 3', err.msg) # Bytes pattern - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile(b'(\xa4))') err = cm.exception self.assertIsInstance(err.pattern, bytes) self.assertEqual(err.pattern, b'(\xa4))') self.assertEqual(err.pos, 3) # Multiline pattern - with self.assertRaises(re.error) as cm: + with self.assertRaises(re.PatternError) as cm: re.compile(""" ( abc @@ -2820,7 +2823,7 @@ def test_re_tests(self): with self.subTest(pattern=pattern, string=s): if outcome == SYNTAX_ERROR: # Expected a syntax error - with self.assertRaises(re.error): + with self.assertRaises(re.PatternError): re.compile(pattern) continue diff --git a/Misc/NEWS.d/next/Library/2023-02-08-00-43-29.gh-issue-83162.ufdI9F.rst b/Misc/NEWS.d/next/Library/2023-02-08-00-43-29.gh-issue-83162.ufdI9F.rst new file mode 100644 index 00000000000000..6074dd7f101a6d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-08-00-43-29.gh-issue-83162.ufdI9F.rst @@ -0,0 +1,3 @@ +Renamed :exc:`!re.error` to :exc:`PatternError` for clarity, and kept +:exc:`!re.error` for backward compatibility. Patch by Matthias Bussonnier and +Adam Chhina. From 1c5fc02fd0576be125638a5261be12eb3224be81 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 11 Dec 2023 21:54:17 +0000 Subject: [PATCH 198/442] gh-71383: Update Tcl/Tk version in Windows to our patched build containing a targeted upstream fix (GH-112973) --- .../Windows/2023-12-11-20-23-04.gh-issue-71383.9pZh6t.rst | 2 ++ PCbuild/get_externals.bat | 6 +++--- PCbuild/tcltk.props | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-11-20-23-04.gh-issue-71383.9pZh6t.rst diff --git a/Misc/NEWS.d/next/Windows/2023-12-11-20-23-04.gh-issue-71383.9pZh6t.rst b/Misc/NEWS.d/next/Windows/2023-12-11-20-23-04.gh-issue-71383.9pZh6t.rst new file mode 100644 index 00000000000000..cf2883357a962a --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-11-20-23-04.gh-issue-71383.9pZh6t.rst @@ -0,0 +1,2 @@ +Update Tcl/Tk in Windows installer to 8.6.13 with a patch to suppress +incorrect ThemeChanged warnings. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 94437f054d788c..6151990096e0be 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -55,8 +55,8 @@ 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 set libraries=%libraries% sqlite-3.43.1.0 -if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.13.0 -if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.13.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 @@ -77,7 +77,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 "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.13.0 +if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.13.1 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 for %%b in (%binaries%) do ( diff --git a/PCbuild/tcltk.props b/PCbuild/tcltk.props index 96dd289face6a5..8ddf01d5dd1dca 100644 --- a/PCbuild/tcltk.props +++ b/PCbuild/tcltk.props @@ -2,7 +2,7 @@ - 8.6.13.0 + 8.6.13.1 $(TclVersion) $([System.Version]::Parse($(TclVersion)).Major) $([System.Version]::Parse($(TclVersion)).Minor) From fed294c6453527addd1644633849e2d8492058c5 Mon Sep 17 00:00:00 2001 From: Yan Yanchii <46005801+WolframAlph@users.noreply.github.com> Date: Tue, 12 Dec 2023 00:23:41 +0100 Subject: [PATCH 199/442] gh-112978: Remove redundant condition inside `take_gil` (gh-112979) --- Python/ceval_gil.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 636e4db898f2d9..7581daa55b5e46 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -307,10 +307,6 @@ take_gil(PyThreadState *tstate) MUTEX_LOCK(gil->mutex); - if (!_Py_atomic_load_int_relaxed(&gil->locked)) { - goto _ready; - } - int drop_requested = 0; while (_Py_atomic_load_int_relaxed(&gil->locked)) { unsigned long saved_switchnum = gil->switch_number; @@ -345,7 +341,6 @@ take_gil(PyThreadState *tstate) } } -_ready: #ifdef FORCE_SWITCHING /* This mutex must be taken before modifying gil->last_holder: see drop_gil(). */ From fdee7b7b3e15931d58f07e5449de2e55b4d48b05 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 11 Dec 2023 19:04:48 -0500 Subject: [PATCH 200/442] gh-112532: Require mimalloc in `--disable-gil` builds (gh-112883) --- Include/internal/pycore_pymem_init.h | 14 +++++++++++++- Lib/test/support/__init__.py | 2 +- Lib/test/test_capi/test_mem.py | 7 +++++++ Lib/test/test_cmd_line.py | 15 ++++++++++++--- Lib/test/test_embed.py | 14 ++++++++++---- Lib/test/test_os.py | 5 ++++- Objects/obmalloc.c | 15 +++++++++++++-- Programs/_testembed.c | 12 ++++++++++++ configure | 2 ++ configure.ac | 2 ++ 10 files changed, 76 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_pymem_init.h b/Include/internal/pycore_pymem_init.h index 11fbe16fa6f1d5..360fb9218a9cda 100644 --- a/Include/internal/pycore_pymem_init.h +++ b/Include/internal/pycore_pymem_init.h @@ -18,7 +18,19 @@ extern void * _PyMem_RawRealloc(void *, void *, size_t); extern void _PyMem_RawFree(void *, void *); #define PYRAW_ALLOC {NULL, _PyMem_RawMalloc, _PyMem_RawCalloc, _PyMem_RawRealloc, _PyMem_RawFree} -#if defined(WITH_PYMALLOC) +#ifdef Py_GIL_DISABLED +// Py_GIL_DISABLED requires mimalloc +extern void* _PyObject_MiMalloc(void *, size_t); +extern void* _PyObject_MiCalloc(void *, size_t, size_t); +extern void _PyObject_MiFree(void *, void *); +extern void* _PyObject_MiRealloc(void *, void *, size_t); +# define PYOBJ_ALLOC {NULL, _PyObject_MiMalloc, _PyObject_MiCalloc, _PyObject_MiRealloc, _PyObject_MiFree} +extern void* _PyMem_MiMalloc(void *, size_t); +extern void* _PyMem_MiCalloc(void *, size_t, size_t); +extern void _PyMem_MiFree(void *, void *); +extern void* _PyMem_MiRealloc(void *, void *, size_t); +# define PYMEM_ALLOC {NULL, _PyMem_MiMalloc, _PyMem_MiCalloc, _PyMem_MiRealloc, _PyMem_MiFree} +#elif defined(WITH_PYMALLOC) extern void* _PyObject_Malloc(void *, size_t); extern void* _PyObject_Calloc(void *, size_t, size_t); extern void _PyObject_Free(void *, void *); diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index c22d73c231b46e..b605951320dc8b 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1844,7 +1844,7 @@ def restore(self): def with_pymalloc(): import _testcapi - return _testcapi.WITH_PYMALLOC + return _testcapi.WITH_PYMALLOC and not Py_GIL_DISABLED def with_mimalloc(): diff --git a/Lib/test/test_capi/test_mem.py b/Lib/test/test_capi/test_mem.py index 72f23b1a34080e..04f17a9ec9e72a 100644 --- a/Lib/test/test_capi/test_mem.py +++ b/Lib/test/test_capi/test_mem.py @@ -152,6 +152,8 @@ class C(): pass self.assertGreaterEqual(count, i*5-2) +# Py_GIL_DISABLED requires mimalloc (not malloc) +@unittest.skipIf(support.Py_GIL_DISABLED, 'need malloc') class PyMemMallocDebugTests(PyMemDebugTests): PYTHONMALLOC = 'malloc_debug' @@ -161,6 +163,11 @@ class PyMemPymallocDebugTests(PyMemDebugTests): PYTHONMALLOC = 'pymalloc_debug' +@unittest.skipUnless(support.with_mimalloc(), 'need mimaloc') +class PyMemMimallocDebugTests(PyMemDebugTests): + PYTHONMALLOC = 'mimalloc_debug' + + @unittest.skipUnless(support.Py_DEBUG, 'need Py_DEBUG') class PyMemDefaultTests(PyMemDebugTests): # test default allocator of Python compiled in debug mode diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 7a27952c345b9c..1fe3b2fe53c0b6 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -738,6 +738,8 @@ def test_xdev(self): out = self.run_xdev("-c", code, check_exitcode=False) if support.with_pymalloc(): alloc_name = "pymalloc_debug" + elif support.Py_GIL_DISABLED: + alloc_name = "mimalloc_debug" else: alloc_name = "malloc_debug" self.assertEqual(out, alloc_name) @@ -814,9 +816,13 @@ def check_pythonmalloc(self, env_var, name): @support.cpython_only def test_pythonmalloc(self): # Test the PYTHONMALLOC environment variable + malloc = not support.Py_GIL_DISABLED pymalloc = support.with_pymalloc() mimalloc = support.with_mimalloc() - if pymalloc: + if support.Py_GIL_DISABLED: + default_name = 'mimalloc_debug' if support.Py_DEBUG else 'mimalloc' + default_name_debug = 'mimalloc_debug' + elif pymalloc: default_name = 'pymalloc_debug' if support.Py_DEBUG else 'pymalloc' default_name_debug = 'pymalloc_debug' else: @@ -826,9 +832,12 @@ def test_pythonmalloc(self): tests = [ (None, default_name), ('debug', default_name_debug), - ('malloc', 'malloc'), - ('malloc_debug', 'malloc_debug'), ] + if malloc: + tests.extend([ + ('malloc', 'malloc'), + ('malloc_debug', 'malloc_debug'), + ]) if pymalloc: tests.extend(( ('pymalloc', 'pymalloc'), diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index d2d6c1b61e46f0..6c60854bbd76cc 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -23,6 +23,12 @@ PYMEM_ALLOCATOR_NOT_SET = 0 PYMEM_ALLOCATOR_DEBUG = 2 PYMEM_ALLOCATOR_MALLOC = 3 +PYMEM_ALLOCATOR_MIMALLOC = 7 +if support.Py_GIL_DISABLED: + ALLOCATOR_FOR_CONFIG = PYMEM_ALLOCATOR_MIMALLOC +else: + ALLOCATOR_FOR_CONFIG = PYMEM_ALLOCATOR_MALLOC + Py_STATS = hasattr(sys, '_stats_on') # _PyCoreConfig_InitCompatConfig() @@ -841,7 +847,7 @@ def test_init_global_config(self): def test_init_from_config(self): preconfig = { - 'allocator': PYMEM_ALLOCATOR_MALLOC, + 'allocator': ALLOCATOR_FOR_CONFIG, 'utf8_mode': 1, } config = { @@ -908,7 +914,7 @@ def test_init_from_config(self): def test_init_compat_env(self): preconfig = { - 'allocator': PYMEM_ALLOCATOR_MALLOC, + 'allocator': ALLOCATOR_FOR_CONFIG, } config = { 'use_hash_seed': 1, @@ -942,7 +948,7 @@ def test_init_compat_env(self): def test_init_python_env(self): preconfig = { - 'allocator': PYMEM_ALLOCATOR_MALLOC, + 'allocator': ALLOCATOR_FOR_CONFIG, 'utf8_mode': 1, } config = { @@ -984,7 +990,7 @@ def test_init_env_dev_mode(self): api=API_COMPAT) def test_init_env_dev_mode_alloc(self): - preconfig = dict(allocator=PYMEM_ALLOCATOR_MALLOC) + preconfig = dict(allocator=ALLOCATOR_FOR_CONFIG) config = dict(dev_mode=1, faulthandler=1, warnoptions=['default']) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index c31c9684051196..d4680ef0f0e03f 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -5080,7 +5080,10 @@ def test_fork(self): support.wait_process(pid, exitcode=0) """ assert_python_ok("-c", code) - assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug") + if support.Py_GIL_DISABLED: + assert_python_ok("-c", code, PYTHONMALLOC="mimalloc_debug") + else: + assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug") @unittest.skipUnless(sys.platform in ("linux", "darwin"), "Only Linux and macOS detect this today.") diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index b737c030957564..99c95d90658b08 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -16,6 +16,10 @@ # include "mimalloc/internal.h" // for stats #endif +#if defined(Py_GIL_DISABLED) && !defined(WITH_MIMALLOC) +# error "Py_GIL_DISABLED requires WITH_MIMALLOC" +#endif + #undef uint #define uint pymem_uint @@ -153,7 +157,12 @@ void* _PyObject_Realloc(void *ctx, void *ptr, size_t size); # define PYMALLOC_ALLOC {NULL, _PyObject_Malloc, _PyObject_Calloc, _PyObject_Realloc, _PyObject_Free} #endif // WITH_PYMALLOC -#if defined(WITH_PYMALLOC) +#if defined(Py_GIL_DISABLED) +// Py_GIL_DISABLED requires using mimalloc for "mem" and "obj" domains. +# define PYRAW_ALLOC MALLOC_ALLOC +# define PYMEM_ALLOC MIMALLOC_ALLOC +# define PYOBJ_ALLOC MIMALLOC_OBJALLOC +#elif defined(WITH_PYMALLOC) # define PYRAW_ALLOC MALLOC_ALLOC # define PYMEM_ALLOC PYMALLOC_ALLOC # define PYOBJ_ALLOC PYMALLOC_ALLOC @@ -350,7 +359,7 @@ _PyMem_GetAllocatorName(const char *name, PyMemAllocatorName *allocator) else if (strcmp(name, "debug") == 0) { *allocator = PYMEM_ALLOCATOR_DEBUG; } -#ifdef WITH_PYMALLOC +#if defined(WITH_PYMALLOC) && !defined(Py_GIL_DISABLED) else if (strcmp(name, "pymalloc") == 0) { *allocator = PYMEM_ALLOCATOR_PYMALLOC; } @@ -366,12 +375,14 @@ _PyMem_GetAllocatorName(const char *name, PyMemAllocatorName *allocator) *allocator = PYMEM_ALLOCATOR_MIMALLOC_DEBUG; } #endif +#ifndef Py_GIL_DISABLED else if (strcmp(name, "malloc") == 0) { *allocator = PYMEM_ALLOCATOR_MALLOC; } else if (strcmp(name, "malloc_debug") == 0) { *allocator = PYMEM_ALLOCATOR_MALLOC_DEBUG; } +#endif else { /* unknown allocator */ return -1; diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 1f9aa4b3d449a1..30998bf80f9ce4 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -576,7 +576,11 @@ static int test_init_from_config(void) _PyPreConfig_InitCompatConfig(&preconfig); putenv("PYTHONMALLOC=malloc_debug"); +#ifndef Py_GIL_DISABLED preconfig.allocator = PYMEM_ALLOCATOR_MALLOC; +#else + preconfig.allocator = PYMEM_ALLOCATOR_MIMALLOC; +#endif putenv("PYTHONUTF8=0"); Py_UTF8Mode = 0; @@ -765,7 +769,11 @@ static int test_init_dont_parse_argv(void) static void set_most_env_vars(void) { putenv("PYTHONHASHSEED=42"); +#ifndef Py_GIL_DISABLED putenv("PYTHONMALLOC=malloc"); +#else + putenv("PYTHONMALLOC=mimalloc"); +#endif putenv("PYTHONTRACEMALLOC=2"); putenv("PYTHONPROFILEIMPORTTIME=1"); putenv("PYTHONNODEBUGRANGES=1"); @@ -851,7 +859,11 @@ static int test_init_env_dev_mode_alloc(void) /* Test initialization from environment variables */ Py_IgnoreEnvironmentFlag = 0; set_all_env_vars_dev_mode(); +#ifndef Py_GIL_DISABLED putenv("PYTHONMALLOC=malloc"); +#else + putenv("PYTHONMALLOC=mimalloc"); +#endif _testembed_Py_InitializeFromConfig(); dump_config(); Py_Finalize(); diff --git a/configure b/configure index 5f880d6d8edd96..c4486441041a70 100755 --- a/configure +++ b/configure @@ -16891,6 +16891,8 @@ printf "%s\n" "#define WITH_MIMALLOC 1" >>confdefs.h MIMALLOC_HEADERS='$(MIMALLOC_HEADERS)' +elif test "$disable_gil" = "yes"; then + as_fn_error $? "--disable-gil requires mimalloc memory allocator (--with-mimalloc)." "$LINENO" 5 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_mimalloc" >&5 diff --git a/configure.ac b/configure.ac index c07d7ce6bdc918..a725309424a49e 100644 --- a/configure.ac +++ b/configure.ac @@ -4558,6 +4558,8 @@ if test "$with_mimalloc" != no; then with_mimalloc=yes AC_DEFINE([WITH_MIMALLOC], [1], [Define if you want to compile in mimalloc memory allocator.]) AC_SUBST([MIMALLOC_HEADERS], ['$(MIMALLOC_HEADERS)']) +elif test "$disable_gil" = "yes"; then + AC_MSG_ERROR([--disable-gil requires mimalloc memory allocator (--with-mimalloc).]) fi AC_MSG_RESULT([$with_mimalloc]) From 5b8664433829ea967c150363cf49a5c4c1380fe8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 11 Dec 2023 16:42:30 -0800 Subject: [PATCH 201/442] A smattering of cleanups in uop debug output and lltrace (#112980) * Include destination T1 opcode in Error debug message * Include destination T1 opcode in DEOPT debug message * Remove obsolete comment from remove_unneeded_uops * Change lltrace_instruction() to print caller's opcode/oparg --- Python/ceval.c | 18 ++++++++++-------- Python/ceval_macros.h | 2 +- Python/optimizer_analysis.c | 1 - 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index f8fa50eb46c75e..d92ab926f84963 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -135,14 +135,14 @@ dump_stack(_PyInterpreterFrame *frame, PyObject **stack_pointer) static void lltrace_instruction(_PyInterpreterFrame *frame, PyObject **stack_pointer, - _Py_CODEUNIT *next_instr) + _Py_CODEUNIT *next_instr, + int opcode, + int oparg) { if (frame->owner == FRAME_OWNED_BY_CSTACK) { return; } dump_stack(frame, stack_pointer); - int oparg = next_instr->op.arg; - int opcode = next_instr->op.code; const char *opname = _PyOpcode_OpName[opcode]; assert(opname != NULL); int offset = (int)(next_instr - _PyCode_CODE(_PyFrame_GetCode(frame))); @@ -1051,9 +1051,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int pop_1_error_tier_two: STACK_SHRINK(1); error_tier_two: - DPRINTF(2, "Error: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", + DPRINTF(2, "Error: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, - (int)(next_uop - current_executor->trace - 1)); + (int)(next_uop - current_executor->trace - 1), + _PyOpcode_OpName[frame->instr_ptr->op.code]); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); frame->return_offset = 0; // Don't leave this random _PyFrame_SetStackPointer(frame, stack_pointer); @@ -1064,14 +1065,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int deoptimize: // On DEOPT_IF we just repeat the last instruction. // This presumes nothing was popped from the stack (nor pushed). - DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d]\n", + frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); + DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, - (int)(next_uop - current_executor->trace - 1)); + (int)(next_uop - current_executor->trace - 1), + _PyOpcode_OpName[frame->instr_ptr->op.code]); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); frame->return_offset = 0; // Dispatch to frame->instr_ptr _PyFrame_SetStackPointer(frame, stack_pointer); - frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); Py_DECREF(current_executor); // Fall through // Jump here from ENTER_EXECUTOR diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index b0cb7c8926338c..f298c602b1042b 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -81,7 +81,7 @@ /* PRE_DISPATCH_GOTO() does lltrace if enabled. Normally a no-op */ #ifdef LLTRACE #define PRE_DISPATCH_GOTO() if (lltrace >= 5) { \ - lltrace_instruction(frame, stack_pointer, next_instr); } + lltrace_instruction(frame, stack_pointer, next_instr, opcode, oparg); } #else #define PRE_DISPATCH_GOTO() ((void)0) #endif diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 0f9bc085f22f1c..8b471d70a10d7d 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -15,7 +15,6 @@ static void remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) { - // Note that we don't enter stubs, those SET_IPs are needed. int last_set_ip = -1; bool maybe_invalid = false; for (int pc = 0; pc < buffer_size; pc++) { From 616622cab7200bac476bf753d6cc98f4ee48808c Mon Sep 17 00:00:00 2001 From: wim glenn Date: Mon, 11 Dec 2023 19:47:12 -0600 Subject: [PATCH 202/442] gh-112983: Add the known magic value of 3495 for Python 3.11 bytecode (#112985) Add the known magic value of 3495 for Python 3.11 bytecode --- Lib/importlib/_bootstrap_external.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 4e96a916324824..d537598ac16433 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -413,6 +413,7 @@ def _write_atomic(path, data, mode=0o666): # Python 3.11a7 3492 (make POP_JUMP_IF_NONE/NOT_NONE/TRUE/FALSE relative) # Python 3.11a7 3493 (Make JUMP_IF_TRUE_OR_POP/JUMP_IF_FALSE_OR_POP relative) # Python 3.11a7 3494 (New location info table) +# Python 3.11b4 3495 (Set line number of module's RESUME instr to 0 per PEP 626) # Python 3.12a1 3500 (Remove PRECALL opcode) # Python 3.12a1 3501 (YIELD_VALUE oparg == stack_depth) # Python 3.12a1 3502 (LOAD_FAST_CHECK, no NULL-check in LOAD_FAST) From e0fb7004ede71389c9dd462cd03352cc3c3a4d8c Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 12 Dec 2023 01:00:51 -0500 Subject: [PATCH 203/442] gh-112953: Rename idlelib/NEWS.txt to News3.txt and update (#112988) --- Lib/idlelib/{NEWS.txt => News3.txt} | 14 +++++++++++++- Lib/idlelib/help_about.py | 6 +++--- Lib/idlelib/idle_test/test_help_about.py | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) rename Lib/idlelib/{NEWS.txt => News3.txt} (99%) diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/News3.txt similarity index 99% rename from Lib/idlelib/NEWS.txt rename to Lib/idlelib/News3.txt index f258797c6e0fb3..4fba4165fddab5 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/News3.txt @@ -1,9 +1,21 @@ +What's New in IDLE 3.13.0 +(since 3.12.0) +Released on 2024-10-xx +========================= + + +gh-112939: Fix processing unsaved files when quitting IDLE on macOS. +Patch by Ronald Oussoren and Christopher Chavez. + +gh-79871: Add docstrings to debugger.py. Fix two bugs in +test_debugger and expand coverage by 47%. Patch by Anthony Shaw. + + What's New in IDLE 3.12.0 (since 3.11.0) Released on 2023-10-02 ========================= - gh-104719: Remove IDLE's modification of tokenize.tabsize and test other uses of tokenize data and methods. diff --git a/Lib/idlelib/help_about.py b/Lib/idlelib/help_about.py index cfa4ca781f087d..aa1c352897f9e7 100644 --- a/Lib/idlelib/help_about.py +++ b/Lib/idlelib/help_about.py @@ -129,11 +129,11 @@ def create_widgets(self): idle.grid(row=12, column=0, sticky=W, padx=10, pady=0) idle_buttons = Frame(frame_background, bg=self.bg) idle_buttons.grid(row=13, column=0, columnspan=3, sticky=NSEW) - self.readme = Button(idle_buttons, text='README', width=8, + self.readme = Button(idle_buttons, text='Readme', width=8, highlightbackground=self.bg, command=self.show_readme) self.readme.pack(side=LEFT, padx=10, pady=10) - self.idle_news = Button(idle_buttons, text='NEWS', width=8, + self.idle_news = Button(idle_buttons, text='News', width=8, highlightbackground=self.bg, command=self.show_idle_news) self.idle_news.pack(side=LEFT, padx=10, pady=10) @@ -167,7 +167,7 @@ def show_readme(self): def show_idle_news(self): "Handle News button event." - self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8') + self.display_file_text('About - News', 'News3.txt', 'utf-8') def display_printer_text(self, title, printer): """Create textview for built-in constants. diff --git a/Lib/idlelib/idle_test/test_help_about.py b/Lib/idlelib/idle_test/test_help_about.py index 8b79487b15d4cd..7e16abdb7c9f96 100644 --- a/Lib/idlelib/idle_test/test_help_about.py +++ b/Lib/idlelib/idle_test/test_help_about.py @@ -71,7 +71,7 @@ def test_file_buttons(self): """Test buttons that display files.""" dialog = self.dialog button_sources = [(self.dialog.readme, 'README.txt', 'readme'), - (self.dialog.idle_news, 'NEWS.txt', 'news'), + (self.dialog.idle_news, 'News3.txt', 'news'), (self.dialog.idle_credits, 'CREDITS.txt', 'credits')] for button, filename, name in button_sources: From 0d2fe6bab01541301abe98a23ee15a16f493fe74 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 12 Dec 2023 10:25:51 +0100 Subject: [PATCH 204/442] gh-87286: Add a number of LOG_* constants to syslog (#24432) * bpo-43120: Add a number of LOG_* constants to syslog This adds a number of syslog facilities to the syslogmodule.c. These values are available on macOS. * Switch contant documentation to the data directive This fixes a CI warning and matches the pattern used in the documentation for ``os``. * Update Doc/library/syslog.rst Co-authored-by: Hugo van Kemenade Co-authored-by: Alex Waygood --- Doc/library/syslog.rst | 74 ++++++++++++++----- Doc/tools/.nitignore | 1 - ...3-12-07-16-55-41.gh-issue-87286.MILC9_.rst | 3 + Modules/syslogmodule.c | 24 ++++++ 4 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-07-16-55-41.gh-issue-87286.MILC9_.rst diff --git a/Doc/library/syslog.rst b/Doc/library/syslog.rst index b5ab446e0096ed..7b27fc7e85b62d 100644 --- a/Doc/library/syslog.rst +++ b/Doc/library/syslog.rst @@ -15,7 +15,7 @@ facility. This module wraps the system ``syslog`` family of routines. A pure Python library that can speak to a syslog server is available in the -:mod:`logging.handlers` module as :class:`SysLogHandler`. +:mod:`logging.handlers` module as :class:`~logging.handlers.SysLogHandler`. The module defines the following functions: @@ -107,22 +107,62 @@ The module defines the following functions: The module defines the following constants: -Priority levels (high to low): - :const:`LOG_EMERG`, :const:`LOG_ALERT`, :const:`LOG_CRIT`, :const:`LOG_ERR`, - :const:`LOG_WARNING`, :const:`LOG_NOTICE`, :const:`LOG_INFO`, - :const:`LOG_DEBUG`. - -Facilities: - :const:`LOG_KERN`, :const:`LOG_USER`, :const:`LOG_MAIL`, :const:`LOG_DAEMON`, - :const:`LOG_AUTH`, :const:`LOG_LPR`, :const:`LOG_NEWS`, :const:`LOG_UUCP`, - :const:`LOG_CRON`, :const:`LOG_SYSLOG`, :const:`LOG_LOCAL0` to - :const:`LOG_LOCAL7`, and, if defined in ````, - :const:`LOG_AUTHPRIV`. - -Log options: - :const:`LOG_PID`, :const:`LOG_CONS`, :const:`LOG_NDELAY`, and, if defined - in ````, :const:`LOG_ODELAY`, :const:`LOG_NOWAIT`, and - :const:`LOG_PERROR`. + +.. data:: LOG_EMERG + LOG_ALERT + LOG_CRIT + LOG_ERR + LOG_WARNING + LOG_NOTICE + LOG_INFO + LOG_DEBUG + + Priority levels (high to low). + + +.. data:: LOG_AUTH + LOG_AUTHPRIV + LOG_CRON + LOG_DAEMON + LOG_FTP + LOG_INSTALL + LOG_KERN + LOG_LAUNCHD + LOG_LPR + LOG_MAIL + LOG_NETINFO + LOG_NEWS + LOG_RAS + LOG_REMOTEAUTH + LOG_SYSLOG + LOG_USER + LOG_UUCP + LOG_LOCAL0 + LOG_LOCAL1 + LOG_LOCAL2 + LOG_LOCAL3 + LOG_LOCAL4 + LOG_LOCAL5 + LOG_LOCAL6 + LOG_LOCAL7 + + Facilities, depending on availability in ```` for :const:`LOG_AUTHPRIV`, + :const:`LOG_FTP`, :const:`LOG_NETINFO`, :const:`LOG_REMOTEAUTH`, + :const:`LOG_INSTALL` and :const:`LOG_RAS`. + + .. versionchanged:: 3.13 + Added :const:`LOG_FTP`, :const:`LOG_NETINFO`, :const:`LOG_REMOTEAUTH`, + :const:`LOG_INSTALL`, :const:`LOG_RAS`, and :const:`LOG_LAUNCHD`. + +.. data:: LOG_PID + LOG_CONS + LOG_NDELAY + LOG_ODELAY + LOG_NOWAIT + LOG_PERROR + + Log options, depending on availability in ```` for + :const:`LOG_ODELAY`, :const:`LOG_NOWAIT` and :const:`LOG_PERROR`. Examples diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 511648ab6991c6..75d50fee33a064 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -91,7 +91,6 @@ Doc/library/ssl.rst Doc/library/stdtypes.rst Doc/library/string.rst Doc/library/subprocess.rst -Doc/library/syslog.rst Doc/library/tarfile.rst Doc/library/termios.rst Doc/library/test.rst diff --git a/Misc/NEWS.d/next/Library/2023-12-07-16-55-41.gh-issue-87286.MILC9_.rst b/Misc/NEWS.d/next/Library/2023-12-07-16-55-41.gh-issue-87286.MILC9_.rst new file mode 100644 index 00000000000000..bfeec3c95207cb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-07-16-55-41.gh-issue-87286.MILC9_.rst @@ -0,0 +1,3 @@ +Added :const:`LOG_FTP`, :const:`LOG_NETINFO`, :const:`LOG_REMOTEAUTH`, +:const:`LOG_INSTALL`, :const:`LOG_RAS`, and :const:`LOG_LAUNCHD` tot the +:mod:`syslog` module, all of them constants on used on macOS. diff --git a/Modules/syslogmodule.c b/Modules/syslogmodule.c index 6a44850e291448..62c7816f891ee2 100644 --- a/Modules/syslogmodule.c +++ b/Modules/syslogmodule.c @@ -406,6 +406,30 @@ syslog_exec(PyObject *module) ADD_INT_MACRO(module, LOG_AUTHPRIV); #endif +#ifdef LOG_FTP + ADD_INT_MACRO(module, LOG_FTP); +#endif + +#ifdef LOG_NETINFO + ADD_INT_MACRO(module, LOG_NETINFO); +#endif + +#ifdef LOG_REMOTEAUTH + ADD_INT_MACRO(module, LOG_REMOTEAUTH); +#endif + +#ifdef LOG_INSTALL + ADD_INT_MACRO(module, LOG_INSTALL); +#endif + +#ifdef LOG_RAS + ADD_INT_MACRO(module, LOG_RAS); +#endif + +#ifdef LOG_LAUNCHD + ADD_INT_MACRO(module, LOG_LAUNCHD); +#endif + return 0; } From c454e934d36193709aadba8e8e28739790086b95 Mon Sep 17 00:00:00 2001 From: Sam James Date: Tue, 12 Dec 2023 10:25:27 +0000 Subject: [PATCH 205/442] gh-112970: Detect and use closefrom() when available (#112969) glibc-2.34 implements closefrom(3) using the same semantics as on BSD. Check for closefrom() in configure and use the check result in fileutils.c, rather than hardcoding a FreeBSD check. Some implementations of closefrom() return an int. Explicitly discard the return value by casting it to void, to avoid future compiler warnings. Signed-off-by: Sam James --- .../Library/2023-12-11-16-13-15.gh-issue-112970.87jmKP.rst | 1 + Python/fileutils.c | 6 +++--- configure | 6 ++++++ configure.ac | 2 +- pyconfig.h.in | 3 +++ 5 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-11-16-13-15.gh-issue-112970.87jmKP.rst diff --git a/Misc/NEWS.d/next/Library/2023-12-11-16-13-15.gh-issue-112970.87jmKP.rst b/Misc/NEWS.d/next/Library/2023-12-11-16-13-15.gh-issue-112970.87jmKP.rst new file mode 100644 index 00000000000000..58ca26af511383 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-11-16-13-15.gh-issue-112970.87jmKP.rst @@ -0,0 +1 @@ +Use :c:func:`!closefrom` on Linux where available (e.g. glibc-2.34), rather than only FreeBSD. diff --git a/Python/fileutils.c b/Python/fileutils.c index 9d12bc89c95436..882d3299575cf3 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2878,9 +2878,9 @@ _Py_GetLocaleconvNumeric(struct lconv *lc, * non-opened fd in the middle. * 2b. If fdwalk(3) isn't available, just do a plain close(2) loop. */ -#ifdef __FreeBSD__ +#ifdef HAVE_CLOSEFROM # define USE_CLOSEFROM -#endif /* __FreeBSD__ */ +#endif /* HAVE_CLOSEFROM */ #ifdef HAVE_FDWALK # define USE_FDWALK @@ -2922,7 +2922,7 @@ _Py_closerange(int first, int last) #ifdef USE_CLOSEFROM if (last >= sysconf(_SC_OPEN_MAX)) { /* Any errors encountered while closing file descriptors are ignored */ - closefrom(first); + (void)closefrom(first); } else #endif /* USE_CLOSEFROM */ diff --git a/configure b/configure index c4486441041a70..cad3bce0c7de87 100755 --- a/configure +++ b/configure @@ -17225,6 +17225,12 @@ if test "x$ac_cv_func_clock" = xyes then : printf "%s\n" "#define HAVE_CLOCK 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "closefrom" "ac_cv_func_closefrom" +if test "x$ac_cv_func_closefrom" = xyes +then : + printf "%s\n" "#define HAVE_CLOSEFROM 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "close_range" "ac_cv_func_close_range" if test "x$ac_cv_func_close_range" = xyes diff --git a/configure.ac b/configure.ac index a725309424a49e..7dda0b3fff95be 100644 --- a/configure.ac +++ b/configure.ac @@ -4745,7 +4745,7 @@ fi # checks for library functions AC_CHECK_FUNCS([ \ - accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ + accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ 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 \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 2978fa2c17301f..9c429c03722383 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -157,6 +157,9 @@ /* Define to 1 if you have the `clock_settime' function. */ #undef HAVE_CLOCK_SETTIME +/* Define to 1 if you have the `closefrom' function. */ +#undef HAVE_CLOSEFROM + /* Define to 1 if you have the `close_range' function. */ #undef HAVE_CLOSE_RANGE From 0c55f270604f8541bcf43526e5cf6c6eddfff451 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 12 Dec 2023 12:12:17 +0000 Subject: [PATCH 206/442] GH-111485: Factor out tier 2 code generation from the rest of the interpreter code generator (GH-112968) --- Include/internal/pycore_uop_ids.h | 2 +- Include/opcode_ids.h | 2 +- Makefile.pre.in | 3 +- Python/bytecodes.c | 3 + Python/executor_cases.c.h | 1616 ++++++++++-------- Python/generated_cases.c.h | 5 +- Tools/cases_generator/analyzer.py | 19 + Tools/cases_generator/generate_cases.py | 8 - Tools/cases_generator/generators_common.py | 175 +- Tools/cases_generator/opcode_id_generator.py | 2 +- Tools/cases_generator/stack.py | 87 + Tools/cases_generator/tier1_generator.py | 228 +-- Tools/cases_generator/tier2_generator.py | 202 +++ Tools/cases_generator/uop_id_generator.py | 13 +- 14 files changed, 1391 insertions(+), 974 deletions(-) create mode 100644 Tools/cases_generator/tier2_generator.py diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 03e089953698c9..c96ea51ae1acb6 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -1,6 +1,6 @@ // This file is generated by Tools/cases_generator/uop_id_generator.py // from: -// ['./Python/bytecodes.c'] +// Python/bytecodes.c // Do not edit! #ifndef Py_CORE_UOP_IDS_H #define Py_CORE_UOP_IDS_H diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index 47f809e345f61c..e2e27ca00fd47b 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -1,6 +1,6 @@ // This file is generated by Tools/cases_generator/opcode_id_generator.py // from: -// ['./Python/bytecodes.c'] +// Python/bytecodes.c // Do not edit! #ifndef Py_OPCODE_IDS_H diff --git a/Makefile.pre.in b/Makefile.pre.in index 4317c947aeb919..5fb6ffc4e8f0cf 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1589,7 +1589,6 @@ regen-cases: $(CASESFLAG) \ -t $(srcdir)/Python/opcode_targets.h.new \ -m $(srcdir)/Include/internal/pycore_opcode_metadata.h.new \ - -e $(srcdir)/Python/executor_cases.c.h.new \ -p $(srcdir)/Lib/_opcode_metadata.py.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c @@ -1599,6 +1598,8 @@ regen-cases: $(srcdir)/Tools/cases_generator/uop_id_generator.py -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) \ + $(srcdir)/Tools/cases_generator/tier2_generator.py -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_ids.h $(srcdir)/Include/internal/pycore_uop_ids.h.new diff --git a/Python/bytecodes.c b/Python/bytecodes.c index bcad8dcf0e7dab..e0f373536ce67c 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3967,6 +3967,7 @@ dummy_func( } inst(EXTENDED_ARG, ( -- )) { + TIER_ONE_ONLY assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -3975,11 +3976,13 @@ dummy_func( } inst(CACHE, (--)) { + TIER_ONE_ONLY assert(0 && "Executing a cache."); Py_UNREACHABLE(); } inst(RESERVED, (--)) { + TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); Py_UNREACHABLE(); } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 974e3f28a411b8..9dda3c9a743258 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1,4 +1,4 @@ -// This file is generated by Tools/cases_generator/generate_cases.py +// This file is generated by Tools/cases_generator/tier2_generator.py // from: // Python/bytecodes.c // Do not edit! @@ -8,102 +8,104 @@ #endif #define TIER_TWO 2 - case NOP: { + case _NOP: { break; } - case RESUME_CHECK: { -#if defined(__EMSCRIPTEN__) - DEOPT_IF(_Py_emscripten_signal_clock == 0, RESUME); + case _RESUME_CHECK: { + #if defined(__EMSCRIPTEN__) + if (_Py_emscripten_signal_clock == 0) goto deoptimize; _Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING; -#endif + #endif uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker); uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version; assert((version & _PY_EVAL_EVENTS_MASK) == 0); - DEOPT_IF(eval_breaker != version, RESUME); + if (eval_breaker != version) goto deoptimize; break; } - case LOAD_FAST_CHECK: { - oparg = CURRENT_OPARG(); + /* _INSTRUMENTED_RESUME is not a viable micro-op for tier 2 */ + + case _LOAD_FAST_CHECK: { PyObject *value; + oparg = CURRENT_OPARG(); value = GETLOCAL(oparg); if (value == NULL) goto unbound_local_error_tier_two; Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case LOAD_FAST: { - oparg = CURRENT_OPARG(); + case _LOAD_FAST: { PyObject *value; + oparg = CURRENT_OPARG(); value = GETLOCAL(oparg); assert(value != NULL); Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case LOAD_FAST_AND_CLEAR: { - oparg = CURRENT_OPARG(); + case _LOAD_FAST_AND_CLEAR: { PyObject *value; + oparg = CURRENT_OPARG(); value = GETLOCAL(oparg); // do not use SETLOCAL here, it decrefs the old value GETLOCAL(oparg) = NULL; - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case LOAD_CONST: { - oparg = CURRENT_OPARG(); + case _LOAD_CONST: { PyObject *value; + oparg = CURRENT_OPARG(); value = GETITEM(FRAME_CO_CONSTS, oparg); Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case STORE_FAST: { - oparg = CURRENT_OPARG(); + case _STORE_FAST: { PyObject *value; + oparg = CURRENT_OPARG(); value = stack_pointer[-1]; SETLOCAL(oparg, value); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case POP_TOP: { + case _POP_TOP: { PyObject *value; value = stack_pointer[-1]; Py_DECREF(value); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case PUSH_NULL: { + case _PUSH_NULL: { PyObject *res; res = NULL; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case END_SEND: { + case _END_SEND: { PyObject *value; PyObject *receiver; value = stack_pointer[-1]; receiver = stack_pointer[-2]; Py_DECREF(receiver); - STACK_SHRINK(1); - stack_pointer[-1] = value; + stack_pointer[-2] = value; + stack_pointer += -1; break; } - case UNARY_NEGATIVE: { + case _UNARY_NEGATIVE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -114,7 +116,7 @@ break; } - case UNARY_NOT: { + case _UNARY_NOT: { PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -136,19 +138,19 @@ break; } - case TO_BOOL_BOOL: { + case _TO_BOOL_BOOL: { PyObject *value; value = stack_pointer[-1]; - DEOPT_IF(!PyBool_Check(value), TO_BOOL); + if (!PyBool_Check(value)) goto deoptimize; STAT_INC(TO_BOOL, hit); break; } - case TO_BOOL_INT: { + case _TO_BOOL_INT: { PyObject *value; PyObject *res; value = stack_pointer[-1]; - DEOPT_IF(!PyLong_CheckExact(value), TO_BOOL); + if (!PyLong_CheckExact(value)) goto deoptimize; STAT_INC(TO_BOOL, hit); if (_PyLong_IsZero((PyLongObject *)value)) { assert(_Py_IsImmortal(value)); @@ -162,11 +164,11 @@ break; } - case TO_BOOL_LIST: { + case _TO_BOOL_LIST: { PyObject *value; PyObject *res; value = stack_pointer[-1]; - DEOPT_IF(!PyList_CheckExact(value), TO_BOOL); + if (!PyList_CheckExact(value)) goto deoptimize; STAT_INC(TO_BOOL, hit); res = Py_SIZE(value) ? Py_True : Py_False; Py_DECREF(value); @@ -174,23 +176,23 @@ break; } - case TO_BOOL_NONE: { + case _TO_BOOL_NONE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; // This one is a bit weird, because we expect *some* failures: - DEOPT_IF(!Py_IsNone(value), TO_BOOL); + if (!Py_IsNone(value)) goto deoptimize; STAT_INC(TO_BOOL, hit); res = Py_False; stack_pointer[-1] = res; break; } - case TO_BOOL_STR: { + case _TO_BOOL_STR: { PyObject *value; PyObject *res; value = stack_pointer[-1]; - DEOPT_IF(!PyUnicode_CheckExact(value), TO_BOOL); + if (!PyUnicode_CheckExact(value)) goto deoptimize; STAT_INC(TO_BOOL, hit); if (value == &_Py_STR(empty)) { assert(_Py_IsImmortal(value)); @@ -205,14 +207,14 @@ break; } - case TO_BOOL_ALWAYS_TRUE: { + case _TO_BOOL_ALWAYS_TRUE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; uint32_t version = (uint32_t)CURRENT_OPERAND(); // This one is a bit weird, because we expect *some* failures: assert(version); - DEOPT_IF(Py_TYPE(value)->tp_version_tag != version, TO_BOOL); + if (Py_TYPE(value)->tp_version_tag != version) goto deoptimize; STAT_INC(TO_BOOL, hit); Py_DECREF(value); res = Py_True; @@ -220,7 +222,7 @@ break; } - case UNARY_INVERT: { + case _UNARY_INVERT: { PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -236,8 +238,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(left), _GUARD_BOTH_INT); - DEOPT_IF(!PyLong_CheckExact(right), _GUARD_BOTH_INT); + if (!PyLong_CheckExact(left)) goto deoptimize; + if (!PyLong_CheckExact(right)) goto deoptimize; break; } @@ -252,8 +254,8 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -268,8 +270,8 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -284,8 +286,8 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); _Py_DECREF_SPECIALIZED(left, (destructor)PyObject_Free); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -294,8 +296,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyFloat_CheckExact(left), _GUARD_BOTH_FLOAT); - DEOPT_IF(!PyFloat_CheckExact(right), _GUARD_BOTH_FLOAT); + if (!PyFloat_CheckExact(left)) goto deoptimize; + if (!PyFloat_CheckExact(right)) goto deoptimize; break; } @@ -307,11 +309,11 @@ left = stack_pointer[-2]; STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval * - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval * + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -323,11 +325,11 @@ left = stack_pointer[-2]; STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval + - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval + + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -339,11 +341,11 @@ left = stack_pointer[-2]; STAT_INC(BINARY_OP, hit); double dres = - ((PyFloatObject *)left)->ob_fval - - ((PyFloatObject *)right)->ob_fval; + ((PyFloatObject *)left)->ob_fval - + ((PyFloatObject *)right)->ob_fval; DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } @@ -352,8 +354,8 @@ PyObject *left; right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyUnicode_CheckExact(left), _GUARD_BOTH_UNICODE); - DEOPT_IF(!PyUnicode_CheckExact(right), _GUARD_BOTH_UNICODE); + if (!PyUnicode_CheckExact(left)) goto deoptimize; + if (!PyUnicode_CheckExact(right)) goto deoptimize; break; } @@ -368,8 +370,40 @@ _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _BINARY_OP_INPLACE_ADD_UNICODE: { + PyObject *right; + PyObject *left; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + assert(next_instr->op.code == STORE_FAST); + PyObject **target_local = &GETLOCAL(next_instr->op.arg); + if (*target_local != left) goto deoptimize; + STAT_INC(BINARY_OP, hit); + /* Handle `left = left + right` or `left += right` for str. + * + * When possible, extend `left` in place rather than + * allocating a new PyUnicodeObject. This attempts to avoid + * quadratic behavior when one neglects to use str.join(). + * + * If `left` has only two references remaining (one from + * the stack, one in the locals), DECREFing `left` leaves + * only the locals reference, so PyUnicode_Append knows + * that the string is safe to mutate. + */ + assert(Py_REFCNT(left) >= 2); + _Py_DECREF_NO_DEALLOC(left); + PyUnicode_Append(target_local, right); + _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); + if (*target_local == NULL) goto pop_2_error_tier_two; + // The STORE_FAST is already done. + assert(next_instr->op.code == STORE_FAST); + SKIP_OVER(1); + stack_pointer += -2; break; } @@ -383,12 +417,12 @@ Py_DECREF(container); Py_DECREF(sub); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case BINARY_SLICE: { + case _BINARY_SLICE: { PyObject *stop; PyObject *start; PyObject *container; @@ -408,12 +442,12 @@ } Py_DECREF(container); if (res == NULL) goto pop_3_error_tier_two; - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; break; } - case STORE_SLICE: { + case _STORE_SLICE: { PyObject *stop; PyObject *start; PyObject *container; @@ -434,88 +468,86 @@ Py_DECREF(v); Py_DECREF(container); if (err) goto pop_4_error_tier_two; - STACK_SHRINK(4); + stack_pointer += -4; break; } - case BINARY_SUBSCR_LIST_INT: { + case _BINARY_SUBSCR_LIST_INT: { PyObject *sub; PyObject *list; PyObject *res; sub = stack_pointer[-1]; list = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); - DEOPT_IF(!PyList_CheckExact(list), BINARY_SUBSCR); - + if (!PyLong_CheckExact(sub)) goto deoptimize; + if (!PyList_CheckExact(list)) goto deoptimize; // Deopt unless 0 <= sub < PyList_Size(list) - DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), BINARY_SUBSCR); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) goto deoptimize; Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; - DEOPT_IF(index >= PyList_GET_SIZE(list), BINARY_SUBSCR); + if (index >= PyList_GET_SIZE(list)) goto deoptimize; STAT_INC(BINARY_SUBSCR, hit); res = PyList_GET_ITEM(list, index); assert(res != NULL); Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case BINARY_SUBSCR_STR_INT: { + case _BINARY_SUBSCR_STR_INT: { PyObject *sub; PyObject *str; PyObject *res; sub = stack_pointer[-1]; str = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); - DEOPT_IF(!PyUnicode_CheckExact(str), BINARY_SUBSCR); - DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), BINARY_SUBSCR); + if (!PyLong_CheckExact(sub)) goto deoptimize; + if (!PyUnicode_CheckExact(str)) goto deoptimize; + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) goto deoptimize; Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; - DEOPT_IF(PyUnicode_GET_LENGTH(str) <= index, BINARY_SUBSCR); + if (PyUnicode_GET_LENGTH(str) <= index) goto deoptimize; // Specialize for reading an ASCII character from any string: Py_UCS4 c = PyUnicode_READ_CHAR(str, index); - DEOPT_IF(Py_ARRAY_LENGTH(_Py_SINGLETON(strings).ascii) <= c, BINARY_SUBSCR); + if (Py_ARRAY_LENGTH(_Py_SINGLETON(strings).ascii) <= c) goto deoptimize; STAT_INC(BINARY_SUBSCR, hit); res = (PyObject*)&_Py_SINGLETON(strings).ascii[c]; _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(str); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case BINARY_SUBSCR_TUPLE_INT: { + case _BINARY_SUBSCR_TUPLE_INT: { PyObject *sub; PyObject *tuple; PyObject *res; sub = stack_pointer[-1]; tuple = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); - DEOPT_IF(!PyTuple_CheckExact(tuple), BINARY_SUBSCR); - + if (!PyLong_CheckExact(sub)) goto deoptimize; + if (!PyTuple_CheckExact(tuple)) goto deoptimize; // Deopt unless 0 <= sub < PyTuple_Size(list) - DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), BINARY_SUBSCR); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) goto deoptimize; Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; - DEOPT_IF(index >= PyTuple_GET_SIZE(tuple), BINARY_SUBSCR); + if (index >= PyTuple_GET_SIZE(tuple)) goto deoptimize; STAT_INC(BINARY_SUBSCR, hit); res = PyTuple_GET_ITEM(tuple, index); assert(res != NULL); Py_INCREF(res); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(tuple); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case BINARY_SUBSCR_DICT: { + case _BINARY_SUBSCR_DICT: { PyObject *sub; PyObject *dict; PyObject *res; sub = stack_pointer[-1]; dict = stack_pointer[-2]; - DEOPT_IF(!PyDict_CheckExact(dict), BINARY_SUBSCR); + if (!PyDict_CheckExact(dict)) goto deoptimize; STAT_INC(BINARY_SUBSCR, hit); int rc = PyDict_GetItemRef(dict, sub, &res); if (rc == 0) { @@ -524,32 +556,35 @@ Py_DECREF(dict); Py_DECREF(sub); if (rc <= 0) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + // not found or error + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case LIST_APPEND: { - oparg = CURRENT_OPARG(); + /* _BINARY_SUBSCR_GETITEM is not a viable micro-op for tier 2 */ + + case _LIST_APPEND: { PyObject *v; PyObject *list; + oparg = CURRENT_OPARG(); v = stack_pointer[-1]; list = stack_pointer[-2 - (oparg-1)]; if (_PyList_AppendTakeRef((PyListObject *)list, v) < 0) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } - case SET_ADD: { - oparg = CURRENT_OPARG(); + case _SET_ADD: { PyObject *v; PyObject *set; + oparg = CURRENT_OPARG(); v = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; int err = PySet_Add(set, v); Py_DECREF(v); if (err) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } @@ -566,54 +601,52 @@ Py_DECREF(container); Py_DECREF(sub); if (err) goto pop_3_error_tier_two; - STACK_SHRINK(3); + stack_pointer += -3; break; } - case STORE_SUBSCR_LIST_INT: { + case _STORE_SUBSCR_LIST_INT: { PyObject *sub; PyObject *list; PyObject *value; sub = stack_pointer[-1]; list = stack_pointer[-2]; value = stack_pointer[-3]; - DEOPT_IF(!PyLong_CheckExact(sub), STORE_SUBSCR); - DEOPT_IF(!PyList_CheckExact(list), STORE_SUBSCR); - + if (!PyLong_CheckExact(sub)) goto deoptimize; + if (!PyList_CheckExact(list)) goto deoptimize; // Ensure nonnegative, zero-or-one-digit ints. - DEOPT_IF(!_PyLong_IsNonNegativeCompact((PyLongObject *)sub), STORE_SUBSCR); + if (!_PyLong_IsNonNegativeCompact((PyLongObject *)sub)) goto deoptimize; Py_ssize_t index = ((PyLongObject*)sub)->long_value.ob_digit[0]; // Ensure index < len(list) - DEOPT_IF(index >= PyList_GET_SIZE(list), STORE_SUBSCR); + if (index >= PyList_GET_SIZE(list)) goto deoptimize; STAT_INC(STORE_SUBSCR, hit); - PyObject *old_value = PyList_GET_ITEM(list, index); PyList_SET_ITEM(list, index, value); assert(old_value != NULL); Py_DECREF(old_value); _Py_DECREF_SPECIALIZED(sub, (destructor)PyObject_Free); Py_DECREF(list); - STACK_SHRINK(3); + stack_pointer += -3; break; } - case STORE_SUBSCR_DICT: { + case _STORE_SUBSCR_DICT: { PyObject *sub; PyObject *dict; PyObject *value; sub = stack_pointer[-1]; dict = stack_pointer[-2]; value = stack_pointer[-3]; - DEOPT_IF(!PyDict_CheckExact(dict), STORE_SUBSCR); + if (!PyDict_CheckExact(dict)) goto deoptimize; STAT_INC(STORE_SUBSCR, hit); int err = _PyDict_SetItem_Take2((PyDictObject *)dict, sub, value); Py_DECREF(dict); if (err) goto pop_3_error_tier_two; - STACK_SHRINK(3); + stack_pointer += -3; break; } - case DELETE_SUBSCR: { + case _DELETE_SUBSCR: { PyObject *sub; PyObject *container; sub = stack_pointer[-1]; @@ -623,14 +656,14 @@ Py_DECREF(container); Py_DECREF(sub); if (err) goto pop_2_error_tier_two; - STACK_SHRINK(2); + stack_pointer += -2; break; } - case CALL_INTRINSIC_1: { - oparg = CURRENT_OPARG(); + case _CALL_INTRINSIC_1: { PyObject *value; PyObject *res; + oparg = CURRENT_OPARG(); value = stack_pointer[-1]; assert(oparg <= MAX_INTRINSIC_1); res = _PyIntrinsics_UnaryFunctions[oparg].func(tstate, value); @@ -640,11 +673,11 @@ break; } - case CALL_INTRINSIC_2: { - oparg = CURRENT_OPARG(); + case _CALL_INTRINSIC_2: { PyObject *value1; PyObject *value2; PyObject *res; + oparg = CURRENT_OPARG(); value1 = stack_pointer[-1]; value2 = stack_pointer[-2]; assert(oparg <= MAX_INTRINSIC_2); @@ -652,19 +685,31 @@ Py_DECREF(value2); Py_DECREF(value1); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } + case _INTERPRETER_EXIT: { + PyObject *retval; + retval = stack_pointer[-1]; + assert(frame == &entry_frame); + assert(_PyFrame_IsIncomplete(frame)); + /* Restore previous frame and return. */ + tstate->current_frame = frame->previous; + assert(!_PyErr_Occurred(tstate)); + tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS; + return retval; + } + case _POP_FRAME: { PyObject *retval; retval = stack_pointer[-1]; - STACK_SHRINK(1); #if TIER_ONE assert(frame != &entry_frame); #endif - STORE_SP(); + stack_pointer += -1; + _PyFrame_SetStackPointer(frame, stack_pointer); assert(EMPTY()); _Py_LeaveRecursiveCallPy(tstate); // GH-99729: We need to unlink the frame *before* clearing it: @@ -674,26 +719,28 @@ _PyFrame_StackPush(frame, retval); LOAD_SP(); LOAD_IP(frame->return_offset); -#if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } -#endif + #endif break; } - case GET_AITER: { + /* _INSTRUMENTED_RETURN_VALUE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_RETURN_CONST is not a viable micro-op for tier 2 */ + + case _GET_AITER: { PyObject *obj; PyObject *iter; obj = stack_pointer[-1]; unaryfunc getter = NULL; PyTypeObject *type = Py_TYPE(obj); - if (type->tp_as_async != NULL) { getter = type->tp_as_async->am_aiter; } - if (getter == NULL) { _PyErr_Format(tstate, PyExc_TypeError, "'async for' requires an object with " @@ -702,14 +749,11 @@ Py_DECREF(obj); if (true) goto pop_1_error_tier_two; } - iter = (*getter)(obj); Py_DECREF(obj); if (iter == NULL) goto pop_1_error_tier_two; - if (Py_TYPE(iter)->tp_as_async == NULL || - Py_TYPE(iter)->tp_as_async->am_anext == NULL) { - + Py_TYPE(iter)->tp_as_async->am_anext == NULL) { _PyErr_Format(tstate, PyExc_TypeError, "'async for' received an object from __aiter__ " "that does not implement __anext__: %.100s", @@ -721,14 +765,13 @@ break; } - case GET_ANEXT: { + case _GET_ANEXT: { PyObject *aiter; PyObject *awaitable; aiter = stack_pointer[-1]; unaryfunc getter = NULL; PyObject *next_iter = NULL; PyTypeObject *type = Py_TYPE(aiter); - if (PyAsyncGen_CheckExact(aiter)) { awaitable = type->tp_as_async->am_anext(aiter); if (awaitable == NULL) { @@ -738,7 +781,6 @@ if (type->tp_as_async != NULL){ getter = type->tp_as_async->am_anext; } - if (getter != NULL) { next_iter = (*getter)(aiter); if (next_iter == NULL) { @@ -752,7 +794,6 @@ type->tp_name); GOTO_ERROR(error); } - awaitable = _PyCoro_GetAwaitableIter(next_iter); if (awaitable == NULL) { _PyErr_FormatFromCause( @@ -760,31 +801,27 @@ "'async for' received an invalid object " "from __anext__: %.100s", Py_TYPE(next_iter)->tp_name); - Py_DECREF(next_iter); GOTO_ERROR(error); } else { Py_DECREF(next_iter); } } - STACK_GROW(1); - stack_pointer[-1] = awaitable; + stack_pointer[0] = awaitable; + stack_pointer += 1; break; } - case GET_AWAITABLE: { - oparg = CURRENT_OPARG(); + case _GET_AWAITABLE: { PyObject *iterable; PyObject *iter; + oparg = CURRENT_OPARG(); iterable = stack_pointer[-1]; iter = _PyCoro_GetAwaitableIter(iterable); - if (iter == NULL) { _PyEval_FormatAwaitableError(tstate, Py_TYPE(iterable), oparg); } - Py_DECREF(iterable); - if (iter != NULL && PyCoro_CheckExact(iter)) { PyObject *yf = _PyGen_yf((PyGenObject*)iter); if (yf != NULL) { @@ -798,30 +835,62 @@ /* The code below jumps to `error` if `iter` is NULL. */ } } - if (iter == NULL) goto pop_1_error_tier_two; stack_pointer[-1] = iter; break; } - case POP_EXCEPT: { + /* _SEND is not a viable micro-op for tier 2 */ + + /* _SEND_GEN is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_YIELD_VALUE is not a viable micro-op for tier 2 */ + + case _YIELD_VALUE: { + PyObject *retval; + oparg = CURRENT_OPARG(); + retval = stack_pointer[-1]; + // NOTE: It's important that YIELD_VALUE never raises an exception! + // The compiler treats any exception raised here as a failed close() + // or throw() call. + assert(frame != &entry_frame); + frame->instr_ptr = next_instr; + PyGenObject *gen = _PyFrame_GetGenerator(frame); + assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1); + assert(oparg == 0 || oparg == 1); + gen->gi_frame_state = FRAME_SUSPENDED + oparg; + _PyFrame_SetStackPointer(frame, stack_pointer - 1); + tstate->exc_info = gen->gi_exc_state.previous_item; + gen->gi_exc_state.previous_item = NULL; + _Py_LeaveRecursiveCallPy(tstate); + _PyInterpreterFrame *gen_frame = frame; + frame = tstate->current_frame = frame->previous; + gen_frame->previous = NULL; + _PyFrame_StackPush(frame, retval); + /* We don't know which of these is relevant here, so keep them equal */ + assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER); + LOAD_IP(1 + INLINE_CACHE_ENTRIES_SEND); + goto resume_frame; + } + + case _POP_EXCEPT: { PyObject *exc_value; exc_value = stack_pointer[-1]; _PyErr_StackItem *exc_info = tstate->exc_info; Py_XSETREF(exc_info->exc_value, exc_value); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case LOAD_ASSERTION_ERROR: { + case _LOAD_ASSERTION_ERROR: { PyObject *value; value = Py_NewRef(PyExc_AssertionError); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case LOAD_BUILD_CLASS: { + case _LOAD_BUILD_CLASS: { PyObject *bc; if (PyMapping_GetOptionalItem(BUILTINS(), &_Py_ID(__build_class__), &bc) < 0) goto error_tier_two; if (bc == NULL) { @@ -829,14 +898,14 @@ "__build_class__ not found"); if (true) goto error_tier_two; } - STACK_GROW(1); - stack_pointer[-1] = bc; + stack_pointer[0] = bc; + stack_pointer += 1; break; } - case STORE_NAME: { - oparg = CURRENT_OPARG(); + case _STORE_NAME: { PyObject *v; + oparg = CURRENT_OPARG(); v = stack_pointer[-1]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); PyObject *ns = LOCALS(); @@ -848,16 +917,16 @@ if (true) goto pop_1_error_tier_two; } if (PyDict_CheckExact(ns)) - err = PyDict_SetItem(ns, name, v); + err = PyDict_SetItem(ns, name, v); else - err = PyObject_SetItem(ns, name, v); + err = PyObject_SetItem(ns, name, v); Py_DECREF(v); if (err) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } - case DELETE_NAME: { + case _DELETE_NAME: { oparg = CURRENT_OPARG(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); PyObject *ns = LOCALS(); @@ -871,99 +940,95 @@ // Can't use ERROR_IF here. if (err != 0) { _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, - name); + NAME_ERROR_MSG, + name); GOTO_ERROR(error); } break; } case _UNPACK_SEQUENCE: { - oparg = CURRENT_OPARG(); PyObject *seq; + oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; PyObject **top = stack_pointer + oparg - 1; int res = _PyEval_UnpackIterable(tstate, seq, oparg, -1, top); Py_DECREF(seq); if (res == 0) goto pop_1_error_tier_two; - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; break; } - case UNPACK_SEQUENCE_TWO_TUPLE: { - oparg = CURRENT_OPARG(); + case _UNPACK_SEQUENCE_TWO_TUPLE: { PyObject *seq; PyObject **values; + oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; - values = stack_pointer - 1; - DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); - DEOPT_IF(PyTuple_GET_SIZE(seq) != 2, UNPACK_SEQUENCE); + values = &stack_pointer[-1]; + if (!PyTuple_CheckExact(seq)) goto deoptimize; + if (PyTuple_GET_SIZE(seq) != 2) goto deoptimize; assert(oparg == 2); STAT_INC(UNPACK_SEQUENCE, hit); values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; break; } - case UNPACK_SEQUENCE_TUPLE: { - oparg = CURRENT_OPARG(); + case _UNPACK_SEQUENCE_TUPLE: { PyObject *seq; PyObject **values; + oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; - values = stack_pointer - 1; - DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); - DEOPT_IF(PyTuple_GET_SIZE(seq) != oparg, UNPACK_SEQUENCE); + values = &stack_pointer[-1]; + if (!PyTuple_CheckExact(seq)) goto deoptimize; + if (PyTuple_GET_SIZE(seq) != oparg) goto deoptimize; STAT_INC(UNPACK_SEQUENCE, hit); PyObject **items = _PyTuple_ITEMS(seq); for (int i = oparg; --i >= 0; ) { *values++ = Py_NewRef(items[i]); } Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; break; } - case UNPACK_SEQUENCE_LIST: { - oparg = CURRENT_OPARG(); + case _UNPACK_SEQUENCE_LIST: { PyObject *seq; PyObject **values; + oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; - values = stack_pointer - 1; - DEOPT_IF(!PyList_CheckExact(seq), UNPACK_SEQUENCE); - DEOPT_IF(PyList_GET_SIZE(seq) != oparg, UNPACK_SEQUENCE); + values = &stack_pointer[-1]; + if (!PyList_CheckExact(seq)) goto deoptimize; + if (PyList_GET_SIZE(seq) != oparg) goto deoptimize; STAT_INC(UNPACK_SEQUENCE, hit); PyObject **items = _PyList_ITEMS(seq); for (int i = oparg; --i >= 0; ) { *values++ = Py_NewRef(items[i]); } Py_DECREF(seq); - STACK_SHRINK(1); - STACK_GROW(oparg); + stack_pointer += -1 + oparg; break; } - case UNPACK_EX: { - oparg = CURRENT_OPARG(); + case _UNPACK_EX: { PyObject *seq; + oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; int totalargs = 1 + (oparg & 0xFF) + (oparg >> 8); PyObject **top = stack_pointer + totalargs - 1; int res = _PyEval_UnpackIterable(tstate, seq, oparg & 0xFF, oparg >> 8, top); Py_DECREF(seq); if (res == 0) goto pop_1_error_tier_two; - STACK_GROW((oparg & 0xFF) + (oparg >> 8)); + stack_pointer += (oparg >> 8) + (oparg & 0xFF); break; } case _STORE_ATTR: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *v; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; v = stack_pointer[-2]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -971,35 +1036,35 @@ Py_DECREF(v); Py_DECREF(owner); if (err) goto pop_2_error_tier_two; - STACK_SHRINK(2); + stack_pointer += -2; break; } - case DELETE_ATTR: { - oparg = CURRENT_OPARG(); + case _DELETE_ATTR: { PyObject *owner; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err = PyObject_DelAttr(owner, name); Py_DECREF(owner); if (err) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } - case STORE_GLOBAL: { - oparg = CURRENT_OPARG(); + case _STORE_GLOBAL: { PyObject *v; + oparg = CURRENT_OPARG(); v = stack_pointer[-1]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err = PyDict_SetItem(GLOBALS(), name, v); Py_DECREF(v); if (err) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } - case DELETE_GLOBAL: { + case _DELETE_GLOBAL: { oparg = CURRENT_OPARG(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err; @@ -1008,14 +1073,14 @@ if (err != 0) { if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + NAME_ERROR_MSG, name); } GOTO_ERROR(error); } break; } - case LOAD_LOCALS: { + case _LOAD_LOCALS: { PyObject *locals; locals = LOCALS(); if (locals == NULL) { @@ -1024,15 +1089,15 @@ if (true) goto error_tier_two; } Py_INCREF(locals); - STACK_GROW(1); - stack_pointer[-1] = locals; + stack_pointer[0] = locals; + stack_pointer += 1; break; } - case LOAD_FROM_DICT_OR_GLOBALS: { - oparg = CURRENT_OPARG(); + case _LOAD_FROM_DICT_OR_GLOBALS: { PyObject *mod_or_class_dict; PyObject *v; + oparg = CURRENT_OPARG(); mod_or_class_dict = stack_pointer[-1]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); if (PyMapping_GetOptionalItem(mod_or_class_dict, name, &v) < 0) { @@ -1048,8 +1113,8 @@ } if (v == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } } @@ -1059,9 +1124,9 @@ break; } - case LOAD_NAME: { - oparg = CURRENT_OPARG(); + case _LOAD_NAME: { PyObject *v; + oparg = CURRENT_OPARG(); PyObject *mod_or_class_dict = LOCALS(); if (mod_or_class_dict == NULL) { _PyErr_SetString(tstate, PyExc_SystemError, @@ -1082,34 +1147,34 @@ } if (v == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } } } - STACK_GROW(1); - stack_pointer[-1] = v; + stack_pointer[0] = v; + stack_pointer += 1; break; } case _LOAD_GLOBAL: { - oparg = CURRENT_OPARG(); PyObject *res; PyObject *null = NULL; + oparg = CURRENT_OPARG(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); if (PyDict_CheckExact(GLOBALS()) && PyDict_CheckExact(BUILTINS())) { res = _PyDict_LoadGlobal((PyDictObject *)GLOBALS(), - (PyDictObject *)BUILTINS(), - name); + (PyDictObject *)BUILTINS(), + name); if (res == NULL) { if (!_PyErr_Occurred(tstate)) { /* _PyDict_LoadGlobal() returns NULL without raising * an exception if the key doesn't exist */ _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + NAME_ERROR_MSG, name); } if (true) goto error_tier_two; } @@ -1124,25 +1189,24 @@ if (PyMapping_GetOptionalItem(BUILTINS(), name, &res) < 0) goto error_tier_two; if (res == NULL) { _PyEval_FormatExcCheckArg( - tstate, PyExc_NameError, - NAME_ERROR_MSG, name); + tstate, PyExc_NameError, + NAME_ERROR_MSG, name); if (true) goto error_tier_two; } } } null = NULL; - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); break; } case _GUARD_GLOBALS_VERSION: { uint16_t version = (uint16_t)CURRENT_OPERAND(); PyDictObject *dict = (PyDictObject *)GLOBALS(); - DEOPT_IF(!PyDict_CheckExact(dict), _GUARD_GLOBALS_VERSION); - DEOPT_IF(dict->ma_keys->dk_version != version, _GUARD_GLOBALS_VERSION); + if (!PyDict_CheckExact(dict)) goto deoptimize; + if (dict->ma_keys->dk_version != version) goto deoptimize; assert(DK_IS_UNICODE(dict->ma_keys)); break; } @@ -1150,51 +1214,49 @@ case _GUARD_BUILTINS_VERSION: { uint16_t version = (uint16_t)CURRENT_OPERAND(); PyDictObject *dict = (PyDictObject *)BUILTINS(); - DEOPT_IF(!PyDict_CheckExact(dict), _GUARD_BUILTINS_VERSION); - DEOPT_IF(dict->ma_keys->dk_version != version, _GUARD_BUILTINS_VERSION); + if (!PyDict_CheckExact(dict)) goto deoptimize; + if (dict->ma_keys->dk_version != version) goto deoptimize; assert(DK_IS_UNICODE(dict->ma_keys)); break; } case _LOAD_GLOBAL_MODULE: { - oparg = CURRENT_OPARG(); PyObject *res; PyObject *null = NULL; + oparg = CURRENT_OPARG(); uint16_t index = (uint16_t)CURRENT_OPERAND(); PyDictObject *dict = (PyDictObject *)GLOBALS(); PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(dict->ma_keys); res = entries[index].me_value; - DEOPT_IF(res == NULL, _LOAD_GLOBAL_MODULE); + if (res == NULL) goto deoptimize; Py_INCREF(res); STAT_INC(LOAD_GLOBAL, hit); null = NULL; - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); break; } case _LOAD_GLOBAL_BUILTINS: { - oparg = CURRENT_OPARG(); PyObject *res; PyObject *null = NULL; + oparg = CURRENT_OPARG(); uint16_t index = (uint16_t)CURRENT_OPERAND(); PyDictObject *bdict = (PyDictObject *)BUILTINS(); PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(bdict->ma_keys); res = entries[index].me_value; - DEOPT_IF(res == NULL, _LOAD_GLOBAL_BUILTINS); + if (res == NULL) goto deoptimize; Py_INCREF(res); STAT_INC(LOAD_GLOBAL, hit); null = NULL; - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[0] = res; + if (oparg & 1) stack_pointer[1] = null; + stack_pointer += 1 + ((oparg & 1)); break; } - case DELETE_FAST: { + case _DELETE_FAST: { oparg = CURRENT_OPARG(); PyObject *v = GETLOCAL(oparg); if (v == NULL) goto unbound_local_error_tier_two; @@ -1202,7 +1264,7 @@ break; } - case MAKE_CELL: { + case _MAKE_CELL: { oparg = CURRENT_OPARG(); // "initial" is probably NULL but not if it's an arg (or set // via PyFrame_LocalsToFast() before MAKE_CELL has run). @@ -1215,7 +1277,7 @@ break; } - case DELETE_DEREF: { + case _DELETE_DEREF: { oparg = CURRENT_OPARG(); PyObject *cell = GETLOCAL(oparg); PyObject *oldobj = PyCell_GET(cell); @@ -1230,10 +1292,10 @@ break; } - case LOAD_FROM_DICT_OR_DEREF: { - oparg = CURRENT_OPARG(); + case _LOAD_FROM_DICT_OR_DEREF: { PyObject *class_dict; PyObject *value; + oparg = CURRENT_OPARG(); class_dict = stack_pointer[-1]; PyObject *name; assert(class_dict); @@ -1256,9 +1318,9 @@ break; } - case LOAD_DEREF: { - oparg = CURRENT_OPARG(); + case _LOAD_DEREF: { PyObject *value; + oparg = CURRENT_OPARG(); PyObject *cell = GETLOCAL(oparg); value = PyCell_GET(cell); if (value == NULL) { @@ -1266,24 +1328,24 @@ if (true) goto error_tier_two; } Py_INCREF(value); - STACK_GROW(1); - stack_pointer[-1] = value; + stack_pointer[0] = value; + stack_pointer += 1; break; } - case STORE_DEREF: { - oparg = CURRENT_OPARG(); + case _STORE_DEREF: { PyObject *v; + oparg = CURRENT_OPARG(); v = stack_pointer[-1]; PyObject *cell = GETLOCAL(oparg); PyObject *oldobj = PyCell_GET(cell); PyCell_SET(cell, v); Py_XDECREF(oldobj); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case COPY_FREE_VARS: { + case _COPY_FREE_VARS: { oparg = CURRENT_OPARG(); /* Copy closure variables to free variables */ PyCodeObject *co = _PyFrame_GetCode(frame); @@ -1298,131 +1360,126 @@ break; } - case BUILD_STRING: { - oparg = CURRENT_OPARG(); + case _BUILD_STRING: { PyObject **pieces; PyObject *str; - pieces = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + pieces = &stack_pointer[-oparg]; str = _PyUnicode_JoinArray(&_Py_STR(empty), pieces, oparg); for (int _i = oparg; --_i >= 0;) { Py_DECREF(pieces[_i]); } - if (str == NULL) { STACK_SHRINK(oparg); goto error_tier_two; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = str; + if (str == NULL) { stack_pointer += -oparg; goto error_tier_two; } + stack_pointer[-oparg] = str; + stack_pointer += 1 - oparg; break; } - case BUILD_TUPLE: { - oparg = CURRENT_OPARG(); + case _BUILD_TUPLE: { PyObject **values; PyObject *tup; - values = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + values = &stack_pointer[-oparg]; tup = _PyTuple_FromArraySteal(values, oparg); - if (tup == NULL) { STACK_SHRINK(oparg); goto error_tier_two; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = tup; + if (tup == NULL) { stack_pointer += -oparg; goto error_tier_two; } + stack_pointer[-oparg] = tup; + stack_pointer += 1 - oparg; break; } - case BUILD_LIST: { - oparg = CURRENT_OPARG(); + case _BUILD_LIST: { PyObject **values; PyObject *list; - values = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + values = &stack_pointer[-oparg]; list = _PyList_FromArraySteal(values, oparg); - if (list == NULL) { STACK_SHRINK(oparg); goto error_tier_two; } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = list; + if (list == NULL) { stack_pointer += -oparg; goto error_tier_two; } + stack_pointer[-oparg] = list; + stack_pointer += 1 - oparg; break; } - case LIST_EXTEND: { - oparg = CURRENT_OPARG(); + case _LIST_EXTEND: { PyObject *iterable; PyObject *list; + oparg = CURRENT_OPARG(); iterable = stack_pointer[-1]; list = stack_pointer[-2 - (oparg-1)]; PyObject *none_val = _PyList_Extend((PyListObject *)list, iterable); if (none_val == NULL) { if (_PyErr_ExceptionMatches(tstate, PyExc_TypeError) && - (Py_TYPE(iterable)->tp_iter == NULL && !PySequence_Check(iterable))) + (Py_TYPE(iterable)->tp_iter == NULL && !PySequence_Check(iterable))) { _PyErr_Clear(tstate); _PyErr_Format(tstate, PyExc_TypeError, - "Value after * must be an iterable, not %.200s", - Py_TYPE(iterable)->tp_name); + "Value after * must be an iterable, not %.200s", + Py_TYPE(iterable)->tp_name); } Py_DECREF(iterable); if (true) goto pop_1_error_tier_two; } assert(Py_IsNone(none_val)); Py_DECREF(iterable); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case SET_UPDATE: { - oparg = CURRENT_OPARG(); + case _SET_UPDATE: { PyObject *iterable; PyObject *set; + oparg = CURRENT_OPARG(); iterable = stack_pointer[-1]; set = stack_pointer[-2 - (oparg-1)]; int err = _PySet_Update(set, iterable); Py_DECREF(iterable); if (err < 0) goto pop_1_error_tier_two; - STACK_SHRINK(1); + stack_pointer += -1; break; } - case BUILD_SET: { - oparg = CURRENT_OPARG(); + case _BUILD_SET: { PyObject **values; PyObject *set; - values = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + values = &stack_pointer[-oparg]; set = PySet_New(NULL); if (set == NULL) - GOTO_ERROR(error); + GOTO_ERROR(error); int err = 0; for (int i = 0; i < oparg; i++) { PyObject *item = values[i]; if (err == 0) - err = PySet_Add(set, item); + err = PySet_Add(set, item); Py_DECREF(item); } if (err != 0) { Py_DECREF(set); - if (true) { STACK_SHRINK(oparg); goto error_tier_two; } + if (true) { stack_pointer += -oparg; goto error_tier_two; } } - STACK_SHRINK(oparg); - STACK_GROW(1); - stack_pointer[-1] = set; + stack_pointer[-oparg] = set; + stack_pointer += 1 - oparg; break; } - case BUILD_MAP: { - oparg = CURRENT_OPARG(); + case _BUILD_MAP: { PyObject **values; PyObject *map; - values = stack_pointer - oparg*2; + oparg = CURRENT_OPARG(); + values = &stack_pointer[-oparg*2]; map = _PyDict_FromItems( - values, 2, - values+1, 2, - oparg); + values, 2, + values+1, 2, + oparg); for (int _i = oparg*2; --_i >= 0;) { Py_DECREF(values[_i]); } - if (map == NULL) { STACK_SHRINK(oparg*2); goto error_tier_two; } - STACK_SHRINK(oparg*2); - STACK_GROW(1); - stack_pointer[-1] = map; + if (map == NULL) { stack_pointer += -oparg*2; goto error_tier_two; } + stack_pointer[-oparg*2] = map; + stack_pointer += 1 - oparg*2; break; } - case SETUP_ANNOTATIONS: { + case _SETUP_ANNOTATIONS: { int err; PyObject *ann_dict; if (LOCALS() == NULL) { @@ -1446,13 +1503,13 @@ break; } - case BUILD_CONST_KEY_MAP: { - oparg = CURRENT_OPARG(); + case _BUILD_CONST_KEY_MAP: { PyObject *keys; PyObject **values; PyObject *map; + oparg = CURRENT_OPARG(); keys = stack_pointer[-1]; - values = stack_pointer - 1 - oparg; + values = &stack_pointer[-1 - oparg]; if (!PyTuple_CheckExact(keys) || PyTuple_GET_SIZE(keys) != (Py_ssize_t)oparg) { _PyErr_SetString(tstate, PyExc_SystemError, @@ -1460,43 +1517,43 @@ GOTO_ERROR(error); // Pop the keys and values. } map = _PyDict_FromItems( - &PyTuple_GET_ITEM(keys, 0), 1, - values, 1, oparg); + &PyTuple_GET_ITEM(keys, 0), 1, + values, 1, oparg); for (int _i = oparg; --_i >= 0;) { Py_DECREF(values[_i]); } Py_DECREF(keys); - if (map == NULL) { STACK_SHRINK(oparg); goto pop_1_error_tier_two; } - STACK_SHRINK(oparg); - stack_pointer[-1] = map; + if (map == NULL) { stack_pointer += -1 - oparg; goto error_tier_two; } + stack_pointer[-1 - oparg] = map; + stack_pointer += -oparg; break; } - case DICT_UPDATE: { - oparg = CURRENT_OPARG(); + case _DICT_UPDATE: { PyObject *update; PyObject *dict; + oparg = CURRENT_OPARG(); update = stack_pointer[-1]; dict = stack_pointer[-2 - (oparg - 1)]; if (PyDict_Update(dict, update) < 0) { if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) { _PyErr_Format(tstate, PyExc_TypeError, - "'%.200s' object is not a mapping", - Py_TYPE(update)->tp_name); + "'%.200s' object is not a mapping", + Py_TYPE(update)->tp_name); } Py_DECREF(update); if (true) goto pop_1_error_tier_two; } Py_DECREF(update); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case DICT_MERGE: { - oparg = CURRENT_OPARG(); + case _DICT_MERGE: { PyObject *update; PyObject *dict; PyObject *callable; + oparg = CURRENT_OPARG(); update = stack_pointer[-1]; dict = stack_pointer[-2 - (oparg - 1)]; callable = stack_pointer[-5 - (oparg - 1)]; @@ -1506,15 +1563,15 @@ if (true) goto pop_1_error_tier_two; } Py_DECREF(update); - STACK_SHRINK(1); + stack_pointer += -1; break; } - case MAP_ADD: { - oparg = CURRENT_OPARG(); + case _MAP_ADD: { PyObject *value; PyObject *key; PyObject *dict; + oparg = CURRENT_OPARG(); value = stack_pointer[-1]; key = stack_pointer[-2]; dict = stack_pointer[-3 - (oparg - 1)]; @@ -1522,22 +1579,24 @@ /* dict[key] = value */ // Do not DECREF INPUTS because the function steals the references if (_PyDict_SetItem_Take2((PyDictObject *)dict, key, value) != 0) goto pop_2_error_tier_two; - STACK_SHRINK(2); + stack_pointer += -2; break; } - case LOAD_SUPER_ATTR_ATTR: { - oparg = CURRENT_OPARG(); + /* _INSTRUMENTED_LOAD_SUPER_ATTR is not a viable micro-op for tier 2 */ + + case _LOAD_SUPER_ATTR_ATTR: { PyObject *self; PyObject *class; PyObject *global_super; PyObject *attr; + oparg = CURRENT_OPARG(); self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; assert(!(oparg & 1)); - DEOPT_IF(global_super != (PyObject *)&PySuper_Type, LOAD_SUPER_ATTR); - DEOPT_IF(!PyType_Check(class), LOAD_SUPER_ATTR); + if (global_super != (PyObject *)&PySuper_Type) goto deoptimize; + if (!PyType_Check(class)) goto deoptimize; STAT_INC(LOAD_SUPER_ATTR, hit); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 2); attr = _PySuper_Lookup((PyTypeObject *)class, self, name, NULL); @@ -1545,24 +1604,24 @@ Py_DECREF(class); Py_DECREF(self); if (attr == NULL) goto pop_3_error_tier_two; - STACK_SHRINK(2); - stack_pointer[-1] = attr; + stack_pointer[-3] = attr; + stack_pointer += -2 + (((0) ? 1 : 0)); break; } - case LOAD_SUPER_ATTR_METHOD: { - oparg = CURRENT_OPARG(); + case _LOAD_SUPER_ATTR_METHOD: { PyObject *self; PyObject *class; PyObject *global_super; PyObject *attr; PyObject *self_or_null; + oparg = CURRENT_OPARG(); self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; assert(oparg & 1); - DEOPT_IF(global_super != (PyObject *)&PySuper_Type, LOAD_SUPER_ATTR); - DEOPT_IF(!PyType_Check(class), LOAD_SUPER_ATTR); + if (global_super != (PyObject *)&PySuper_Type) goto deoptimize; + if (!PyType_Check(class)) goto deoptimize; STAT_INC(LOAD_SUPER_ATTR, hit); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 2); PyTypeObject *cls = (PyTypeObject *)class; @@ -1581,17 +1640,17 @@ Py_DECREF(self); self_or_null = NULL; } - STACK_SHRINK(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self_or_null; + stack_pointer[-3] = attr; + stack_pointer[-2] = self_or_null; + stack_pointer += -1; break; } case _LOAD_ATTR: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *self_or_null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); if (oparg & 1) { @@ -1611,7 +1670,7 @@ the second element of the stack to NULL, to signal CALL that it's not a method call. NULL | meth | arg1 | ... | argN - */ + */ Py_DECREF(owner); if (attr == NULL) goto pop_1_error_tier_two; self_or_null = NULL; @@ -1623,9 +1682,9 @@ Py_DECREF(owner); if (attr == NULL) goto pop_1_error_tier_two; } - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = self_or_null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = self_or_null; + stack_pointer += ((oparg & 1)); break; } @@ -1635,7 +1694,7 @@ uint32_t type_version = (uint32_t)CURRENT_OPERAND(); PyTypeObject *tp = Py_TYPE(owner); assert(type_version != 0); - DEOPT_IF(tp->tp_version_tag != type_version, _GUARD_TYPE_VERSION); + if (tp->tp_version_tag != type_version) goto deoptimize; break; } @@ -1645,27 +1704,27 @@ assert(Py_TYPE(owner)->tp_dictoffset < 0); assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner); - DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv), _CHECK_MANAGED_OBJECT_HAS_VALUES); + if (!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv)) goto deoptimize; break; } case _LOAD_ATTR_INSTANCE_VALUE: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); attr = _PyDictOrValues_GetValues(dorv)->values[index]; - DEOPT_IF(attr == NULL, _LOAD_ATTR_INSTANCE_VALUE); + if (attr == NULL) goto deoptimize; STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); null = NULL; Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); break; } @@ -1673,18 +1732,18 @@ PyObject *owner; owner = stack_pointer[-1]; uint32_t type_version = (uint32_t)CURRENT_OPERAND(); - DEOPT_IF(!PyModule_CheckExact(owner), _CHECK_ATTR_MODULE); + if (!PyModule_CheckExact(owner)) goto deoptimize; PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; assert(dict != NULL); - DEOPT_IF(dict->ma_keys->dk_version != type_version, _CHECK_ATTR_MODULE); + if (dict->ma_keys->dk_version != type_version) goto deoptimize; break; } case _LOAD_ATTR_MODULE: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; @@ -1692,14 +1751,14 @@ assert(index < dict->ma_keys->dk_nentries); PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + index; attr = ep->me_value; - DEOPT_IF(attr == NULL, _LOAD_ATTR_MODULE); + if (attr == NULL) goto deoptimize; STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); null = NULL; Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); break; } @@ -1708,62 +1767,62 @@ owner = stack_pointer[-1]; assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - DEOPT_IF(_PyDictOrValues_IsValues(dorv), _CHECK_ATTR_WITH_HINT); + if (_PyDictOrValues_IsValues(dorv)) goto deoptimize; PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(dorv); - DEOPT_IF(dict == NULL, _CHECK_ATTR_WITH_HINT); + if (dict == NULL) goto deoptimize; assert(PyDict_CheckExact((PyObject *)dict)); break; } case _LOAD_ATTR_WITH_HINT: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; uint16_t hint = (uint16_t)CURRENT_OPERAND(); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(dorv); - DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, _LOAD_ATTR_WITH_HINT); + if (hint >= (size_t)dict->ma_keys->dk_nentries) goto deoptimize; PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); if (DK_IS_UNICODE(dict->ma_keys)) { PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint; - DEOPT_IF(ep->me_key != name, _LOAD_ATTR_WITH_HINT); + if (ep->me_key != name) goto deoptimize; attr = ep->me_value; } else { PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + hint; - DEOPT_IF(ep->me_key != name, _LOAD_ATTR_WITH_HINT); + if (ep->me_key != name) goto deoptimize; attr = ep->me_value; } - DEOPT_IF(attr == NULL, _LOAD_ATTR_WITH_HINT); + if (attr == NULL) goto deoptimize; STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); null = NULL; Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); break; } case _LOAD_ATTR_SLOT: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); char *addr = (char *)owner + index; attr = *(PyObject **)addr; - DEOPT_IF(attr == NULL, _LOAD_ATTR_SLOT); + if (attr == NULL) goto deoptimize; STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); null = NULL; Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); break; } @@ -1771,36 +1830,40 @@ PyObject *owner; owner = stack_pointer[-1]; uint32_t type_version = (uint32_t)CURRENT_OPERAND(); - DEOPT_IF(!PyType_Check(owner), _CHECK_ATTR_CLASS); + if (!PyType_Check(owner)) goto deoptimize; assert(type_version != 0); - DEOPT_IF(((PyTypeObject *)owner)->tp_version_tag != type_version, _CHECK_ATTR_CLASS); + if (((PyTypeObject *)owner)->tp_version_tag != type_version) goto deoptimize; break; } case _LOAD_ATTR_CLASS: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; PyObject *null = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); attr = Py_NewRef(descr); null = NULL; Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = attr; - if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; } + stack_pointer[-1] = attr; + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += ((oparg & 1)); break; } + /* _LOAD_ATTR_PROPERTY is not a viable micro-op for tier 2 */ + + /* _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN is not a viable micro-op for tier 2 */ + case _GUARD_DORV_VALUES: { PyObject *owner; owner = stack_pointer[-1]; assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - DEOPT_IF(!_PyDictOrValues_IsValues(dorv), _GUARD_DORV_VALUES); + if (!_PyDictOrValues_IsValues(dorv)) goto deoptimize; break; } @@ -1822,10 +1885,12 @@ Py_DECREF(old_value); } Py_DECREF(owner); - STACK_SHRINK(2); + stack_pointer += -2; break; } + /* _STORE_ATTR_WITH_HINT is not a viable micro-op for tier 2 */ + case _STORE_ATTR_SLOT: { PyObject *owner; PyObject *value; @@ -1838,15 +1903,15 @@ *(PyObject **)addr = value; Py_XDECREF(old_value); Py_DECREF(owner); - STACK_SHRINK(2); + stack_pointer += -2; break; } case _COMPARE_OP: { - oparg = CURRENT_OPARG(); PyObject *right; PyObject *left; PyObject *res; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; assert((oparg >> 5) <= Py_GE); @@ -1860,20 +1925,20 @@ if (res_bool < 0) goto pop_2_error_tier_two; res = res_bool ? Py_True : Py_False; } - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case COMPARE_OP_FLOAT: { - oparg = CURRENT_OPARG(); + case _COMPARE_OP_FLOAT: { PyObject *right; PyObject *left; PyObject *res; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyFloat_CheckExact(right), COMPARE_OP); + if (!PyFloat_CheckExact(left)) goto deoptimize; + if (!PyFloat_CheckExact(right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); double dleft = PyFloat_AS_DOUBLE(left); double dright = PyFloat_AS_DOUBLE(right); @@ -1883,22 +1948,22 @@ _Py_DECREF_SPECIALIZED(right, _PyFloat_ExactDealloc); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case COMPARE_OP_INT: { - oparg = CURRENT_OPARG(); + case _COMPARE_OP_INT: { PyObject *right; PyObject *left; PyObject *res; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyLong_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyLong_CheckExact(right), COMPARE_OP); - DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)left), COMPARE_OP); - DEOPT_IF(!_PyLong_IsCompact((PyLongObject *)right), COMPARE_OP); + if (!PyLong_CheckExact(left)) goto deoptimize; + if (!PyLong_CheckExact(right)) goto deoptimize; + if (!_PyLong_IsCompact((PyLongObject *)left)) goto deoptimize; + if (!_PyLong_IsCompact((PyLongObject *)right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); assert(_PyLong_DigitCount((PyLongObject *)left) <= 1 && _PyLong_DigitCount((PyLongObject *)right) <= 1); @@ -1910,20 +1975,20 @@ _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); res = (sign_ish & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case COMPARE_OP_STR: { - oparg = CURRENT_OPARG(); + case _COMPARE_OP_STR: { PyObject *right; PyObject *left; PyObject *res; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; - DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); - DEOPT_IF(!PyUnicode_CheckExact(right), COMPARE_OP); + if (!PyUnicode_CheckExact(left)) goto deoptimize; + if (!PyUnicode_CheckExact(right)) goto deoptimize; STAT_INC(COMPARE_OP, hit); int eq = _PyUnicode_Equal(left, right); assert((oparg >> 5) == Py_EQ || (oparg >> 5) == Py_NE); @@ -1934,32 +1999,32 @@ assert(COMPARISON_NOT_EQUALS + 1 == COMPARISON_EQUALS); res = ((COMPARISON_NOT_EQUALS + eq) & oparg) ? Py_True : Py_False; // It's always a bool, so we don't care about oparg & 16. - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case IS_OP: { - oparg = CURRENT_OPARG(); + case _IS_OP: { PyObject *right; PyObject *left; PyObject *b; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; int res = Py_Is(left, right) ^ oparg; Py_DECREF(left); Py_DECREF(right); b = res ? Py_True : Py_False; - STACK_SHRINK(1); - stack_pointer[-1] = b; + stack_pointer[-2] = b; + stack_pointer += -1; break; } - case CONTAINS_OP: { - oparg = CURRENT_OPARG(); + case _CONTAINS_OP: { PyObject *right; PyObject *left; PyObject *b; + oparg = CURRENT_OPARG(); right = stack_pointer[-1]; left = stack_pointer[-2]; int res = PySequence_Contains(right, left); @@ -1967,12 +2032,12 @@ Py_DECREF(right); if (res < 0) goto pop_2_error_tier_two; b = (res ^ oparg) ? Py_True : Py_False; - STACK_SHRINK(1); - stack_pointer[-1] = b; + stack_pointer[-2] = b; + stack_pointer += -1; break; } - case CHECK_EG_MATCH: { + case _CHECK_EG_MATCH: { PyObject *match_type; PyObject *exc_value; PyObject *rest; @@ -1984,18 +2049,15 @@ Py_DECREF(match_type); if (true) goto pop_2_error_tier_two; } - match = NULL; rest = NULL; int res = _PyEval_ExceptionGroupMatch(exc_value, match_type, - &match, &rest); + &match, &rest); Py_DECREF(exc_value); Py_DECREF(match_type); if (res < 0) goto pop_2_error_tier_two; - assert((match == NULL) == (rest == NULL)); if (match == NULL) goto pop_2_error_tier_two; - if (!Py_IsNone(match)) { PyErr_SetHandledException(match); } @@ -2004,7 +2066,7 @@ break; } - case CHECK_EXC_MATCH: { + case _CHECK_EXC_MATCH: { PyObject *right; PyObject *left; PyObject *b; @@ -2012,10 +2074,9 @@ left = stack_pointer[-2]; assert(PyExceptionInstance_Check(left)); if (_PyEval_CheckExceptTypeValid(tstate, right) < 0) { - Py_DECREF(right); - if (true) goto pop_1_error_tier_two; + Py_DECREF(right); + if (true) goto pop_1_error_tier_two; } - int res = PyErr_GivenExceptionMatches(left, right); Py_DECREF(right); b = res ? Py_True : Py_False; @@ -2023,6 +2084,18 @@ break; } + case _JUMP_FORWARD: { + oparg = CURRENT_OPARG(); + JUMPBY(oparg); + break; + } + + /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ + + /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ + + /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ + case _IS_NONE: { PyObject *value; PyObject *b; @@ -2038,7 +2111,18 @@ break; } - case GET_LEN: { + case _JUMP_BACKWARD_NO_INTERRUPT: { + oparg = CURRENT_OPARG(); + /* This bytecode is used in the `yield from` or `await` loop. + * If there is an interrupt, we want it handled in the innermost + * generator or coroutine, so we deliberately do not check it here. + * (see bpo-30039). + */ + JUMPBY(-oparg); + break; + } + + case _GET_LEN: { PyObject *obj; PyObject *len_o; obj = stack_pointer[-1]; @@ -2047,17 +2131,17 @@ if (len_i < 0) goto error_tier_two; len_o = PyLong_FromSsize_t(len_i); if (len_o == NULL) goto error_tier_two; - STACK_GROW(1); - stack_pointer[-1] = len_o; + stack_pointer[0] = len_o; + stack_pointer += 1; break; } - case MATCH_CLASS: { - oparg = CURRENT_OPARG(); + case _MATCH_CLASS: { PyObject *names; PyObject *type; PyObject *subject; PyObject *attrs; + oparg = CURRENT_OPARG(); names = stack_pointer[-1]; type = stack_pointer[-2]; subject = stack_pointer[-3]; @@ -2073,36 +2157,37 @@ } else { if (_PyErr_Occurred(tstate)) goto pop_3_error_tier_two; + // Error! attrs = Py_None; // Failure! } - STACK_SHRINK(2); - stack_pointer[-1] = attrs; + stack_pointer[-3] = attrs; + stack_pointer += -2; break; } - case MATCH_MAPPING: { + case _MATCH_MAPPING: { PyObject *subject; PyObject *res; subject = stack_pointer[-1]; int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING; res = match ? Py_True : Py_False; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case MATCH_SEQUENCE: { + case _MATCH_SEQUENCE: { PyObject *subject; PyObject *res; subject = stack_pointer[-1]; int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE; res = match ? Py_True : Py_False; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case MATCH_KEYS: { + case _MATCH_KEYS: { PyObject *keys; PyObject *subject; PyObject *values_or_none; @@ -2111,12 +2196,12 @@ // On successful match, PUSH(values). Otherwise, PUSH(None). values_or_none = _PyEval_MatchKeys(tstate, subject, keys); if (values_or_none == NULL) goto error_tier_two; - STACK_GROW(1); - stack_pointer[-1] = values_or_none; + stack_pointer[0] = values_or_none; + stack_pointer += 1; break; } - case GET_ITER: { + case _GET_ITER: { PyObject *iterable; PyObject *iter; iterable = stack_pointer[-1]; @@ -2128,7 +2213,7 @@ break; } - case GET_YIELD_FROM_ITER: { + case _GET_YIELD_FROM_ITER: { PyObject *iterable; PyObject *iter; iterable = stack_pointer[-1]; @@ -2160,6 +2245,8 @@ break; } + /* _FOR_ITER is not a viable micro-op for tier 2 */ + case _FOR_ITER_TIER_TWO: { PyObject *iter; PyObject *next; @@ -2177,29 +2264,33 @@ Py_DECREF(iter); STACK_SHRINK(1); /* The translator sets the deopt target just past END_FOR */ - DEOPT_IF(true, _FOR_ITER_TIER_TWO); + if (true) goto deoptimize; } // Common case: no jump, leave it to the code generator - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; break; } + /* _INSTRUMENTED_FOR_ITER is not a viable micro-op for tier 2 */ + case _ITER_CHECK_LIST: { PyObject *iter; iter = stack_pointer[-1]; - DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, _ITER_CHECK_LIST); + if (Py_TYPE(iter) != &PyListIter_Type) goto deoptimize; break; } + /* _ITER_JUMP_LIST is not a viable micro-op for tier 2 */ + case _GUARD_NOT_EXHAUSTED_LIST: { PyObject *iter; iter = stack_pointer[-1]; _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; - DEOPT_IF(seq == NULL, _GUARD_NOT_EXHAUSTED_LIST); - DEOPT_IF(it->it_index >= PyList_GET_SIZE(seq), _GUARD_NOT_EXHAUSTED_LIST); + if (seq == NULL) goto deoptimize; + if (it->it_index >= PyList_GET_SIZE(seq)) goto deoptimize; break; } @@ -2213,26 +2304,28 @@ assert(seq); assert(it->it_index < PyList_GET_SIZE(seq)); next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; break; } case _ITER_CHECK_TUPLE: { PyObject *iter; iter = stack_pointer[-1]; - DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, _ITER_CHECK_TUPLE); + if (Py_TYPE(iter) != &PyTupleIter_Type) goto deoptimize; break; } + /* _ITER_JUMP_TUPLE is not a viable micro-op for tier 2 */ + case _GUARD_NOT_EXHAUSTED_TUPLE: { PyObject *iter; iter = stack_pointer[-1]; _PyTupleIterObject *it = (_PyTupleIterObject *)iter; assert(Py_TYPE(iter) == &PyTupleIter_Type); PyTupleObject *seq = it->it_seq; - DEOPT_IF(seq == NULL, _GUARD_NOT_EXHAUSTED_TUPLE); - DEOPT_IF(it->it_index >= PyTuple_GET_SIZE(seq), _GUARD_NOT_EXHAUSTED_TUPLE); + if (seq == NULL) goto deoptimize; + if (it->it_index >= PyTuple_GET_SIZE(seq)) goto deoptimize; break; } @@ -2246,8 +2339,8 @@ assert(seq); assert(it->it_index < PyTuple_GET_SIZE(seq)); next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; break; } @@ -2255,16 +2348,18 @@ PyObject *iter; iter = stack_pointer[-1]; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; - DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, _ITER_CHECK_RANGE); + if (Py_TYPE(r) != &PyRangeIter_Type) goto deoptimize; break; } + /* _ITER_JUMP_RANGE is not a viable micro-op for tier 2 */ + case _GUARD_NOT_EXHAUSTED_RANGE: { PyObject *iter; iter = stack_pointer[-1]; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; assert(Py_TYPE(r) == &PyRangeIter_Type); - DEOPT_IF(r->len <= 0, _GUARD_NOT_EXHAUSTED_RANGE); + if (r->len <= 0) goto deoptimize; break; } @@ -2280,12 +2375,14 @@ r->len--; next = PyLong_FromLong(value); if (next == NULL) goto error_tier_two; - STACK_GROW(1); - stack_pointer[-1] = next; + stack_pointer[0] = next; + stack_pointer += 1; break; } - case BEFORE_ASYNC_WITH: { + /* _FOR_ITER_GEN is not a viable micro-op for tier 2 */ + + case _BEFORE_ASYNC_WITH: { PyObject *mgr; PyObject *exit; PyObject *res; @@ -2319,13 +2416,13 @@ Py_DECREF(exit); if (true) goto pop_1_error_tier_two; } - STACK_GROW(1); - stack_pointer[-2] = exit; - stack_pointer[-1] = res; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case BEFORE_WITH: { + case _BEFORE_WITH: { PyObject *mgr; PyObject *exit; PyObject *res; @@ -2362,13 +2459,13 @@ Py_DECREF(exit); if (true) goto pop_1_error_tier_two; } - STACK_GROW(1); - stack_pointer[-2] = exit; - stack_pointer[-1] = res; + stack_pointer[-1] = exit; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case WITH_EXCEPT_START: { + case _WITH_EXCEPT_START: { PyObject *val; PyObject *lasti; PyObject *exit_func; @@ -2383,9 +2480,8 @@ - exit_func: FOURTH = the context.__exit__ bound method We call FOURTH(type(TOP), TOP, GetTraceback(TOP)). Then we push the __exit__ return value. - */ + */ PyObject *exc, *tb; - assert(val && PyExceptionInstance_Check(val)); exc = PyExceptionInstance_Class(val); tb = PyException_GetTraceback(val); @@ -2399,14 +2495,14 @@ (void)lasti; // Shut up compiler warning if asserts are off PyObject *stack[4] = {NULL, exc, val, tb}; res = PyObject_Vectorcall(exit_func, stack + 1, - 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); + 3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL); if (res == NULL) goto error_tier_two; - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; break; } - case PUSH_EXC_INFO: { + case _PUSH_EXC_INFO: { PyObject *new_exc; PyObject *prev_exc; new_exc = stack_pointer[-1]; @@ -2419,9 +2515,9 @@ } assert(PyExceptionInstance_Check(new_exc)); exc_info->exc_value = Py_NewRef(new_exc); - STACK_GROW(1); - stack_pointer[-2] = prev_exc; - stack_pointer[-1] = new_exc; + stack_pointer[-1] = prev_exc; + stack_pointer[0] = new_exc; + stack_pointer += 1; break; } @@ -2430,7 +2526,7 @@ owner = stack_pointer[-1]; assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(owner); - DEOPT_IF(!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv), _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT); + if (!_PyDictOrValues_IsValues(*dorv) && !_PyObject_MakeInstanceAttributesFromDict(owner, dorv)) goto deoptimize; break; } @@ -2440,17 +2536,17 @@ uint32_t keys_version = (uint32_t)CURRENT_OPERAND(); PyTypeObject *owner_cls = Py_TYPE(owner); PyHeapTypeObject *owner_heap_type = (PyHeapTypeObject *)owner_cls; - DEOPT_IF(owner_heap_type->ht_cached_keys->dk_version != keys_version, _GUARD_KEYS_VERSION); + if (owner_heap_type->ht_cached_keys->dk_version != keys_version) goto deoptimize; break; } case _LOAD_ATTR_METHOD_WITH_VALUES: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); /* Cached method object */ STAT_INC(LOAD_ATTR, hit); @@ -2458,19 +2554,19 @@ attr = Py_NewRef(descr); assert(_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)); self = owner; - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); break; } case _LOAD_ATTR_METHOD_NO_DICT: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); assert(Py_TYPE(owner)->tp_dictoffset == 0); STAT_INC(LOAD_ATTR, hit); @@ -2478,33 +2574,34 @@ assert(_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)); attr = Py_NewRef(descr); self = owner; - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); break; } case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); assert((oparg & 1) == 0); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; + stack_pointer += (((0) ? 1 : 0)); break; } case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); assert((oparg & 1) == 0); assert(Py_TYPE(owner)->tp_dictoffset == 0); STAT_INC(LOAD_ATTR, hit); @@ -2512,6 +2609,7 @@ Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; + stack_pointer += (((0) ? 1 : 0)); break; } @@ -2522,45 +2620,49 @@ assert(dictoffset > 0); PyObject *dict = *(PyObject **)((char *)owner + dictoffset); /* This object has a __dict__, just not yet created */ - DEOPT_IF(dict != NULL, _CHECK_ATTR_METHOD_LAZY_DICT); + if (dict != NULL) goto deoptimize; break; } case _LOAD_ATTR_METHOD_LAZY_DICT: { - oparg = CURRENT_OPARG(); PyObject *owner; PyObject *attr; - PyObject *self; + PyObject *self = NULL; + oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject *descr = (PyObject *)CURRENT_OPERAND(); + PyObject * descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); assert(_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)); attr = Py_NewRef(descr); self = owner; - STACK_GROW(1); - stack_pointer[-2] = attr; - stack_pointer[-1] = self; + stack_pointer[-1] = attr; + if (1) stack_pointer[0] = self; + stack_pointer += (((1) ? 1 : 0)); break; } + /* _INSTRUMENTED_CALL is not a viable micro-op for tier 2 */ + + /* _CALL is not a viable micro-op for tier 2 */ + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { - oparg = CURRENT_OPARG(); PyObject *null; PyObject *callable; + oparg = CURRENT_OPARG(); null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - DEOPT_IF(null != NULL, _CHECK_CALL_BOUND_METHOD_EXACT_ARGS); - DEOPT_IF(Py_TYPE(callable) != &PyMethod_Type, _CHECK_CALL_BOUND_METHOD_EXACT_ARGS); + if (null != NULL) goto deoptimize; + if (Py_TYPE(callable) != &PyMethod_Type) goto deoptimize; break; } case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { - oparg = CURRENT_OPARG(); PyObject *callable; PyObject *func; PyObject *self; + oparg = CURRENT_OPARG(); callable = stack_pointer[-2 - oparg]; STAT_INC(CALL, hit); self = Py_NewRef(((PyMethodObject *)callable)->im_self); @@ -2574,43 +2676,43 @@ } case _CHECK_PEP_523: { - DEOPT_IF(tstate->interp->eval_frame, _CHECK_PEP_523); + if (tstate->interp->eval_frame) goto deoptimize; break; } case _CHECK_FUNCTION_EXACT_ARGS: { - oparg = CURRENT_OPARG(); PyObject *self_or_null; PyObject *callable; + oparg = CURRENT_OPARG(); self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)CURRENT_OPERAND(); - DEOPT_IF(!PyFunction_Check(callable), _CHECK_FUNCTION_EXACT_ARGS); + if (!PyFunction_Check(callable)) goto deoptimize; PyFunctionObject *func = (PyFunctionObject *)callable; - DEOPT_IF(func->func_version != func_version, _CHECK_FUNCTION_EXACT_ARGS); + if (func->func_version != func_version) goto deoptimize; PyCodeObject *code = (PyCodeObject *)func->func_code; - DEOPT_IF(code->co_argcount != oparg + (self_or_null != NULL), _CHECK_FUNCTION_EXACT_ARGS); + if (code->co_argcount != oparg + (self_or_null != NULL)) goto deoptimize; break; } case _CHECK_STACK_SPACE: { - oparg = CURRENT_OPARG(); PyObject *callable; + oparg = CURRENT_OPARG(); callable = stack_pointer[-2 - oparg]; PyFunctionObject *func = (PyFunctionObject *)callable; PyCodeObject *code = (PyCodeObject *)func->func_code; - DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), _CHECK_STACK_SPACE); - DEOPT_IF(tstate->py_recursion_remaining <= 1, _CHECK_STACK_SPACE); + if (!_PyThreadState_HasStackSpace(tstate, code->co_framesize)) goto deoptimize; + if (tstate->py_recursion_remaining <= 1) goto deoptimize; break; } case _INIT_CALL_PY_EXACT_ARGS: { - oparg = CURRENT_OPARG(); PyObject **args; PyObject *self_or_null; PyObject *callable; _PyInterpreterFrame *new_frame; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int argcount = oparg; @@ -2624,129 +2726,130 @@ for (int i = 0; i < argcount; i++) { new_frame->localsplus[i] = args[i]; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = (PyObject *)new_frame; + stack_pointer[-2 - oparg] = (PyObject *)new_frame; + stack_pointer += -1 - oparg; break; } case _PUSH_FRAME: { _PyInterpreterFrame *new_frame; new_frame = (_PyInterpreterFrame *)stack_pointer[-1]; - STACK_SHRINK(1); // Write it out explicitly because it's subtly different. // Eventually this should be the only occurrence of this code. assert(tstate->interp->eval_frame == NULL); - STORE_SP(); + stack_pointer += -1; + _PyFrame_SetStackPointer(frame, stack_pointer); new_frame->previous = frame; CALL_STAT_INC(inlined_py_calls); frame = tstate->current_frame = new_frame; tstate->py_recursion_remaining--; LOAD_SP(); LOAD_IP(0); -#if LLTRACE && TIER_ONE + #if LLTRACE && TIER_ONE lltrace = maybe_lltrace_resume_frame(frame, &entry_frame, GLOBALS()); if (lltrace < 0) { goto exit_unwind; } -#endif + #endif + stack_pointer += (((0) ? 1 : 0)); break; } - case CALL_TYPE_1: { - oparg = CURRENT_OPARG(); + /* _CALL_PY_WITH_DEFAULTS is not a viable micro-op for tier 2 */ + + case _CALL_TYPE_1: { PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); - DEOPT_IF(null != NULL, CALL); + if (null != NULL) goto deoptimize; PyObject *obj = args[0]; - DEOPT_IF(callable != (PyObject *)&PyType_Type, CALL); + if (callable != (PyObject *)&PyType_Type) goto deoptimize; STAT_INC(CALL, hit); res = Py_NewRef(Py_TYPE(obj)); Py_DECREF(obj); Py_DECREF(&PyType_Type); // I.e., callable - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; break; } - case CALL_STR_1: { - oparg = CURRENT_OPARG(); + case _CALL_STR_1: { PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); - DEOPT_IF(null != NULL, CALL); - DEOPT_IF(callable != (PyObject *)&PyUnicode_Type, CALL); + if (null != NULL) goto deoptimize; + if (callable != (PyObject *)&PyUnicode_Type) goto deoptimize; STAT_INC(CALL, hit); PyObject *arg = args[0]; res = PyObject_Str(arg); Py_DECREF(arg); Py_DECREF(&PyUnicode_Type); // I.e., callable - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_TUPLE_1: { - oparg = CURRENT_OPARG(); + case _CALL_TUPLE_1: { PyObject **args; PyObject *null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 1); - DEOPT_IF(null != NULL, CALL); - DEOPT_IF(callable != (PyObject *)&PyTuple_Type, CALL); + if (null != NULL) goto deoptimize; + if (callable != (PyObject *)&PyTuple_Type) goto deoptimize; STAT_INC(CALL, hit); PyObject *arg = args[0]; res = PySequence_Tuple(arg); Py_DECREF(arg); Py_DECREF(&PyTuple_Type); // I.e., tuple - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case EXIT_INIT_CHECK: { + /* _CALL_ALLOC_AND_ENTER_INIT is not a viable micro-op for tier 2 */ + + case _EXIT_INIT_CHECK: { PyObject *should_be_none; should_be_none = stack_pointer[-1]; assert(STACK_LEVEL() == 2); if (should_be_none != Py_None) { PyErr_Format(PyExc_TypeError, - "__init__() should return None, not '%.200s'", - Py_TYPE(should_be_none)->tp_name); + "__init__() should return None, not '%.200s'", + Py_TYPE(should_be_none)->tp_name); GOTO_ERROR(error); } - STACK_SHRINK(1); + stack_pointer += -1; break; } - case CALL_BUILTIN_CLASS: { - oparg = CURRENT_OPARG(); + case _CALL_BUILTIN_CLASS: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -2754,9 +2857,9 @@ args--; total_args++; } - DEOPT_IF(!PyType_Check(callable), CALL); + if (!PyType_Check(callable)) goto deoptimize; PyTypeObject *tp = (PyTypeObject *)callable; - DEOPT_IF(tp->tp_vectorcall == NULL, CALL); + if (tp->tp_vectorcall == NULL) goto deoptimize; STAT_INC(CALL, hit); res = tp->tp_vectorcall((PyObject *)tp, args, total_args, NULL); /* Free the arguments. */ @@ -2764,21 +2867,20 @@ Py_DECREF(args[i]); } Py_DECREF(tp); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_BUILTIN_O: { - oparg = CURRENT_OPARG(); + case _CALL_BUILTIN_O: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_O functions */ @@ -2787,9 +2889,9 @@ args--; total_args++; } - DEOPT_IF(total_args != 1, CALL); - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL); + if (total_args != 1) goto deoptimize; + if (!PyCFunction_CheckExact(callable)) goto deoptimize; + if (PyCFunction_GET_FLAGS(callable) != METH_O) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); // This is slower but CPython promises to check all non-vectorcall @@ -2801,24 +2903,22 @@ res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(arg); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_BUILTIN_FAST: { - oparg = CURRENT_OPARG(); + case _CALL_BUILTIN_FAST: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_FASTCALL functions, without keywords */ @@ -2827,8 +2927,8 @@ args--; total_args++; } - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL, CALL); + if (!PyCFunction_CheckExact(callable)) goto deoptimize; + if (PyCFunction_GET_FLAGS(callable) != METH_FASTCALL) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); /* res = func(self, args, nargs) */ @@ -2837,32 +2937,30 @@ args, total_args); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - /* Not deopting because this doesn't mean our optimization was - wrong. `res` can be NULL for valid reasons. Eg. getattr(x, - 'invalid'). In those cases an exception is set, so we must - handle it. - */ - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + /* Not deopting because this doesn't mean our optimization was + wrong. `res` can be NULL for valid reasons. Eg. getattr(x, + 'invalid'). In those cases an exception is set, so we must + handle it. + */ + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_BUILTIN_FAST_WITH_KEYWORDS: { - oparg = CURRENT_OPARG(); + case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ @@ -2871,36 +2969,34 @@ args--; total_args++; } - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS), CALL); + if (!PyCFunction_CheckExact(callable)) goto deoptimize; + if (PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS)) goto deoptimize; STAT_INC(CALL, hit); /* res = func(self, args, nargs, kwnames) */ _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable); + (_PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunction_GET_FUNCTION(callable); res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_LEN: { - oparg = CURRENT_OPARG(); + case _CALL_LEN: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* len(o) */ @@ -2909,9 +3005,9 @@ args--; total_args++; } - DEOPT_IF(total_args != 1, CALL); + if (total_args != 1) goto deoptimize; PyInterpreterState *interp = tstate->interp; - DEOPT_IF(callable != interp->callable_cache.len, CALL); + if (callable != interp->callable_cache.len) goto deoptimize; STAT_INC(CALL, hit); PyObject *arg = args[0]; Py_ssize_t len_i = PyObject_Length(arg); @@ -2920,23 +3016,21 @@ } res = PyLong_FromSsize_t(len_i); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(callable); Py_DECREF(arg); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; break; } - case CALL_ISINSTANCE: { - oparg = CURRENT_OPARG(); + case _CALL_ISINSTANCE: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; /* isinstance(o, o2) */ @@ -2945,9 +3039,9 @@ args--; total_args++; } - DEOPT_IF(total_args != 2, CALL); + if (total_args != 2) goto deoptimize; PyInterpreterState *interp = tstate->interp; - DEOPT_IF(callable != interp->callable_cache.isinstance, CALL); + if (callable != interp->callable_cache.isinstance) goto deoptimize; STAT_INC(CALL, hit); PyObject *cls = args[1]; PyObject *inst = args[0]; @@ -2957,24 +3051,48 @@ } res = PyBool_FromLong(retval); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(inst); Py_DECREF(cls); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; break; } - case CALL_METHOD_DESCRIPTOR_O: { + case _CALL_LIST_APPEND: { + PyObject **args; + PyObject *self; + PyObject *callable; oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; + self = stack_pointer[-1 - oparg]; + callable = stack_pointer[-2 - oparg]; + assert(oparg == 1); + PyInterpreterState *interp = tstate->interp; + if (callable != interp->callable_cache.list_append) goto deoptimize; + assert(self != NULL); + if (!PyList_Check(self)) goto deoptimize; + STAT_INC(CALL, hit); + if (_PyList_AppendTakeRef((PyListObject *)self, args[0]) < 0) { + goto pop_1_error; // Since arg is DECREF'ed already + } + Py_DECREF(self); + Py_DECREF(callable); + STACK_SHRINK(3); + // Skip POP_TOP + assert(next_instr->op.code == POP_TOP); + SKIP_OVER(1); + DISPATCH(); + } + + case _CALL_METHOD_DESCRIPTOR_O: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -2983,13 +3101,13 @@ total_args++; } PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(total_args != 2, CALL); - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + if (total_args != 2) goto deoptimize; + if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) goto deoptimize; PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != METH_O, CALL); + if (meth->ml_flags != METH_O) goto deoptimize; PyObject *arg = args[1]; PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); + if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; // This is slower but CPython promises to check all non-vectorcall @@ -3003,21 +3121,20 @@ Py_DECREF(self); Py_DECREF(arg); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { - oparg = CURRENT_OPARG(); + case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -3026,39 +3143,37 @@ total_args++; } PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) goto deoptimize; PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != (METH_FASTCALL|METH_KEYWORDS), CALL); + if (meth->ml_flags != (METH_FASTCALL|METH_KEYWORDS)) goto deoptimize; PyTypeObject *d_type = method->d_common.d_type; PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, d_type), CALL); + if (!Py_IS_TYPE(self, d_type)) goto deoptimize; STAT_INC(CALL, hit); int nargs = total_args - 1; _PyCFunctionFastWithKeywords cfunc = - (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; res = cfunc(self, args + 1, nargs, NULL); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ for (int i = 0; i < total_args; i++) { Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_METHOD_DESCRIPTOR_NOARGS: { - oparg = CURRENT_OPARG(); + case _CALL_METHOD_DESCRIPTOR_NOARGS: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(oparg == 0 || oparg == 1); @@ -3067,13 +3182,13 @@ args--; total_args++; } - DEOPT_IF(total_args != 1, CALL); + if (total_args != 1) goto deoptimize; PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) goto deoptimize; PyMethodDef *meth = method->d_method; PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); - DEOPT_IF(meth->ml_flags != METH_NOARGS, CALL); + if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; + if (meth->ml_flags != METH_NOARGS) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; // This is slower but CPython promises to check all non-vectorcall @@ -3086,21 +3201,20 @@ assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); Py_DECREF(self); Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case CALL_METHOD_DESCRIPTOR_FAST: { - oparg = CURRENT_OPARG(); + case _CALL_METHOD_DESCRIPTOR_FAST: { PyObject **args; PyObject *self_or_null; PyObject *callable; PyObject *res; - args = stack_pointer - oparg; + oparg = CURRENT_OPARG(); + args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; int total_args = oparg; @@ -3110,14 +3224,14 @@ } PyMethodDescrObject *method = (PyMethodDescrObject *)callable; /* Builtin METH_FASTCALL methods, without keywords */ - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) goto deoptimize; PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != METH_FASTCALL, CALL); + if (meth->ml_flags != METH_FASTCALL) goto deoptimize; PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); + if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; STAT_INC(CALL, hit); _PyCFunctionFast cfunc = - (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + (_PyCFunctionFast)(void(*)(void))meth->ml_meth; int nargs = total_args - 1; res = cfunc(self, args + 1, nargs); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3126,93 +3240,121 @@ Py_DECREF(args[i]); } Py_DECREF(callable); - if (res == NULL) { STACK_SHRINK(oparg); goto pop_2_error_tier_two; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); - stack_pointer[-1] = res; + if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } + stack_pointer[-2 - oparg] = res; + stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); break; } - case MAKE_FUNCTION: { + /* _INSTRUMENTED_CALL_KW is not a viable micro-op for tier 2 */ + + /* _CALL_KW is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ + + /* _CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ + + case _MAKE_FUNCTION: { PyObject *codeobj; PyObject *func; codeobj = stack_pointer[-1]; - PyFunctionObject *func_obj = (PyFunctionObject *) - PyFunction_New(codeobj, GLOBALS()); - + PyFunction_New(codeobj, GLOBALS()); Py_DECREF(codeobj); if (func_obj == NULL) { GOTO_ERROR(error); } - _PyFunction_SetVersion( - func_obj, ((PyCodeObject *)codeobj)->co_version); + func_obj, ((PyCodeObject *)codeobj)->co_version); func = (PyObject *)func_obj; stack_pointer[-1] = func; break; } - case SET_FUNCTION_ATTRIBUTE: { - oparg = CURRENT_OPARG(); + case _SET_FUNCTION_ATTRIBUTE: { PyObject *func; PyObject *attr; + oparg = CURRENT_OPARG(); func = stack_pointer[-1]; attr = stack_pointer[-2]; assert(PyFunction_Check(func)); PyFunctionObject *func_obj = (PyFunctionObject *)func; switch(oparg) { case MAKE_FUNCTION_CLOSURE: - assert(func_obj->func_closure == NULL); - func_obj->func_closure = attr; - break; + assert(func_obj->func_closure == NULL); + func_obj->func_closure = attr; + break; case MAKE_FUNCTION_ANNOTATIONS: - assert(func_obj->func_annotations == NULL); - func_obj->func_annotations = attr; - break; + assert(func_obj->func_annotations == NULL); + func_obj->func_annotations = attr; + break; case MAKE_FUNCTION_KWDEFAULTS: - assert(PyDict_CheckExact(attr)); - assert(func_obj->func_kwdefaults == NULL); - func_obj->func_kwdefaults = attr; - break; + assert(PyDict_CheckExact(attr)); + assert(func_obj->func_kwdefaults == NULL); + func_obj->func_kwdefaults = attr; + break; case MAKE_FUNCTION_DEFAULTS: - assert(PyTuple_CheckExact(attr)); - assert(func_obj->func_defaults == NULL); - func_obj->func_defaults = attr; - break; + assert(PyTuple_CheckExact(attr)); + assert(func_obj->func_defaults == NULL); + func_obj->func_defaults = attr; + break; default: - Py_UNREACHABLE(); + Py_UNREACHABLE(); } - STACK_SHRINK(1); - stack_pointer[-1] = func; + stack_pointer[-2] = func; + stack_pointer += -1; break; } - case BUILD_SLICE: { - oparg = CURRENT_OPARG(); + case _RETURN_GENERATOR: { + assert(PyFunction_Check(frame->f_funcobj)); + PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; + PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); + if (gen == NULL) { + GOTO_ERROR(error); + } + assert(EMPTY()); + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe; + frame->instr_ptr = next_instr; + _PyFrame_Copy(frame, gen_frame); + assert(frame->frame_obj == NULL); + gen->gi_frame_state = FRAME_CREATED; + gen_frame->owner = FRAME_OWNED_BY_GENERATOR; + _Py_LeaveRecursiveCallPy(tstate); + assert(frame != &entry_frame); + _PyInterpreterFrame *prev = frame->previous; + _PyThreadState_PopFrame(tstate, frame); + frame = tstate->current_frame = prev; + _PyFrame_StackPush(frame, (PyObject *)gen); + LOAD_IP(frame->return_offset); + goto resume_frame; + } + + case _BUILD_SLICE: { PyObject *step = NULL; PyObject *stop; PyObject *start; PyObject *slice; - if (oparg == 3) { step = stack_pointer[-(oparg == 3 ? 1 : 0)]; } - stop = stack_pointer[-1 - (oparg == 3 ? 1 : 0)]; - start = stack_pointer[-2 - (oparg == 3 ? 1 : 0)]; + oparg = CURRENT_OPARG(); + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; + start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - if (slice == NULL) { STACK_SHRINK(((oparg == 3) ? 1 : 0)); goto pop_2_error_tier_two; } - STACK_SHRINK(((oparg == 3) ? 1 : 0)); - STACK_SHRINK(1); - stack_pointer[-1] = slice; + if (slice == NULL) { stack_pointer += -2 - (((oparg == 3) ? 1 : 0)); goto error_tier_two; } + stack_pointer[-2 - (((oparg == 3) ? 1 : 0))] = slice; + stack_pointer += -1 - (((oparg == 3) ? 1 : 0)); break; } - case CONVERT_VALUE: { - oparg = CURRENT_OPARG(); + case _CONVERT_VALUE: { PyObject *value; PyObject *result; + oparg = CURRENT_OPARG(); value = stack_pointer[-1]; convertion_func_ptr conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); @@ -3224,7 +3366,7 @@ break; } - case FORMAT_SIMPLE: { + case _FORMAT_SIMPLE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; @@ -3242,7 +3384,7 @@ break; } - case FORMAT_WITH_SPEC: { + case _FORMAT_WITH_SPEC: { PyObject *fmt_spec; PyObject *value; PyObject *res; @@ -3252,28 +3394,28 @@ Py_DECREF(value); Py_DECREF(fmt_spec); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case COPY: { - oparg = CURRENT_OPARG(); + case _COPY: { PyObject *bottom; PyObject *top; + oparg = CURRENT_OPARG(); bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = Py_NewRef(bottom); - STACK_GROW(1); - stack_pointer[-1] = top; + stack_pointer[0] = top; + stack_pointer += 1; break; } case _BINARY_OP: { - oparg = CURRENT_OPARG(); PyObject *rhs; PyObject *lhs; PyObject *res; + oparg = CURRENT_OPARG(); rhs = stack_pointer[-1]; lhs = stack_pointer[-2]; assert(_PyEval_BinaryOps[oparg]); @@ -3281,15 +3423,15 @@ Py_DECREF(lhs); Py_DECREF(rhs); if (res == NULL) goto pop_2_error_tier_two; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; break; } - case SWAP: { - oparg = CURRENT_OPARG(); + case _SWAP: { PyObject *top; PyObject *bottom; + oparg = CURRENT_OPARG(); top = stack_pointer[-1]; bottom = stack_pointer[-2 - (oparg-2)]; assert(oparg >= 2); @@ -3298,38 +3440,52 @@ break; } + /* _INSTRUMENTED_INSTRUCTION is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_JUMP_FORWARD is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_JUMP_BACKWARD is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_NONE is not a viable micro-op for tier 2 */ + + /* _INSTRUMENTED_POP_JUMP_IF_NOT_NONE is not a viable micro-op for tier 2 */ + case _GUARD_IS_TRUE_POP: { PyObject *flag; flag = stack_pointer[-1]; - DEOPT_IF(Py_IsFalse(flag), _GUARD_IS_TRUE_POP); + if (Py_IsFalse(flag)) goto deoptimize; assert(Py_IsTrue(flag)); - STACK_SHRINK(1); + stack_pointer += -1; break; } case _GUARD_IS_FALSE_POP: { PyObject *flag; flag = stack_pointer[-1]; - DEOPT_IF(Py_IsTrue(flag), _GUARD_IS_FALSE_POP); + if (Py_IsTrue(flag)) goto deoptimize; assert(Py_IsFalse(flag)); - STACK_SHRINK(1); + stack_pointer += -1; break; } case _GUARD_IS_NONE_POP: { PyObject *val; val = stack_pointer[-1]; - DEOPT_IF(!Py_IsNone(val), _GUARD_IS_NONE_POP); - STACK_SHRINK(1); + if (!Py_IsNone(val)) goto deoptimize; + stack_pointer += -1; break; } case _GUARD_IS_NOT_NONE_POP: { PyObject *val; val = stack_pointer[-1]; - DEOPT_IF(Py_IsNone(val), _GUARD_IS_NOT_NONE_POP); + if (Py_IsNone(val)) goto deoptimize; Py_DECREF(val); - STACK_SHRINK(1); + stack_pointer += -1; break; } @@ -3365,8 +3521,8 @@ } case _INSERT: { - oparg = CURRENT_OPARG(); PyObject *top; + oparg = CURRENT_OPARG(); top = stack_pointer[-1]; // Inserts TOS at position specified by oparg; memmove(&stack_pointer[-1 - oparg], &stack_pointer[-oparg], oparg * sizeof(stack_pointer[0])); @@ -3376,7 +3532,7 @@ case _CHECK_VALIDITY: { TIER_TWO_ONLY - DEOPT_IF(!current_executor->base.vm_data.valid, _CHECK_VALIDITY); + if (!current_executor->base.vm_data.valid) goto deoptimize; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 24243ecfb5b8df..8f68bc6cb5ab40 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1,6 +1,6 @@ // This file is generated by Tools/cases_generator/tier1_generator.py // from: -// ['./Python/bytecodes.c'] +// Python/bytecodes.c // Do not edit! #ifdef TIER_TWO @@ -725,6 +725,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(CACHE); + TIER_ONE_ONLY assert(0 && "Executing a cache."); Py_UNREACHABLE(); } @@ -2364,6 +2365,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(EXTENDED_ARG); + TIER_ONE_ONLY assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -4704,6 +4706,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RESERVED); + TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); Py_UNREACHABLE(); } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 945de63d209935..bcc13538e51d9b 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -15,6 +15,7 @@ class Properties: needs_this: bool always_exits: bool stores_sp: bool + tier_one_only: bool def dump(self, indent: str) -> None: print(indent, end="") @@ -33,6 +34,7 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=any(p.needs_this for p in properties), always_exits=any(p.always_exits for p in properties), stores_sp=any(p.stores_sp for p in properties), + tier_one_only=any(p.tier_one_only for p in properties), ) @@ -46,6 +48,7 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=False, always_exits=False, stores_sp=False, + tier_one_only=False, ) @@ -124,6 +127,21 @@ def size(self) -> int: self._size = sum(c.size for c in self.caches) return self._size + def is_viable(self) -> bool: + if self.name == "_SAVE_RETURN_OFFSET": + return True # Adjusts next_instr, but only in tier 1 code + if self.properties.needs_this: + return False + if "INSTRUMENTED" in self.name: + return False + if "replaced" in self.annotations: + return False + if self.name in ("INTERPRETER_EXIT", "JUMP_BACKWARD"): + return False + if len([c for c in self.caches if c.name != "unused"]) > 1: + return False + return True + Part = Uop | Skip @@ -292,6 +310,7 @@ def compute_properties(op: parser.InstDef) -> Properties: needs_this=variable_used(op, "this_instr"), always_exits=always_exits(op), stores_sp=variable_used(op, "STORE_SP"), + tier_one_only=variable_used(op, "TIER_ONE_ONLY"), ) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index f7c362131a7a9f..c6ed5911b846bf 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -128,13 +128,6 @@ arg_parser.add_argument( "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" ) -arg_parser.add_argument( - "-e", - "--executor-cases", - type=str, - help="Write executor cases to this file", - default=DEFAULT_EXECUTOR_OUTPUT, -) arg_parser.add_argument( "-a", "--abstract-interpreter-cases", @@ -846,7 +839,6 @@ def main() -> None: a.assign_opcode_ids() a.write_opcode_targets(args.opcode_targets_h) a.write_metadata(args.metadata, args.pymetadata) - a.write_executor_instructions(args.executor_cases, args.emit_line_directives) a.write_abstract_interpreter_instructions( args.abstract_interpreter_cases, args.emit_line_directives ) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 76900d1efffd5d..e0674a7343498d 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -1,19 +1,186 @@ from pathlib import Path from typing import TextIO +from analyzer import ( + Analysis, + Instruction, + Uop, + Part, + analyze_files, + Skip, + StackItem, + analysis_error, +) +from cwriter import CWriter +from typing import Callable, Mapping, TextIO, Iterator +from lexer import Token +from stack import StackOffset, Stack + + ROOT = Path(__file__).parent.parent.parent -DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute() +DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute().as_posix() def root_relative_path(filename: str) -> str: - return Path(filename).relative_to(ROOT).as_posix() + return Path(filename).absolute().relative_to(ROOT).as_posix() -def write_header(generator: str, source: str, outfile: TextIO) -> None: +def write_header(generator: str, sources: list[str], outfile: TextIO) -> None: outfile.write( f"""// This file is generated by {root_relative_path(generator)} // from: -// {source} +// {", ".join(root_relative_path(src) for src in sources)} // Do not edit! """ ) + + +def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + out.emit(tkn) + + +def replace_deopt( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + unused: Stack, + inst: Instruction | None, +) -> None: + out.emit_at("DEOPT_IF", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "RPAREN") + next(tkn_iter) # Semi colon + out.emit(", ") + assert inst is not None + assert inst.family is not None + out.emit(inst.family.name) + out.emit(");\n") + + +def replace_error( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + out.emit_at("if ", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "COMMA") + label = next(tkn_iter).text + next(tkn_iter) # RPAREN + next(tkn_iter) # Semi colon + out.emit(") ") + c_offset = stack.peek_offset.to_c() + try: + offset = -int(c_offset) + close = ";\n" + except ValueError: + offset = None + out.emit(f"{{ stack_pointer += {c_offset}; ") + close = "; }\n" + out.emit("goto ") + if offset: + out.emit(f"pop_{offset}_") + out.emit(label) + out.emit(close) + + +def replace_decrefs( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + out.emit_at("", tkn) + for var in uop.stack.inputs: + if var.name == "unused" or var.name == "null" or var.peek: + continue + if var.size != "1": + out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") + out.emit(f"Py_DECREF({var.name}[_i]);\n") + out.emit("}\n") + elif var.condition: + out.emit(f"Py_XDECREF({var.name});\n") + else: + out.emit(f"Py_DECREF({var.name});\n") + + +def replace_store_sp( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + out.emit_at("", tkn) + stack.flush(out) + out.emit("_PyFrame_SetStackPointer(frame, stack_pointer);\n") + + +def replace_check_eval_breaker( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + next(tkn_iter) + next(tkn_iter) + next(tkn_iter) + if not uop.properties.ends_with_eval_breaker: + out.emit_at("CHECK_EVAL_BREAKER();", tkn) + + +REPLACEMENT_FUNCTIONS = { + "DEOPT_IF": replace_deopt, + "ERROR_IF": replace_error, + "DECREF_INPUTS": replace_decrefs, + "CHECK_EVAL_BREAKER": replace_check_eval_breaker, + "STORE_SP": replace_store_sp, +} + +ReplacementFunctionType = Callable[ + [CWriter, Token, Iterator[Token], Uop, Stack, Instruction | None], None +] + + +def emit_tokens( + out: CWriter, + uop: Uop, + stack: Stack, + inst: Instruction | None, + replacement_functions: Mapping[ + str, ReplacementFunctionType + ] = REPLACEMENT_FUNCTIONS, +) -> None: + tkns = uop.body[1:-1] + if not tkns: + return + tkn_iter = iter(tkns) + out.start_line() + for tkn in tkn_iter: + if tkn.kind == "IDENTIFIER" and tkn.text in replacement_functions: + replacement_functions[tkn.text](out, tkn, tkn_iter, uop, stack, inst) + else: + out.emit(tkn) diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py index a1f6f62156ebd3..ddbb409bbced39 100644 --- a/Tools/cases_generator/opcode_id_generator.py +++ b/Tools/cases_generator/opcode_id_generator.py @@ -24,7 +24,7 @@ DEFAULT_OUTPUT = ROOT / "Include/opcode_ids.h" -def generate_opcode_header(filenames: str, analysis: Analysis, outfile: TextIO) -> None: +def generate_opcode_header(filenames: list[str], analysis: Analysis, outfile: TextIO) -> None: write_header(__file__, filenames, outfile) out = CWriter(outfile, 0, False) out.emit("\n") diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 9cd8e1a3b2edf8..c36a56ebf2d381 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -2,6 +2,7 @@ from analyzer import StackItem from dataclasses import dataclass from formatting import maybe_parenthesize +from cwriter import CWriter def var_size(var: StackItem) -> str: @@ -79,3 +80,89 @@ def to_c(self) -> str: def clear(self) -> None: self.popped = [] self.pushed = [] + + +class SizeMismatch(Exception): + pass + + +class Stack: + def __init__(self) -> None: + self.top_offset = StackOffset() + self.base_offset = StackOffset() + self.peek_offset = StackOffset() + self.variables: list[StackItem] = [] + self.defined: set[str] = set() + + def pop(self, var: StackItem) -> str: + self.top_offset.pop(var) + if not var.peek: + self.peek_offset.pop(var) + indirect = "&" if var.is_array() else "" + if self.variables: + popped = self.variables.pop() + if popped.size != var.size: + raise SizeMismatch( + f"Size mismatch when popping '{popped.name}' from stack to assign to {var.name}. " + f"Expected {var.size} got {popped.size}" + ) + if popped.name == var.name: + return "" + elif popped.name == "unused": + self.defined.add(var.name) + return ( + f"{var.name} = {indirect}stack_pointer[{self.top_offset.to_c()}];\n" + ) + elif var.name == "unused": + return "" + else: + self.defined.add(var.name) + return f"{var.name} = {popped.name};\n" + self.base_offset.pop(var) + if var.name == "unused": + return "" + else: + self.defined.add(var.name) + cast = f"({var.type})" if (not indirect and var.type) else "" + assign = ( + f"{var.name} = {cast}{indirect}stack_pointer[{self.base_offset.to_c()}];" + ) + if var.condition: + return f"if ({var.condition}) {{ {assign} }}\n" + return f"{assign}\n" + + def push(self, var: StackItem) -> str: + self.variables.append(var) + if var.is_array() and var.name not in self.defined and var.name != "unused": + c_offset = self.top_offset.to_c() + self.top_offset.push(var) + self.defined.add(var.name) + return f"{var.name} = &stack_pointer[{c_offset}];\n" + else: + self.top_offset.push(var) + return "" + + def flush(self, out: CWriter) -> None: + for var in self.variables: + if not var.peek: + cast = "(PyObject *)" if var.type else "" + if var.name != "unused" and not var.is_array(): + if var.condition: + out.emit(f" if ({var.condition}) ") + out.emit( + f"stack_pointer[{self.base_offset.to_c()}] = {cast}{var.name};\n" + ) + self.base_offset.push(var) + if self.base_offset.to_c() != self.top_offset.to_c(): + print("base", self.base_offset.to_c(), "top", self.top_offset.to_c()) + assert False + number = self.base_offset.to_c() + if number != "0": + out.emit(f"stack_pointer += {number};\n") + self.variables = [] + self.base_offset.clear() + self.top_offset.clear() + self.peek_offset.clear() + + def as_comment(self) -> str: + return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 56ae660a686822..11885dca6fe1a2 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -21,11 +21,12 @@ DEFAULT_INPUT, ROOT, write_header, + emit_tokens, ) from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token -from stack import StackOffset +from stack import StackOffset, Stack, SizeMismatch DEFAULT_OUTPUT = ROOT / "Python/generated_cases.c.h" @@ -34,88 +35,6 @@ FOOTER = "#undef TIER_ONE\n" -class SizeMismatch(Exception): - pass - - -class Stack: - def __init__(self) -> None: - self.top_offset = StackOffset() - self.base_offset = StackOffset() - self.peek_offset = StackOffset() - self.variables: list[StackItem] = [] - self.defined: set[str] = set() - - def pop(self, var: StackItem) -> str: - self.top_offset.pop(var) - if not var.peek: - self.peek_offset.pop(var) - indirect = "&" if var.is_array() else "" - if self.variables: - popped = self.variables.pop() - if popped.size != var.size: - raise SizeMismatch( - f"Size mismatch when popping '{popped.name}' from stack to assign to {var.name}. " - f"Expected {var.size} got {popped.size}" - ) - if popped.name == var.name: - return "" - elif popped.name == "unused": - self.defined.add(var.name) - return ( - f"{var.name} = {indirect}stack_pointer[{self.top_offset.to_c()}];\n" - ) - elif var.name == "unused": - return "" - else: - self.defined.add(var.name) - return f"{var.name} = {popped.name};\n" - self.base_offset.pop(var) - if var.name == "unused": - return "" - else: - self.defined.add(var.name) - assign = f"{var.name} = {indirect}stack_pointer[{self.base_offset.to_c()}];" - if var.condition: - return f"if ({var.condition}) {{ {assign} }}\n" - return f"{assign}\n" - - def push(self, var: StackItem) -> str: - self.variables.append(var) - if var.is_array() and var.name not in self.defined and var.name != "unused": - c_offset = self.top_offset.to_c() - self.top_offset.push(var) - self.defined.add(var.name) - return f"{var.name} = &stack_pointer[{c_offset}];\n" - else: - self.top_offset.push(var) - return "" - - def flush(self, out: CWriter) -> None: - for var in self.variables: - if not var.peek: - if var.name != "unused" and not var.is_array(): - if var.condition: - out.emit(f" if ({var.condition}) ") - out.emit( - f"stack_pointer[{self.base_offset.to_c()}] = {var.name};\n" - ) - self.base_offset.push(var) - if self.base_offset.to_c() != self.top_offset.to_c(): - print("base", self.base_offset.to_c(), "top", self.top_offset.to_c()) - assert False - number = self.base_offset.to_c() - if number != "0": - out.emit(f"stack_pointer += {number};\n") - self.variables = [] - self.base_offset.clear() - self.top_offset.clear() - self.peek_offset.clear() - - def as_comment(self) -> str: - return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" - - def declare_variables(inst: Instruction, out: CWriter) -> None: variables = {"unused"} for uop in inst.parts: @@ -138,145 +57,6 @@ def declare_variables(inst: Instruction, out: CWriter) -> None: out.emit(f"{type}{var.name};\n") -def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: - parens = 0 - for tkn in tkn_iter: - if tkn.kind == end and parens == 0: - return - if tkn.kind == "LPAREN": - parens += 1 - if tkn.kind == "RPAREN": - parens -= 1 - out.emit(tkn) - - -def replace_deopt( - out: CWriter, - tkn: Token, - tkn_iter: Iterator[Token], - uop: Uop, - unused: Stack, - inst: Instruction, -) -> None: - out.emit_at("DEOPT_IF", tkn) - out.emit(next(tkn_iter)) - emit_to(out, tkn_iter, "RPAREN") - next(tkn_iter) # Semi colon - out.emit(", ") - assert inst.family is not None - out.emit(inst.family.name) - out.emit(");\n") - - -def replace_error( - out: CWriter, - tkn: Token, - tkn_iter: Iterator[Token], - uop: Uop, - stack: Stack, - inst: Instruction, -) -> None: - out.emit_at("if ", tkn) - out.emit(next(tkn_iter)) - emit_to(out, tkn_iter, "COMMA") - label = next(tkn_iter).text - next(tkn_iter) # RPAREN - next(tkn_iter) # Semi colon - out.emit(") ") - c_offset = stack.peek_offset.to_c() - try: - offset = -int(c_offset) - close = ";\n" - except ValueError: - offset = None - out.emit(f"{{ stack_pointer += {c_offset}; ") - close = "; }\n" - out.emit("goto ") - if offset: - out.emit(f"pop_{offset}_") - out.emit(label) - out.emit(close) - - -def replace_decrefs( - out: CWriter, - tkn: Token, - tkn_iter: Iterator[Token], - uop: Uop, - stack: Stack, - inst: Instruction, -) -> None: - next(tkn_iter) - next(tkn_iter) - next(tkn_iter) - out.emit_at("", tkn) - for var in uop.stack.inputs: - if var.name == "unused" or var.name == "null" or var.peek: - continue - if var.size != "1": - out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") - out.emit(f"Py_DECREF({var.name}[_i]);\n") - out.emit("}\n") - elif var.condition: - out.emit(f"Py_XDECREF({var.name});\n") - else: - out.emit(f"Py_DECREF({var.name});\n") - - -def replace_store_sp( - out: CWriter, - tkn: Token, - tkn_iter: Iterator[Token], - uop: Uop, - stack: Stack, - inst: Instruction, -) -> None: - next(tkn_iter) - next(tkn_iter) - next(tkn_iter) - out.emit_at("", tkn) - stack.flush(out) - out.emit("_PyFrame_SetStackPointer(frame, stack_pointer);\n") - - -def replace_check_eval_breaker( - out: CWriter, - tkn: Token, - tkn_iter: Iterator[Token], - uop: Uop, - stack: Stack, - inst: Instruction, -) -> None: - next(tkn_iter) - next(tkn_iter) - next(tkn_iter) - if not uop.properties.ends_with_eval_breaker: - out.emit_at("CHECK_EVAL_BREAKER();", tkn) - - -REPLACEMENT_FUNCTIONS = { - "DEOPT_IF": replace_deopt, - "ERROR_IF": replace_error, - "DECREF_INPUTS": replace_decrefs, - "CHECK_EVAL_BREAKER": replace_check_eval_breaker, - "STORE_SP": replace_store_sp, -} - - -# Move this to formatter -def emit_tokens(out: CWriter, uop: Uop, stack: Stack, inst: Instruction) -> None: - tkns = uop.body[1:-1] - if not tkns: - return - tkn_iter = iter(tkns) - out.start_line() - for tkn in tkn_iter: - if tkn.kind == "IDENTIFIER" and tkn.text in REPLACEMENT_FUNCTIONS: - REPLACEMENT_FUNCTIONS[tkn.text](out, tkn, tkn_iter, uop, stack, inst) - else: - out.emit(tkn) - - def write_uop( uop: Part, out: CWriter, offset: int, stack: Stack, inst: Instruction, braces: bool ) -> int: @@ -334,7 +114,7 @@ def uses_this(inst: Instruction) -> bool: def generate_tier1( - filenames: str, analysis: Analysis, outfile: TextIO, lines: bool + filenames: list[str], analysis: Analysis, outfile: TextIO, lines: bool ) -> None: write_header(__file__, filenames, outfile) outfile.write( @@ -404,7 +184,7 @@ def generate_tier1( if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: - args.input.append(DEFAULT_INPUT.as_posix()) + args.input.append(DEFAULT_INPUT) data = analyze_files(args.input) with open(args.output, "w") as outfile: generate_tier1(args.input, data, outfile, args.emit_line_directives) diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py new file mode 100644 index 00000000000000..d2b503961aa7b9 --- /dev/null +++ b/Tools/cases_generator/tier2_generator.py @@ -0,0 +1,202 @@ +"""Generate the cases for the tier 2 interpreter. +Reads the instruction definitions from bytecodes.c. +Writes the cases to executor_cases.c.h, which is #included in ceval.c. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + Uop, + Part, + analyze_files, + Skip, + StackItem, + analysis_error, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, + emit_tokens, + emit_to, + REPLACEMENT_FUNCTIONS, +) +from cwriter import CWriter +from typing import TextIO, Iterator +from lexer import Token +from stack import StackOffset, Stack, SizeMismatch + +DEFAULT_OUTPUT = ROOT / "Python/executor_cases.c.h" + + +def declare_variables(uop: Uop, out: CWriter) -> None: + variables = {"unused"} + for var in reversed(uop.stack.inputs): + if var.name not in variables: + type = var.type if var.type else "PyObject *" + variables.add(var.name) + if var.condition: + out.emit(f"{type}{var.name} = NULL;\n") + else: + out.emit(f"{type}{var.name};\n") + for var in uop.stack.outputs: + if var.name not in variables: + variables.add(var.name) + type = var.type if var.type else "PyObject *" + if var.condition: + out.emit(f"{type}{var.name} = NULL;\n") + else: + out.emit(f"{type}{var.name};\n") + + +def tier2_replace_error( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + stack: Stack, + inst: Instruction | None, +) -> None: + out.emit_at("if ", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "COMMA") + label = next(tkn_iter).text + next(tkn_iter) # RPAREN + next(tkn_iter) # Semi colon + out.emit(") ") + c_offset = stack.peek_offset.to_c() + try: + offset = -int(c_offset) + close = ";\n" + except ValueError: + offset = None + out.emit(f"{{ stack_pointer += {c_offset}; ") + close = "; }\n" + out.emit("goto ") + if offset: + out.emit(f"pop_{offset}_") + out.emit(label + "_tier_two") + out.emit(close) + + +def tier2_replace_deopt( + out: CWriter, + tkn: Token, + tkn_iter: Iterator[Token], + uop: Uop, + unused: Stack, + inst: Instruction | None, +) -> None: + out.emit_at("if ", tkn) + out.emit(next(tkn_iter)) + emit_to(out, tkn_iter, "RPAREN") + next(tkn_iter) # Semi colon + out.emit(") goto deoptimize;\n") + + +TIER2_REPLACEMENT_FUNCTIONS = REPLACEMENT_FUNCTIONS.copy() +TIER2_REPLACEMENT_FUNCTIONS["ERROR_IF"] = tier2_replace_error +TIER2_REPLACEMENT_FUNCTIONS["DEOPT_IF"] = tier2_replace_deopt + + +def is_super(uop: Uop) -> bool: + for tkn in uop.body: + if tkn.kind == "IDENTIFIER" and tkn.text == "oparg1": + return True + return False + + +def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: + try: + out.start_line() + if uop.properties.oparg: + out.emit("oparg = CURRENT_OPARG();\n") + for var in reversed(uop.stack.inputs): + out.emit(stack.pop(var)) + if not uop.properties.stores_sp: + for i, var in enumerate(uop.stack.outputs): + out.emit(stack.push(var)) + for cache in uop.caches: + if cache.name != "unused": + if cache.size == 4: + type = "PyObject *" + else: + type = f"uint{cache.size*16}_t" + out.emit(f"{type} {cache.name} = ({type})CURRENT_OPERAND();\n") + emit_tokens(out, uop, stack, None, TIER2_REPLACEMENT_FUNCTIONS) + if uop.properties.stores_sp: + for i, var in enumerate(uop.stack.outputs): + out.emit(stack.push(var)) + except SizeMismatch as ex: + raise analysis_error(ex.args[0], uop.body[0]) + + +SKIPS = ("_EXTENDED_ARG",) + + +def generate_tier2( + filenames: list[str], analysis: Analysis, outfile: TextIO, lines: bool +) -> None: + write_header(__file__, filenames, outfile) + outfile.write( + """ +#ifdef TIER_ONE + #error "This file is for Tier 2 only" +#endif +#define TIER_TWO 2 +""" + ) + out = CWriter(outfile, 2, lines) + out.emit("\n") + for name, uop in analysis.uops.items(): + if uop.properties.tier_one_only: + continue + if is_super(uop): + continue + if not uop.is_viable(): + out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n") + continue + out.emit(f"case {uop.name}: {{\n") + declare_variables(uop, out) + stack = Stack() + write_uop(uop, out, stack) + out.start_line() + if not uop.properties.always_exits: + stack.flush(out) + if uop.properties.ends_with_eval_breaker: + out.emit("CHECK_EVAL_BREAKER();\n") + out.emit("break;\n") + out.start_line() + out.emit("}") + out.emit("\n\n") + outfile.write("#undef TIER_TWO\n") + + +arg_parser = argparse.ArgumentParser( + description="Generate the code for the tier 2 interpreter.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "-l", "--emit-line-directives", help="Emit #line directives", action="store_true" +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_tier2(args.input, data, outfile, args.emit_line_directives) diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py index 4a96dbc171ee22..277da25835f6fb 100644 --- a/Tools/cases_generator/uop_id_generator.py +++ b/Tools/cases_generator/uop_id_generator.py @@ -24,8 +24,11 @@ DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_ids.h" +OMIT = {"_CACHE", "_RESERVED", "_EXTENDED_ARG"} + + def generate_uop_ids( - filenames: str, analysis: Analysis, outfile: TextIO, distinct_namespace: bool + filenames: list[str], analysis: Analysis, outfile: TextIO, distinct_namespace: bool ) -> None: write_header(__file__, filenames, outfile) out = CWriter(outfile, 0, False) @@ -45,11 +48,15 @@ def generate_uop_ids( next_id += 1 out.emit(f"#define _SET_IP {next_id}\n") next_id += 1 - PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP", "_CACHE", "_RESERVED", "_EXTENDED_ARG"} + PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP"} for uop in analysis.uops.values(): if uop.name in PRE_DEFINED: continue + # TODO: We should omit all tier-1 only uops, but + # generate_cases.py still generates code for those. + if uop.name in OMIT: + continue if uop.implicitly_created and not distinct_namespace: out.emit(f"#define {uop.name} {uop.name[1:]}\n") else: @@ -85,7 +92,7 @@ def generate_uop_ids( if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: - args.input.append(DEFAULT_INPUT.as_posix()) + args.input.append(DEFAULT_INPUT) data = analyze_files(args.input) with open(args.output, "w") as outfile: generate_uop_ids(args.input, data, outfile, args.namespace) From f26bfe4b25f7e5a4f68fcac26207b7175abad208 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 12 Dec 2023 13:57:45 +0100 Subject: [PATCH 207/442] gh-51944: fix type and missing addition in gh-112823 (#112996) Fix the defition of VDSUSP and add CCTS_OFLOW (which was mentioned in NEWS, but not actually addded) --- Modules/termios.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/termios.c b/Modules/termios.c index 1d97a3a2757966..c4f0fd9d50044a 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -840,6 +840,9 @@ static struct constant { #ifdef CDSR_OFLOW {"CDSR_OFLOW", CDSR_OFLOW}, #endif +#ifdef CCTS_OFLOW + {"CCTS_OFLOW", CCTS_OFLOW}, +#endif #ifdef CCAR_OFLOW {"CCAR_OFLOW", CCAR_OFLOW}, #endif @@ -912,7 +915,7 @@ static struct constant { {"VSTOP", VSTOP}, {"VSUSP", VSUSP}, #ifdef VDSUSP - {"VDSUSP", VREPRINT}, + {"VDSUSP", VDSUSP}, #endif {"VEOL", VEOL}, #ifdef VREPRINT From 86a77f4e1a5ceaff1036b0072521e12752b5df47 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 08:24:31 -0700 Subject: [PATCH 208/442] gh-76785: Fixes for test.support.interpreters (gh-112982) This involves a number of changes for PEP 734. --- .github/CODEOWNERS | 15 +- Include/internal/pycore_crossinterp.h | 10 + Include/internal/pycore_interp.h | 6 +- Lib/test/support/interpreters/__init__.py | 160 +++ .../channels.py} | 124 +- Lib/test/support/interpreters/queues.py | 156 +++ Lib/test/test__xxinterpchannels.py | 3 +- Lib/test/test__xxsubinterpreters.py | 82 +- Lib/test/test_capi/test_misc.py | 132 ++ Lib/test/test_import/__init__.py | 11 +- Lib/test/test_importlib/test_util.py | 16 +- Lib/test/test_interpreters.py | 1136 ----------------- Lib/test/test_interpreters/__init__.py | 5 + Lib/test/test_interpreters/__main__.py | 4 + Lib/test/test_interpreters/test_api.py | 642 ++++++++++ Lib/test/test_interpreters/test_channels.py | 328 +++++ Lib/test/test_interpreters/test_lifecycle.py | 189 +++ Lib/test/test_interpreters/test_queues.py | 233 ++++ Lib/test/test_interpreters/test_stress.py | 38 + Lib/test/test_interpreters/utils.py | 73 ++ Lib/test/test_sys.py | 4 +- Lib/test/test_threading.py | 2 +- Modules/_testcapimodule.c | 34 + Modules/_testinternalcapi.c | 12 + Modules/_xxinterpchannelsmodule.c | 157 --- Modules/_xxsubinterpretersmodule.c | 367 +++++- Python/crossinterp.c | 62 + Python/pylifecycle.c | 6 + Python/pystate.c | 2 +- Tools/c-analyzer/cpython/globals-to-fix.tsv | 4 + 30 files changed, 2506 insertions(+), 1507 deletions(-) create mode 100644 Lib/test/support/interpreters/__init__.py rename Lib/test/support/{interpreters.py => interpreters/channels.py} (56%) create mode 100644 Lib/test/support/interpreters/queues.py delete mode 100644 Lib/test/test_interpreters.py create mode 100644 Lib/test/test_interpreters/__init__.py create mode 100644 Lib/test/test_interpreters/__main__.py create mode 100644 Lib/test/test_interpreters/test_api.py create mode 100644 Lib/test/test_interpreters/test_channels.py create mode 100644 Lib/test/test_interpreters/test_lifecycle.py create mode 100644 Lib/test/test_interpreters/test_queues.py create mode 100644 Lib/test/test_interpreters/test_stress.py create mode 100644 Lib/test/test_interpreters/utils.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aa6d937d9cbc31..f8291d8689dd95 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -78,6 +78,11 @@ Python/traceback.c @iritkatriel **/*importlib/resources/* @jaraco @warsaw @FFY00 **/importlib/metadata/* @jaraco @warsaw +# Subinterpreters +Lib/test/support/interpreters/** @ericsnowcurrently +Modules/_xx*interp*module.c @ericsnowcurrently +Lib/test/test_interpreters/** @ericsnowcurrently + # Dates and times **/*datetime* @pganssle @abalkin **/*str*time* @pganssle @abalkin @@ -148,7 +153,15 @@ Doc/c-api/stable.rst @encukou **/*itertools* @rhettinger **/*collections* @rhettinger **/*random* @rhettinger -**/*queue* @rhettinger +Doc/**/*queue* @rhettinger +PCbuild/**/*queue* @rhettinger +Modules/_queuemodule.c @rhettinger +Lib/*queue*.py @rhettinger +Lib/asyncio/*queue*.py @rhettinger +Lib/multiprocessing/*queue*.py @rhettinger +Lib/test/*queue*.py @rhettinger +Lib/test_asyncio/*queue*.py @rhettinger +Lib/test_multiprocessing/*queue*.py @rhettinger **/*bisect* @rhettinger **/*heapq* @rhettinger **/*functools* @rhettinger diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 2e6d09a49f95d3..ce95979f8d343b 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -11,6 +11,13 @@ extern "C" { #include "pycore_lock.h" // PyMutex #include "pycore_pyerrors.h" +/**************/ +/* exceptions */ +/**************/ + +PyAPI_DATA(PyObject *) PyExc_InterpreterError; +PyAPI_DATA(PyObject *) PyExc_InterpreterNotFoundError; + /***************************/ /* cross-interpreter calls */ @@ -160,6 +167,9 @@ struct _xi_state { extern PyStatus _PyXI_Init(PyInterpreterState *interp); extern void _PyXI_Fini(PyInterpreterState *interp); +extern PyStatus _PyXI_InitTypes(PyInterpreterState *interp); +extern void _PyXI_FiniTypes(PyInterpreterState *interp); + /***************************/ /* short-term data sharing */ diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 2a683196eeced3..04d7a6a615e370 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -250,9 +250,9 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState *interp, PyThreadState *tst // Export for the _xxinterpchannels module. PyAPI_FUNC(PyInterpreterState *) _PyInterpreterState_LookUpID(int64_t); -extern int _PyInterpreterState_IDInitref(PyInterpreterState *); -extern int _PyInterpreterState_IDIncref(PyInterpreterState *); -extern void _PyInterpreterState_IDDecref(PyInterpreterState *); +PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *); +PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *); +PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *); extern const PyConfig* _PyInterpreterState_GetConfig(PyInterpreterState *interp); diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py new file mode 100644 index 00000000000000..2d6376deb5907e --- /dev/null +++ b/Lib/test/support/interpreters/__init__.py @@ -0,0 +1,160 @@ +"""Subinterpreters High Level Module.""" + +import threading +import weakref +import _xxsubinterpreters as _interpreters + +# aliases: +from _xxsubinterpreters import ( + InterpreterError, InterpreterNotFoundError, + is_shareable, +) + + +__all__ = [ + 'get_current', 'get_main', 'create', 'list_all', 'is_shareable', + 'Interpreter', + 'InterpreterError', 'InterpreterNotFoundError', 'ExecFailure', + 'create_queue', 'Queue', 'QueueEmpty', 'QueueFull', +] + + +_queuemod = None + +def __getattr__(name): + if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'): + global create_queue, Queue, QueueEmpty, QueueFull + ns = globals() + from .queues import ( + create as create_queue, + Queue, QueueEmpty, QueueFull, + ) + return ns[name] + else: + raise AttributeError(name) + + +class ExecFailure(RuntimeError): + + def __init__(self, excinfo): + msg = excinfo.formatted + if not msg: + if excinfo.type and snapshot.msg: + msg = f'{snapshot.type.__name__}: {snapshot.msg}' + else: + msg = snapshot.type.__name__ or snapshot.msg + super().__init__(msg) + self.snapshot = excinfo + + +def create(): + """Return a new (idle) Python interpreter.""" + id = _interpreters.create(isolated=True) + return Interpreter(id) + + +def list_all(): + """Return all existing interpreters.""" + return [Interpreter(id) for id in _interpreters.list_all()] + + +def get_current(): + """Return the currently running interpreter.""" + id = _interpreters.get_current() + return Interpreter(id) + + +def get_main(): + """Return the main interpreter.""" + id = _interpreters.get_main() + return Interpreter(id) + + +_known = weakref.WeakValueDictionary() + +class Interpreter: + """A single Python interpreter.""" + + def __new__(cls, id, /): + # There is only one instance for any given ID. + if not isinstance(id, int): + raise TypeError(f'id must be an int, got {id!r}') + id = int(id) + try: + self = _known[id] + assert hasattr(self, '_ownsref') + except KeyError: + # This may raise InterpreterNotFoundError: + _interpreters._incref(id) + try: + self = super().__new__(cls) + self._id = id + self._ownsref = True + except BaseException: + _interpreters._deccref(id) + raise + _known[id] = self + return self + + def __repr__(self): + return f'{type(self).__name__}({self.id})' + + def __hash__(self): + return hash(self._id) + + def __del__(self): + self._decref() + + def _decref(self): + if not self._ownsref: + return + self._ownsref = False + try: + _interpreters._decref(self.id) + except InterpreterNotFoundError: + pass + + @property + def id(self): + return self._id + + def is_running(self): + """Return whether or not the identified interpreter is running.""" + return _interpreters.is_running(self._id) + + def close(self): + """Finalize and destroy the interpreter. + + Attempting to destroy the current interpreter results + in a RuntimeError. + """ + return _interpreters.destroy(self._id) + + def exec_sync(self, code, /, channels=None): + """Run the given source code in the interpreter. + + This is essentially the same as calling the builtin "exec" + with this interpreter, using the __dict__ of its __main__ + module as both globals and locals. + + There is no return value. + + If the code raises an unhandled exception then an ExecFailure + is raised, which summarizes the unhandled exception. The actual + exception is discarded because objects cannot be shared between + interpreters. + + This blocks the current Python thread until done. During + that time, the previous interpreter is allowed to run + in other threads. + """ + excinfo = _interpreters.exec(self._id, code, channels) + if excinfo is not None: + raise ExecFailure(excinfo) + + def run(self, code, /, channels=None): + def task(): + self.exec_sync(code, channels=channels) + t = threading.Thread(target=task) + t.start() + return t diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters/channels.py similarity index 56% rename from Lib/test/support/interpreters.py rename to Lib/test/support/interpreters/channels.py index 089fe7ef56df78..75a5a60f54f926 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters/channels.py @@ -1,11 +1,9 @@ -"""Subinterpreters High Level Module.""" +"""Cross-interpreter Channels High Level Module.""" import time -import _xxsubinterpreters as _interpreters import _xxinterpchannels as _channels # aliases: -from _xxsubinterpreters import is_shareable from _xxinterpchannels import ( ChannelError, ChannelNotFoundError, ChannelClosedError, ChannelEmptyError, ChannelNotEmptyError, @@ -13,123 +11,13 @@ __all__ = [ - 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', - 'RunFailedError', + 'create', 'list_all', 'SendChannel', 'RecvChannel', - 'create_channel', 'list_all_channels', 'is_shareable', - 'ChannelError', 'ChannelNotFoundError', - 'ChannelEmptyError', - ] + 'ChannelError', 'ChannelNotFoundError', 'ChannelEmptyError', +] -class RunFailedError(RuntimeError): - - def __init__(self, excinfo): - msg = excinfo.formatted - if not msg: - if excinfo.type and snapshot.msg: - msg = f'{snapshot.type.__name__}: {snapshot.msg}' - else: - msg = snapshot.type.__name__ or snapshot.msg - super().__init__(msg) - self.snapshot = excinfo - - -def create(*, isolated=True): - """Return a new (idle) Python interpreter.""" - id = _interpreters.create(isolated=isolated) - return Interpreter(id, isolated=isolated) - - -def list_all(): - """Return all existing interpreters.""" - return [Interpreter(id) for id in _interpreters.list_all()] - - -def get_current(): - """Return the currently running interpreter.""" - id = _interpreters.get_current() - return Interpreter(id) - - -def get_main(): - """Return the main interpreter.""" - id = _interpreters.get_main() - return Interpreter(id) - - -class Interpreter: - """A single Python interpreter.""" - - def __init__(self, id, *, isolated=None): - if not isinstance(id, (int, _interpreters.InterpreterID)): - raise TypeError(f'id must be an int, got {id!r}') - self._id = id - self._isolated = isolated - - def __repr__(self): - data = dict(id=int(self._id), isolated=self._isolated) - kwargs = (f'{k}={v!r}' for k, v in data.items()) - return f'{type(self).__name__}({", ".join(kwargs)})' - - def __hash__(self): - return hash(self._id) - - def __eq__(self, other): - if not isinstance(other, Interpreter): - return NotImplemented - else: - return other._id == self._id - - @property - def id(self): - return self._id - - @property - def isolated(self): - if self._isolated is None: - # XXX The low-level function has not been added yet. - # See bpo-.... - self._isolated = _interpreters.is_isolated(self._id) - return self._isolated - - def is_running(self): - """Return whether or not the identified interpreter is running.""" - return _interpreters.is_running(self._id) - - def close(self): - """Finalize and destroy the interpreter. - - Attempting to destroy the current interpreter results - in a RuntimeError. - """ - return _interpreters.destroy(self._id) - - # XXX Rename "run" to "exec"? - def run(self, src_str, /, channels=None): - """Run the given source code in the interpreter. - - This is essentially the same as calling the builtin "exec" - with this interpreter, using the __dict__ of its __main__ - module as both globals and locals. - - There is no return value. - - If the code raises an unhandled exception then a RunFailedError - is raised, which summarizes the unhandled exception. The actual - exception is discarded because objects cannot be shared between - interpreters. - - This blocks the current Python thread until done. During - that time, the previous interpreter is allowed to run - in other threads. - """ - excinfo = _interpreters.exec(self._id, src_str, channels) - if excinfo is not None: - raise RunFailedError(excinfo) - - -def create_channel(): +def create(): """Return (recv, send) for a new cross-interpreter channel. The channel may be used to pass data safely between interpreters. @@ -139,7 +27,7 @@ def create_channel(): return recv, send -def list_all_channels(): +def list_all(): """Return a list of (recv, send) for all open channels.""" return [(RecvChannel(cid), SendChannel(cid)) for cid in _channels.list_all()] diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py new file mode 100644 index 00000000000000..ed6b0d551dd890 --- /dev/null +++ b/Lib/test/support/interpreters/queues.py @@ -0,0 +1,156 @@ +"""Cross-interpreter Queues High Level Module.""" + +import queue +import time +import weakref +import _xxinterpchannels as _channels +import _xxinterpchannels as _queues + +# aliases: +from _xxinterpchannels import ( + ChannelError as QueueError, + ChannelNotFoundError as QueueNotFoundError, +) + +__all__ = [ + 'create', 'list_all', + 'Queue', + 'QueueError', 'QueueNotFoundError', 'QueueEmpty', 'QueueFull', +] + + +def create(maxsize=0): + """Return a new cross-interpreter queue. + + The queue may be used to pass data safely between interpreters. + """ + # XXX honor maxsize + qid = _queues.create() + return Queue._with_maxsize(qid, maxsize) + + +def list_all(): + """Return a list of all open queues.""" + return [Queue(qid) + for qid in _queues.list_all()] + + +class QueueEmpty(queue.Empty): + """Raised from get_nowait() when the queue is empty. + + It is also raised from get() if it times out. + """ + + +class QueueFull(queue.Full): + """Raised from put_nowait() when the queue is full. + + It is also raised from put() if it times out. + """ + + +_known_queues = weakref.WeakValueDictionary() + +class Queue: + """A cross-interpreter queue.""" + + @classmethod + def _with_maxsize(cls, id, maxsize): + if not isinstance(maxsize, int): + raise TypeError(f'maxsize must be an int, got {maxsize!r}') + elif maxsize < 0: + maxsize = 0 + else: + maxsize = int(maxsize) + self = cls(id) + self._maxsize = maxsize + return self + + def __new__(cls, id, /): + # There is only one instance for any given ID. + if isinstance(id, int): + id = _channels._channel_id(id, force=False) + elif not isinstance(id, _channels.ChannelID): + raise TypeError(f'id must be an int, got {id!r}') + key = int(id) + try: + self = _known_queues[key] + except KeyError: + self = super().__new__(cls) + self._id = id + self._maxsize = 0 + _known_queues[key] = self + return self + + def __repr__(self): + return f'{type(self).__name__}({self.id})' + + def __hash__(self): + return hash(self._id) + + @property + def id(self): + return int(self._id) + + @property + def maxsize(self): + return self._maxsize + + @property + def _info(self): + return _channels.get_info(self._id) + + def empty(self): + return self._info.count == 0 + + def full(self): + if self._maxsize <= 0: + return False + return self._info.count >= self._maxsize + + def qsize(self): + return self._info.count + + def put(self, obj, timeout=None): + # XXX block if full + _channels.send(self._id, obj, blocking=False) + + def put_nowait(self, obj): + # XXX raise QueueFull if full + return _channels.send(self._id, obj, blocking=False) + + def get(self, timeout=None, *, + _sentinel=object(), + _delay=10 / 1000, # 10 milliseconds + ): + """Return the next object from the queue. + + This blocks while the queue is empty. + """ + if timeout is not None: + timeout = int(timeout) + if timeout < 0: + raise ValueError(f'timeout value must be non-negative') + end = time.time() + timeout + obj = _channels.recv(self._id, _sentinel) + while obj is _sentinel: + time.sleep(_delay) + if timeout is not None and time.time() >= end: + raise QueueEmpty + obj = _channels.recv(self._id, _sentinel) + return obj + + def get_nowait(self, *, _sentinel=object()): + """Return the next object from the channel. + + If the queue is empty then raise QueueEmpty. Otherwise this + is the same as get(). + """ + obj = _channels.recv(self._id, _sentinel) + if obj is _sentinel: + raise QueueEmpty + return obj + + +# XXX add this: +#_channels._register_queue_type(Queue) diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index 2b75e2f1916c82..13c8a10296e502 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -79,8 +79,7 @@ def __new__(cls, name=None, id=None): name = 'interp' elif name == 'main': raise ValueError('name mismatch (unexpected "main")') - if not isinstance(id, interpreters.InterpreterID): - id = interpreters.InterpreterID(id) + assert isinstance(id, int), repr(id) elif not name or name == 'main': name = 'main' id = main diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py index 64a9db95e5eaf5..260ab64b07cb2d 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -15,6 +15,7 @@ interpreters = import_helper.import_module('_xxsubinterpreters') +from _xxsubinterpreters import InterpreterNotFoundError ################################## @@ -266,7 +267,7 @@ def test_main(self): main = interpreters.get_main() cur = interpreters.get_current() self.assertEqual(cur, main) - self.assertIsInstance(cur, interpreters.InterpreterID) + self.assertIsInstance(cur, int) def test_subinterpreter(self): main = interpreters.get_main() @@ -275,7 +276,7 @@ def test_subinterpreter(self): import _xxsubinterpreters as _interpreters cur = _interpreters.get_current() print(cur) - assert isinstance(cur, _interpreters.InterpreterID) + assert isinstance(cur, int) """)) cur = int(out.strip()) _, expected = interpreters.list_all() @@ -289,7 +290,7 @@ def test_from_main(self): [expected] = interpreters.list_all() main = interpreters.get_main() self.assertEqual(main, expected) - self.assertIsInstance(main, interpreters.InterpreterID) + self.assertIsInstance(main, int) def test_from_subinterpreter(self): [expected] = interpreters.list_all() @@ -298,7 +299,7 @@ def test_from_subinterpreter(self): import _xxsubinterpreters as _interpreters main = _interpreters.get_main() print(main) - assert isinstance(main, _interpreters.InterpreterID) + assert isinstance(main, int) """)) main = int(out.strip()) self.assertEqual(main, expected) @@ -333,11 +334,11 @@ def test_from_subinterpreter(self): def test_already_destroyed(self): interp = interpreters.create() interpreters.destroy(interp) - with self.assertRaises(RuntimeError): + with self.assertRaises(InterpreterNotFoundError): interpreters.is_running(interp) def test_does_not_exist(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(InterpreterNotFoundError): interpreters.is_running(1_000_000) def test_bad_id(self): @@ -345,70 +346,11 @@ def test_bad_id(self): interpreters.is_running(-1) -class InterpreterIDTests(TestBase): - - def test_with_int(self): - id = interpreters.InterpreterID(10, force=True) - - self.assertEqual(int(id), 10) - - def test_coerce_id(self): - class Int(str): - def __index__(self): - return 10 - - id = interpreters.InterpreterID(Int(), force=True) - self.assertEqual(int(id), 10) - - def test_bad_id(self): - self.assertRaises(TypeError, interpreters.InterpreterID, object()) - self.assertRaises(TypeError, interpreters.InterpreterID, 10.0) - self.assertRaises(TypeError, interpreters.InterpreterID, '10') - self.assertRaises(TypeError, interpreters.InterpreterID, b'10') - self.assertRaises(ValueError, interpreters.InterpreterID, -1) - self.assertRaises(OverflowError, interpreters.InterpreterID, 2**64) - - def test_does_not_exist(self): - id = interpreters.create() - with self.assertRaises(RuntimeError): - interpreters.InterpreterID(int(id) + 1) # unforced - - def test_str(self): - id = interpreters.InterpreterID(10, force=True) - self.assertEqual(str(id), '10') - - def test_repr(self): - id = interpreters.InterpreterID(10, force=True) - self.assertEqual(repr(id), 'InterpreterID(10)') - - def test_equality(self): - id1 = interpreters.create() - id2 = interpreters.InterpreterID(int(id1)) - id3 = interpreters.create() - - self.assertTrue(id1 == id1) - self.assertTrue(id1 == id2) - self.assertTrue(id1 == int(id1)) - self.assertTrue(int(id1) == id1) - self.assertTrue(id1 == float(int(id1))) - self.assertTrue(float(int(id1)) == id1) - self.assertFalse(id1 == float(int(id1)) + 0.1) - self.assertFalse(id1 == str(int(id1))) - self.assertFalse(id1 == 2**1000) - self.assertFalse(id1 == float('inf')) - self.assertFalse(id1 == 'spam') - self.assertFalse(id1 == id3) - - self.assertFalse(id1 != id1) - self.assertFalse(id1 != id2) - self.assertTrue(id1 != id3) - - class CreateTests(TestBase): def test_in_main(self): id = interpreters.create() - self.assertIsInstance(id, interpreters.InterpreterID) + self.assertIsInstance(id, int) self.assertIn(id, interpreters.list_all()) @@ -444,7 +386,7 @@ def test_in_subinterpreter(self): import _xxsubinterpreters as _interpreters id = _interpreters.create() print(id) - assert isinstance(id, _interpreters.InterpreterID) + assert isinstance(id, int) """)) id2 = int(out.strip()) @@ -536,11 +478,11 @@ def f(): def test_already_destroyed(self): id = interpreters.create() interpreters.destroy(id) - with self.assertRaises(RuntimeError): + with self.assertRaises(InterpreterNotFoundError): interpreters.destroy(id) def test_does_not_exist(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(InterpreterNotFoundError): interpreters.destroy(1_000_000) def test_bad_id(self): @@ -741,7 +683,7 @@ def test_does_not_exist(self): id = 0 while id in interpreters.list_all(): id += 1 - with self.assertRaises(RuntimeError): + with self.assertRaises(InterpreterNotFoundError): interpreters.run_string(id, 'print("spam")') def test_error_id(self): diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 3d86ae37190475..e6b532e858c8f9 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -1527,6 +1527,7 @@ def test_isolated_subinterpreter(self): maxtext = 250 main_interpid = 0 interpid = _interpreters.create() + self.addCleanup(lambda: _interpreters.destroy(interpid)) _interpreters.run_string(interpid, f"""if True: import json import os @@ -2020,6 +2021,137 @@ def test_module_state_shared_in_global(self): self.assertEqual(main_attr_id, subinterp_attr_id) +@requires_subinterpreters +class InterpreterIDTests(unittest.TestCase): + + InterpreterID = _testcapi.get_interpreterid_type() + + def new_interpreter(self): + def ensure_destroyed(interpid): + try: + _interpreters.destroy(interpid) + except _interpreters.InterpreterNotFoundError: + pass + id = _interpreters.create() + self.addCleanup(lambda: ensure_destroyed(id)) + return id + + def test_with_int(self): + id = self.InterpreterID(10, force=True) + + self.assertEqual(int(id), 10) + + def test_coerce_id(self): + class Int(str): + def __index__(self): + return 10 + + id = self.InterpreterID(Int(), force=True) + self.assertEqual(int(id), 10) + + def test_bad_id(self): + for badid in [ + object(), + 10.0, + '10', + b'10', + ]: + with self.subTest(badid): + with self.assertRaises(TypeError): + self.InterpreterID(badid) + + badid = -1 + with self.subTest(badid): + with self.assertRaises(ValueError): + self.InterpreterID(badid) + + badid = 2**64 + with self.subTest(badid): + with self.assertRaises(OverflowError): + self.InterpreterID(badid) + + def test_exists(self): + id = self.new_interpreter() + with self.assertRaises(_interpreters.InterpreterNotFoundError): + self.InterpreterID(int(id) + 1) # unforced + + def test_does_not_exist(self): + id = self.new_interpreter() + with self.assertRaises(_interpreters.InterpreterNotFoundError): + self.InterpreterID(int(id) + 1) # unforced + + def test_destroyed(self): + id = _interpreters.create() + _interpreters.destroy(id) + with self.assertRaises(_interpreters.InterpreterNotFoundError): + self.InterpreterID(id) # unforced + + def test_str(self): + id = self.InterpreterID(10, force=True) + self.assertEqual(str(id), '10') + + def test_repr(self): + id = self.InterpreterID(10, force=True) + self.assertEqual(repr(id), 'InterpreterID(10)') + + def test_equality(self): + id1 = self.new_interpreter() + id2 = self.InterpreterID(id1) + id3 = self.InterpreterID( + self.new_interpreter()) + + self.assertTrue(id2 == id2) # identity + self.assertTrue(id2 == id1) # int-equivalent + self.assertTrue(id1 == id2) # reversed + self.assertTrue(id2 == int(id2)) + self.assertTrue(id2 == float(int(id2))) + self.assertTrue(float(int(id2)) == id2) + self.assertFalse(id2 == float(int(id2)) + 0.1) + self.assertFalse(id2 == str(int(id2))) + self.assertFalse(id2 == 2**1000) + self.assertFalse(id2 == float('inf')) + self.assertFalse(id2 == 'spam') + self.assertFalse(id2 == id3) + + self.assertFalse(id2 != id2) + self.assertFalse(id2 != id1) + self.assertFalse(id1 != id2) + self.assertTrue(id2 != id3) + + def test_linked_lifecycle(self): + id1 = _interpreters.create() + _testcapi.unlink_interpreter_refcount(id1) + self.assertEqual( + _testinternalcapi.get_interpreter_refcount(id1), + 0) + + id2 = self.InterpreterID(id1) + self.assertEqual( + _testinternalcapi.get_interpreter_refcount(id1), + 1) + + # The interpreter isn't linked to ID objects, so it isn't destroyed. + del id2 + self.assertEqual( + _testinternalcapi.get_interpreter_refcount(id1), + 0) + + _testcapi.link_interpreter_refcount(id1) + self.assertEqual( + _testinternalcapi.get_interpreter_refcount(id1), + 0) + + id3 = self.InterpreterID(id1) + self.assertEqual( + _testinternalcapi.get_interpreter_refcount(id1), + 1) + + # The interpreter is linked now so is destroyed. + del id3 + with self.assertRaises(_interpreters.InterpreterNotFoundError): + _testinternalcapi.get_interpreter_refcount(id1) + + class BuiltinStaticTypesTests(unittest.TestCase): TYPES = [ diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index bbfbb57b1d8299..48c0a43f29e27f 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1971,6 +1971,7 @@ def test_disallowed_reimport(self): print(_testsinglephase) ''') interpid = _interpreters.create() + self.addCleanup(lambda: _interpreters.destroy(interpid)) excsnap = _interpreters.run_string(interpid, script) self.assertIsNot(excsnap, None) @@ -2105,12 +2106,18 @@ def re_load(self, name, mod): def add_subinterpreter(self): interpid = _interpreters.create(isolated=False) - _interpreters.run_string(interpid, textwrap.dedent(''' + def ensure_destroyed(): + try: + _interpreters.destroy(interpid) + except _interpreters.InterpreterNotFoundError: + pass + self.addCleanup(ensure_destroyed) + _interpreters.exec(interpid, textwrap.dedent(''' import sys import _testinternalcapi ''')) def clean_up(): - _interpreters.run_string(interpid, textwrap.dedent(f''' + _interpreters.exec(interpid, textwrap.dedent(f''' name = {self.NAME!r} if name in sys.modules: sys.modules.pop(name)._clear_globals() diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 914176559806f4..fe5e7b31d9c32b 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -657,14 +657,26 @@ class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): def run_with_own_gil(self, script): interpid = _interpreters.create(isolated=True) - excsnap = _interpreters.run_string(interpid, script) + def ensure_destroyed(): + try: + _interpreters.destroy(interpid) + except _interpreters.InterpreterNotFoundError: + pass + self.addCleanup(ensure_destroyed) + excsnap = _interpreters.exec(interpid, script) if excsnap is not None: if excsnap.type.__name__ == 'ImportError': raise ImportError(excsnap.msg) def run_with_shared_gil(self, script): interpid = _interpreters.create(isolated=False) - excsnap = _interpreters.run_string(interpid, script) + def ensure_destroyed(): + try: + _interpreters.destroy(interpid) + except _interpreters.InterpreterNotFoundError: + pass + self.addCleanup(ensure_destroyed) + excsnap = _interpreters.exec(interpid, script) if excsnap is not None: if excsnap.type.__name__ == 'ImportError': raise ImportError(excsnap.msg) diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py deleted file mode 100644 index 5663706c0ccfb7..00000000000000 --- a/Lib/test/test_interpreters.py +++ /dev/null @@ -1,1136 +0,0 @@ -import contextlib -import json -import os -import os.path -import sys -import threading -from textwrap import dedent -import unittest -import time - -from test import support -from test.support import import_helper -from test.support import threading_helper -from test.support import os_helper -_interpreters = import_helper.import_module('_xxsubinterpreters') -_channels = import_helper.import_module('_xxinterpchannels') -from test.support import interpreters - - -def _captured_script(script): - r, w = os.pipe() - indented = script.replace('\n', '\n ') - wrapped = dedent(f""" - import contextlib - with open({w}, 'w', encoding='utf-8') as spipe: - with contextlib.redirect_stdout(spipe): - {indented} - """) - return wrapped, open(r, encoding='utf-8') - - -def clean_up_interpreters(): - for interp in interpreters.list_all(): - if interp.id == 0: # main - continue - try: - interp.close() - except RuntimeError: - pass # already destroyed - - -def _run_output(interp, request, channels=None): - script, rpipe = _captured_script(request) - with rpipe: - interp.run(script, channels=channels) - return rpipe.read() - - -@contextlib.contextmanager -def _running(interp): - r, w = os.pipe() - def run(): - interp.run(dedent(f""" - # wait for "signal" - with open({r}) as rpipe: - rpipe.read() - """)) - - t = threading.Thread(target=run) - t.start() - - yield - - with open(w, 'w') as spipe: - spipe.write('done') - t.join() - - -class TestBase(unittest.TestCase): - - def pipe(self): - def ensure_closed(fd): - try: - os.close(fd) - except OSError: - pass - r, w = os.pipe() - self.addCleanup(lambda: ensure_closed(r)) - self.addCleanup(lambda: ensure_closed(w)) - return r, w - - def tearDown(self): - clean_up_interpreters() - - -class CreateTests(TestBase): - - def test_in_main(self): - interp = interpreters.create() - self.assertIsInstance(interp, interpreters.Interpreter) - self.assertIn(interp, interpreters.list_all()) - - def test_in_thread(self): - lock = threading.Lock() - interp = None - def f(): - nonlocal interp - interp = interpreters.create() - lock.acquire() - lock.release() - t = threading.Thread(target=f) - with lock: - t.start() - t.join() - self.assertIn(interp, interpreters.list_all()) - - def test_in_subinterpreter(self): - main, = interpreters.list_all() - interp = interpreters.create() - out = _run_output(interp, dedent(""" - from test.support import interpreters - interp = interpreters.create() - print(interp.id) - """)) - interp2 = interpreters.Interpreter(int(out)) - self.assertEqual(interpreters.list_all(), [main, interp, interp2]) - - def test_after_destroy_all(self): - before = set(interpreters.list_all()) - # Create 3 subinterpreters. - interp_lst = [] - for _ in range(3): - interps = interpreters.create() - interp_lst.append(interps) - # Now destroy them. - for interp in interp_lst: - interp.close() - # Finally, create another. - interp = interpreters.create() - self.assertEqual(set(interpreters.list_all()), before | {interp}) - - def test_after_destroy_some(self): - before = set(interpreters.list_all()) - # Create 3 subinterpreters. - interp1 = interpreters.create() - interp2 = interpreters.create() - interp3 = interpreters.create() - # Now destroy 2 of them. - interp1.close() - interp2.close() - # Finally, create another. - interp = interpreters.create() - self.assertEqual(set(interpreters.list_all()), before | {interp3, interp}) - - -class GetCurrentTests(TestBase): - - def test_main(self): - main = interpreters.get_main() - current = interpreters.get_current() - self.assertEqual(current, main) - - def test_subinterpreter(self): - main = _interpreters.get_main() - interp = interpreters.create() - out = _run_output(interp, dedent(""" - from test.support import interpreters - cur = interpreters.get_current() - print(cur.id) - """)) - current = interpreters.Interpreter(int(out)) - self.assertNotEqual(current, main) - - -class ListAllTests(TestBase): - - def test_initial(self): - interps = interpreters.list_all() - self.assertEqual(1, len(interps)) - - def test_after_creating(self): - main = interpreters.get_current() - first = interpreters.create() - second = interpreters.create() - - ids = [] - for interp in interpreters.list_all(): - ids.append(interp.id) - - self.assertEqual(ids, [main.id, first.id, second.id]) - - def test_after_destroying(self): - main = interpreters.get_current() - first = interpreters.create() - second = interpreters.create() - first.close() - - ids = [] - for interp in interpreters.list_all(): - ids.append(interp.id) - - self.assertEqual(ids, [main.id, second.id]) - - -class TestInterpreterAttrs(TestBase): - - def test_id_type(self): - main = interpreters.get_main() - current = interpreters.get_current() - interp = interpreters.create() - self.assertIsInstance(main.id, _interpreters.InterpreterID) - self.assertIsInstance(current.id, _interpreters.InterpreterID) - self.assertIsInstance(interp.id, _interpreters.InterpreterID) - - def test_main_id(self): - main = interpreters.get_main() - self.assertEqual(main.id, 0) - - def test_custom_id(self): - interp = interpreters.Interpreter(1) - self.assertEqual(interp.id, 1) - - with self.assertRaises(TypeError): - interpreters.Interpreter('1') - - def test_id_readonly(self): - interp = interpreters.Interpreter(1) - with self.assertRaises(AttributeError): - interp.id = 2 - - @unittest.skip('not ready yet (see bpo-32604)') - def test_main_isolated(self): - main = interpreters.get_main() - self.assertFalse(main.isolated) - - @unittest.skip('not ready yet (see bpo-32604)') - def test_subinterpreter_isolated_default(self): - interp = interpreters.create() - self.assertFalse(interp.isolated) - - def test_subinterpreter_isolated_explicit(self): - interp1 = interpreters.create(isolated=True) - interp2 = interpreters.create(isolated=False) - self.assertTrue(interp1.isolated) - self.assertFalse(interp2.isolated) - - @unittest.skip('not ready yet (see bpo-32604)') - def test_custom_isolated_default(self): - interp = interpreters.Interpreter(1) - self.assertFalse(interp.isolated) - - def test_custom_isolated_explicit(self): - interp1 = interpreters.Interpreter(1, isolated=True) - interp2 = interpreters.Interpreter(1, isolated=False) - self.assertTrue(interp1.isolated) - self.assertFalse(interp2.isolated) - - def test_isolated_readonly(self): - interp = interpreters.Interpreter(1) - with self.assertRaises(AttributeError): - interp.isolated = True - - def test_equality(self): - interp1 = interpreters.create() - interp2 = interpreters.create() - self.assertEqual(interp1, interp1) - self.assertNotEqual(interp1, interp2) - - -class TestInterpreterIsRunning(TestBase): - - def test_main(self): - main = interpreters.get_main() - self.assertTrue(main.is_running()) - - @unittest.skip('Fails on FreeBSD') - def test_subinterpreter(self): - interp = interpreters.create() - self.assertFalse(interp.is_running()) - - with _running(interp): - self.assertTrue(interp.is_running()) - self.assertFalse(interp.is_running()) - - def test_finished(self): - r, w = self.pipe() - interp = interpreters.create() - interp.run(f"""if True: - import os - os.write({w}, b'x') - """) - self.assertFalse(interp.is_running()) - self.assertEqual(os.read(r, 1), b'x') - - def test_from_subinterpreter(self): - interp = interpreters.create() - out = _run_output(interp, dedent(f""" - import _xxsubinterpreters as _interpreters - if _interpreters.is_running({interp.id}): - print(True) - else: - print(False) - """)) - self.assertEqual(out.strip(), 'True') - - def test_already_destroyed(self): - interp = interpreters.create() - interp.close() - with self.assertRaises(RuntimeError): - interp.is_running() - - def test_does_not_exist(self): - interp = interpreters.Interpreter(1_000_000) - with self.assertRaises(RuntimeError): - interp.is_running() - - def test_bad_id(self): - interp = interpreters.Interpreter(-1) - with self.assertRaises(ValueError): - interp.is_running() - - def test_with_only_background_threads(self): - r_interp, w_interp = self.pipe() - r_thread, w_thread = self.pipe() - - DONE = b'D' - FINISHED = b'F' - - interp = interpreters.create() - interp.run(f"""if True: - import os - import threading - - def task(): - v = os.read({r_thread}, 1) - assert v == {DONE!r} - os.write({w_interp}, {FINISHED!r}) - t = threading.Thread(target=task) - t.start() - """) - self.assertFalse(interp.is_running()) - - os.write(w_thread, DONE) - interp.run('t.join()') - self.assertEqual(os.read(r_interp, 1), FINISHED) - - -class TestInterpreterClose(TestBase): - - def test_basic(self): - main = interpreters.get_main() - interp1 = interpreters.create() - interp2 = interpreters.create() - interp3 = interpreters.create() - self.assertEqual(set(interpreters.list_all()), - {main, interp1, interp2, interp3}) - interp2.close() - self.assertEqual(set(interpreters.list_all()), - {main, interp1, interp3}) - - def test_all(self): - before = set(interpreters.list_all()) - interps = set() - for _ in range(3): - interp = interpreters.create() - interps.add(interp) - self.assertEqual(set(interpreters.list_all()), before | interps) - for interp in interps: - interp.close() - self.assertEqual(set(interpreters.list_all()), before) - - def test_main(self): - main, = interpreters.list_all() - with self.assertRaises(RuntimeError): - main.close() - - def f(): - with self.assertRaises(RuntimeError): - main.close() - - t = threading.Thread(target=f) - t.start() - t.join() - - def test_already_destroyed(self): - interp = interpreters.create() - interp.close() - with self.assertRaises(RuntimeError): - interp.close() - - def test_does_not_exist(self): - interp = interpreters.Interpreter(1_000_000) - with self.assertRaises(RuntimeError): - interp.close() - - def test_bad_id(self): - interp = interpreters.Interpreter(-1) - with self.assertRaises(ValueError): - interp.close() - - def test_from_current(self): - main, = interpreters.list_all() - interp = interpreters.create() - out = _run_output(interp, dedent(f""" - from test.support import interpreters - interp = interpreters.Interpreter({int(interp.id)}) - try: - interp.close() - except RuntimeError: - print('failed') - """)) - self.assertEqual(out.strip(), 'failed') - self.assertEqual(set(interpreters.list_all()), {main, interp}) - - def test_from_sibling(self): - main, = interpreters.list_all() - interp1 = interpreters.create() - interp2 = interpreters.create() - self.assertEqual(set(interpreters.list_all()), - {main, interp1, interp2}) - interp1.run(dedent(f""" - from test.support import interpreters - interp2 = interpreters.Interpreter(int({interp2.id})) - interp2.close() - interp3 = interpreters.create() - interp3.close() - """)) - self.assertEqual(set(interpreters.list_all()), {main, interp1}) - - def test_from_other_thread(self): - interp = interpreters.create() - def f(): - interp.close() - - t = threading.Thread(target=f) - t.start() - t.join() - - @unittest.skip('Fails on FreeBSD') - def test_still_running(self): - main, = interpreters.list_all() - interp = interpreters.create() - with _running(interp): - with self.assertRaises(RuntimeError): - interp.close() - self.assertTrue(interp.is_running()) - - def test_subthreads_still_running(self): - r_interp, w_interp = self.pipe() - r_thread, w_thread = self.pipe() - - FINISHED = b'F' - - interp = interpreters.create() - interp.run(f"""if True: - import os - import threading - import time - - done = False - - def notify_fini(): - global done - done = True - t.join() - threading._register_atexit(notify_fini) - - def task(): - while not done: - time.sleep(0.1) - os.write({w_interp}, {FINISHED!r}) - t = threading.Thread(target=task) - t.start() - """) - interp.close() - - self.assertEqual(os.read(r_interp, 1), FINISHED) - - -class TestInterpreterRun(TestBase): - - def test_success(self): - interp = interpreters.create() - script, file = _captured_script('print("it worked!", end="")') - with file: - interp.run(script) - out = file.read() - - self.assertEqual(out, 'it worked!') - - def test_failure(self): - interp = interpreters.create() - with self.assertRaises(interpreters.RunFailedError): - interp.run('raise Exception') - - def test_in_thread(self): - interp = interpreters.create() - script, file = _captured_script('print("it worked!", end="")') - with file: - def f(): - interp.run(script) - - t = threading.Thread(target=f) - t.start() - t.join() - out = file.read() - - self.assertEqual(out, 'it worked!') - - @support.requires_fork() - def test_fork(self): - interp = interpreters.create() - import tempfile - with tempfile.NamedTemporaryFile('w+', encoding='utf-8') as file: - file.write('') - file.flush() - - expected = 'spam spam spam spam spam' - script = dedent(f""" - import os - try: - os.fork() - except RuntimeError: - with open('{file.name}', 'w', encoding='utf-8') as out: - out.write('{expected}') - """) - interp.run(script) - - file.seek(0) - content = file.read() - self.assertEqual(content, expected) - - @unittest.skip('Fails on FreeBSD') - def test_already_running(self): - interp = interpreters.create() - with _running(interp): - with self.assertRaises(RuntimeError): - interp.run('print("spam")') - - def test_does_not_exist(self): - interp = interpreters.Interpreter(1_000_000) - with self.assertRaises(RuntimeError): - interp.run('print("spam")') - - def test_bad_id(self): - interp = interpreters.Interpreter(-1) - with self.assertRaises(ValueError): - interp.run('print("spam")') - - def test_bad_script(self): - interp = interpreters.create() - with self.assertRaises(TypeError): - interp.run(10) - - def test_bytes_for_script(self): - interp = interpreters.create() - with self.assertRaises(TypeError): - interp.run(b'print("spam")') - - def test_with_background_threads_still_running(self): - r_interp, w_interp = self.pipe() - r_thread, w_thread = self.pipe() - - RAN = b'R' - DONE = b'D' - FINISHED = b'F' - - interp = interpreters.create() - interp.run(f"""if True: - import os - import threading - - def task(): - v = os.read({r_thread}, 1) - assert v == {DONE!r} - os.write({w_interp}, {FINISHED!r}) - t = threading.Thread(target=task) - t.start() - os.write({w_interp}, {RAN!r}) - """) - interp.run(f"""if True: - os.write({w_interp}, {RAN!r}) - """) - - os.write(w_thread, DONE) - interp.run('t.join()') - self.assertEqual(os.read(r_interp, 1), RAN) - self.assertEqual(os.read(r_interp, 1), RAN) - self.assertEqual(os.read(r_interp, 1), FINISHED) - - # test_xxsubinterpreters covers the remaining Interpreter.run() behavior. - - -class StressTests(TestBase): - - # In these tests we generally want a lot of interpreters, - # but not so many that any test takes too long. - - @support.requires_resource('cpu') - def test_create_many_sequential(self): - alive = [] - for _ in range(100): - interp = interpreters.create() - alive.append(interp) - - @support.requires_resource('cpu') - def test_create_many_threaded(self): - alive = [] - def task(): - interp = interpreters.create() - alive.append(interp) - threads = (threading.Thread(target=task) for _ in range(200)) - with threading_helper.start_threads(threads): - pass - - -class StartupTests(TestBase): - - # We want to ensure the initial state of subinterpreters - # matches expectations. - - _subtest_count = 0 - - @contextlib.contextmanager - def subTest(self, *args): - with super().subTest(*args) as ctx: - self._subtest_count += 1 - try: - yield ctx - finally: - if self._debugged_in_subtest: - if self._subtest_count == 1: - # The first subtest adds a leading newline, so we - # compensate here by not printing a trailing newline. - print('### end subtest debug ###', end='') - else: - print('### end subtest debug ###') - self._debugged_in_subtest = False - - def debug(self, msg, *, header=None): - if header: - self._debug(f'--- {header} ---') - if msg: - if msg.endswith(os.linesep): - self._debug(msg[:-len(os.linesep)]) - else: - self._debug(msg) - self._debug('') - self._debug('------') - else: - self._debug(msg) - - _debugged = False - _debugged_in_subtest = False - def _debug(self, msg): - if not self._debugged: - print() - self._debugged = True - if self._subtest is not None: - if True: - if not self._debugged_in_subtest: - self._debugged_in_subtest = True - print('### start subtest debug ###') - print(msg) - else: - print(msg) - - def create_temp_dir(self): - import tempfile - tmp = tempfile.mkdtemp(prefix='test_interpreters_') - tmp = os.path.realpath(tmp) - self.addCleanup(os_helper.rmtree, tmp) - return tmp - - def write_script(self, *path, text): - filename = os.path.join(*path) - dirname = os.path.dirname(filename) - if dirname: - os.makedirs(dirname, exist_ok=True) - with open(filename, 'w', encoding='utf-8') as outfile: - outfile.write(dedent(text)) - return filename - - @support.requires_subprocess() - def run_python(self, argv, *, cwd=None): - # This method is inspired by - # EmbeddingTestsMixin.run_embedded_interpreter() in test_embed.py. - import shlex - import subprocess - if isinstance(argv, str): - argv = shlex.split(argv) - argv = [sys.executable, *argv] - try: - proc = subprocess.run( - argv, - cwd=cwd, - capture_output=True, - text=True, - ) - except Exception as exc: - self.debug(f'# cmd: {shlex.join(argv)}') - if isinstance(exc, FileNotFoundError) and not exc.filename: - if os.path.exists(argv[0]): - exists = 'exists' - else: - exists = 'does not exist' - self.debug(f'{argv[0]} {exists}') - raise # re-raise - assert proc.stderr == '' or proc.returncode != 0, proc.stderr - if proc.returncode != 0 and support.verbose: - self.debug(f'# python3 {shlex.join(argv[1:])} failed:') - self.debug(proc.stdout, header='stdout') - self.debug(proc.stderr, header='stderr') - self.assertEqual(proc.returncode, 0) - self.assertEqual(proc.stderr, '') - return proc.stdout - - def test_sys_path_0(self): - # The main interpreter's sys.path[0] should be used by subinterpreters. - script = ''' - import sys - from test.support import interpreters - - orig = sys.path[0] - - interp = interpreters.create() - interp.run(f"""if True: - import json - import sys - print(json.dumps({{ - 'main': {orig!r}, - 'sub': sys.path[0], - }}, indent=4), flush=True) - """) - ''' - # / - # pkg/ - # __init__.py - # __main__.py - # script.py - # script.py - cwd = self.create_temp_dir() - self.write_script(cwd, 'pkg', '__init__.py', text='') - self.write_script(cwd, 'pkg', '__main__.py', text=script) - self.write_script(cwd, 'pkg', 'script.py', text=script) - self.write_script(cwd, 'script.py', text=script) - - cases = [ - ('script.py', cwd), - ('-m script', cwd), - ('-m pkg', cwd), - ('-m pkg.script', cwd), - ('-c "import script"', ''), - ] - for argv, expected in cases: - with self.subTest(f'python3 {argv}'): - out = self.run_python(argv, cwd=cwd) - data = json.loads(out) - sp0_main, sp0_sub = data['main'], data['sub'] - self.assertEqual(sp0_sub, sp0_main) - self.assertEqual(sp0_sub, expected) - # XXX Also check them all with the -P cmdline flag? - - -class FinalizationTests(TestBase): - - def test_gh_109793(self): - import subprocess - argv = [sys.executable, '-c', '''if True: - import _xxsubinterpreters as _interpreters - interpid = _interpreters.create() - raise Exception - '''] - proc = subprocess.run(argv, capture_output=True, text=True) - self.assertIn('Traceback', proc.stderr) - if proc.returncode == 0 and support.verbose: - print() - print("--- cmd unexpected succeeded ---") - print(f"stdout:\n{proc.stdout}") - print(f"stderr:\n{proc.stderr}") - print("------") - self.assertEqual(proc.returncode, 1) - - -class TestIsShareable(TestBase): - - def test_default_shareables(self): - shareables = [ - # singletons - None, - # builtin objects - b'spam', - 'spam', - 10, - -10, - True, - False, - 100.0, - (), - (1, ('spam', 'eggs'), True), - ] - for obj in shareables: - with self.subTest(obj): - shareable = interpreters.is_shareable(obj) - self.assertTrue(shareable) - - def test_not_shareable(self): - class Cheese: - def __init__(self, name): - self.name = name - def __str__(self): - return self.name - - class SubBytes(bytes): - """A subclass of a shareable type.""" - - not_shareables = [ - # singletons - NotImplemented, - ..., - # builtin types and objects - type, - object, - object(), - Exception(), - # user-defined types and objects - Cheese, - Cheese('Wensleydale'), - SubBytes(b'spam'), - ] - for obj in not_shareables: - with self.subTest(repr(obj)): - self.assertFalse( - interpreters.is_shareable(obj)) - - -class TestChannels(TestBase): - - def test_create(self): - r, s = interpreters.create_channel() - self.assertIsInstance(r, interpreters.RecvChannel) - self.assertIsInstance(s, interpreters.SendChannel) - - def test_list_all(self): - self.assertEqual(interpreters.list_all_channels(), []) - created = set() - for _ in range(3): - ch = interpreters.create_channel() - created.add(ch) - after = set(interpreters.list_all_channels()) - self.assertEqual(after, created) - - def test_shareable(self): - rch, sch = interpreters.create_channel() - - self.assertTrue( - interpreters.is_shareable(rch)) - self.assertTrue( - interpreters.is_shareable(sch)) - - sch.send_nowait(rch) - sch.send_nowait(sch) - rch2 = rch.recv() - sch2 = rch.recv() - - self.assertEqual(rch2, rch) - self.assertEqual(sch2, sch) - - def test_is_closed(self): - rch, sch = interpreters.create_channel() - rbefore = rch.is_closed - sbefore = sch.is_closed - rch.close() - rafter = rch.is_closed - safter = sch.is_closed - - self.assertFalse(rbefore) - self.assertFalse(sbefore) - self.assertTrue(rafter) - self.assertTrue(safter) - - -class TestRecvChannelAttrs(TestBase): - - def test_id_type(self): - rch, _ = interpreters.create_channel() - self.assertIsInstance(rch.id, _channels.ChannelID) - - def test_custom_id(self): - rch = interpreters.RecvChannel(1) - self.assertEqual(rch.id, 1) - - with self.assertRaises(TypeError): - interpreters.RecvChannel('1') - - def test_id_readonly(self): - rch = interpreters.RecvChannel(1) - with self.assertRaises(AttributeError): - rch.id = 2 - - def test_equality(self): - ch1, _ = interpreters.create_channel() - ch2, _ = interpreters.create_channel() - self.assertEqual(ch1, ch1) - self.assertNotEqual(ch1, ch2) - - -class TestSendChannelAttrs(TestBase): - - def test_id_type(self): - _, sch = interpreters.create_channel() - self.assertIsInstance(sch.id, _channels.ChannelID) - - def test_custom_id(self): - sch = interpreters.SendChannel(1) - self.assertEqual(sch.id, 1) - - with self.assertRaises(TypeError): - interpreters.SendChannel('1') - - def test_id_readonly(self): - sch = interpreters.SendChannel(1) - with self.assertRaises(AttributeError): - sch.id = 2 - - def test_equality(self): - _, ch1 = interpreters.create_channel() - _, ch2 = interpreters.create_channel() - self.assertEqual(ch1, ch1) - self.assertNotEqual(ch1, ch2) - - -class TestSendRecv(TestBase): - - def test_send_recv_main(self): - r, s = interpreters.create_channel() - orig = b'spam' - s.send_nowait(orig) - obj = r.recv() - - self.assertEqual(obj, orig) - self.assertIsNot(obj, orig) - - def test_send_recv_same_interpreter(self): - interp = interpreters.create() - interp.run(dedent(""" - from test.support import interpreters - r, s = interpreters.create_channel() - orig = b'spam' - s.send_nowait(orig) - obj = r.recv() - assert obj == orig, 'expected: obj == orig' - assert obj is not orig, 'expected: obj is not orig' - """)) - - @unittest.skip('broken (see BPO-...)') - def test_send_recv_different_interpreters(self): - r1, s1 = interpreters.create_channel() - r2, s2 = interpreters.create_channel() - orig1 = b'spam' - s1.send_nowait(orig1) - out = _run_output( - interpreters.create(), - dedent(f""" - obj1 = r.recv() - assert obj1 == b'spam', 'expected: obj1 == orig1' - # When going to another interpreter we get a copy. - assert id(obj1) != {id(orig1)}, 'expected: obj1 is not orig1' - orig2 = b'eggs' - print(id(orig2)) - s.send_nowait(orig2) - """), - channels=dict(r=r1, s=s2), - ) - obj2 = r2.recv() - - self.assertEqual(obj2, b'eggs') - self.assertNotEqual(id(obj2), int(out)) - - def test_send_recv_different_threads(self): - r, s = interpreters.create_channel() - - def f(): - while True: - try: - obj = r.recv() - break - except interpreters.ChannelEmptyError: - time.sleep(0.1) - s.send(obj) - t = threading.Thread(target=f) - t.start() - - orig = b'spam' - s.send(orig) - obj = r.recv() - t.join() - - self.assertEqual(obj, orig) - self.assertIsNot(obj, orig) - - def test_send_recv_nowait_main(self): - r, s = interpreters.create_channel() - orig = b'spam' - s.send_nowait(orig) - obj = r.recv_nowait() - - self.assertEqual(obj, orig) - self.assertIsNot(obj, orig) - - def test_send_recv_nowait_main_with_default(self): - r, _ = interpreters.create_channel() - obj = r.recv_nowait(None) - - self.assertIsNone(obj) - - def test_send_recv_nowait_same_interpreter(self): - interp = interpreters.create() - interp.run(dedent(""" - from test.support import interpreters - r, s = interpreters.create_channel() - orig = b'spam' - s.send_nowait(orig) - obj = r.recv_nowait() - assert obj == orig, 'expected: obj == orig' - # When going back to the same interpreter we get the same object. - assert obj is not orig, 'expected: obj is not orig' - """)) - - @unittest.skip('broken (see BPO-...)') - def test_send_recv_nowait_different_interpreters(self): - r1, s1 = interpreters.create_channel() - r2, s2 = interpreters.create_channel() - orig1 = b'spam' - s1.send_nowait(orig1) - out = _run_output( - interpreters.create(), - dedent(f""" - obj1 = r.recv_nowait() - assert obj1 == b'spam', 'expected: obj1 == orig1' - # When going to another interpreter we get a copy. - assert id(obj1) != {id(orig1)}, 'expected: obj1 is not orig1' - orig2 = b'eggs' - print(id(orig2)) - s.send_nowait(orig2) - """), - channels=dict(r=r1, s=s2), - ) - obj2 = r2.recv_nowait() - - self.assertEqual(obj2, b'eggs') - self.assertNotEqual(id(obj2), int(out)) - - def test_recv_timeout(self): - r, _ = interpreters.create_channel() - with self.assertRaises(TimeoutError): - r.recv(timeout=1) - - def test_recv_channel_does_not_exist(self): - ch = interpreters.RecvChannel(1_000_000) - with self.assertRaises(interpreters.ChannelNotFoundError): - ch.recv() - - def test_send_channel_does_not_exist(self): - ch = interpreters.SendChannel(1_000_000) - with self.assertRaises(interpreters.ChannelNotFoundError): - ch.send(b'spam') - - def test_recv_nowait_channel_does_not_exist(self): - ch = interpreters.RecvChannel(1_000_000) - with self.assertRaises(interpreters.ChannelNotFoundError): - ch.recv_nowait() - - def test_send_nowait_channel_does_not_exist(self): - ch = interpreters.SendChannel(1_000_000) - with self.assertRaises(interpreters.ChannelNotFoundError): - ch.send_nowait(b'spam') - - def test_recv_nowait_empty(self): - ch, _ = interpreters.create_channel() - with self.assertRaises(interpreters.ChannelEmptyError): - ch.recv_nowait() - - def test_recv_nowait_default(self): - default = object() - rch, sch = interpreters.create_channel() - obj1 = rch.recv_nowait(default) - sch.send_nowait(None) - sch.send_nowait(1) - sch.send_nowait(b'spam') - sch.send_nowait(b'eggs') - obj2 = rch.recv_nowait(default) - obj3 = rch.recv_nowait(default) - obj4 = rch.recv_nowait() - obj5 = rch.recv_nowait(default) - obj6 = rch.recv_nowait(default) - - self.assertIs(obj1, default) - self.assertIs(obj2, None) - self.assertEqual(obj3, 1) - self.assertEqual(obj4, b'spam') - self.assertEqual(obj5, b'eggs') - self.assertIs(obj6, default) - - def test_send_buffer(self): - buf = bytearray(b'spamspamspam') - obj = None - rch, sch = interpreters.create_channel() - - def f(): - nonlocal obj - while True: - try: - obj = rch.recv() - break - except interpreters.ChannelEmptyError: - time.sleep(0.1) - t = threading.Thread(target=f) - t.start() - - sch.send_buffer(buf) - t.join() - - self.assertIsNot(obj, buf) - self.assertIsInstance(obj, memoryview) - self.assertEqual(obj, buf) - - buf[4:8] = b'eggs' - self.assertEqual(obj, buf) - obj[4:8] = b'ham.' - self.assertEqual(obj, buf) - - def test_send_buffer_nowait(self): - buf = bytearray(b'spamspamspam') - rch, sch = interpreters.create_channel() - sch.send_buffer_nowait(buf) - obj = rch.recv() - - self.assertIsNot(obj, buf) - self.assertIsInstance(obj, memoryview) - self.assertEqual(obj, buf) - - buf[4:8] = b'eggs' - self.assertEqual(obj, buf) - obj[4:8] = b'ham.' - self.assertEqual(obj, buf) diff --git a/Lib/test/test_interpreters/__init__.py b/Lib/test/test_interpreters/__init__.py new file mode 100644 index 00000000000000..4b16ecc31156a5 --- /dev/null +++ b/Lib/test/test_interpreters/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_interpreters/__main__.py b/Lib/test/test_interpreters/__main__.py new file mode 100644 index 00000000000000..8641229877b2be --- /dev/null +++ b/Lib/test/test_interpreters/__main__.py @@ -0,0 +1,4 @@ +from . import load_tests +import unittest + +nittest.main() diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py new file mode 100644 index 00000000000000..e4ae9d005b5282 --- /dev/null +++ b/Lib/test/test_interpreters/test_api.py @@ -0,0 +1,642 @@ +import os +import threading +from textwrap import dedent +import unittest + +from test import support +from test.support import import_helper +# Raise SkipTest if subinterpreters not supported. +import_helper.import_module('_xxsubinterpreters') +from test.support import interpreters +from test.support.interpreters import InterpreterNotFoundError +from .utils import _captured_script, _run_output, _running, TestBase + + +class ModuleTests(TestBase): + + def test_queue_aliases(self): + first = [ + interpreters.create_queue, + interpreters.Queue, + interpreters.QueueEmpty, + interpreters.QueueFull, + ] + second = [ + interpreters.create_queue, + interpreters.Queue, + interpreters.QueueEmpty, + interpreters.QueueFull, + ] + self.assertEqual(second, first) + + +class CreateTests(TestBase): + + def test_in_main(self): + interp = interpreters.create() + self.assertIsInstance(interp, interpreters.Interpreter) + self.assertIn(interp, interpreters.list_all()) + + def test_in_thread(self): + lock = threading.Lock() + interp = None + def f(): + nonlocal interp + interp = interpreters.create() + lock.acquire() + lock.release() + t = threading.Thread(target=f) + with lock: + t.start() + t.join() + self.assertIn(interp, interpreters.list_all()) + + def test_in_subinterpreter(self): + main, = interpreters.list_all() + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import interpreters + interp = interpreters.create() + print(interp.id) + """)) + interp2 = interpreters.Interpreter(int(out)) + self.assertEqual(interpreters.list_all(), [main, interp, interp2]) + + def test_after_destroy_all(self): + before = set(interpreters.list_all()) + # Create 3 subinterpreters. + interp_lst = [] + for _ in range(3): + interps = interpreters.create() + interp_lst.append(interps) + # Now destroy them. + for interp in interp_lst: + interp.close() + # Finally, create another. + interp = interpreters.create() + self.assertEqual(set(interpreters.list_all()), before | {interp}) + + def test_after_destroy_some(self): + before = set(interpreters.list_all()) + # Create 3 subinterpreters. + interp1 = interpreters.create() + interp2 = interpreters.create() + interp3 = interpreters.create() + # Now destroy 2 of them. + interp1.close() + interp2.close() + # Finally, create another. + interp = interpreters.create() + self.assertEqual(set(interpreters.list_all()), before | {interp3, interp}) + + +class GetMainTests(TestBase): + + def test_id(self): + main = interpreters.get_main() + self.assertEqual(main.id, 0) + + def test_current(self): + main = interpreters.get_main() + current = interpreters.get_current() + self.assertIs(main, current) + + def test_idempotent(self): + main1 = interpreters.get_main() + main2 = interpreters.get_main() + self.assertIs(main1, main2) + + +class GetCurrentTests(TestBase): + + def test_main(self): + main = interpreters.get_main() + current = interpreters.get_current() + self.assertEqual(current, main) + + def test_subinterpreter(self): + main = interpreters.get_main() + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import interpreters + cur = interpreters.get_current() + print(cur.id) + """)) + current = interpreters.Interpreter(int(out)) + self.assertEqual(current, interp) + self.assertNotEqual(current, main) + + def test_idempotent(self): + with self.subTest('main'): + cur1 = interpreters.get_current() + cur2 = interpreters.get_current() + self.assertIs(cur1, cur2) + + with self.subTest('subinterpreter'): + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import interpreters + cur = interpreters.get_current() + print(id(cur)) + cur = interpreters.get_current() + print(id(cur)) + """)) + objid1, objid2 = (int(v) for v in out.splitlines()) + self.assertEqual(objid1, objid2) + + with self.subTest('per-interpreter'): + interp = interpreters.create() + out = _run_output(interp, dedent(""" + from test.support import interpreters + cur = interpreters.get_current() + print(id(cur)) + """)) + id1 = int(out) + id2 = id(interp) + self.assertNotEqual(id1, id2) + + +class ListAllTests(TestBase): + + def test_initial(self): + interps = interpreters.list_all() + self.assertEqual(1, len(interps)) + + def test_after_creating(self): + main = interpreters.get_current() + first = interpreters.create() + second = interpreters.create() + + ids = [] + for interp in interpreters.list_all(): + ids.append(interp.id) + + self.assertEqual(ids, [main.id, first.id, second.id]) + + def test_after_destroying(self): + main = interpreters.get_current() + first = interpreters.create() + second = interpreters.create() + first.close() + + ids = [] + for interp in interpreters.list_all(): + ids.append(interp.id) + + self.assertEqual(ids, [main.id, second.id]) + + def test_idempotent(self): + main = interpreters.get_current() + first = interpreters.create() + second = interpreters.create() + expected = [main, first, second] + + actual = interpreters.list_all() + + self.assertEqual(actual, expected) + for interp1, interp2 in zip(actual, expected): + self.assertIs(interp1, interp2) + + +class InterpreterObjectTests(TestBase): + + def test_init_int(self): + interpid = interpreters.get_current().id + interp = interpreters.Interpreter(interpid) + self.assertEqual(interp.id, interpid) + + def test_init_interpreter_id(self): + interpid = interpreters.get_current()._id + interp = interpreters.Interpreter(interpid) + self.assertEqual(interp._id, interpid) + + def test_init_unsupported(self): + actualid = interpreters.get_current().id + for interpid in [ + str(actualid), + float(actualid), + object(), + None, + '', + ]: + with self.subTest(repr(interpid)): + with self.assertRaises(TypeError): + interpreters.Interpreter(interpid) + + def test_idempotent(self): + main = interpreters.get_main() + interp = interpreters.Interpreter(main.id) + self.assertIs(interp, main) + + def test_init_does_not_exist(self): + with self.assertRaises(InterpreterNotFoundError): + interpreters.Interpreter(1_000_000) + + def test_init_bad_id(self): + with self.assertRaises(ValueError): + interpreters.Interpreter(-1) + + def test_id_type(self): + main = interpreters.get_main() + current = interpreters.get_current() + interp = interpreters.create() + self.assertIsInstance(main.id, int) + self.assertIsInstance(current.id, int) + self.assertIsInstance(interp.id, int) + + def test_id_readonly(self): + interp = interpreters.create() + with self.assertRaises(AttributeError): + interp.id = 1_000_000 + + def test_hashable(self): + interp = interpreters.create() + expected = hash(interp.id) + actual = hash(interp) + self.assertEqual(actual, expected) + + def test_equality(self): + interp1 = interpreters.create() + interp2 = interpreters.create() + self.assertEqual(interp1, interp1) + self.assertNotEqual(interp1, interp2) + + +class TestInterpreterIsRunning(TestBase): + + def test_main(self): + main = interpreters.get_main() + self.assertTrue(main.is_running()) + + @unittest.skip('Fails on FreeBSD') + def test_subinterpreter(self): + interp = interpreters.create() + self.assertFalse(interp.is_running()) + + with _running(interp): + self.assertTrue(interp.is_running()) + self.assertFalse(interp.is_running()) + + def test_finished(self): + r, w = self.pipe() + interp = interpreters.create() + interp.exec_sync(f"""if True: + import os + os.write({w}, b'x') + """) + self.assertFalse(interp.is_running()) + self.assertEqual(os.read(r, 1), b'x') + + def test_from_subinterpreter(self): + interp = interpreters.create() + out = _run_output(interp, dedent(f""" + import _xxsubinterpreters as _interpreters + if _interpreters.is_running({interp.id}): + print(True) + else: + print(False) + """)) + self.assertEqual(out.strip(), 'True') + + def test_already_destroyed(self): + interp = interpreters.create() + interp.close() + with self.assertRaises(InterpreterNotFoundError): + interp.is_running() + + def test_with_only_background_threads(self): + r_interp, w_interp = self.pipe() + r_thread, w_thread = self.pipe() + + DONE = b'D' + FINISHED = b'F' + + interp = interpreters.create() + interp.exec_sync(f"""if True: + import os + import threading + + def task(): + v = os.read({r_thread}, 1) + assert v == {DONE!r} + os.write({w_interp}, {FINISHED!r}) + t = threading.Thread(target=task) + t.start() + """) + self.assertFalse(interp.is_running()) + + os.write(w_thread, DONE) + interp.exec_sync('t.join()') + self.assertEqual(os.read(r_interp, 1), FINISHED) + + +class TestInterpreterClose(TestBase): + + def test_basic(self): + main = interpreters.get_main() + interp1 = interpreters.create() + interp2 = interpreters.create() + interp3 = interpreters.create() + self.assertEqual(set(interpreters.list_all()), + {main, interp1, interp2, interp3}) + interp2.close() + self.assertEqual(set(interpreters.list_all()), + {main, interp1, interp3}) + + def test_all(self): + before = set(interpreters.list_all()) + interps = set() + for _ in range(3): + interp = interpreters.create() + interps.add(interp) + self.assertEqual(set(interpreters.list_all()), before | interps) + for interp in interps: + interp.close() + self.assertEqual(set(interpreters.list_all()), before) + + def test_main(self): + main, = interpreters.list_all() + with self.assertRaises(RuntimeError): + main.close() + + def f(): + with self.assertRaises(RuntimeError): + main.close() + + t = threading.Thread(target=f) + t.start() + t.join() + + def test_already_destroyed(self): + interp = interpreters.create() + interp.close() + with self.assertRaises(InterpreterNotFoundError): + interp.close() + + def test_from_current(self): + main, = interpreters.list_all() + interp = interpreters.create() + out = _run_output(interp, dedent(f""" + from test.support import interpreters + interp = interpreters.Interpreter({interp.id}) + try: + interp.close() + except RuntimeError: + print('failed') + """)) + self.assertEqual(out.strip(), 'failed') + self.assertEqual(set(interpreters.list_all()), {main, interp}) + + def test_from_sibling(self): + main, = interpreters.list_all() + interp1 = interpreters.create() + interp2 = interpreters.create() + self.assertEqual(set(interpreters.list_all()), + {main, interp1, interp2}) + interp1.exec_sync(dedent(f""" + from test.support import interpreters + interp2 = interpreters.Interpreter({interp2.id}) + interp2.close() + interp3 = interpreters.create() + interp3.close() + """)) + self.assertEqual(set(interpreters.list_all()), {main, interp1}) + + def test_from_other_thread(self): + interp = interpreters.create() + def f(): + interp.close() + + t = threading.Thread(target=f) + t.start() + t.join() + + @unittest.skip('Fails on FreeBSD') + def test_still_running(self): + main, = interpreters.list_all() + interp = interpreters.create() + with _running(interp): + with self.assertRaises(RuntimeError): + interp.close() + self.assertTrue(interp.is_running()) + + def test_subthreads_still_running(self): + r_interp, w_interp = self.pipe() + r_thread, w_thread = self.pipe() + + FINISHED = b'F' + + interp = interpreters.create() + interp.exec_sync(f"""if True: + import os + import threading + import time + + done = False + + def notify_fini(): + global done + done = True + t.join() + threading._register_atexit(notify_fini) + + def task(): + while not done: + time.sleep(0.1) + os.write({w_interp}, {FINISHED!r}) + t = threading.Thread(target=task) + t.start() + """) + interp.close() + + self.assertEqual(os.read(r_interp, 1), FINISHED) + + +class TestInterpreterExecSync(TestBase): + + def test_success(self): + interp = interpreters.create() + script, file = _captured_script('print("it worked!", end="")') + with file: + interp.exec_sync(script) + out = file.read() + + self.assertEqual(out, 'it worked!') + + def test_failure(self): + interp = interpreters.create() + with self.assertRaises(interpreters.ExecFailure): + interp.exec_sync('raise Exception') + + def test_in_thread(self): + interp = interpreters.create() + script, file = _captured_script('print("it worked!", end="")') + with file: + def f(): + interp.exec_sync(script) + + t = threading.Thread(target=f) + t.start() + t.join() + out = file.read() + + self.assertEqual(out, 'it worked!') + + @support.requires_fork() + def test_fork(self): + interp = interpreters.create() + import tempfile + with tempfile.NamedTemporaryFile('w+', encoding='utf-8') as file: + file.write('') + file.flush() + + expected = 'spam spam spam spam spam' + script = dedent(f""" + import os + try: + os.fork() + except RuntimeError: + with open('{file.name}', 'w', encoding='utf-8') as out: + out.write('{expected}') + """) + interp.exec_sync(script) + + file.seek(0) + content = file.read() + self.assertEqual(content, expected) + + @unittest.skip('Fails on FreeBSD') + def test_already_running(self): + interp = interpreters.create() + with _running(interp): + with self.assertRaises(RuntimeError): + interp.exec_sync('print("spam")') + + def test_bad_script(self): + interp = interpreters.create() + with self.assertRaises(TypeError): + interp.exec_sync(10) + + def test_bytes_for_script(self): + interp = interpreters.create() + with self.assertRaises(TypeError): + interp.exec_sync(b'print("spam")') + + def test_with_background_threads_still_running(self): + r_interp, w_interp = self.pipe() + r_thread, w_thread = self.pipe() + + RAN = b'R' + DONE = b'D' + FINISHED = b'F' + + interp = interpreters.create() + interp.exec_sync(f"""if True: + import os + import threading + + def task(): + v = os.read({r_thread}, 1) + assert v == {DONE!r} + os.write({w_interp}, {FINISHED!r}) + t = threading.Thread(target=task) + t.start() + os.write({w_interp}, {RAN!r}) + """) + interp.exec_sync(f"""if True: + os.write({w_interp}, {RAN!r}) + """) + + os.write(w_thread, DONE) + interp.exec_sync('t.join()') + self.assertEqual(os.read(r_interp, 1), RAN) + self.assertEqual(os.read(r_interp, 1), RAN) + self.assertEqual(os.read(r_interp, 1), FINISHED) + + # test_xxsubinterpreters covers the remaining + # Interpreter.exec_sync() behavior. + + +class TestInterpreterRun(TestBase): + + def test_success(self): + interp = interpreters.create() + script, file = _captured_script('print("it worked!", end="")') + with file: + t = interp.run(script) + t.join() + out = file.read() + + self.assertEqual(out, 'it worked!') + + def test_failure(self): + caught = False + def excepthook(args): + nonlocal caught + caught = True + threading.excepthook = excepthook + try: + interp = interpreters.create() + t = interp.run('raise Exception') + t.join() + + self.assertTrue(caught) + except BaseException: + threading.excepthook = threading.__excepthook__ + + +class TestIsShareable(TestBase): + + def test_default_shareables(self): + shareables = [ + # singletons + None, + # builtin objects + b'spam', + 'spam', + 10, + -10, + True, + False, + 100.0, + (), + (1, ('spam', 'eggs'), True), + ] + for obj in shareables: + with self.subTest(obj): + shareable = interpreters.is_shareable(obj) + self.assertTrue(shareable) + + def test_not_shareable(self): + class Cheese: + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + + class SubBytes(bytes): + """A subclass of a shareable type.""" + + not_shareables = [ + # singletons + NotImplemented, + ..., + # builtin types and objects + type, + object, + object(), + Exception(), + # user-defined types and objects + Cheese, + Cheese('Wensleydale'), + SubBytes(b'spam'), + ] + for obj in not_shareables: + with self.subTest(repr(obj)): + self.assertFalse( + interpreters.is_shareable(obj)) + + +if __name__ == '__main__': + # Test needs to be a package, so we can do relative imports. + unittest.main() diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py new file mode 100644 index 00000000000000..3c3e18832d4168 --- /dev/null +++ b/Lib/test/test_interpreters/test_channels.py @@ -0,0 +1,328 @@ +import threading +from textwrap import dedent +import unittest +import time + +from test.support import import_helper +# Raise SkipTest if subinterpreters not supported. +_channels = import_helper.import_module('_xxinterpchannels') +from test.support import interpreters +from test.support.interpreters import channels +from .utils import _run_output, TestBase + + +class TestChannels(TestBase): + + def test_create(self): + r, s = channels.create() + self.assertIsInstance(r, channels.RecvChannel) + self.assertIsInstance(s, channels.SendChannel) + + def test_list_all(self): + self.assertEqual(channels.list_all(), []) + created = set() + for _ in range(3): + ch = channels.create() + created.add(ch) + after = set(channels.list_all()) + self.assertEqual(after, created) + + def test_shareable(self): + rch, sch = channels.create() + + self.assertTrue( + interpreters.is_shareable(rch)) + self.assertTrue( + interpreters.is_shareable(sch)) + + sch.send_nowait(rch) + sch.send_nowait(sch) + rch2 = rch.recv() + sch2 = rch.recv() + + self.assertEqual(rch2, rch) + self.assertEqual(sch2, sch) + + def test_is_closed(self): + rch, sch = channels.create() + rbefore = rch.is_closed + sbefore = sch.is_closed + rch.close() + rafter = rch.is_closed + safter = sch.is_closed + + self.assertFalse(rbefore) + self.assertFalse(sbefore) + self.assertTrue(rafter) + self.assertTrue(safter) + + +class TestRecvChannelAttrs(TestBase): + + def test_id_type(self): + rch, _ = channels.create() + self.assertIsInstance(rch.id, _channels.ChannelID) + + def test_custom_id(self): + rch = channels.RecvChannel(1) + self.assertEqual(rch.id, 1) + + with self.assertRaises(TypeError): + channels.RecvChannel('1') + + def test_id_readonly(self): + rch = channels.RecvChannel(1) + with self.assertRaises(AttributeError): + rch.id = 2 + + def test_equality(self): + ch1, _ = channels.create() + ch2, _ = channels.create() + self.assertEqual(ch1, ch1) + self.assertNotEqual(ch1, ch2) + + +class TestSendChannelAttrs(TestBase): + + def test_id_type(self): + _, sch = channels.create() + self.assertIsInstance(sch.id, _channels.ChannelID) + + def test_custom_id(self): + sch = channels.SendChannel(1) + self.assertEqual(sch.id, 1) + + with self.assertRaises(TypeError): + channels.SendChannel('1') + + def test_id_readonly(self): + sch = channels.SendChannel(1) + with self.assertRaises(AttributeError): + sch.id = 2 + + def test_equality(self): + _, ch1 = channels.create() + _, ch2 = channels.create() + self.assertEqual(ch1, ch1) + self.assertNotEqual(ch1, ch2) + + +class TestSendRecv(TestBase): + + def test_send_recv_main(self): + r, s = channels.create() + orig = b'spam' + s.send_nowait(orig) + obj = r.recv() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + def test_send_recv_same_interpreter(self): + interp = interpreters.create() + interp.exec_sync(dedent(""" + from test.support.interpreters import channels + r, s = channels.create() + orig = b'spam' + s.send_nowait(orig) + obj = r.recv() + assert obj == orig, 'expected: obj == orig' + assert obj is not orig, 'expected: obj is not orig' + """)) + + @unittest.skip('broken (see BPO-...)') + def test_send_recv_different_interpreters(self): + r1, s1 = channels.create() + r2, s2 = channels.create() + orig1 = b'spam' + s1.send_nowait(orig1) + out = _run_output( + interpreters.create(), + dedent(f""" + obj1 = r.recv() + assert obj1 == b'spam', 'expected: obj1 == orig1' + # When going to another interpreter we get a copy. + assert id(obj1) != {id(orig1)}, 'expected: obj1 is not orig1' + orig2 = b'eggs' + print(id(orig2)) + s.send_nowait(orig2) + """), + channels=dict(r=r1, s=s2), + ) + obj2 = r2.recv() + + self.assertEqual(obj2, b'eggs') + self.assertNotEqual(id(obj2), int(out)) + + def test_send_recv_different_threads(self): + r, s = channels.create() + + def f(): + while True: + try: + obj = r.recv() + break + except channels.ChannelEmptyError: + time.sleep(0.1) + s.send(obj) + t = threading.Thread(target=f) + t.start() + + orig = b'spam' + s.send(orig) + obj = r.recv() + t.join() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + def test_send_recv_nowait_main(self): + r, s = channels.create() + orig = b'spam' + s.send_nowait(orig) + obj = r.recv_nowait() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + def test_send_recv_nowait_main_with_default(self): + r, _ = channels.create() + obj = r.recv_nowait(None) + + self.assertIsNone(obj) + + def test_send_recv_nowait_same_interpreter(self): + interp = interpreters.create() + interp.exec_sync(dedent(""" + from test.support.interpreters import channels + r, s = channels.create() + orig = b'spam' + s.send_nowait(orig) + obj = r.recv_nowait() + assert obj == orig, 'expected: obj == orig' + # When going back to the same interpreter we get the same object. + assert obj is not orig, 'expected: obj is not orig' + """)) + + @unittest.skip('broken (see BPO-...)') + def test_send_recv_nowait_different_interpreters(self): + r1, s1 = channels.create() + r2, s2 = channels.create() + orig1 = b'spam' + s1.send_nowait(orig1) + out = _run_output( + interpreters.create(), + dedent(f""" + obj1 = r.recv_nowait() + assert obj1 == b'spam', 'expected: obj1 == orig1' + # When going to another interpreter we get a copy. + assert id(obj1) != {id(orig1)}, 'expected: obj1 is not orig1' + orig2 = b'eggs' + print(id(orig2)) + s.send_nowait(orig2) + """), + channels=dict(r=r1, s=s2), + ) + obj2 = r2.recv_nowait() + + self.assertEqual(obj2, b'eggs') + self.assertNotEqual(id(obj2), int(out)) + + def test_recv_timeout(self): + r, _ = channels.create() + with self.assertRaises(TimeoutError): + r.recv(timeout=1) + + def test_recv_channel_does_not_exist(self): + ch = channels.RecvChannel(1_000_000) + with self.assertRaises(channels.ChannelNotFoundError): + ch.recv() + + def test_send_channel_does_not_exist(self): + ch = channels.SendChannel(1_000_000) + with self.assertRaises(channels.ChannelNotFoundError): + ch.send(b'spam') + + def test_recv_nowait_channel_does_not_exist(self): + ch = channels.RecvChannel(1_000_000) + with self.assertRaises(channels.ChannelNotFoundError): + ch.recv_nowait() + + def test_send_nowait_channel_does_not_exist(self): + ch = channels.SendChannel(1_000_000) + with self.assertRaises(channels.ChannelNotFoundError): + ch.send_nowait(b'spam') + + def test_recv_nowait_empty(self): + ch, _ = channels.create() + with self.assertRaises(channels.ChannelEmptyError): + ch.recv_nowait() + + def test_recv_nowait_default(self): + default = object() + rch, sch = channels.create() + obj1 = rch.recv_nowait(default) + sch.send_nowait(None) + sch.send_nowait(1) + sch.send_nowait(b'spam') + sch.send_nowait(b'eggs') + obj2 = rch.recv_nowait(default) + obj3 = rch.recv_nowait(default) + obj4 = rch.recv_nowait() + obj5 = rch.recv_nowait(default) + obj6 = rch.recv_nowait(default) + + self.assertIs(obj1, default) + self.assertIs(obj2, None) + self.assertEqual(obj3, 1) + self.assertEqual(obj4, b'spam') + self.assertEqual(obj5, b'eggs') + self.assertIs(obj6, default) + + def test_send_buffer(self): + buf = bytearray(b'spamspamspam') + obj = None + rch, sch = channels.create() + + def f(): + nonlocal obj + while True: + try: + obj = rch.recv() + break + except channels.ChannelEmptyError: + time.sleep(0.1) + t = threading.Thread(target=f) + t.start() + + sch.send_buffer(buf) + t.join() + + self.assertIsNot(obj, buf) + self.assertIsInstance(obj, memoryview) + self.assertEqual(obj, buf) + + buf[4:8] = b'eggs' + self.assertEqual(obj, buf) + obj[4:8] = b'ham.' + self.assertEqual(obj, buf) + + def test_send_buffer_nowait(self): + buf = bytearray(b'spamspamspam') + rch, sch = channels.create() + sch.send_buffer_nowait(buf) + obj = rch.recv() + + self.assertIsNot(obj, buf) + self.assertIsInstance(obj, memoryview) + self.assertEqual(obj, buf) + + buf[4:8] = b'eggs' + self.assertEqual(obj, buf) + obj[4:8] = b'ham.' + self.assertEqual(obj, buf) + + +if __name__ == '__main__': + # Test needs to be a package, so we can do relative imports. + unittest.main() diff --git a/Lib/test/test_interpreters/test_lifecycle.py b/Lib/test/test_interpreters/test_lifecycle.py new file mode 100644 index 00000000000000..c2917d839904f9 --- /dev/null +++ b/Lib/test/test_interpreters/test_lifecycle.py @@ -0,0 +1,189 @@ +import contextlib +import json +import os +import os.path +import sys +from textwrap import dedent +import unittest + +from test import support +from test.support import import_helper +from test.support import os_helper +# Raise SkipTest if subinterpreters not supported. +import_helper.import_module('_xxsubinterpreters') +from .utils import TestBase + + +class StartupTests(TestBase): + + # We want to ensure the initial state of subinterpreters + # matches expectations. + + _subtest_count = 0 + + @contextlib.contextmanager + def subTest(self, *args): + with super().subTest(*args) as ctx: + self._subtest_count += 1 + try: + yield ctx + finally: + if self._debugged_in_subtest: + if self._subtest_count == 1: + # The first subtest adds a leading newline, so we + # compensate here by not printing a trailing newline. + print('### end subtest debug ###', end='') + else: + print('### end subtest debug ###') + self._debugged_in_subtest = False + + def debug(self, msg, *, header=None): + if header: + self._debug(f'--- {header} ---') + if msg: + if msg.endswith(os.linesep): + self._debug(msg[:-len(os.linesep)]) + else: + self._debug(msg) + self._debug('') + self._debug('------') + else: + self._debug(msg) + + _debugged = False + _debugged_in_subtest = False + def _debug(self, msg): + if not self._debugged: + print() + self._debugged = True + if self._subtest is not None: + if True: + if not self._debugged_in_subtest: + self._debugged_in_subtest = True + print('### start subtest debug ###') + print(msg) + else: + print(msg) + + def create_temp_dir(self): + import tempfile + tmp = tempfile.mkdtemp(prefix='test_interpreters_') + tmp = os.path.realpath(tmp) + self.addCleanup(os_helper.rmtree, tmp) + return tmp + + def write_script(self, *path, text): + filename = os.path.join(*path) + dirname = os.path.dirname(filename) + if dirname: + os.makedirs(dirname, exist_ok=True) + with open(filename, 'w', encoding='utf-8') as outfile: + outfile.write(dedent(text)) + return filename + + @support.requires_subprocess() + def run_python(self, argv, *, cwd=None): + # This method is inspired by + # EmbeddingTestsMixin.run_embedded_interpreter() in test_embed.py. + import shlex + import subprocess + if isinstance(argv, str): + argv = shlex.split(argv) + argv = [sys.executable, *argv] + try: + proc = subprocess.run( + argv, + cwd=cwd, + capture_output=True, + text=True, + ) + except Exception as exc: + self.debug(f'# cmd: {shlex.join(argv)}') + if isinstance(exc, FileNotFoundError) and not exc.filename: + if os.path.exists(argv[0]): + exists = 'exists' + else: + exists = 'does not exist' + self.debug(f'{argv[0]} {exists}') + raise # re-raise + assert proc.stderr == '' or proc.returncode != 0, proc.stderr + if proc.returncode != 0 and support.verbose: + self.debug(f'# python3 {shlex.join(argv[1:])} failed:') + self.debug(proc.stdout, header='stdout') + self.debug(proc.stderr, header='stderr') + self.assertEqual(proc.returncode, 0) + self.assertEqual(proc.stderr, '') + return proc.stdout + + def test_sys_path_0(self): + # The main interpreter's sys.path[0] should be used by subinterpreters. + script = ''' + import sys + from test.support import interpreters + + orig = sys.path[0] + + interp = interpreters.create() + interp.exec_sync(f"""if True: + import json + import sys + print(json.dumps({{ + 'main': {orig!r}, + 'sub': sys.path[0], + }}, indent=4), flush=True) + """) + ''' + # / + # pkg/ + # __init__.py + # __main__.py + # script.py + # script.py + cwd = self.create_temp_dir() + self.write_script(cwd, 'pkg', '__init__.py', text='') + self.write_script(cwd, 'pkg', '__main__.py', text=script) + self.write_script(cwd, 'pkg', 'script.py', text=script) + self.write_script(cwd, 'script.py', text=script) + + cases = [ + ('script.py', cwd), + ('-m script', cwd), + ('-m pkg', cwd), + ('-m pkg.script', cwd), + ('-c "import script"', ''), + ] + for argv, expected in cases: + with self.subTest(f'python3 {argv}'): + out = self.run_python(argv, cwd=cwd) + data = json.loads(out) + sp0_main, sp0_sub = data['main'], data['sub'] + self.assertEqual(sp0_sub, sp0_main) + self.assertEqual(sp0_sub, expected) + # XXX Also check them all with the -P cmdline flag? + + +class FinalizationTests(TestBase): + + def test_gh_109793(self): + # Make sure finalization finishes and the correct error code + # is reported, even when subinterpreters get cleaned up at the end. + import subprocess + argv = [sys.executable, '-c', '''if True: + from test.support import interpreters + interp = interpreters.create() + raise Exception + '''] + proc = subprocess.run(argv, capture_output=True, text=True) + self.assertIn('Traceback', proc.stderr) + if proc.returncode == 0 and support.verbose: + print() + print("--- cmd unexpected succeeded ---") + print(f"stdout:\n{proc.stdout}") + print(f"stderr:\n{proc.stderr}") + print("------") + self.assertEqual(proc.returncode, 1) + + +if __name__ == '__main__': + # Test needs to be a package, so we can do relative imports. + unittest.main() diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py new file mode 100644 index 00000000000000..2af90b14d3e3c4 --- /dev/null +++ b/Lib/test/test_interpreters/test_queues.py @@ -0,0 +1,233 @@ +import threading +from textwrap import dedent +import unittest +import time + +from test.support import import_helper +# Raise SkipTest if subinterpreters not supported. +import_helper.import_module('_xxinterpchannels') +#import_helper.import_module('_xxinterpqueues') +from test.support import interpreters +from test.support.interpreters import queues +from .utils import _run_output, TestBase + + +class QueueTests(TestBase): + + def test_create(self): + with self.subTest('vanilla'): + queue = queues.create() + self.assertEqual(queue.maxsize, 0) + + with self.subTest('small maxsize'): + queue = queues.create(3) + self.assertEqual(queue.maxsize, 3) + + with self.subTest('big maxsize'): + queue = queues.create(100) + self.assertEqual(queue.maxsize, 100) + + with self.subTest('no maxsize'): + queue = queues.create(0) + self.assertEqual(queue.maxsize, 0) + + with self.subTest('negative maxsize'): + queue = queues.create(-1) + self.assertEqual(queue.maxsize, 0) + + with self.subTest('bad maxsize'): + with self.assertRaises(TypeError): + queues.create('1') + + @unittest.expectedFailure + def test_shareable(self): + queue1 = queues.create() + queue2 = queues.create() + queue1.put(queue2) + queue3 = queue1.get() + self.assertIs(queue3, queue1) + + def test_id_type(self): + queue = queues.create() + self.assertIsInstance(queue.id, int) + + def test_custom_id(self): + with self.assertRaises(queues.QueueNotFoundError): + queues.Queue(1_000_000) + + def test_id_readonly(self): + queue = queues.create() + with self.assertRaises(AttributeError): + queue.id = 1_000_000 + + def test_maxsize_readonly(self): + queue = queues.create(10) + with self.assertRaises(AttributeError): + queue.maxsize = 1_000_000 + + def test_hashable(self): + queue = queues.create() + expected = hash(queue.id) + actual = hash(queue) + self.assertEqual(actual, expected) + + def test_equality(self): + queue1 = queues.create() + queue2 = queues.create() + self.assertEqual(queue1, queue1) + self.assertNotEqual(queue1, queue2) + + +class TestQueueOps(TestBase): + + def test_empty(self): + queue = queues.create() + before = queue.empty() + queue.put(None) + during = queue.empty() + queue.get() + after = queue.empty() + + self.assertIs(before, True) + self.assertIs(during, False) + self.assertIs(after, True) + + def test_full(self): + expected = [False, False, False, True, False, False, False] + actual = [] + queue = queues.create(3) + for _ in range(3): + actual.append(queue.full()) + queue.put(None) + actual.append(queue.full()) + for _ in range(3): + queue.get() + actual.append(queue.full()) + + self.assertEqual(actual, expected) + + def test_qsize(self): + expected = [0, 1, 2, 3, 2, 3, 2, 1, 0, 1, 0] + actual = [] + queue = queues.create() + for _ in range(3): + actual.append(queue.qsize()) + queue.put(None) + actual.append(queue.qsize()) + queue.get() + actual.append(queue.qsize()) + queue.put(None) + actual.append(queue.qsize()) + for _ in range(3): + queue.get() + actual.append(queue.qsize()) + queue.put(None) + actual.append(queue.qsize()) + queue.get() + actual.append(queue.qsize()) + + self.assertEqual(actual, expected) + + def test_put_get_main(self): + expected = list(range(20)) + queue = queues.create() + for i in range(20): + queue.put(i) + actual = [queue.get() for _ in range(20)] + + self.assertEqual(actual, expected) + + @unittest.expectedFailure + def test_put_timeout(self): + queue = queues.create(2) + queue.put(None) + queue.put(None) + with self.assertRaises(queues.QueueFull): + queue.put(None, timeout=0.1) + queue.get() + queue.put(None) + + @unittest.expectedFailure + def test_put_nowait(self): + queue = queues.create(2) + queue.put_nowait(None) + queue.put_nowait(None) + with self.assertRaises(queues.QueueFull): + queue.put_nowait(None) + queue.get() + queue.put_nowait(None) + + def test_get_timeout(self): + queue = queues.create() + with self.assertRaises(queues.QueueEmpty): + queue.get(timeout=0.1) + + def test_get_nowait(self): + queue = queues.create() + with self.assertRaises(queues.QueueEmpty): + queue.get_nowait() + + def test_put_get_same_interpreter(self): + interp = interpreters.create() + interp.exec_sync(dedent(""" + from test.support.interpreters import queues + queue = queues.create() + orig = b'spam' + queue.put(orig) + obj = queue.get() + assert obj == orig, 'expected: obj == orig' + assert obj is not orig, 'expected: obj is not orig' + """)) + + @unittest.expectedFailure + def test_put_get_different_interpreters(self): + queue1 = queues.create() + queue2 = queues.create() + obj1 = b'spam' + queue1.put(obj1) + out = _run_output( + interpreters.create(), + dedent(f""" + import test.support.interpreters.queue as queues + queue1 = queues.Queue({queue1.id}) + queue2 = queues.Queue({queue2.id}) + obj = queue1.get() + assert obj == b'spam', 'expected: obj == obj1' + # When going to another interpreter we get a copy. + assert id(obj) != {id(obj1)}, 'expected: obj is not obj1' + obj2 = b'eggs' + print(id(obj2)) + queue2.put(obj2) + """)) + obj2 = queue2.get() + + self.assertEqual(obj2, b'eggs') + self.assertNotEqual(id(obj2), int(out)) + + def test_put_get_different_threads(self): + queue1 = queues.create() + queue2 = queues.create() + + def f(): + while True: + try: + obj = queue1.get(timeout=0.1) + break + except queues.QueueEmpty: + continue + queue2.put(obj) + t = threading.Thread(target=f) + t.start() + + orig = b'spam' + queue1.put(orig) + obj = queue2.get() + t.join() + + self.assertEqual(obj, orig) + self.assertIsNot(obj, orig) + + +if __name__ == '__main__': + # Test needs to be a package, so we can do relative imports. + unittest.main() diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py new file mode 100644 index 00000000000000..3cc570b3bf7128 --- /dev/null +++ b/Lib/test/test_interpreters/test_stress.py @@ -0,0 +1,38 @@ +import threading +import unittest + +from test import support +from test.support import import_helper +from test.support import threading_helper +# Raise SkipTest if subinterpreters not supported. +import_helper.import_module('_xxsubinterpreters') +from test.support import interpreters +from .utils import TestBase + + +class StressTests(TestBase): + + # In these tests we generally want a lot of interpreters, + # but not so many that any test takes too long. + + @support.requires_resource('cpu') + def test_create_many_sequential(self): + alive = [] + for _ in range(100): + interp = interpreters.create() + alive.append(interp) + + @support.requires_resource('cpu') + def test_create_many_threaded(self): + alive = [] + def task(): + interp = interpreters.create() + alive.append(interp) + threads = (threading.Thread(target=task) for _ in range(200)) + with threading_helper.start_threads(threads): + pass + + +if __name__ == '__main__': + # Test needs to be a package, so we can do relative imports. + unittest.main() diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py new file mode 100644 index 00000000000000..623c8737b79831 --- /dev/null +++ b/Lib/test/test_interpreters/utils.py @@ -0,0 +1,73 @@ +import contextlib +import os +import threading +from textwrap import dedent +import unittest + +from test.support import interpreters + + +def _captured_script(script): + r, w = os.pipe() + indented = script.replace('\n', '\n ') + wrapped = dedent(f""" + import contextlib + with open({w}, 'w', encoding='utf-8') as spipe: + with contextlib.redirect_stdout(spipe): + {indented} + """) + return wrapped, open(r, encoding='utf-8') + + +def clean_up_interpreters(): + for interp in interpreters.list_all(): + if interp.id == 0: # main + continue + try: + interp.close() + except RuntimeError: + pass # already destroyed + + +def _run_output(interp, request, channels=None): + script, rpipe = _captured_script(request) + with rpipe: + interp.exec_sync(script, channels=channels) + return rpipe.read() + + +@contextlib.contextmanager +def _running(interp): + r, w = os.pipe() + def run(): + interp.exec_sync(dedent(f""" + # wait for "signal" + with open({r}) as rpipe: + rpipe.read() + """)) + + t = threading.Thread(target=run) + t.start() + + yield + + with open(w, 'w') as spipe: + spipe.write('done') + t.join() + + +class TestBase(unittest.TestCase): + + def pipe(self): + def ensure_closed(fd): + try: + os.close(fd) + except OSError: + pass + r, w = os.pipe() + self.addCleanup(lambda: ensure_closed(r)) + self.addCleanup(lambda: ensure_closed(w)) + return r, w + + def tearDown(self): + clean_up_interpreters() diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index db5ba16c4d9739..6c87dfabad9f0f 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -729,7 +729,7 @@ def test_subinterp_intern_dynamically_allocated(self): self.assertIs(t, s) interp = interpreters.create() - interp.run(textwrap.dedent(f''' + interp.exec_sync(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) != {id(s)}, (id(t), {id(s)}) @@ -744,7 +744,7 @@ def test_subinterp_intern_statically_allocated(self): t = sys.intern(s) interp = interpreters.create() - interp.run(textwrap.dedent(f''' + interp.exec_sync(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) == {id(t)}, (id(t), {id(t)}) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 146e2dbc0fc396..a5744a4037ecea 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1365,7 +1365,7 @@ def test_threads_join_with_no_main(self): DONE = b'D' interp = interpreters.create() - interp.run(f"""if True: + interp.exec_sync(f"""if True: import os import threading import time diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9fdd67093338e4..b3ddfae58e6fc0 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -13,6 +13,7 @@ #include "_testcapi/parts.h" #include "frameobject.h" // PyFrame_New() +#include "interpreteridobject.h" // PyInterpreterID_Type #include "marshal.h" // PyMarshal_WriteLongToFile() #include // FLT_MAX @@ -1451,6 +1452,36 @@ run_in_subinterp(PyObject *self, PyObject *args) return PyLong_FromLong(r); } +static PyObject * +get_interpreterid_type(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return Py_NewRef(&PyInterpreterID_Type); +} + +static PyObject * +link_interpreter_refcount(PyObject *self, PyObject *idobj) +{ + PyInterpreterState *interp = PyInterpreterID_LookUp(idobj); + if (interp == NULL) { + assert(PyErr_Occurred()); + return NULL; + } + _PyInterpreterState_RequireIDRef(interp, 1); + Py_RETURN_NONE; +} + +static PyObject * +unlink_interpreter_refcount(PyObject *self, PyObject *idobj) +{ + PyInterpreterState *interp = PyInterpreterID_LookUp(idobj); + if (interp == NULL) { + assert(PyErr_Occurred()); + return NULL; + } + _PyInterpreterState_RequireIDRef(interp, 0); + Py_RETURN_NONE; +} + static PyMethodDef ml; static PyObject * @@ -3237,6 +3268,9 @@ static PyMethodDef TestMethods[] = { {"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}, + {"get_interpreterid_type", get_interpreterid_type, METH_NOARGS}, + {"link_interpreter_refcount", link_interpreter_refcount, METH_O}, + {"unlink_interpreter_refcount", unlink_interpreter_refcount, METH_O}, {"create_cfunction", create_cfunction, METH_NOARGS}, {"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS, PyDoc_STR("set_error_class(error_class) -> None")}, diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index ba7653f2d9c7aa..7d277df164d3ec 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1475,6 +1475,17 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs) } +static PyObject * +get_interpreter_refcount(PyObject *self, PyObject *idobj) +{ + PyInterpreterState *interp = PyInterpreterID_LookUp(idobj); + if (interp == NULL) { + return NULL; + } + return PyLong_FromLongLong(interp->id_refcount); +} + + static void _xid_capsule_destructor(PyObject *capsule) { @@ -1693,6 +1704,7 @@ static PyMethodDef module_functions[] = { {"run_in_subinterp_with_config", _PyCFunction_CAST(run_in_subinterp_with_config), METH_VARARGS | METH_KEYWORDS}, + {"get_interpreter_refcount", get_interpreter_refcount, METH_O}, {"compile_perf_trampoline_entry", compile_perf_trampoline_entry, METH_VARARGS}, {"perf_trampoline_set_persist_after_fork", perf_trampoline_set_persist_after_fork, METH_VARARGS}, {"get_crossinterp_data", get_crossinterp_data, METH_VARARGS}, diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 1c9ae3b87adf7c..97729ec269cb62 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -8,7 +8,6 @@ #include "Python.h" #include "interpreteridobject.h" #include "pycore_crossinterp.h" // struct _xid -#include "pycore_pybuffer.h" // _PyBuffer_ReleaseInInterpreterAndRawFree() #include "pycore_interp.h" // _PyInterpreterState_LookUpID() #ifdef MS_WINDOWS @@ -263,136 +262,6 @@ wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout) } -/* Cross-interpreter Buffer Views *******************************************/ - -// XXX Release when the original interpreter is destroyed. - -typedef struct { - PyObject_HEAD - Py_buffer *view; - int64_t interpid; -} XIBufferViewObject; - -static PyObject * -xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data) -{ - assert(data->data != NULL); - assert(data->obj == NULL); - assert(data->interpid >= 0); - XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject)); - if (self == NULL) { - return NULL; - } - PyObject_Init((PyObject *)self, cls); - self->view = (Py_buffer *)data->data; - self->interpid = data->interpid; - return (PyObject *)self; -} - -static void -xibufferview_dealloc(XIBufferViewObject *self) -{ - PyInterpreterState *interp = _PyInterpreterState_LookUpID(self->interpid); - /* If the interpreter is no longer alive then we have problems, - since other objects may be using the buffer still. */ - assert(interp != NULL); - - if (_PyBuffer_ReleaseInInterpreterAndRawFree(interp, self->view) < 0) { - // XXX Emit a warning? - PyErr_Clear(); - } - - PyTypeObject *tp = Py_TYPE(self); - tp->tp_free(self); - /* "Instances of heap-allocated types hold a reference to their type." - * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol - * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse - */ - // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse, - // like we do for _abc._abc_data? - Py_DECREF(tp); -} - -static int -xibufferview_getbuf(XIBufferViewObject *self, Py_buffer *view, int flags) -{ - /* Only PyMemoryView_FromObject() should ever call this, - via _memoryview_from_xid() below. */ - *view = *self->view; - view->obj = (PyObject *)self; - // XXX Should we leave it alone? - view->internal = NULL; - return 0; -} - -static PyType_Slot XIBufferViewType_slots[] = { - {Py_tp_dealloc, (destructor)xibufferview_dealloc}, - {Py_bf_getbuffer, (getbufferproc)xibufferview_getbuf}, - // We don't bother with Py_bf_releasebuffer since we don't need it. - {0, NULL}, -}; - -static PyType_Spec XIBufferViewType_spec = { - .name = MODULE_NAME ".CrossInterpreterBufferView", - .basicsize = sizeof(XIBufferViewObject), - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), - .slots = XIBufferViewType_slots, -}; - - -/* extra XID types **********************************************************/ - -static PyTypeObject * _get_current_xibufferview_type(void); - -static PyObject * -_memoryview_from_xid(_PyCrossInterpreterData *data) -{ - PyTypeObject *cls = _get_current_xibufferview_type(); - if (cls == NULL) { - return NULL; - } - PyObject *obj = xibufferview_from_xid(cls, data); - if (obj == NULL) { - return NULL; - } - return PyMemoryView_FromObject(obj); -} - -static int -_memoryview_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - Py_buffer *view = PyMem_RawMalloc(sizeof(Py_buffer)); - if (view == NULL) { - return -1; - } - if (PyObject_GetBuffer(obj, view, PyBUF_FULL_RO) < 0) { - PyMem_RawFree(view); - return -1; - } - _PyCrossInterpreterData_Init(data, tstate->interp, view, NULL, - _memoryview_from_xid); - return 0; -} - -static int -register_builtin_xid_types(struct xid_class_registry *classes) -{ - PyTypeObject *cls; - crossinterpdatafunc func; - - // builtin memoryview - cls = &PyMemoryView_Type; - func = _memoryview_shared; - if (register_xid_class(cls, func, classes)) { - return -1; - } - - return 0; -} - - /* module state *************************************************************/ typedef struct { @@ -405,7 +274,6 @@ typedef struct { /* heap types */ PyTypeObject *ChannelInfoType; PyTypeObject *ChannelIDType; - PyTypeObject *XIBufferViewType; /* exceptions */ PyObject *ChannelError; @@ -449,7 +317,6 @@ traverse_module_state(module_state *state, visitproc visit, void *arg) /* heap types */ Py_VISIT(state->ChannelInfoType); Py_VISIT(state->ChannelIDType); - Py_VISIT(state->XIBufferViewType); /* exceptions */ Py_VISIT(state->ChannelError); @@ -474,7 +341,6 @@ clear_module_state(module_state *state) (void)_PyCrossInterpreterData_UnregisterClass(state->ChannelIDType); } Py_CLEAR(state->ChannelIDType); - Py_CLEAR(state->XIBufferViewType); /* exceptions */ Py_CLEAR(state->ChannelError); @@ -487,17 +353,6 @@ clear_module_state(module_state *state) } -static PyTypeObject * -_get_current_xibufferview_type(void) -{ - module_state *state = _get_current_module_state(); - if (state == NULL) { - return NULL; - } - return state->XIBufferViewType; -} - - /* channel-specific code ****************************************************/ #define CHANNEL_SEND 1 @@ -3463,18 +3318,6 @@ module_exec(PyObject *mod) goto error; } - // XIBufferView - state->XIBufferViewType = add_new_type(mod, &XIBufferViewType_spec, NULL, - xid_classes); - if (state->XIBufferViewType == NULL) { - goto error; - } - - // Register external types. - if (register_builtin_xid_types(xid_classes) < 0) { - goto error; - } - /* Make sure chnnels drop objects owned by this interpreter. */ PyInterpreterState *interp = _get_current_interp(); PyUnstable_AtExit(interp, clear_interpreter, (void *)interp); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 02c2abed27ddfa..37959e953ee4f5 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -6,11 +6,14 @@ #endif #include "Python.h" +#include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_crossinterp.h" // struct _xid -#include "pycore_pyerrors.h" // _Py_excinfo +#include "pycore_interp.h" // _PyInterpreterState_IDIncref() #include "pycore_initconfig.h" // _PyErr_SetFromPyStatus() +#include "pycore_long.h" // _PyLong_IsNegative() #include "pycore_modsupport.h" // _PyArg_BadArgument() -#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() +#include "pycore_pybuffer.h" // _PyBuffer_ReleaseInInterpreterAndRawFree() +#include "pycore_pyerrors.h" // _Py_excinfo #include "pycore_pystate.h" // _PyInterpreterState_SetRunningMain() #include "interpreteridobject.h" @@ -28,11 +31,260 @@ _get_current_interp(void) return PyInterpreterState_Get(); } +static int64_t +pylong_to_interpid(PyObject *idobj) +{ + assert(PyLong_CheckExact(idobj)); + + if (_PyLong_IsNegative((PyLongObject *)idobj)) { + PyErr_Format(PyExc_ValueError, + "interpreter ID must be a non-negative int, got %R", + idobj); + return -1; + } + + int overflow; + long long id = PyLong_AsLongLongAndOverflow(idobj, &overflow); + if (id == -1) { + if (!overflow) { + assert(PyErr_Occurred()); + return -1; + } + assert(!PyErr_Occurred()); + // For now, we don't worry about if LLONG_MAX < INT64_MAX. + goto bad_id; + } +#if LLONG_MAX > INT64_MAX + if (id > INT64_MAX) { + goto bad_id; + } +#endif + return (int64_t)id; + +bad_id: + PyErr_Format(PyExc_RuntimeError, + "unrecognized interpreter ID %O", idobj); + return -1; +} + +static int64_t +convert_interpid_obj(PyObject *arg) +{ + int64_t id = -1; + if (_PyIndex_Check(arg)) { + PyObject *idobj = PyNumber_Long(arg); + if (idobj == NULL) { + return -1; + } + id = pylong_to_interpid(idobj); + Py_DECREF(idobj); + if (id < 0) { + return -1; + } + } + else { + PyErr_Format(PyExc_TypeError, + "interpreter ID must be an int, got %.100s", + Py_TYPE(arg)->tp_name); + return -1; + } + return id; +} + +static PyInterpreterState * +look_up_interp(PyObject *arg) +{ + int64_t id = convert_interpid_obj(arg); + if (id < 0) { + return NULL; + } + return _PyInterpreterState_LookUpID(id); +} + + +static PyObject * +interpid_to_pylong(int64_t id) +{ + assert(id < LLONG_MAX); + return PyLong_FromLongLong(id); +} + +static PyObject * +get_interpid_obj(PyInterpreterState *interp) +{ + if (_PyInterpreterState_IDInitref(interp) != 0) { + return NULL; + }; + int64_t id = PyInterpreterState_GetID(interp); + if (id < 0) { + return NULL; + } + return interpid_to_pylong(id); +} + +static PyObject * +_get_current_module(void) +{ + PyObject *name = PyUnicode_FromString(MODULE_NAME); + if (name == NULL) { + return NULL; + } + PyObject *mod = PyImport_GetModule(name); + Py_DECREF(name); + if (mod == NULL) { + return NULL; + } + assert(mod != Py_None); + return mod; +} + + +/* Cross-interpreter Buffer Views *******************************************/ + +// XXX Release when the original interpreter is destroyed. + +typedef struct { + PyObject_HEAD + Py_buffer *view; + int64_t interpid; +} XIBufferViewObject; + +static PyObject * +xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data) +{ + assert(data->data != NULL); + assert(data->obj == NULL); + assert(data->interpid >= 0); + XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject)); + if (self == NULL) { + return NULL; + } + PyObject_Init((PyObject *)self, cls); + self->view = (Py_buffer *)data->data; + self->interpid = data->interpid; + return (PyObject *)self; +} + +static void +xibufferview_dealloc(XIBufferViewObject *self) +{ + PyInterpreterState *interp = _PyInterpreterState_LookUpID(self->interpid); + /* If the interpreter is no longer alive then we have problems, + since other objects may be using the buffer still. */ + assert(interp != NULL); + + if (_PyBuffer_ReleaseInInterpreterAndRawFree(interp, self->view) < 0) { + // XXX Emit a warning? + PyErr_Clear(); + } + + PyTypeObject *tp = Py_TYPE(self); + tp->tp_free(self); + /* "Instances of heap-allocated types hold a reference to their type." + * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol + * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse + */ + // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse, + // like we do for _abc._abc_data? + Py_DECREF(tp); +} + +static int +xibufferview_getbuf(XIBufferViewObject *self, Py_buffer *view, int flags) +{ + /* Only PyMemoryView_FromObject() should ever call this, + via _memoryview_from_xid() below. */ + *view = *self->view; + view->obj = (PyObject *)self; + // XXX Should we leave it alone? + view->internal = NULL; + return 0; +} + +static PyType_Slot XIBufferViewType_slots[] = { + {Py_tp_dealloc, (destructor)xibufferview_dealloc}, + {Py_bf_getbuffer, (getbufferproc)xibufferview_getbuf}, + // We don't bother with Py_bf_releasebuffer since we don't need it. + {0, NULL}, +}; + +static PyType_Spec XIBufferViewType_spec = { + .name = MODULE_NAME ".CrossInterpreterBufferView", + .basicsize = sizeof(XIBufferViewObject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), + .slots = XIBufferViewType_slots, +}; + + +static PyTypeObject * _get_current_xibufferview_type(void); + +static PyObject * +_memoryview_from_xid(_PyCrossInterpreterData *data) +{ + PyTypeObject *cls = _get_current_xibufferview_type(); + if (cls == NULL) { + return NULL; + } + PyObject *obj = xibufferview_from_xid(cls, data); + if (obj == NULL) { + return NULL; + } + return PyMemoryView_FromObject(obj); +} + +static int +_memoryview_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + Py_buffer *view = PyMem_RawMalloc(sizeof(Py_buffer)); + if (view == NULL) { + return -1; + } + if (PyObject_GetBuffer(obj, view, PyBUF_FULL_RO) < 0) { + PyMem_RawFree(view); + return -1; + } + _PyCrossInterpreterData_Init(data, tstate->interp, view, NULL, + _memoryview_from_xid); + return 0; +} + +static int +register_memoryview_xid(PyObject *mod, PyTypeObject **p_state) +{ + // XIBufferView + assert(*p_state == NULL); + PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec( + mod, &XIBufferViewType_spec, NULL); + if (cls == NULL) { + return -1; + } + if (PyModule_AddType(mod, cls) < 0) { + Py_DECREF(cls); + return -1; + } + *p_state = cls; + + // Register XID for the builtin memoryview type. + if (_PyCrossInterpreterData_RegisterClass( + &PyMemoryView_Type, _memoryview_shared) < 0) { + return -1; + } + // We don't ever bother un-registering memoryview. + + return 0; +} + + /* module state *************************************************************/ typedef struct { int _notused; + + /* heap types */ + PyTypeObject *XIBufferViewType; } module_state; static inline module_state * @@ -44,19 +296,51 @@ get_module_state(PyObject *mod) return state; } +static module_state * +_get_current_module_state(void) +{ + PyObject *mod = _get_current_module(); + if (mod == NULL) { + // XXX import it? + PyErr_SetString(PyExc_RuntimeError, + MODULE_NAME " module not imported yet"); + return NULL; + } + module_state *state = get_module_state(mod); + Py_DECREF(mod); + return state; +} + static int traverse_module_state(module_state *state, visitproc visit, void *arg) { + /* heap types */ + Py_VISIT(state->XIBufferViewType); + return 0; } static int clear_module_state(module_state *state) { + /* heap types */ + Py_CLEAR(state->XIBufferViewType); + return 0; } +static PyTypeObject * +_get_current_xibufferview_type(void) +{ + module_state *state = _get_current_module_state(); + if (state == NULL) { + return NULL; + } + return state->XIBufferViewType; +} + + /* Python code **************************************************************/ static const char * @@ -254,7 +538,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) assert(tstate != NULL); PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); - PyObject *idobj = PyInterpreterState_GetIDObject(interp); + PyObject *idobj = get_interpid_obj(interp); if (idobj == NULL) { // XXX Possible GILState issues? save_tstate = PyThreadState_Swap(tstate); @@ -273,7 +557,9 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) PyDoc_STRVAR(create_doc, "create() -> ID\n\ \n\ -Create a new interpreter and return a unique generated ID."); +Create a new interpreter and return a unique generated ID.\n\ +\n\ +The caller is responsible for destroying the interpreter before exiting."); static PyObject * @@ -288,7 +574,7 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) } // Look up the interpreter. - PyInterpreterState *interp = PyInterpreterID_LookUp(id); + PyInterpreterState *interp = look_up_interp(id); if (interp == NULL) { return NULL; } @@ -345,7 +631,7 @@ interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) interp = PyInterpreterState_Head(); while (interp != NULL) { - id = PyInterpreterState_GetIDObject(interp); + id = get_interpid_obj(interp); if (id == NULL) { Py_DECREF(ids); return NULL; @@ -377,7 +663,7 @@ interp_get_current(PyObject *self, PyObject *Py_UNUSED(ignored)) if (interp == NULL) { return NULL; } - return PyInterpreterState_GetIDObject(interp); + return get_interpid_obj(interp); } PyDoc_STRVAR(get_current_doc, @@ -391,7 +677,7 @@ interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored)) { // Currently, 0 is always the main interpreter. int64_t id = 0; - return PyInterpreterID_New(id); + return PyLong_FromLongLong(id); } PyDoc_STRVAR(get_main_doc, @@ -481,7 +767,7 @@ _interp_exec(PyObject *self, PyObject **p_excinfo) { // Look up the interpreter. - PyInterpreterState *interp = PyInterpreterID_LookUp(id_arg); + PyInterpreterState *interp = look_up_interp(id_arg); if (interp == NULL) { return -1; } @@ -667,7 +953,7 @@ interp_is_running(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - PyInterpreterState *interp = PyInterpreterID_LookUp(id); + PyInterpreterState *interp = look_up_interp(id); if (interp == NULL) { return NULL; } @@ -683,6 +969,49 @@ PyDoc_STRVAR(is_running_doc, Return whether or not the identified interpreter is running."); +static PyObject * +interp_incref(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"id", NULL}; + PyObject *id; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O:_incref", kwlist, &id)) { + return NULL; + } + + PyInterpreterState *interp = look_up_interp(id); + if (interp == NULL) { + return NULL; + } + if (_PyInterpreterState_IDInitref(interp) < 0) { + return NULL; + } + _PyInterpreterState_IDIncref(interp); + + Py_RETURN_NONE; +} + + +static PyObject * +interp_decref(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"id", NULL}; + PyObject *id; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O:_incref", kwlist, &id)) { + return NULL; + } + + PyInterpreterState *interp = look_up_interp(id); + if (interp == NULL) { + return NULL; + } + _PyInterpreterState_IDDecref(interp); + + Py_RETURN_NONE; +} + + static PyMethodDef module_functions[] = { {"create", _PyCFunction_CAST(interp_create), METH_VARARGS | METH_KEYWORDS, create_doc}, @@ -707,6 +1036,11 @@ static PyMethodDef module_functions[] = { {"is_shareable", _PyCFunction_CAST(object_is_shareable), METH_VARARGS | METH_KEYWORDS, is_shareable_doc}, + {"_incref", _PyCFunction_CAST(interp_incref), + METH_VARARGS | METH_KEYWORDS, NULL}, + {"_decref", _PyCFunction_CAST(interp_decref), + METH_VARARGS | METH_KEYWORDS, NULL}, + {NULL, NULL} /* sentinel */ }; @@ -720,8 +1054,17 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { - // PyInterpreterID - if (PyModule_AddType(mod, &PyInterpreterID_Type) < 0) { + module_state *state = get_module_state(mod); + + // exceptions + if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterError) < 0) { + goto error; + } + if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterNotFoundError) < 0) { + goto error; + } + + if (register_memoryview_xid(mod, &state->XIBufferViewType) < 0) { goto error; } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index f74fee38648266..a31b5ef4613dbd 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -12,6 +12,53 @@ #include "pycore_weakref.h" // _PyWeakref_GET_REF() +/**************/ +/* exceptions */ +/**************/ + +/* InterpreterError extends Exception */ + +static PyTypeObject _PyExc_InterpreterError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "InterpreterError", + .tp_doc = PyDoc_STR("An interpreter was not found."), + //.tp_base = (PyTypeObject *)PyExc_BaseException, +}; +PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; + +/* InterpreterNotFoundError extends InterpreterError */ + +static PyTypeObject _PyExc_InterpreterNotFoundError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "InterpreterNotFoundError", + .tp_doc = PyDoc_STR("An interpreter was not found."), + .tp_base = &_PyExc_InterpreterError, +}; +PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; + +/* lifecycle */ + +static int +init_exceptions(PyInterpreterState *interp) +{ + _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { + return -1; + } + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { + return -1; + } + return 0; +} + +static void +fini_exceptions(PyInterpreterState *interp) +{ + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); +} + + /***************************/ /* cross-interpreter calls */ /***************************/ @@ -2099,3 +2146,18 @@ _PyXI_Fini(PyInterpreterState *interp) _xidregistry_fini(_get_global_xidregistry(interp->runtime)); } } + +PyStatus +_PyXI_InitTypes(PyInterpreterState *interp) +{ + if (init_exceptions(interp) < 0) { + return _PyStatus_ERR("failed to initialize an exception type"); + } + return _PyStatus_OK(); +} + +void +_PyXI_FiniTypes(PyInterpreterState *interp) +{ + fini_exceptions(interp); +} diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 45a119fcca7f2c..b5c7dc5da596de 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -734,6 +734,11 @@ pycore_init_types(PyInterpreterState *interp) return status; } + status = _PyXI_InitTypes(interp); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + return _PyStatus_OK(); } @@ -1742,6 +1747,7 @@ finalize_interp_types(PyInterpreterState *interp) { _PyUnicode_FiniTypes(interp); _PySys_FiniTypes(interp); + _PyXI_FiniTypes(interp); _PyExc_Fini(interp); _PyAsyncGen_Fini(interp); _PyContext_Fini(interp); diff --git a/Python/pystate.c b/Python/pystate.c index 1a7c0c968504d1..f0c5259967d907 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1216,7 +1216,7 @@ _PyInterpreterState_LookUpID(int64_t requested_id) HEAD_UNLOCK(runtime); } if (interp == NULL && !PyErr_Occurred()) { - PyErr_Format(PyExc_RuntimeError, + PyErr_Format(PyExc_InterpreterNotFoundError, "unrecognized interpreter ID %lld", requested_id); } return interp; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index aa8ce49ae86376..e3a1b5d532bda2 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -290,6 +290,10 @@ Objects/exceptions.c - PyExc_UnicodeWarning - Objects/exceptions.c - PyExc_BytesWarning - Objects/exceptions.c - PyExc_ResourceWarning - Objects/exceptions.c - PyExc_EncodingWarning - +Python/crossinterp.c - _PyExc_InterpreterError - +Python/crossinterp.c - _PyExc_InterpreterNotFoundError - +Python/crossinterp.c - PyExc_InterpreterError - +Python/crossinterp.c - PyExc_InterpreterNotFoundError - ##----------------------- ## singletons From eafc2381a0b891383098b08300ae766868a19ba6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 12 Dec 2023 18:29:08 +0200 Subject: [PATCH 209/442] Update pre-commit to fix Sphinx Lint (#113015) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 35d9c64a8c5c15..9bd9c59a1ddc74 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.2 + rev: v0.1.7 hooks: - id: ruff name: Run Ruff on Lib/test/ @@ -24,7 +24,7 @@ repos: types_or: [c, inc, python, rst] - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.8.1 + rev: v0.9.1 hooks: - id: sphinx-lint args: [--enable=default-role] From fe9991bb672dd95fb9cd38b5a03180719ac4e722 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 12 Dec 2023 18:31:04 +0200 Subject: [PATCH 210/442] gh-112999: Replace the outdated "deprecated" directives with "versionchanged" (GH-113000) --- Doc/library/hmac.rst | 17 +++++++---------- Doc/library/random.rst | 8 ++++---- Doc/using/cmdline.rst | 4 +--- Lib/hmac.py | 2 +- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Doc/library/hmac.rst b/Doc/library/hmac.rst index b2ca0455d3745c..43012e03c580e8 100644 --- a/Doc/library/hmac.rst +++ b/Doc/library/hmac.rst @@ -14,7 +14,7 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. -.. function:: new(key, msg=None, digestmod='') +.. function:: new(key, msg=None, digestmod) Return a new hmac object. *key* is a bytes or bytearray object giving the secret key. If *msg* is present, the method call ``update(msg)`` is made. @@ -27,10 +27,9 @@ This module implements the HMAC algorithm as described by :rfc:`2104`. Parameter *msg* can be of any type supported by :mod:`hashlib`. Parameter *digestmod* can be the name of a hash algorithm. - .. deprecated-removed:: 3.4 3.8 - MD5 as implicit default digest for *digestmod* is deprecated. - The digestmod parameter is now required. Pass it as a keyword - argument to avoid awkwardness when you do not have an initial msg. + .. versionchanged:: 3.8 + The *digestmod* argument is now required. Pass it as a keyword + argument to avoid awkwardness when you do not have an initial *msg*. .. function:: digest(key, msg, digest) @@ -114,11 +113,9 @@ A hash object has the following attributes: .. versionadded:: 3.4 -.. deprecated:: 3.9 - - The undocumented attributes ``HMAC.digest_cons``, ``HMAC.inner``, and - ``HMAC.outer`` are internal implementation details and will be removed in - Python 3.10. +.. versionchanged:: 3.10 + Removed the undocumented attributes ``HMAC.digest_cons``, ``HMAC.inner``, + and ``HMAC.outer``. This module also provides the following helper function: diff --git a/Doc/library/random.rst b/Doc/library/random.rst index 76ae97a8be7e63..feaf260caf3568 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -220,8 +220,8 @@ Functions for sequences generated. For example, a sequence of length 2080 is the largest that can fit within the period of the Mersenne Twister random number generator. - .. deprecated-removed:: 3.9 3.11 - The optional parameter *random*. + .. versionchanged:: 3.11 + Removed the optional parameter *random*. .. function:: sample(population, k, *, counts=None) @@ -407,9 +407,9 @@ Alternative Generator Class that implements the default pseudo-random number generator used by the :mod:`random` module. - .. deprecated-removed:: 3.9 3.11 + .. versionchanged:: 3.11 Formerly the *seed* could be any hashable object. Now it is limited to: - :class:`NoneType`, :class:`int`, :class:`float`, :class:`str`, + ``None``, :class:`int`, :class:`float`, :class:`str`, :class:`bytes`, or :class:`bytearray`. .. class:: SystemRandom([seed]) diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 56235bf4c28c7c..dac4956b551dd3 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -590,9 +590,7 @@ Miscellaneous options .. versionadded:: 3.10 The ``-X warn_default_encoding`` option. - - .. deprecated-removed:: 3.9 3.10 - The ``-X oldparser`` option. + Removed the ``-X oldparser`` option. .. versionadded:: 3.11 The ``-X no_debug_ranges`` option. diff --git a/Lib/hmac.py b/Lib/hmac.py index 8b4f920db954ca..8b4eb2fe741e60 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -53,7 +53,7 @@ def __init__(self, key, msg=None, digestmod=''): raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) if not digestmod: - raise TypeError("Missing required parameter 'digestmod'.") + raise TypeError("Missing required argument 'digestmod'.") if _hashopenssl and isinstance(digestmod, (str, _functype)): try: From cde141717578f22947553db776980aa3e8801353 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 09:54:39 -0700 Subject: [PATCH 211/442] gh-76785: Fix test_threading on Non-Subinterpreter Builds (gh-113014) gh-112982 broke test_threading on one of the s390 buildbots (Fedora Clang Installed). Apparently ImportError is raised (rather than ModuleNotFoundError) for the name part of "from" imports. This fixes that by catching ImportError in test_threading.py. --- Lib/test/test_threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index a5744a4037ecea..3060af44fd7e3d 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -28,7 +28,7 @@ try: from test.support import interpreters -except ModuleNotFoundError: +except ImportError: interpreters = None threading_helper.requires_working_threading(module=True) From a49b427b0265c415d9089da0be39f4b5ccd1f15f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 10:43:30 -0700 Subject: [PATCH 212/442] gh-76785: More Fixes for test.support.interpreters (gh-113012) This brings the module (along with the associated extension modules) mostly in sync with PEP 734. There are only a few small things to wrap up. --- Lib/test/support/interpreters/queues.py | 158 +- Lib/test/test_interpreters/test_queues.py | 96 +- Modules/Setup | 1 + Modules/Setup.stdlib.in | 3 + Modules/_xxinterpchannelsmodule.c | 5 +- Modules/_xxinterpqueuesmodule.c | 1685 +++++++++++++++++++ PC/config.c | 2 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 + Tools/build/generate_stdlib_module_names.py | 1 + Tools/c-analyzer/cpython/ignored.tsv | 1 + configure | 29 + configure.ac | 2 + 13 files changed, 1899 insertions(+), 88 deletions(-) create mode 100644 Modules/_xxinterpqueuesmodule.c diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py index ed6b0d551dd890..aead0c40ca9667 100644 --- a/Lib/test/support/interpreters/queues.py +++ b/Lib/test/support/interpreters/queues.py @@ -3,13 +3,11 @@ import queue import time import weakref -import _xxinterpchannels as _channels -import _xxinterpchannels as _queues +import _xxinterpqueues as _queues # aliases: -from _xxinterpchannels import ( - ChannelError as QueueError, - ChannelNotFoundError as QueueNotFoundError, +from _xxinterpqueues import ( + QueueError, QueueNotFoundError, ) __all__ = [ @@ -19,14 +17,27 @@ ] +class QueueEmpty(_queues.QueueEmpty, queue.Empty): + """Raised from get_nowait() when the queue is empty. + + It is also raised from get() if it times out. + """ + + +class QueueFull(_queues.QueueFull, queue.Full): + """Raised from put_nowait() when the queue is full. + + It is also raised from put() if it times out. + """ + + def create(maxsize=0): """Return a new cross-interpreter queue. The queue may be used to pass data safely between interpreters. """ - # XXX honor maxsize - qid = _queues.create() - return Queue._with_maxsize(qid, maxsize) + qid = _queues.create(maxsize) + return Queue(qid) def list_all(): @@ -35,53 +46,37 @@ def list_all(): for qid in _queues.list_all()] -class QueueEmpty(queue.Empty): - """Raised from get_nowait() when the queue is empty. - - It is also raised from get() if it times out. - """ - - -class QueueFull(queue.Full): - """Raised from put_nowait() when the queue is full. - - It is also raised from put() if it times out. - """ - _known_queues = weakref.WeakValueDictionary() class Queue: """A cross-interpreter queue.""" - @classmethod - def _with_maxsize(cls, id, maxsize): - if not isinstance(maxsize, int): - raise TypeError(f'maxsize must be an int, got {maxsize!r}') - elif maxsize < 0: - maxsize = 0 - else: - maxsize = int(maxsize) - self = cls(id) - self._maxsize = maxsize - return self - def __new__(cls, id, /): # There is only one instance for any given ID. if isinstance(id, int): - id = _channels._channel_id(id, force=False) - elif not isinstance(id, _channels.ChannelID): + id = int(id) + else: raise TypeError(f'id must be an int, got {id!r}') - key = int(id) try: - self = _known_queues[key] + self = _known_queues[id] except KeyError: self = super().__new__(cls) self._id = id - self._maxsize = 0 - _known_queues[key] = self + _known_queues[id] = self + _queues.bind(id) return self + def __del__(self): + try: + _queues.release(self._id) + except QueueNotFoundError: + pass + try: + del _known_queues[self._id] + except KeyError: + pass + def __repr__(self): return f'{type(self).__name__}({self.id})' @@ -90,39 +85,58 @@ def __hash__(self): @property def id(self): - return int(self._id) + return self._id @property def maxsize(self): - return self._maxsize - - @property - def _info(self): - return _channels.get_info(self._id) + try: + return self._maxsize + except AttributeError: + self._maxsize = _queues.get_maxsize(self._id) + return self._maxsize def empty(self): - return self._info.count == 0 + return self.qsize() == 0 def full(self): - if self._maxsize <= 0: - return False - return self._info.count >= self._maxsize + return _queues.is_full(self._id) def qsize(self): - return self._info.count + return _queues.get_count(self._id) - def put(self, obj, timeout=None): - # XXX block if full - _channels.send(self._id, obj, blocking=False) + def put(self, obj, timeout=None, *, + _delay=10 / 1000, # 10 milliseconds + ): + """Add the object to the queue. + + This blocks while the queue is full. + """ + if timeout is not None: + timeout = int(timeout) + if timeout < 0: + raise ValueError(f'timeout value must be non-negative') + end = time.time() + timeout + while True: + try: + _queues.put(self._id, obj) + except _queues.QueueFull as exc: + if timeout is not None and time.time() >= end: + exc.__class__ = QueueFull + raise # re-raise + time.sleep(_delay) + else: + break def put_nowait(self, obj): - # XXX raise QueueFull if full - return _channels.send(self._id, obj, blocking=False) + try: + return _queues.put(self._id, obj) + except _queues.QueueFull as exc: + exc.__class__ = QueueFull + raise # re-raise def get(self, timeout=None, *, - _sentinel=object(), - _delay=10 / 1000, # 10 milliseconds - ): + _delay=10 / 1000, # 10 milliseconds + ): """Return the next object from the queue. This blocks while the queue is empty. @@ -132,25 +146,27 @@ def get(self, timeout=None, *, if timeout < 0: raise ValueError(f'timeout value must be non-negative') end = time.time() + timeout - obj = _channels.recv(self._id, _sentinel) - while obj is _sentinel: - time.sleep(_delay) - if timeout is not None and time.time() >= end: - raise QueueEmpty - obj = _channels.recv(self._id, _sentinel) + while True: + try: + return _queues.get(self._id) + except _queues.QueueEmpty as exc: + if timeout is not None and time.time() >= end: + exc.__class__ = QueueEmpty + raise # re-raise + time.sleep(_delay) return obj - def get_nowait(self, *, _sentinel=object()): + def get_nowait(self): """Return the next object from the channel. If the queue is empty then raise QueueEmpty. Otherwise this is the same as get(). """ - obj = _channels.recv(self._id, _sentinel) - if obj is _sentinel: - raise QueueEmpty - return obj + try: + return _queues.get(self._id) + except _queues.QueueEmpty as exc: + exc.__class__ = QueueEmpty + raise # re-raise -# XXX add this: -#_channels._register_queue_type(Queue) +_queues._register_queue_type(Queue) diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 2af90b14d3e3c4..2a8ca99c1f6e3f 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -5,13 +5,21 @@ from test.support import import_helper # Raise SkipTest if subinterpreters not supported. -import_helper.import_module('_xxinterpchannels') -#import_helper.import_module('_xxinterpqueues') +_queues = import_helper.import_module('_xxinterpqueues') from test.support import interpreters from test.support.interpreters import queues from .utils import _run_output, TestBase +class TestBase(TestBase): + def tearDown(self): + for qid in _queues.list_all(): + try: + _queues.destroy(qid) + except Exception: + pass + + class QueueTests(TestBase): def test_create(self): @@ -32,20 +40,47 @@ def test_create(self): self.assertEqual(queue.maxsize, 0) with self.subTest('negative maxsize'): - queue = queues.create(-1) - self.assertEqual(queue.maxsize, 0) + queue = queues.create(-10) + self.assertEqual(queue.maxsize, -10) with self.subTest('bad maxsize'): with self.assertRaises(TypeError): queues.create('1') - @unittest.expectedFailure def test_shareable(self): queue1 = queues.create() - queue2 = queues.create() - queue1.put(queue2) - queue3 = queue1.get() - self.assertIs(queue3, queue1) + + interp = interpreters.create() + interp.exec_sync(dedent(f""" + from test.support.interpreters import queues + queue1 = queues.Queue({queue1.id}) + """)); + + with self.subTest('same interpreter'): + queue2 = queues.create() + queue1.put(queue2) + queue3 = queue1.get() + self.assertIs(queue3, queue2) + + with self.subTest('from current interpreter'): + queue4 = queues.create() + queue1.put(queue4) + out = _run_output(interp, dedent(""" + queue4 = queue1.get() + print(queue4.id) + """)) + qid = int(out) + self.assertEqual(qid, queue4.id) + + with self.subTest('from subinterpreter'): + out = _run_output(interp, dedent(""" + queue5 = queues.create() + queue1.put(queue5) + print(queue5.id) + """)) + qid = int(out) + queue5 = queue1.get() + self.assertEqual(queue5.id, qid) def test_id_type(self): queue = queues.create() @@ -137,7 +172,6 @@ def test_put_get_main(self): self.assertEqual(actual, expected) - @unittest.expectedFailure def test_put_timeout(self): queue = queues.create(2) queue.put(None) @@ -147,7 +181,6 @@ def test_put_timeout(self): queue.get() queue.put(None) - @unittest.expectedFailure def test_put_nowait(self): queue = queues.create(2) queue.put_nowait(None) @@ -179,31 +212,64 @@ def test_put_get_same_interpreter(self): assert obj is not orig, 'expected: obj is not orig' """)) - @unittest.expectedFailure def test_put_get_different_interpreters(self): + interp = interpreters.create() queue1 = queues.create() queue2 = queues.create() + self.assertEqual(len(queues.list_all()), 2) + obj1 = b'spam' queue1.put(obj1) + out = _run_output( - interpreters.create(), + interp, dedent(f""" - import test.support.interpreters.queue as queues + from test.support.interpreters import queues queue1 = queues.Queue({queue1.id}) queue2 = queues.Queue({queue2.id}) + assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1' obj = queue1.get() + assert queue1.qsize() == 0, 'expected: queue1.qsize() == 0' assert obj == b'spam', 'expected: obj == obj1' # When going to another interpreter we get a copy. assert id(obj) != {id(obj1)}, 'expected: obj is not obj1' obj2 = b'eggs' print(id(obj2)) + assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0' queue2.put(obj2) + assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1' """)) - obj2 = queue2.get() + self.assertEqual(len(queues.list_all()), 2) + self.assertEqual(queue1.qsize(), 0) + self.assertEqual(queue2.qsize(), 1) + obj2 = queue2.get() self.assertEqual(obj2, b'eggs') self.assertNotEqual(id(obj2), int(out)) + def test_put_cleared_with_subinterpreter(self): + interp = interpreters.create() + queue = queues.create() + + out = _run_output( + interp, + dedent(f""" + from test.support.interpreters import queues + queue = queues.Queue({queue.id}) + obj1 = b'spam' + obj2 = b'eggs' + queue.put(obj1) + queue.put(obj2) + """)) + self.assertEqual(queue.qsize(), 2) + + obj1 = queue.get() + self.assertEqual(obj1, b'spam') + self.assertEqual(queue.qsize(), 1) + + del interp + self.assertEqual(queue.qsize(), 0) + def test_put_get_different_threads(self): queue1 = queues.create() queue2 = queues.create() diff --git a/Modules/Setup b/Modules/Setup index 1367f0ef4fa54a..8ad9a5aebbfcaa 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -273,6 +273,7 @@ PYTHONPATH=$(COREPYTHONPATH) #_xxsubinterpreters _xxsubinterpretersmodule.c #_xxinterpchannels _xxinterpchannelsmodule.c +#_xxinterpqueues _xxinterpqueuesmodule.c #_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c #_testbuffer _testbuffer.c #_testinternalcapi _testinternalcapi.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 54650ea9c1d4ac..8a65a9cffb1b9d 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -41,8 +41,11 @@ @MODULE__QUEUE_TRUE@_queue _queuemodule.c @MODULE__RANDOM_TRUE@_random _randommodule.c @MODULE__STRUCT_TRUE@_struct _struct.c + +# build supports subinterpreters @MODULE__XXSUBINTERPRETERS_TRUE@_xxsubinterpreters _xxsubinterpretersmodule.c @MODULE__XXINTERPCHANNELS_TRUE@_xxinterpchannels _xxinterpchannelsmodule.c +@MODULE__XXINTERPQUEUES_TRUE@_xxinterpqueues _xxinterpqueuesmodule.c @MODULE__ZONEINFO_TRUE@_zoneinfo _zoneinfo.c # needs libm diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 97729ec269cb62..4e9b8a82a3f630 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -2629,10 +2629,11 @@ _get_current_channelend_type(int end) cls = state->recv_channel_type; } if (cls == NULL) { - PyObject *highlevel = PyImport_ImportModule("interpreters"); + // Force the module to be loaded, to register the type. + PyObject *highlevel = PyImport_ImportModule("interpreters.channel"); if (highlevel == NULL) { PyErr_Clear(); - highlevel = PyImport_ImportModule("test.support.interpreters"); + highlevel = PyImport_ImportModule("test.support.interpreters.channel"); if (highlevel == NULL) { return NULL; } diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c new file mode 100644 index 00000000000000..2cc3a2ac5dc85f --- /dev/null +++ b/Modules/_xxinterpqueuesmodule.c @@ -0,0 +1,1685 @@ +/* interpreters module */ +/* low-level access to interpreter primitives */ + +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + +#include "Python.h" +#include "pycore_crossinterp.h" // struct _xid + + +#define MODULE_NAME "_xxinterpqueues" + + +#define GLOBAL_MALLOC(TYPE) \ + PyMem_RawMalloc(sizeof(TYPE)) +#define GLOBAL_FREE(VAR) \ + PyMem_RawFree(VAR) + + +#define XID_IGNORE_EXC 1 +#define XID_FREE 2 + +static int +_release_xid_data(_PyCrossInterpreterData *data, int flags) +{ + int ignoreexc = flags & XID_IGNORE_EXC; + PyObject *exc; + if (ignoreexc) { + exc = PyErr_GetRaisedException(); + } + int res; + if (flags & XID_FREE) { + res = _PyCrossInterpreterData_ReleaseAndRawFree(data); + } + else { + res = _PyCrossInterpreterData_Release(data); + } + if (res < 0) { + /* The owning interpreter is already destroyed. */ + if (ignoreexc) { + // XXX Emit a warning? + PyErr_Clear(); + } + } + if (flags & XID_FREE) { + /* Either way, we free the data. */ + } + if (ignoreexc) { + PyErr_SetRaisedException(exc); + } + return res; +} + + +static PyInterpreterState * +_get_current_interp(void) +{ + // PyInterpreterState_Get() aborts if lookup fails, so don't need + // to check the result for NULL. + return PyInterpreterState_Get(); +} + +static PyObject * +_get_current_module(void) +{ + PyObject *name = PyUnicode_FromString(MODULE_NAME); + if (name == NULL) { + return NULL; + } + PyObject *mod = PyImport_GetModule(name); + Py_DECREF(name); + if (mod == NULL) { + return NULL; + } + assert(mod != Py_None); + return mod; +} + + +struct idarg_int64_converter_data { + // input: + const char *label; + // output: + int64_t id; +}; + +static int +idarg_int64_converter(PyObject *arg, void *ptr) +{ + int64_t id; + struct idarg_int64_converter_data *data = ptr; + + const char *label = data->label; + if (label == NULL) { + label = "ID"; + } + + if (PyIndex_Check(arg)) { + int overflow = 0; + id = PyLong_AsLongLongAndOverflow(arg, &overflow); + if (id == -1 && PyErr_Occurred()) { + return 0; + } + else if (id == -1 && overflow == 1) { + PyErr_Format(PyExc_OverflowError, + "max %s is %lld, got %R", label, INT64_MAX, arg); + return 0; + } + else if (id < 0) { + PyErr_Format(PyExc_ValueError, + "%s must be a non-negative int, got %R", label, arg); + return 0; + } + } + else { + PyErr_Format(PyExc_TypeError, + "%s must be an int, got %.100s", + label, Py_TYPE(arg)->tp_name); + return 0; + } + data->id = id; + return 1; +} + + +/* module state *************************************************************/ + +typedef struct { + /* external types (added at runtime by interpreters module) */ + PyTypeObject *queue_type; + + /* QueueError (and its subclasses) */ + PyObject *QueueError; + PyObject *QueueNotFoundError; + PyObject *QueueEmpty; + PyObject *QueueFull; +} module_state; + +static inline module_state * +get_module_state(PyObject *mod) +{ + assert(mod != NULL); + module_state *state = PyModule_GetState(mod); + assert(state != NULL); + return state; +} + +static int +traverse_module_state(module_state *state, visitproc visit, void *arg) +{ + /* external types */ + Py_VISIT(state->queue_type); + + /* QueueError */ + Py_VISIT(state->QueueError); + Py_VISIT(state->QueueNotFoundError); + Py_VISIT(state->QueueEmpty); + Py_VISIT(state->QueueFull); + + return 0; +} + +static int +clear_module_state(module_state *state) +{ + /* external types */ + Py_CLEAR(state->queue_type); + + /* QueueError */ + Py_CLEAR(state->QueueError); + Py_CLEAR(state->QueueNotFoundError); + Py_CLEAR(state->QueueEmpty); + Py_CLEAR(state->QueueFull); + + return 0; +} + + +/* error codes **************************************************************/ + +#define ERR_EXCEPTION_RAISED (-1) +// multi-queue errors +#define ERR_QUEUES_ALLOC (-11) +#define ERR_QUEUE_ALLOC (-12) +#define ERR_NO_NEXT_QUEUE_ID (-13) +#define ERR_QUEUE_NOT_FOUND (-14) +// single-queue errors +#define ERR_QUEUE_EMPTY (-21) +#define ERR_QUEUE_FULL (-22) + +static int +resolve_module_errcode(module_state *state, int errcode, int64_t qid, + PyObject **p_exctype, PyObject **p_msgobj) +{ + PyObject *exctype = NULL; + PyObject *msg = NULL; + switch (errcode) { + case ERR_NO_NEXT_QUEUE_ID: + exctype = state->QueueError; + msg = PyUnicode_FromString("ran out of queue IDs"); + break; + case ERR_QUEUE_NOT_FOUND: + exctype = state->QueueNotFoundError; + msg = PyUnicode_FromFormat("queue %" PRId64 " not found", qid); + break; + case ERR_QUEUE_EMPTY: + exctype = state->QueueEmpty; + msg = PyUnicode_FromFormat("queue %" PRId64 " is empty", qid); + break; + case ERR_QUEUE_FULL: + exctype = state->QueueFull; + msg = PyUnicode_FromFormat("queue %" PRId64 " is full", qid); + break; + default: + PyErr_Format(PyExc_ValueError, + "unsupported error code %d", errcode); + return -1; + } + + if (msg == NULL) { + assert(PyErr_Occurred()); + return -1; + } + *p_exctype = exctype; + *p_msgobj = msg; + return 0; +} + + +/* QueueError ***************************************************************/ + +static int +add_exctype(PyObject *mod, PyObject **p_state_field, + const char *qualname, const char *doc, PyObject *base) +{ + const char *dot = strrchr(qualname, '.'); + assert(dot != NULL); + const char *name = dot+1; + assert(*p_state_field == NULL); + assert(!PyObject_HasAttrStringWithError(mod, name)); + PyObject *exctype = PyErr_NewExceptionWithDoc(qualname, doc, base, NULL); + if (exctype == NULL) { + return -1; + } + if (PyModule_AddType(mod, (PyTypeObject *)exctype) < 0) { + Py_DECREF(exctype); + return -1; + } + *p_state_field = exctype; + return 0; +} + +static int +add_QueueError(PyObject *mod) +{ + module_state *state = get_module_state(mod); + +#define PREFIX "test.support.interpreters." +#define ADD_EXCTYPE(NAME, BASE, DOC) \ + if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \ + return -1; \ + } + ADD_EXCTYPE(QueueError, PyExc_RuntimeError, + "Indicates that a queue-related error happened.") + ADD_EXCTYPE(QueueNotFoundError, state->QueueError, NULL) + ADD_EXCTYPE(QueueEmpty, state->QueueError, NULL) + ADD_EXCTYPE(QueueFull, state->QueueError, NULL) +#undef ADD_EXCTYPE +#undef PREFIX + + return 0; +} + +static int +handle_queue_error(int err, PyObject *mod, int64_t qid) +{ + if (err == 0) { + assert(!PyErr_Occurred()); + return 0; + } + assert(err < 0); + assert((err == -1) == (PyErr_Occurred() != NULL)); + + module_state *state; + switch (err) { + case ERR_QUEUE_ALLOC: // fall through + case ERR_QUEUES_ALLOC: + PyErr_NoMemory(); + break; + default: + state = get_module_state(mod); + assert(state->QueueError != NULL); + PyObject *exctype = NULL; + PyObject *msg = NULL; + if (resolve_module_errcode(state, err, qid, &exctype, &msg) < 0) { + return -1; + } + PyObject *exc = PyObject_CallOneArg(exctype, msg); + Py_DECREF(msg); + if (exc == NULL) { + return -1; + } + PyErr_SetObject(exctype, exc); + Py_DECREF(exc); + } + return 1; +} + + +/* the basic queue **********************************************************/ + +struct _queueitem; + +typedef struct _queueitem { + _PyCrossInterpreterData *data; + struct _queueitem *next; +} _queueitem; + +static void +_queueitem_init(_queueitem *item, _PyCrossInterpreterData *data) +{ + *item = (_queueitem){ + .data = data, + }; +} + +static void +_queueitem_clear(_queueitem *item) +{ + item->next = NULL; + + if (item->data != NULL) { + // It was allocated in queue_put(). + (void)_release_xid_data(item->data, XID_IGNORE_EXC & XID_FREE); + item->data = NULL; + } +} + +static _queueitem * +_queueitem_new(_PyCrossInterpreterData *data) +{ + _queueitem *item = GLOBAL_MALLOC(_queueitem); + if (item == NULL) { + PyErr_NoMemory(); + return NULL; + } + _queueitem_init(item, data); + return item; +} + +static void +_queueitem_free(_queueitem *item) +{ + _queueitem_clear(item); + GLOBAL_FREE(item); +} + +static void +_queueitem_free_all(_queueitem *item) +{ + while (item != NULL) { + _queueitem *last = item; + item = item->next; + _queueitem_free(last); + } +} + +static void +_queueitem_popped(_queueitem *item, _PyCrossInterpreterData **p_data) +{ + *p_data = item->data; + // We clear them here, so they won't be released in _queueitem_clear(). + item->data = NULL; + _queueitem_free(item); +} + + +/* the queue */ +typedef struct _queue { + Py_ssize_t num_waiters; // protected by global lock + PyThread_type_lock mutex; + int alive; + struct _queueitems { + Py_ssize_t maxsize; + Py_ssize_t count; + _queueitem *first; + _queueitem *last; + } items; +} _queue; + +static int +_queue_init(_queue *queue, Py_ssize_t maxsize) +{ + PyThread_type_lock mutex = PyThread_allocate_lock(); + if (mutex == NULL) { + return ERR_QUEUE_ALLOC; + } + *queue = (_queue){ + .mutex = mutex, + .alive = 1, + .items = { + .maxsize = maxsize, + }, + }; + return 0; +} + +static void +_queue_clear(_queue *queue) +{ + assert(!queue->alive); + assert(queue->num_waiters == 0); + _queueitem_free_all(queue->items.first); + assert(queue->mutex != NULL); + PyThread_free_lock(queue->mutex); + *queue = (_queue){0}; +} + +static void +_queue_kill_and_wait(_queue *queue) +{ + // Mark it as dead. + PyThread_acquire_lock(queue->mutex, WAIT_LOCK); + assert(queue->alive); + queue->alive = 0; + PyThread_release_lock(queue->mutex); + + // Wait for all waiters to fail. + while (queue->num_waiters > 0) { + PyThread_acquire_lock(queue->mutex, WAIT_LOCK); + PyThread_release_lock(queue->mutex); + }; +} + +static void +_queue_mark_waiter(_queue *queue, PyThread_type_lock parent_mutex) +{ + if (parent_mutex != NULL) { + PyThread_acquire_lock(parent_mutex, WAIT_LOCK); + queue->num_waiters += 1; + PyThread_release_lock(parent_mutex); + } + else { + // The caller must be holding the parent lock already. + queue->num_waiters += 1; + } +} + +static void +_queue_unmark_waiter(_queue *queue, PyThread_type_lock parent_mutex) +{ + if (parent_mutex != NULL) { + PyThread_acquire_lock(parent_mutex, WAIT_LOCK); + queue->num_waiters -= 1; + PyThread_release_lock(parent_mutex); + } + else { + // The caller must be holding the parent lock already. + queue->num_waiters -= 1; + } +} + +static int +_queue_lock(_queue *queue) +{ + // The queue must be marked as a waiter already. + PyThread_acquire_lock(queue->mutex, WAIT_LOCK); + if (!queue->alive) { + PyThread_release_lock(queue->mutex); + return ERR_QUEUE_NOT_FOUND; + } + return 0; +} + +static void +_queue_unlock(_queue *queue) +{ + PyThread_release_lock(queue->mutex); +} + +static int +_queue_add(_queue *queue, _PyCrossInterpreterData *data) +{ + int err = _queue_lock(queue); + if (err < 0) { + return err; + } + + Py_ssize_t maxsize = queue->items.maxsize; + if (maxsize <= 0) { + maxsize = PY_SSIZE_T_MAX; + } + if (queue->items.count >= maxsize) { + _queue_unlock(queue); + return ERR_QUEUE_FULL; + } + + _queueitem *item = _queueitem_new(data); + if (item == NULL) { + _queue_unlock(queue); + return -1; + } + + queue->items.count += 1; + if (queue->items.first == NULL) { + queue->items.first = item; + } + else { + queue->items.last->next = item; + } + queue->items.last = item; + + _queue_unlock(queue); + return 0; +} + +static int +_queue_next(_queue *queue, _PyCrossInterpreterData **p_data) +{ + int err = _queue_lock(queue); + if (err < 0) { + return err; + } + + assert(queue->items.count >= 0); + _queueitem *item = queue->items.first; + if (item == NULL) { + _queue_unlock(queue); + return ERR_QUEUE_EMPTY; + } + queue->items.first = item->next; + if (queue->items.last == item) { + queue->items.last = NULL; + } + queue->items.count -= 1; + + _queueitem_popped(item, p_data); + + _queue_unlock(queue); + return 0; +} + +static int +_queue_get_maxsize(_queue *queue, Py_ssize_t *p_maxsize) +{ + int err = _queue_lock(queue); + if (err < 0) { + return err; + } + + *p_maxsize = queue->items.maxsize; + + _queue_unlock(queue); + return 0; +} + +static int +_queue_is_full(_queue *queue, int *p_is_full) +{ + int err = _queue_lock(queue); + if (err < 0) { + return err; + } + + assert(queue->items.count <= queue->items.maxsize); + *p_is_full = queue->items.count == queue->items.maxsize; + + _queue_unlock(queue); + return 0; +} + +static int +_queue_get_count(_queue *queue, Py_ssize_t *p_count) +{ + int err = _queue_lock(queue); + if (err < 0) { + return err; + } + + *p_count = queue->items.count; + + _queue_unlock(queue); + return 0; +} + +static void +_queue_clear_interpreter(_queue *queue, int64_t interpid) +{ + int err = _queue_lock(queue); + if (err == ERR_QUEUE_NOT_FOUND) { + // The queue is already destroyed, so there's nothing to clear. + assert(!PyErr_Occurred()); + return; + } + assert(err == 0); // There should be no other errors. + + _queueitem *prev = NULL; + _queueitem *next = queue->items.first; + while (next != NULL) { + _queueitem *item = next; + next = item->next; + if (item->data->interpid == interpid) { + if (prev == NULL) { + queue->items.first = item->next; + } + else { + prev->next = item->next; + } + _queueitem_free(item); + queue->items.count -= 1; + } + else { + prev = item; + } + } + + _queue_unlock(queue); +} + + +/* external queue references ************************************************/ + +struct _queueref; + +typedef struct _queueref { + struct _queueref *next; + int64_t qid; + Py_ssize_t refcount; + _queue *queue; +} _queueref; + +static _queueref * +_queuerefs_find(_queueref *first, int64_t qid, _queueref **pprev) +{ + _queueref *prev = NULL; + _queueref *ref = first; + while (ref != NULL) { + if (ref->qid == qid) { + break; + } + prev = ref; + ref = ref->next; + } + if (pprev != NULL) { + *pprev = prev; + } + return ref; +} + + +/* a collection of queues ***************************************************/ + +typedef struct _queues { + PyThread_type_lock mutex; + _queueref *head; + int64_t count; + int64_t next_id; +} _queues; + +static void +_queues_init(_queues *queues, PyThread_type_lock mutex) +{ + queues->mutex = mutex; + queues->head = NULL; + queues->count = 0; + queues->next_id = 1; +} + +static void +_queues_fini(_queues *queues) +{ + assert(queues->count == 0); + assert(queues->head == NULL); + if (queues->mutex != NULL) { + PyThread_free_lock(queues->mutex); + queues->mutex = NULL; + } +} + +static int64_t +_queues_next_id(_queues *queues) // needs lock +{ + int64_t qid = queues->next_id; + if (qid < 0) { + /* overflow */ + return ERR_NO_NEXT_QUEUE_ID; + } + queues->next_id += 1; + return qid; +} + +static int +_queues_lookup(_queues *queues, int64_t qid, _queue **res) +{ + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + _queueref *ref = _queuerefs_find(queues->head, qid, NULL); + if (ref == NULL) { + PyThread_release_lock(queues->mutex); + return ERR_QUEUE_NOT_FOUND; + } + assert(ref->queue != NULL); + _queue *queue = ref->queue; + _queue_mark_waiter(queue, NULL); + // The caller must unmark it. + + PyThread_release_lock(queues->mutex); + + *res = queue; + return 0; +} + +static int64_t +_queues_add(_queues *queues, _queue *queue) +{ + int64_t qid = -1; + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + // Create a new ref. + int64_t _qid = _queues_next_id(queues); + if (_qid < 0) { + goto done; + } + _queueref *ref = GLOBAL_MALLOC(_queueref); + if (ref == NULL) { + qid = ERR_QUEUE_ALLOC; + goto done; + } + *ref = (_queueref){ + .qid = _qid, + .queue = queue, + }; + + // Add it to the list. + // We assume that the queue is a new one (not already in the list). + ref->next = queues->head; + queues->head = ref; + queues->count += 1; + + qid = _qid; +done: + PyThread_release_lock(queues->mutex); + return qid; +} + +static void +_queues_remove_ref(_queues *queues, _queueref *ref, _queueref *prev, + _queue **p_queue) +{ + assert(ref->queue != NULL); + + if (ref == queues->head) { + queues->head = ref->next; + } + else { + prev->next = ref->next; + } + ref->next = NULL; + queues->count -= 1; + + *p_queue = ref->queue; + ref->queue = NULL; + GLOBAL_FREE(ref); +} + +static int +_queues_remove(_queues *queues, int64_t qid, _queue **p_queue) +{ + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + _queueref *prev = NULL; + _queueref *ref = _queuerefs_find(queues->head, qid, &prev); + if (ref == NULL) { + PyThread_release_lock(queues->mutex); + return ERR_QUEUE_NOT_FOUND; + } + + _queues_remove_ref(queues, ref, prev, p_queue); + PyThread_release_lock(queues->mutex); + + return 0; +} + +static int +_queues_incref(_queues *queues, int64_t qid) +{ + // XXX Track interpreter IDs? + int res = -1; + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + _queueref *ref = _queuerefs_find(queues->head, qid, NULL); + if (ref == NULL) { + assert(!PyErr_Occurred()); + res = ERR_QUEUE_NOT_FOUND; + goto done; + } + ref->refcount += 1; + + res = 0; +done: + PyThread_release_lock(queues->mutex); + return res; +} + +static void _queue_free(_queue *); + +static void +_queues_decref(_queues *queues, int64_t qid) +{ + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + _queueref *prev = NULL; + _queueref *ref = _queuerefs_find(queues->head, qid, &prev); + if (ref == NULL) { + assert(!PyErr_Occurred()); + // Already destroyed. + // XXX Warn? + goto finally; + } + assert(ref->refcount > 0); + ref->refcount -= 1; + + // Destroy if no longer used. + assert(ref->queue != NULL); + if (ref->refcount == 0) { + _queue *queue = NULL; + _queues_remove_ref(queues, ref, prev, &queue); + PyThread_release_lock(queues->mutex); + + _queue_kill_and_wait(queue); + _queue_free(queue); + return; + } + +finally: + PyThread_release_lock(queues->mutex); +} + +static int64_t * +_queues_list_all(_queues *queues, int64_t *count) +{ + int64_t *qids = NULL; + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + int64_t *ids = PyMem_NEW(int64_t, (Py_ssize_t)(queues->count)); + if (ids == NULL) { + goto done; + } + _queueref *ref = queues->head; + for (int64_t i=0; ref != NULL; ref = ref->next, i++) { + ids[i] = ref->qid; + } + *count = queues->count; + + qids = ids; +done: + PyThread_release_lock(queues->mutex); + return qids; +} + +static void +_queues_clear_interpreter(_queues *queues, int64_t interpid) +{ + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + + _queueref *ref = queues->head; + for (; ref != NULL; ref = ref->next) { + assert(ref->queue != NULL); + _queue_clear_interpreter(ref->queue, interpid); + } + + PyThread_release_lock(queues->mutex); +} + + +/* "high"-level queue-related functions *************************************/ + +static void +_queue_free(_queue *queue) +{ + _queue_clear(queue); + GLOBAL_FREE(queue); +} + +// Create a new queue. +static int64_t +queue_create(_queues *queues, Py_ssize_t maxsize) +{ + _queue *queue = GLOBAL_MALLOC(_queue); + if (queue == NULL) { + return ERR_QUEUE_ALLOC; + } + int err = _queue_init(queue, maxsize); + if (err < 0) { + GLOBAL_FREE(queue); + return (int64_t)err; + } + int64_t qid = _queues_add(queues, queue); + if (qid < 0) { + _queue_clear(queue); + GLOBAL_FREE(queue); + } + return qid; +} + +// Completely destroy the queue. +static int +queue_destroy(_queues *queues, int64_t qid) +{ + _queue *queue = NULL; + int err = _queues_remove(queues, qid, &queue); + if (err < 0) { + return err; + } + _queue_kill_and_wait(queue); + _queue_free(queue); + return 0; +} + +// Push an object onto the queue. +static int +queue_put(_queues *queues, int64_t qid, PyObject *obj) +{ + // Look up the queue. + _queue *queue = NULL; + int err = _queues_lookup(queues, qid, &queue); + if (err != 0) { + return err; + } + assert(queue != NULL); + + // Convert the object to cross-interpreter data. + _PyCrossInterpreterData *data = GLOBAL_MALLOC(_PyCrossInterpreterData); + if (data == NULL) { + _queue_unmark_waiter(queue, queues->mutex); + return -1; + } + if (_PyObject_GetCrossInterpreterData(obj, data) != 0) { + _queue_unmark_waiter(queue, queues->mutex); + GLOBAL_FREE(data); + return -1; + } + + // Add the data to the queue. + int res = _queue_add(queue, data); + _queue_unmark_waiter(queue, queues->mutex); + if (res != 0) { + // We may chain an exception here: + (void)_release_xid_data(data, 0); + GLOBAL_FREE(data); + return res; + } + + return 0; +} + +// Pop the next object off the queue. Fail if empty. +// XXX Support a "wait" mutex? +static int +queue_get(_queues *queues, int64_t qid, PyObject **res) +{ + int err; + *res = NULL; + + // Look up the queue. + _queue *queue = NULL; + err = _queues_lookup(queues, qid, &queue); + if (err != 0) { + return err; + } + // Past this point we are responsible for releasing the mutex. + assert(queue != NULL); + + // Pop off the next item from the queue. + _PyCrossInterpreterData *data = NULL; + err = _queue_next(queue, &data); + _queue_unmark_waiter(queue, queues->mutex); + if (err != 0) { + return err; + } + else if (data == NULL) { + assert(!PyErr_Occurred()); + return 0; + } + + // Convert the data back to an object. + PyObject *obj = _PyCrossInterpreterData_NewObject(data); + if (obj == NULL) { + assert(PyErr_Occurred()); + // It was allocated in queue_put(), so we free it. + (void)_release_xid_data(data, XID_IGNORE_EXC | XID_FREE); + return -1; + } + // It was allocated in queue_put(), so we free it. + int release_res = _release_xid_data(data, XID_FREE); + if (release_res < 0) { + // The source interpreter has been destroyed already. + assert(PyErr_Occurred()); + Py_DECREF(obj); + return -1; + } + + *res = obj; + return 0; +} + +static int +queue_get_maxsize(_queues *queues, int64_t qid, Py_ssize_t *p_maxsize) +{ + _queue *queue = NULL; + int err = _queues_lookup(queues, qid, &queue); + if (err < 0) { + return err; + } + err = _queue_get_maxsize(queue, p_maxsize); + _queue_unmark_waiter(queue, queues->mutex); + return err; +} + +static int +queue_is_full(_queues *queues, int64_t qid, int *p_is_full) +{ + _queue *queue = NULL; + int err = _queues_lookup(queues, qid, &queue); + if (err < 0) { + return err; + } + err = _queue_is_full(queue, p_is_full); + _queue_unmark_waiter(queue, queues->mutex); + return err; +} + +static int +queue_get_count(_queues *queues, int64_t qid, Py_ssize_t *p_count) +{ + _queue *queue = NULL; + int err = _queues_lookup(queues, qid, &queue); + if (err < 0) { + return err; + } + err = _queue_get_count(queue, p_count); + _queue_unmark_waiter(queue, queues->mutex); + return err; +} + + +/* external Queue objects ***************************************************/ + +static int _queueobj_shared(PyThreadState *, + PyObject *, _PyCrossInterpreterData *); + +static int +set_external_queue_type(PyObject *module, PyTypeObject *queue_type) +{ + module_state *state = get_module_state(module); + + if (state->queue_type != NULL) { + PyErr_SetString(PyExc_TypeError, "already registered"); + return -1; + } + state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); + + if (_PyCrossInterpreterData_RegisterClass(queue_type, _queueobj_shared) < 0) { + return -1; + } + + return 0; +} + +static PyTypeObject * +get_external_queue_type(PyObject *module) +{ + module_state *state = get_module_state(module); + + PyTypeObject *cls = state->queue_type; + if (cls == NULL) { + // Force the module to be loaded, to register the type. + PyObject *highlevel = PyImport_ImportModule("interpreters.queue"); + if (highlevel == NULL) { + PyErr_Clear(); + highlevel = PyImport_ImportModule("test.support.interpreters.queue"); + if (highlevel == NULL) { + return NULL; + } + } + Py_DECREF(highlevel); + cls = state->queue_type; + assert(cls != NULL); + } + return cls; +} + + +// XXX Use a new __xid__ protocol instead? + +struct _queueid_xid { + int64_t qid; +}; + +static _queues * _get_global_queues(void); + +static void * +_queueid_xid_new(int64_t qid) +{ + _queues *queues = _get_global_queues(); + if (_queues_incref(queues, qid) < 0) { + return NULL; + } + + struct _queueid_xid *data = PyMem_RawMalloc(sizeof(struct _queueid_xid)); + if (data == NULL) { + _queues_incref(queues, qid); + return NULL; + } + data->qid = qid; + return (void *)data; +} + +static void +_queueid_xid_free(void *data) +{ + int64_t qid = ((struct _queueid_xid *)data)->qid; + PyMem_RawFree(data); + _queues *queues = _get_global_queues(); + _queues_decref(queues, qid); +} + +static PyObject * +_queueobj_from_xid(_PyCrossInterpreterData *data) +{ + int64_t qid = *(int64_t *)data->data; + PyObject *qidobj = PyLong_FromLongLong(qid); + if (qidobj == NULL) { + return NULL; + } + + PyObject *mod = _get_current_module(); + if (mod == NULL) { + // XXX import it? + PyErr_SetString(PyExc_RuntimeError, + MODULE_NAME " module not imported yet"); + return NULL; + } + + PyTypeObject *cls = get_external_queue_type(mod); + Py_DECREF(mod); + if (cls == NULL) { + Py_DECREF(qidobj); + return NULL; + } + PyObject *obj = PyObject_CallOneArg((PyObject *)cls, (PyObject *)qidobj); + Py_DECREF(qidobj); + return obj; +} + +static int +_queueobj_shared(PyThreadState *tstate, PyObject *queueobj, + _PyCrossInterpreterData *data) +{ + PyObject *qidobj = PyObject_GetAttrString(queueobj, "_id"); + if (qidobj == NULL) { + return -1; + } + struct idarg_int64_converter_data converted = { + .label = "queue ID", + }; + int res = idarg_int64_converter(qidobj, &converted); + Py_DECREF(qidobj); + if (!res) { + assert(PyErr_Occurred()); + return -1; + } + + void *raw = _queueid_xid_new(converted.id); + if (raw == NULL) { + Py_DECREF(qidobj); + return -1; + } + _PyCrossInterpreterData_Init(data, tstate->interp, raw, NULL, + _queueobj_from_xid); + Py_DECREF(qidobj); + data->free = _queueid_xid_free; + return 0; +} + + +/* module level code ********************************************************/ + +/* globals is the process-global state for the module. It holds all + the data that we need to share between interpreters, so it cannot + hold PyObject values. */ +static struct globals { + int module_count; + _queues queues; +} _globals = {0}; + +static int +_globals_init(void) +{ + // XXX This isn't thread-safe. + _globals.module_count++; + if (_globals.module_count > 1) { + // Already initialized. + return 0; + } + + assert(_globals.queues.mutex == NULL); + PyThread_type_lock mutex = PyThread_allocate_lock(); + if (mutex == NULL) { + return ERR_QUEUES_ALLOC; + } + _queues_init(&_globals.queues, mutex); + return 0; +} + +static void +_globals_fini(void) +{ + // XXX This isn't thread-safe. + _globals.module_count--; + if (_globals.module_count > 0) { + return; + } + + _queues_fini(&_globals.queues); +} + +static _queues * +_get_global_queues(void) +{ + return &_globals.queues; +} + + +static void +clear_interpreter(void *data) +{ + if (_globals.module_count == 0) { + return; + } + PyInterpreterState *interp = (PyInterpreterState *)data; + assert(interp == _get_current_interp()); + int64_t interpid = PyInterpreterState_GetID(interp); + _queues_clear_interpreter(&_globals.queues, interpid); +} + + +typedef struct idarg_int64_converter_data qidarg_converter_data; + +static int +qidarg_converter(PyObject *arg, void *ptr) +{ + qidarg_converter_data *data = ptr; + if (data->label == NULL) { + data->label = "queue ID"; + } + return idarg_int64_converter(arg, ptr); +} + + +static PyObject * +queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"maxsize", NULL}; + Py_ssize_t maxsize = -1; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|n:create", kwlist, + &maxsize)) { + return NULL; + } + + int64_t qid = queue_create(&_globals.queues, maxsize); + if (qid < 0) { + (void)handle_queue_error((int)qid, self, qid); + return NULL; + } + + PyObject *qidobj = PyLong_FromLongLong(qid); + if (qidobj == NULL) { + PyObject *exc = PyErr_GetRaisedException(); + int err = queue_destroy(&_globals.queues, qid); + if (handle_queue_error(err, self, qid)) { + // XXX issue a warning? + PyErr_Clear(); + } + PyErr_SetRaisedException(exc); + return NULL; + } + + return qidobj; +} + +PyDoc_STRVAR(queuesmod_create_doc, +"create() -> qid\n\ +\n\ +Create a new cross-interpreter queue and return its unique generated ID.\n\ +It is a new reference as though bind() had been called on the queue."); + +static PyObject * +queuesmod_destroy(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:destroy", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + int err = queue_destroy(&_globals.queues, qid); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(queuesmod_destroy_doc, +"destroy(qid)\n\ +\n\ +Clear and destroy the queue. Afterward attempts to use the queue\n\ +will behave as though it never existed."); + +static PyObject * +queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + int64_t count = 0; + int64_t *qids = _queues_list_all(&_globals.queues, &count); + if (qids == NULL) { + if (count == 0) { + return PyList_New(0); + } + return NULL; + } + PyObject *ids = PyList_New((Py_ssize_t)count); + if (ids == NULL) { + goto finally; + } + int64_t *cur = qids; + for (int64_t i=0; i < count; cur++, i++) { + PyObject *qidobj = PyLong_FromLongLong(*cur); + if (qidobj == NULL) { + Py_SETREF(ids, NULL); + break; + } + PyList_SET_ITEM(ids, (Py_ssize_t)i, qidobj); + } + +finally: + PyMem_Free(qids); + return ids; +} + +PyDoc_STRVAR(queuesmod_list_all_doc, +"list_all() -> [qid]\n\ +\n\ +Return the list of IDs for all queues."); + +static PyObject * +queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", "obj", NULL}; + qidarg_converter_data qidarg; + PyObject *obj; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O:put", kwlist, + qidarg_converter, &qidarg, &obj)) { + return NULL; + } + int64_t qid = qidarg.id; + + /* Queue up the object. */ + int err = queue_put(&_globals.queues, qid, obj); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(queuesmod_put_doc, +"put(qid, obj)\n\ +\n\ +Add the object's data to the queue."); + +static PyObject * +queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", "default", NULL}; + qidarg_converter_data qidarg; + PyObject *dflt = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O:get", kwlist, + qidarg_converter, &qidarg, &dflt)) { + return NULL; + } + int64_t qid = qidarg.id; + + PyObject *obj = NULL; + int err = queue_get(&_globals.queues, qid, &obj); + if (err == ERR_QUEUE_EMPTY && dflt != NULL) { + assert(obj == NULL); + obj = Py_NewRef(dflt); + } + else if (handle_queue_error(err, self, qid)) { + return NULL; + } + return obj; +} + +PyDoc_STRVAR(queuesmod_get_doc, +"get(qid, [default]) -> obj\n\ +\n\ +Return a new object from the data at the front of the queue.\n\ +\n\ +If there is nothing to receive then raise QueueEmpty, unless\n\ +a default value is provided. In that case return it."); + +static PyObject * +queuesmod_bind(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:bind", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + // XXX Check module state if bound already. + + int err = _queues_incref(&_globals.queues, qid); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + + // XXX Update module state. + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(queuesmod_bind_doc, +"bind(qid)\n\ +\n\ +Take a reference to the identified queue.\n\ +The queue is not destroyed until there are no references left."); + +static PyObject * +queuesmod_release(PyObject *self, PyObject *args, PyObject *kwds) +{ + // Note that only the current interpreter is affected. + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:release", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + // XXX Check module state if bound already. + // XXX Update module state. + + _queues_decref(&_globals.queues, qid); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(queuesmod_release_doc, +"release(qid)\n\ +\n\ +Release a reference to the queue.\n\ +The queue is destroyed once there are no references left."); + +static PyObject * +queuesmod_get_maxsize(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:get_maxsize", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + Py_ssize_t maxsize = -1; + int err = queue_get_maxsize(&_globals.queues, qid, &maxsize); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + return PyLong_FromLongLong(maxsize); +} + +PyDoc_STRVAR(queuesmod_get_maxsize_doc, +"get_maxsize(qid)\n\ +\n\ +Return the maximum number of items in the queue."); + +static PyObject * +queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:is_full", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + int is_full; + int err = queue_is_full(&_globals.queues, qid, &is_full); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + if (is_full) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(queuesmod_is_full_doc, +"is_full(qid)\n\ +\n\ +Return true if the queue has a maxsize and has reached it."); + +static PyObject * +queuesmod_get_count(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:get_count", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + Py_ssize_t count = -1; + int err = queue_get_count(&_globals.queues, qid, &count); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + assert(count >= 0); + return PyLong_FromSsize_t(count); +} + +PyDoc_STRVAR(queuesmod_get_count_doc, +"get_count(qid)\n\ +\n\ +Return the number of items in the queue."); + +static PyObject * +queuesmod__register_queue_type(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"queuetype", NULL}; + PyObject *queuetype; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O:_register_queue_type", kwlist, + &queuetype)) { + return NULL; + } + if (!PyType_Check(queuetype)) { + PyErr_SetString(PyExc_TypeError, "expected a type for 'queuetype'"); + return NULL; + } + PyTypeObject *cls_queue = (PyTypeObject *)queuetype; + + if (set_external_queue_type(self, cls_queue) < 0) { + return NULL; + } + + Py_RETURN_NONE; +} + +static PyMethodDef module_functions[] = { + {"create", _PyCFunction_CAST(queuesmod_create), + METH_VARARGS | METH_KEYWORDS, queuesmod_create_doc}, + {"destroy", _PyCFunction_CAST(queuesmod_destroy), + METH_VARARGS | METH_KEYWORDS, queuesmod_destroy_doc}, + {"list_all", queuesmod_list_all, + METH_NOARGS, queuesmod_list_all_doc}, + {"put", _PyCFunction_CAST(queuesmod_put), + METH_VARARGS | METH_KEYWORDS, queuesmod_put_doc}, + {"get", _PyCFunction_CAST(queuesmod_get), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_doc}, + {"bind", _PyCFunction_CAST(queuesmod_bind), + METH_VARARGS | METH_KEYWORDS, queuesmod_bind_doc}, + {"release", _PyCFunction_CAST(queuesmod_release), + METH_VARARGS | METH_KEYWORDS, queuesmod_release_doc}, + {"get_maxsize", _PyCFunction_CAST(queuesmod_get_maxsize), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_maxsize_doc}, + {"is_full", _PyCFunction_CAST(queuesmod_is_full), + METH_VARARGS | METH_KEYWORDS, queuesmod_is_full_doc}, + {"get_count", _PyCFunction_CAST(queuesmod_get_count), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_count_doc}, + {"_register_queue_type", _PyCFunction_CAST(queuesmod__register_queue_type), + METH_VARARGS | METH_KEYWORDS, NULL}, + + {NULL, NULL} /* sentinel */ +}; + + +/* initialization function */ + +PyDoc_STRVAR(module_doc, +"This module provides primitive operations to manage Python interpreters.\n\ +The 'interpreters' module provides a more convenient interface."); + +static int +module_exec(PyObject *mod) +{ + if (_globals_init() != 0) { + return -1; + } + + /* Add exception types */ + if (add_QueueError(mod) < 0) { + goto error; + } + + /* Make sure queues drop objects owned by this interpreter. */ + PyInterpreterState *interp = _get_current_interp(); + PyUnstable_AtExit(interp, clear_interpreter, (void *)interp); + + return 0; + +error: + _globals_fini(); + return -1; +} + +static struct PyModuleDef_Slot module_slots[] = { + {Py_mod_exec, module_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {0, NULL}, +}; + +static int +module_traverse(PyObject *mod, visitproc visit, void *arg) +{ + module_state *state = get_module_state(mod); + traverse_module_state(state, visit, arg); + return 0; +} + +static int +module_clear(PyObject *mod) +{ + module_state *state = get_module_state(mod); + + if (state->queue_type != NULL) { + (void)_PyCrossInterpreterData_UnregisterClass(state->queue_type); + } + + // Now we clear the module state. + clear_module_state(state); + return 0; +} + +static void +module_free(void *mod) +{ + module_state *state = get_module_state(mod); + + // Now we clear the module state. + clear_module_state(state); + + _globals_fini(); +} + +static struct PyModuleDef moduledef = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = MODULE_NAME, + .m_doc = module_doc, + .m_size = sizeof(module_state), + .m_methods = module_functions, + .m_slots = module_slots, + .m_traverse = module_traverse, + .m_clear = module_clear, + .m_free = (freefunc)module_free, +}; + +PyMODINIT_FUNC +PyInit__xxinterpqueues(void) +{ + return PyModuleDef_Init(&moduledef); +} diff --git a/PC/config.c b/PC/config.c index da2bde640961e0..f754ce6d3b057b 100644 --- a/PC/config.c +++ b/PC/config.c @@ -37,6 +37,7 @@ extern PyObject* PyInit__weakref(void); extern PyObject* PyInit_xxsubtype(void); extern PyObject* PyInit__xxsubinterpreters(void); extern PyObject* PyInit__xxinterpchannels(void); +extern PyObject* PyInit__xxinterpqueues(void); extern PyObject* PyInit__random(void); extern PyObject* PyInit_itertools(void); extern PyObject* PyInit__collections(void); @@ -142,6 +143,7 @@ struct _inittab _PyImport_Inittab[] = { {"xxsubtype", PyInit_xxsubtype}, {"_xxsubinterpreters", PyInit__xxsubinterpreters}, {"_xxinterpchannels", PyInit__xxinterpchannels}, + {"_xxinterpqueues", PyInit__xxinterpqueues}, #ifdef _Py_HAVE_ZLIB {"zlib", PyInit_zlib}, #endif diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 278f1f5622543c..778fc834c0db9c 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -458,6 +458,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index c9b34c64fbf75f..a96ca24cf08b66 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1505,6 +1505,9 @@ Modules + + Modules + Parser diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py index 766a85d3d6f39e..5dce4e042d1eb4 100644 --- a/Tools/build/generate_stdlib_module_names.py +++ b/Tools/build/generate_stdlib_module_names.py @@ -36,6 +36,7 @@ '_testsinglephase', '_xxsubinterpreters', '_xxinterpchannels', + '_xxinterpqueues', '_xxtestfuzz', 'idlelib.idle_test', 'test', diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index ff6e1ef4f993ba..2f9e80d6ab6737 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -165,6 +165,7 @@ Python/pylifecycle.c fatal_error reentrant - # explicitly protected, internal-only Modules/_xxinterpchannelsmodule.c - _globals - +Modules/_xxinterpqueuesmodule.c - _globals - # set once during module init Modules/_decimal/_decimal.c - minalloc_is_set - diff --git a/configure b/configure index cad3bce0c7de87..668a0efd77db0e 100755 --- a/configure +++ b/configure @@ -769,6 +769,8 @@ MODULE__MULTIPROCESSING_FALSE MODULE__MULTIPROCESSING_TRUE MODULE__ZONEINFO_FALSE MODULE__ZONEINFO_TRUE +MODULE__XXINTERPQUEUES_FALSE +MODULE__XXINTERPQUEUES_TRUE MODULE__XXINTERPCHANNELS_FALSE MODULE__XXINTERPCHANNELS_TRUE MODULE__XXSUBINTERPRETERS_FALSE @@ -28025,6 +28027,7 @@ case $ac_sys_system in #( py_cv_module__tkinter=n/a py_cv_module__xxsubinterpreters=n/a py_cv_module__xxinterpchannels=n/a + py_cv_module__xxinterpqueues=n/a py_cv_module_grp=n/a py_cv_module_pwd=n/a py_cv_module_resource=n/a @@ -28524,6 +28527,28 @@ then : +fi + + + if test "$py_cv_module__xxinterpqueues" != "n/a" +then : + py_cv_module__xxinterpqueues=yes +fi + if test "$py_cv_module__xxinterpqueues" = yes; then + MODULE__XXINTERPQUEUES_TRUE= + MODULE__XXINTERPQUEUES_FALSE='#' +else + MODULE__XXINTERPQUEUES_TRUE='#' + MODULE__XXINTERPQUEUES_FALSE= +fi + + as_fn_append MODULE_BLOCK "MODULE__XXINTERPQUEUES_STATE=$py_cv_module__xxinterpqueues$as_nl" + if test "x$py_cv_module__xxinterpqueues" = xyes +then : + + + + fi @@ -30760,6 +30785,10 @@ if test -z "${MODULE__XXINTERPCHANNELS_TRUE}" && test -z "${MODULE__XXINTERPCHAN as_fn_error $? "conditional \"MODULE__XXINTERPCHANNELS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${MODULE__XXINTERPQUEUES_TRUE}" && test -z "${MODULE__XXINTERPQUEUES_FALSE}"; then + as_fn_error $? "conditional \"MODULE__XXINTERPQUEUES\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${MODULE__ZONEINFO_TRUE}" && test -z "${MODULE__ZONEINFO_FALSE}"; then as_fn_error $? "conditional \"MODULE__ZONEINFO\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 diff --git a/configure.ac b/configure.ac index 7dda0b3fff95be..020553abd71b4f 100644 --- a/configure.ac +++ b/configure.ac @@ -7120,6 +7120,7 @@ AS_CASE([$ac_sys_system], [_tkinter], [_xxsubinterpreters], [_xxinterpchannels], + [_xxinterpqueues], [grp], [pwd], [resource], @@ -7236,6 +7237,7 @@ PY_STDLIB_MOD_SIMPLE([_struct]) PY_STDLIB_MOD_SIMPLE([_typing]) PY_STDLIB_MOD_SIMPLE([_xxsubinterpreters]) PY_STDLIB_MOD_SIMPLE([_xxinterpchannels]) +PY_STDLIB_MOD_SIMPLE([_xxinterpqueues]) PY_STDLIB_MOD_SIMPLE([_zoneinfo]) dnl multiprocessing modules From 9898e6104171dcdd88b32776e69ca2cddf515e63 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 11:06:06 -0700 Subject: [PATCH 213/442] gh-76785: Add Interpreter.prepare_main() (gh-113021) This is one of the last pieces to get test.support.interpreters in sync with PEP 734. --- Lib/test/support/interpreters/__init__.py | 16 +++++-- Lib/test/test__xxinterpchannels.py | 4 +- Lib/test/test__xxsubinterpreters.py | 24 ++++++---- Lib/test/test_interpreters/test_api.py | 57 +++++++++++++++++++++++ Lib/test/test_interpreters/utils.py | 6 ++- Modules/_xxsubinterpretersmodule.c | 56 ++++++++++++++++++++++ 6 files changed, 146 insertions(+), 17 deletions(-) diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index 2d6376deb5907e..9cd1c3de0274d2 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -130,7 +130,15 @@ def close(self): """ return _interpreters.destroy(self._id) - def exec_sync(self, code, /, channels=None): + def prepare_main(self, ns=None, /, **kwargs): + """Bind the given values into the interpreter's __main__. + + The values must be shareable. + """ + ns = dict(ns, **kwargs) if ns is not None else kwargs + _interpreters.set___main___attrs(self._id, ns) + + def exec_sync(self, code, /): """Run the given source code in the interpreter. This is essentially the same as calling the builtin "exec" @@ -148,13 +156,13 @@ def exec_sync(self, code, /, channels=None): that time, the previous interpreter is allowed to run in other threads. """ - excinfo = _interpreters.exec(self._id, code, channels) + excinfo = _interpreters.exec(self._id, code) if excinfo is not None: raise ExecFailure(excinfo) - def run(self, code, /, channels=None): + def run(self, code, /): def task(): - self.exec_sync(code, channels=channels) + self.exec_sync(code) t = threading.Thread(target=task) t.start() return t diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index 13c8a10296e502..cc2ed7849b0c0f 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -586,12 +586,12 @@ def test_run_string_arg_unresolved(self): cid = channels.create() interp = interpreters.create() + interpreters.set___main___attrs(interp, dict(cid=cid.send)) out = _run_output(interp, dedent(""" import _xxinterpchannels as _channels print(cid.end) _channels.send(cid, b'spam', blocking=False) - """), - dict(cid=cid.send)) + """)) obj = channels.recv(cid) self.assertEqual(obj, b'spam') diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py index 260ab64b07cb2d..a76e4d0ade5b8a 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -33,10 +33,10 @@ def _captured_script(script): return wrapped, open(r, encoding="utf-8") -def _run_output(interp, request, shared=None): +def _run_output(interp, request): script, rpipe = _captured_script(request) with rpipe: - interpreters.run_string(interp, script, shared) + interpreters.run_string(interp, script) return rpipe.read() @@ -630,10 +630,10 @@ def test_shareable_types(self): ] for obj in objects: with self.subTest(obj): + interpreters.set___main___attrs(interp, dict(obj=obj)) interpreters.run_string( interp, f'assert(obj == {obj!r})', - shared=dict(obj=obj), ) def test_os_exec(self): @@ -721,7 +721,8 @@ def test_with_shared(self): with open({w}, 'wb') as chan: pickle.dump(ns, chan) """) - interpreters.run_string(self.id, script, shared) + interpreters.set___main___attrs(self.id, shared) + interpreters.run_string(self.id, script) with open(r, 'rb') as chan: ns = pickle.load(chan) @@ -742,7 +743,8 @@ def test_shared_overwrites(self): ns2 = dict(vars()) del ns2['__builtins__'] """) - interpreters.run_string(self.id, script, shared) + interpreters.set___main___attrs(self.id, shared) + interpreters.run_string(self.id, script) r, w = os.pipe() script = dedent(f""" @@ -773,7 +775,8 @@ def test_shared_overwrites_default_vars(self): with open({w}, 'wb') as chan: pickle.dump(ns, chan) """) - interpreters.run_string(self.id, script, shared) + interpreters.set___main___attrs(self.id, shared) + interpreters.run_string(self.id, script) with open(r, 'rb') as chan: ns = pickle.load(chan) @@ -1036,7 +1039,8 @@ def script(): with open(w, 'w', encoding="utf-8") as spipe: with contextlib.redirect_stdout(spipe): print('it worked!', end='') - interpreters.run_func(self.id, script, shared=dict(w=w)) + interpreters.set___main___attrs(self.id, dict(w=w)) + interpreters.run_func(self.id, script) with open(r, encoding="utf-8") as outfile: out = outfile.read() @@ -1052,7 +1056,8 @@ def script(): with contextlib.redirect_stdout(spipe): print('it worked!', end='') def f(): - interpreters.run_func(self.id, script, shared=dict(w=w)) + interpreters.set___main___attrs(self.id, dict(w=w)) + interpreters.run_func(self.id, script) t = threading.Thread(target=f) t.start() t.join() @@ -1072,7 +1077,8 @@ def script(): with contextlib.redirect_stdout(spipe): print('it worked!', end='') code = script.__code__ - interpreters.run_func(self.id, code, shared=dict(w=w)) + interpreters.set___main___attrs(self.id, dict(w=w)) + interpreters.run_func(self.id, code) with open(r, encoding="utf-8") as outfile: out = outfile.read() diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index e4ae9d005b5282..b702338c3de1ad 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -452,6 +452,63 @@ def task(): self.assertEqual(os.read(r_interp, 1), FINISHED) +class TestInterpreterPrepareMain(TestBase): + + def test_empty(self): + interp = interpreters.create() + with self.assertRaises(ValueError): + interp.prepare_main() + + def test_dict(self): + values = {'spam': 42, 'eggs': 'ham'} + interp = interpreters.create() + interp.prepare_main(values) + out = _run_output(interp, dedent(""" + print(spam, eggs) + """)) + self.assertEqual(out.strip(), '42 ham') + + def test_tuple(self): + values = {'spam': 42, 'eggs': 'ham'} + values = tuple(values.items()) + interp = interpreters.create() + interp.prepare_main(values) + out = _run_output(interp, dedent(""" + print(spam, eggs) + """)) + self.assertEqual(out.strip(), '42 ham') + + def test_kwargs(self): + values = {'spam': 42, 'eggs': 'ham'} + interp = interpreters.create() + interp.prepare_main(**values) + out = _run_output(interp, dedent(""" + print(spam, eggs) + """)) + self.assertEqual(out.strip(), '42 ham') + + def test_dict_and_kwargs(self): + values = {'spam': 42, 'eggs': 'ham'} + interp = interpreters.create() + interp.prepare_main(values, foo='bar') + out = _run_output(interp, dedent(""" + print(spam, eggs, foo) + """)) + self.assertEqual(out.strip(), '42 ham bar') + + def test_not_shareable(self): + interp = interpreters.create() + # XXX TypeError? + with self.assertRaises(ValueError): + interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'}) + + # Make sure neither was actually bound. + with self.assertRaises(interpreters.ExecFailure): + interp.exec_sync('print(foo)') + with self.assertRaises(interpreters.ExecFailure): + interp.exec_sync('print(spam)') + + class TestInterpreterExecSync(TestBase): def test_success(self): diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 623c8737b79831..11b6f126dff0f4 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -29,10 +29,12 @@ def clean_up_interpreters(): pass # already destroyed -def _run_output(interp, request, channels=None): +def _run_output(interp, request, init=None): script, rpipe = _captured_script(request) with rpipe: - interp.exec_sync(script, channels=channels) + if init: + interp.prepare_main(init) + interp.exec_sync(script) return rpipe.read() diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 37959e953ee4f5..4bb54c93b0a61b 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -685,6 +685,60 @@ PyDoc_STRVAR(get_main_doc, \n\ Return the ID of main interpreter."); +static PyObject * +interp_set___main___attrs(PyObject *self, PyObject *args) +{ + PyObject *id, *updates; + if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".set___main___attrs", + &id, &updates)) + { + return NULL; + } + + // Look up the interpreter. + PyInterpreterState *interp = PyInterpreterID_LookUp(id); + if (interp == NULL) { + return NULL; + } + + // Check the updates. + if (updates != Py_None) { + Py_ssize_t size = PyObject_Size(updates); + if (size < 0) { + return NULL; + } + if (size == 0) { + PyErr_SetString(PyExc_ValueError, + "arg 2 must be a non-empty mapping"); + return NULL; + } + } + + _PyXI_session session = {0}; + + // Prep and switch interpreters, including apply the updates. + if (_PyXI_Enter(&session, interp, updates) < 0) { + if (!PyErr_Occurred()) { + _PyXI_ApplyCapturedException(&session); + assert(PyErr_Occurred()); + } + else { + assert(!_PyXI_HasCapturedException(&session)); + } + return NULL; + } + + // Clean up and switch back. + _PyXI_Exit(&session); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(set___main___attrs_doc, +"set___main___attrs(id, ns)\n\ +\n\ +Bind the given attributes in the interpreter's __main__ module."); + static PyUnicodeObject * convert_script_arg(PyObject *arg, const char *fname, const char *displayname, const char *expected) @@ -1033,6 +1087,8 @@ static PyMethodDef module_functions[] = { {"run_func", _PyCFunction_CAST(interp_run_func), METH_VARARGS | METH_KEYWORDS, run_func_doc}, + {"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs), + METH_VARARGS, set___main___attrs_doc}, {"is_shareable", _PyCFunction_CAST(object_is_shareable), METH_VARARGS | METH_KEYWORDS, is_shareable_doc}, From 956023826a393b5704d3414dcd01f1bcbeaeda15 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 12 Dec 2023 19:02:24 +0000 Subject: [PATCH 214/442] GH-108866: Guarantee forward progress in executors. (GH-113006) --- Include/cpython/optimizer.h | 2 +- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uops.h | 2 +- .../2023-12-12-04-53-19.gh-issue-108866.xbJ-9a.rst | 3 +++ Python/bytecodes.c | 11 ++++------- Python/generated_cases.c.h | 12 +++++------- Python/optimizer.c | 8 ++++---- 7 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-12-04-53-19.gh-issue-108866.xbJ-9a.rst diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index adc2c1fc442280..d521eac79d1b97 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -32,7 +32,7 @@ typedef struct { typedef struct _PyExecutorObject { PyObject_VAR_HEAD /* WARNING: execute consumes a reference to self. This is necessary to allow executors to tail call into each other. */ - struct _PyInterpreterFrame *(*execute)(struct _PyExecutorObject *self, struct _PyInterpreterFrame *frame, PyObject **stack_pointer); + _Py_CODEUNIT *(*execute)(struct _PyExecutorObject *self, struct _PyInterpreterFrame *frame, PyObject **stack_pointer); _PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */ /* Data needed by the executor goes here, but is opaque to the VM */ } _PyExecutorObject; diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 1f460640a1e398..2c512d97c421c9 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1568,7 +1568,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG }, + [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, diff --git a/Include/internal/pycore_uops.h b/Include/internal/pycore_uops.h index ea8f90bf8c1d8f..153884f4bd2902 100644 --- a/Include/internal/pycore_uops.h +++ b/Include/internal/pycore_uops.h @@ -24,7 +24,7 @@ typedef struct { _PyUOpInstruction trace[1]; } _PyUOpExecutorObject; -_PyInterpreterFrame *_PyUOpExecute( +_Py_CODEUNIT *_PyUOpExecute( _PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer); diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-12-04-53-19.gh-issue-108866.xbJ-9a.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-12-04-53-19.gh-issue-108866.xbJ-9a.rst new file mode 100644 index 00000000000000..96606924d4a3ec --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-12-04-53-19.gh-issue-108866.xbJ-9a.rst @@ -0,0 +1,3 @@ +Change the API and contract of ``_PyExecutorObject`` to return the +next_instr pointer, instead of the frame, and to always execute at least one +instruction. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index e0f373536ce67c..1ae83422730f8f 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2352,20 +2352,17 @@ dummy_func( PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; - int original_oparg = executor->vm_data.oparg | (oparg & 0xfffff00); - JUMPBY(1-original_oparg); - frame->instr_ptr = next_instr; Py_INCREF(executor); if (executor->execute == _PyUOpExecute) { current_executor = (_PyUOpExecutorObject *)executor; GOTO_TIER_TWO(); } - frame = executor->execute(executor, frame, stack_pointer); - if (frame == NULL) { - frame = tstate->current_frame; + next_instr = executor->execute(executor, frame, stack_pointer); + frame = tstate->current_frame; + if (next_instr == NULL) { goto resume_with_error; } - goto enter_tier_one; + stack_pointer = _PyFrame_GetStackPointer(frame); } replaced op(_POP_JUMP_IF_FALSE, (unused/1, cond -- )) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 8f68bc6cb5ab40..65e6f11f68b38c 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2328,20 +2328,18 @@ CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; - int original_oparg = executor->vm_data.oparg | (oparg & 0xfffff00); - JUMPBY(1-original_oparg); - frame->instr_ptr = next_instr; Py_INCREF(executor); if (executor->execute == _PyUOpExecute) { current_executor = (_PyUOpExecutorObject *)executor; GOTO_TIER_TWO(); } - frame = executor->execute(executor, frame, stack_pointer); - if (frame == NULL) { - frame = tstate->current_frame; + next_instr = executor->execute(executor, frame, stack_pointer); + frame = tstate->current_frame; + if (next_instr == NULL) { goto resume_with_error; } - goto enter_tier_one; + stack_pointer = _PyFrame_GetStackPointer(frame); + DISPATCH(); } TARGET(EXIT_INIT_CHECK) { diff --git a/Python/optimizer.c b/Python/optimizer.c index dd24fbebbfd2a9..7c46bd69157170 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -167,6 +167,7 @@ _PyOptimizer_BackEdge(_PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNI } _PyOptimizerObject *opt = interp->optimizer; _PyExecutorObject *executor = NULL; + /* Start optimizing at the destination to guarantee forward progress */ int err = opt->optimize(opt, code, dest, &executor, (int)(stack_pointer - _PyFrame_Stackbase(frame))); if (err <= 0) { assert(executor == NULL); @@ -247,14 +248,13 @@ PyTypeObject _PyCounterExecutor_Type = { .tp_methods = executor_methods, }; -static _PyInterpreterFrame * +static _Py_CODEUNIT * counter_execute(_PyExecutorObject *self, _PyInterpreterFrame *frame, PyObject **stack_pointer) { ((_PyCounterExecutorObject *)self)->optimizer->count++; _PyFrame_SetStackPointer(frame, stack_pointer); - frame->instr_ptr = ((_PyCounterExecutorObject *)self)->next_instr; Py_DECREF(self); - return frame; + return ((_PyCounterExecutorObject *)self)->next_instr; } static int @@ -891,7 +891,7 @@ uop_optimize( /* Dummy execute() function for UOp Executor. * The actual implementation is inlined in ceval.c, * in _PyEval_EvalFrameDefault(). */ -_PyInterpreterFrame * +_Py_CODEUNIT * _PyUOpExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject **stack_pointer) { Py_FatalError("Tier 2 is now inlined into Tier 1"); From 81a15ea74e2607728fceb822dfcc1aabff00478a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Tue, 12 Dec 2023 20:21:12 +0000 Subject: [PATCH 215/442] gh-101100: Further improve docs on function attributes (#113001) --- Doc/c-api/function.rst | 2 +- Doc/reference/datamodel.rst | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 75a05b4197c460..e7fb5090c09933 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -35,7 +35,7 @@ There are a few functions specific to Python functions. must be a dictionary with the global variables accessible to the function. The function's docstring and name are retrieved from the code object. - :func:`~function.__module__` + :attr:`~function.__module__` is retrieved from *globals*. The argument defaults, annotations and closure are set to ``NULL``. :attr:`~function.__qualname__` is set to the same value as the code object's :attr:`~codeobject.co_qualname` field. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index de79d72a05c68a..16ee3e300c2b8b 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -601,7 +601,7 @@ Most of these attributes check the type of the assigned value: or ``None`` if unavailable. * - .. attribute:: function.__defaults__ - - A :class:`tuple` containing default parameter values + - A :class:`tuple` containing default :term:`parameter` values for those parameters that have defaults, or ``None`` if no parameters have a default value. @@ -614,19 +614,22 @@ Most of these attributes check the type of the assigned value: See also: :attr:`__dict__ attributes `. * - .. attribute:: function.__annotations__ - - A :class:`dictionary ` containing annotations of parameters. + - A :class:`dictionary ` containing annotations of + :term:`parameters `. The keys of the dictionary are the parameter names, and ``'return'`` for the return annotation, if provided. See also: :ref:`annotations-howto`. * - .. attribute:: function.__kwdefaults__ - A :class:`dictionary ` containing defaults for keyword-only - parameters. + :term:`parameters `. * - .. attribute:: function.__type_params__ - A :class:`tuple` containing the :ref:`type parameters ` of a :ref:`generic function `. + .. versionadded:: 3.12 + Function objects also support getting and setting arbitrary attributes, which can be used, for example, to attach metadata to functions. Regular attribute dot-notation is used to get and set such attributes. From dfaa9e060bf6d69cb862a2ac140b8fccbebf3000 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 12 Dec 2023 16:17:08 -0500 Subject: [PATCH 216/442] gh-113010: Don't decrement deferred in pystats (#113032) This fixes a recently introduced bug where the deferred count is being unnecessarily decremented to counteract an increment elsewhere that is no longer happening. This caused the values to flip around to "very large" 64-bit numbers. --- Python/ceval_macros.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index f298c602b1042b..ac44aecae046d8 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -258,10 +258,6 @@ GETITEM(PyObject *v, Py_ssize_t i) { if (ADAPTIVE_COUNTER_IS_ZERO(next_instr->cache)) { \ STAT_INC((INSTNAME), deopt); \ } \ - else { \ - /* This is about to be (incorrectly) incremented: */ \ - STAT_DEC((INSTNAME), deferred); \ - } \ } while (0) #else #define UPDATE_MISS_STATS(INSTNAME) ((void)0) From 7316dfb0ebc46aedf484c1f15f03a0a309d12a42 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 12 Dec 2023 13:43:08 -0800 Subject: [PATCH 217/442] gh-112320: Implement on-trace confidence tracking for branches (#112321) We track the confidence as a scaled int. --- Include/cpython/pystats.h | 1 + Lib/test/test_capi/test_misc.py | 31 +++++++++++++++++++ ...-11-22-13-17-54.gh-issue-112320.EddM51.rst | 4 +++ Python/optimizer.c | 20 ++++++++++-- Python/specialize.c | 1 + Tools/scripts/summarize_stats.py | 2 ++ 6 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index 294bf1505f0115..ba67eefef3e37a 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -114,6 +114,7 @@ typedef struct _optimization_stats { uint64_t trace_too_short; uint64_t inner_loop; uint64_t recursive_call; + uint64_t low_confidence; UOpStats opcode[512]; uint64_t unsupported_opcode[256]; uint64_t trace_length_hist[_Py_UOP_HIST_SIZE]; diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index e6b532e858c8f9..776ee913a02216 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2985,6 +2985,37 @@ def testfunc(n, m): uops = {opname for opname, _, _ in ex} self.assertIn("_FOR_ITER_TIER_TWO", uops) + def test_confidence_score(self): + def testfunc(n): + bits = 0 + for i in range(n): + if i & 0x01: + bits += 1 + if i & 0x02: + bits += 1 + if i&0x04: + bits += 1 + if i&0x08: + bits += 1 + if i&0x10: + bits += 1 + if i&0x20: + bits += 1 + return bits + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + x = testfunc(20) + + self.assertEqual(x, 40) + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + ops = [opname for opname, _, _ in ex] + count = ops.count("_GUARD_IS_TRUE_POP") + # Because Each 'if' halves the score, the second branch is + # too much already. + self.assertEqual(count, 1) + @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') class TestPyThreadId(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst new file mode 100644 index 00000000000000..0da2fd33b0ea52 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-22-13-17-54.gh-issue-112320.EddM51.rst @@ -0,0 +1,4 @@ +The Tier 2 translator now tracks the confidence level for staying "on trace" +(i.e. not exiting back to the Tier 1 interpreter) for branch instructions +based on the number of bits set in the branch "counter". Trace translation +ends when the confidence drops below 1/3rd. diff --git a/Python/optimizer.c b/Python/optimizer.c index 7c46bd69157170..d44e733bc346fa 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -409,6 +409,9 @@ BRANCH_TO_GUARD[4][2] = { #define TRACE_STACK_SIZE 5 +#define CONFIDENCE_RANGE 1000 +#define CONFIDENCE_CUTOFF 333 + /* Returns 1 on success, * 0 if it failed to produce a worthwhile trace, * and -1 on an error. @@ -431,6 +434,7 @@ translate_bytecode_to_trace( _Py_CODEUNIT *instr; } trace_stack[TRACE_STACK_SIZE]; int trace_stack_depth = 0; + int confidence = CONFIDENCE_RANGE; // Adjusted by branch instructions #ifdef Py_DEBUG char *python_lltrace = Py_GETENV("PYTHON_LLTRACE"); @@ -513,7 +517,6 @@ translate_bytecode_to_trace( uint32_t oparg = instr->op.arg; uint32_t extras = 0; - if (opcode == EXTENDED_ARG) { instr++; extras += 1; @@ -543,11 +546,22 @@ translate_bytecode_to_trace( int counter = instr[1].cache; int bitcount = _Py_popcount32(counter); int jump_likely = bitcount > 8; + if (jump_likely) { + confidence = confidence * bitcount / 16; + } + else { + confidence = confidence * (16 - bitcount) / 16; + } + if (confidence < CONFIDENCE_CUTOFF) { + DPRINTF(2, "Confidence too low (%d)\n", confidence); + OPT_STAT_INC(low_confidence); + goto done; + } uint32_t uopcode = BRANCH_TO_GUARD[opcode - POP_JUMP_IF_FALSE][jump_likely]; _Py_CODEUNIT *next_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; - DPRINTF(4, "%s(%d): counter=%x, bitcount=%d, likely=%d, uopcode=%s\n", + DPRINTF(2, "%s(%d): counter=%x, bitcount=%d, likely=%d, confidence=%d, uopcode=%s\n", _PyUOpName(opcode), oparg, - counter, bitcount, jump_likely, _PyUOpName(uopcode)); + counter, bitcount, jump_likely, confidence, _PyUOpName(uopcode)); ADD_TO_TRACE(uopcode, max_length, 0, target); if (jump_likely) { _Py_CODEUNIT *target_instr = next_instr + oparg; diff --git a/Python/specialize.c b/Python/specialize.c index ba704cbbb464d7..7c2a4a42b1dcc3 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -233,6 +233,7 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) fprintf(out, "Optimization trace too short: %" PRIu64 "\n", stats->trace_too_short); fprintf(out, "Optimization inner loop: %" PRIu64 "\n", stats->inner_loop); fprintf(out, "Optimization recursive call: %" PRIu64 "\n", stats->recursive_call); + fprintf(out, "Optimization low confidence: %" PRIu64 "\n", stats->low_confidence); print_histogram(out, "Trace length", stats->trace_length_hist); print_histogram(out, "Trace run length", stats->trace_run_length_hist); diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 360b7c720bd1f0..80a1280c025aca 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -386,6 +386,7 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: trace_too_short = self._data["Optimization trace too short"] inner_loop = self._data["Optimization inner loop"] recursive_call = self._data["Optimization recursive call"] + low_confidence = self._data["Optimization low confidence"] return { "Optimization attempts": (attempts, None), @@ -396,6 +397,7 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: "Trace too short": (trace_too_short, attempts), "Inner loop found": (inner_loop, attempts), "Recursive call": (recursive_call, attempts), + "Low confidence": (low_confidence, attempts), "Traces executed": (executed, None), "Uops executed": (uops, executed), } From 8a4c1f3ff1e3d7ed2e00e77b94056f9bb7f9ae3b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 17:00:54 -0700 Subject: [PATCH 218/442] gh-76785: Show the Traceback for Uncaught Subinterpreter Exceptions (gh-113034) When an exception is uncaught in Interpreter.exec_sync(), it helps to show that exception's error display if uncaught in the calling interpreter. We do so here by generating a TracebackException in the subinterpreter and passing it between interpreters using pickle. --- Include/internal/pycore_crossinterp.h | 2 + Lib/test/support/interpreters/__init__.py | 27 ++- Lib/test/test_interpreters/test_api.py | 48 +++++ Lib/test/test_interpreters/utils.py | 72 +++++++ Python/crossinterp.c | 218 ++++++++++++++++++++-- 5 files changed, 351 insertions(+), 16 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index ce95979f8d343b..414e32b5155f62 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -188,6 +188,8 @@ typedef struct _excinfo { const char *module; } type; const char *msg; + const char *pickled; + Py_ssize_t pickled_len; } _PyXI_excinfo; diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index 9cd1c3de0274d2..d619bea3e32f5d 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -34,17 +34,36 @@ def __getattr__(name): raise AttributeError(name) +_EXEC_FAILURE_STR = """ +{superstr} + +Uncaught in the interpreter: + +{formatted} +""".strip() + class ExecFailure(RuntimeError): def __init__(self, excinfo): msg = excinfo.formatted if not msg: - if excinfo.type and snapshot.msg: - msg = f'{snapshot.type.__name__}: {snapshot.msg}' + if excinfo.type and excinfo.msg: + msg = f'{excinfo.type.__name__}: {excinfo.msg}' else: - msg = snapshot.type.__name__ or snapshot.msg + msg = excinfo.type.__name__ or excinfo.msg super().__init__(msg) - self.snapshot = excinfo + self.excinfo = excinfo + + def __str__(self): + try: + formatted = ''.join(self.excinfo.tbexc.format()).rstrip() + except Exception: + return super().__str__() + else: + return _EXEC_FAILURE_STR.format( + superstr=super().__str__(), + formatted=formatted, + ) def create(): diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index b702338c3de1ad..aefd326977095f 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -525,6 +525,54 @@ def test_failure(self): with self.assertRaises(interpreters.ExecFailure): interp.exec_sync('raise Exception') + def test_display_preserved_exception(self): + tempdir = self.temp_dir() + modfile = self.make_module('spam', tempdir, text=""" + def ham(): + raise RuntimeError('uh-oh!') + + def eggs(): + ham() + """) + scriptfile = self.make_script('script.py', tempdir, text=""" + from test.support import interpreters + + def script(): + import spam + spam.eggs() + + interp = interpreters.create() + interp.exec_sync(script) + """) + + stdout, stderr = self.assert_python_failure(scriptfile) + self.maxDiff = None + interpmod_line, = (l for l in stderr.splitlines() if ' exec_sync' in l) + # File "{interpreters.__file__}", line 179, in exec_sync + self.assertEqual(stderr, dedent(f"""\ + Traceback (most recent call last): + File "{scriptfile}", line 9, in + interp.exec_sync(script) + ~~~~~~~~~~~~~~~~^^^^^^^^ + {interpmod_line.strip()} + raise ExecFailure(excinfo) + test.support.interpreters.ExecFailure: RuntimeError: uh-oh! + + Uncaught in the interpreter: + + Traceback (most recent call last): + File "{scriptfile}", line 6, in script + spam.eggs() + ~~~~~~~~~^^ + File "{modfile}", line 6, in eggs + ham() + ~~~^^ + File "{modfile}", line 3, in ham + raise RuntimeError('uh-oh!') + RuntimeError: uh-oh! + """)) + self.assertEqual(stdout, '') + def test_in_thread(self): interp = interpreters.create() script, file = _captured_script('print("it worked!", end="")') diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 11b6f126dff0f4..3a37ed09dd8943 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -1,9 +1,16 @@ import contextlib import os +import os.path +import subprocess +import sys +import tempfile import threading from textwrap import dedent import unittest +from test import support +from test.support import os_helper + from test.support import interpreters @@ -71,5 +78,70 @@ def ensure_closed(fd): self.addCleanup(lambda: ensure_closed(w)) return r, w + def temp_dir(self): + tempdir = tempfile.mkdtemp() + tempdir = os.path.realpath(tempdir) + self.addCleanup(lambda: os_helper.rmtree(tempdir)) + return tempdir + + def make_script(self, filename, dirname=None, text=None): + if text: + text = dedent(text) + if dirname is None: + dirname = self.temp_dir() + filename = os.path.join(dirname, filename) + + os.makedirs(os.path.dirname(filename), exist_ok=True) + with open(filename, 'w', encoding='utf-8') as outfile: + outfile.write(text or '') + return filename + + def make_module(self, name, pathentry=None, text=None): + if text: + text = dedent(text) + if pathentry is None: + pathentry = self.temp_dir() + else: + os.makedirs(pathentry, exist_ok=True) + *subnames, basename = name.split('.') + + dirname = pathentry + for subname in subnames: + dirname = os.path.join(dirname, subname) + if os.path.isdir(dirname): + pass + elif os.path.exists(dirname): + raise Exception(dirname) + else: + os.mkdir(dirname) + initfile = os.path.join(dirname, '__init__.py') + if not os.path.exists(initfile): + with open(initfile, 'w'): + pass + filename = os.path.join(dirname, basename + '.py') + + with open(filename, 'w', encoding='utf-8') as outfile: + outfile.write(text or '') + return filename + + @support.requires_subprocess() + def run_python(self, *argv): + proc = subprocess.run( + [sys.executable, *argv], + capture_output=True, + text=True, + ) + return proc.returncode, proc.stdout, proc.stderr + + def assert_python_ok(self, *argv): + exitcode, stdout, stderr = self.run_python(*argv) + self.assertNotEqual(exitcode, 1) + return stdout, stderr + + def assert_python_failure(self, *argv): + exitcode, stdout, stderr = self.run_python(*argv) + self.assertNotEqual(exitcode, 0) + return stdout, stderr + def tearDown(self): clean_up_interpreters() diff --git a/Python/crossinterp.c b/Python/crossinterp.c index a31b5ef4613dbd..edd61cf99f3f52 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -944,6 +944,26 @@ _xidregistry_fini(struct _xidregistry *registry) /* convenience utilities */ /*************************/ +static const char * +_copy_raw_string(const char *str, Py_ssize_t len) +{ + size_t size = len + 1; + if (len <= 0) { + size = strlen(str) + 1; + } + char *copied = PyMem_RawMalloc(size); + if (copied == NULL) { + return NULL; + } + if (len <= 0) { + strcpy(copied, str); + } + else { + memcpy(copied, str, size); + } + return copied; +} + static const char * _copy_string_obj_raw(PyObject *strobj) { @@ -961,6 +981,80 @@ _copy_string_obj_raw(PyObject *strobj) return copied; } + +static int +_pickle_object(PyObject *obj, const char **p_pickled, Py_ssize_t *p_len) +{ + assert(!PyErr_Occurred()); + PyObject *picklemod = PyImport_ImportModule("_pickle"); + if (picklemod == NULL) { + PyErr_Clear(); + picklemod = PyImport_ImportModule("pickle"); + if (picklemod == NULL) { + return -1; + } + } + PyObject *dumps = PyObject_GetAttrString(picklemod, "dumps"); + Py_DECREF(picklemod); + if (dumps == NULL) { + return -1; + } + PyObject *pickledobj = PyObject_CallOneArg(dumps, obj); + Py_DECREF(dumps); + if (pickledobj == NULL) { + return -1; + } + + char *pickled = NULL; + Py_ssize_t len = 0; + if (PyBytes_AsStringAndSize(pickledobj, &pickled, &len) < 0) { + Py_DECREF(pickledobj); + return -1; + } + const char *copied = _copy_raw_string(pickled, len); + Py_DECREF(pickledobj); + if (copied == NULL) { + return -1; + } + + *p_pickled = copied; + *p_len = len; + return 0; +} + +static int +_unpickle_object(const char *pickled, Py_ssize_t size, PyObject **p_obj) +{ + assert(!PyErr_Occurred()); + PyObject *picklemod = PyImport_ImportModule("_pickle"); + if (picklemod == NULL) { + PyErr_Clear(); + picklemod = PyImport_ImportModule("pickle"); + if (picklemod == NULL) { + return -1; + } + } + PyObject *loads = PyObject_GetAttrString(picklemod, "loads"); + Py_DECREF(picklemod); + if (loads == NULL) { + return -1; + } + PyObject *pickledobj = PyBytes_FromStringAndSize(pickled, size); + if (pickledobj == NULL) { + Py_DECREF(loads); + return -1; + } + PyObject *obj = PyObject_CallOneArg(loads, pickledobj); + Py_DECREF(loads); + Py_DECREF(pickledobj); + if (obj == NULL) { + return -1; + } + *p_obj = obj; + return 0; +} + + static int _release_xid_data(_PyCrossInterpreterData *data, int rawfree) { @@ -1094,6 +1188,9 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info) if (info->msg != NULL) { PyMem_RawFree((void *)info->msg); } + if (info->pickled != NULL) { + PyMem_RawFree((void *)info->pickled); + } *info = (_PyXI_excinfo){{NULL}}; } @@ -1129,6 +1226,63 @@ _PyXI_excinfo_format(_PyXI_excinfo *info) } } +static int +_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc) +{ + PyObject *args = NULL; + PyObject *kwargs = NULL; + PyObject *create = NULL; + + // This is inspired by _PyErr_Display(). + PyObject *tbmod = PyImport_ImportModule("traceback"); + if (tbmod == NULL) { + return -1; + } + PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException"); + Py_DECREF(tbmod); + if (tbexc_type == NULL) { + return -1; + } + create = PyObject_GetAttrString(tbexc_type, "from_exception"); + Py_DECREF(tbexc_type); + if (create == NULL) { + return -1; + } + + args = PyTuple_Pack(1, exc); + if (args == NULL) { + goto error; + } + + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) { + goto error; + } + if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) { + goto error; + } + + PyObject *tbexc = PyObject_Call(create, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(create); + if (tbexc == NULL) { + goto error; + } + + *p_tbexc = tbexc; + return 0; + +error: + Py_XDECREF(args); + Py_XDECREF(kwargs); + Py_XDECREF(create); + return -1; +} + static const char * _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) { @@ -1158,6 +1312,24 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) goto error; } + // Pickle a traceback.TracebackException. + PyObject *tbexc = NULL; + if (_convert_exc_to_TracebackException(exc, &tbexc) < 0) { +#ifdef Py_DEBUG + PyErr_FormatUnraisable("Exception ignored while creating TracebackException"); +#endif + PyErr_Clear(); + } + else { + if (_pickle_object(tbexc, &info->pickled, &info->pickled_len) < 0) { +#ifdef Py_DEBUG + PyErr_FormatUnraisable("Exception ignored while pickling TracebackException"); +#endif + PyErr_Clear(); + } + Py_DECREF(tbexc); + } + return NULL; error: @@ -1169,9 +1341,28 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) static void _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) { + PyObject *tbexc = NULL; + if (info->pickled != NULL) { + if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { + PyErr_Clear(); + } + } + PyObject *formatted = _PyXI_excinfo_format(info); PyErr_SetObject(exctype, formatted); Py_DECREF(formatted); + + if (tbexc != NULL) { + PyObject *exc = PyErr_GetRaisedException(); + if (PyObject_SetAttrString(exc, "_tbexc", tbexc) < 0) { +#ifdef Py_DEBUG + PyErr_FormatUnraisable("Exception ignored when setting _tbexc"); +#endif + PyErr_Clear(); + } + Py_DECREF(tbexc); + PyErr_SetRaisedException(exc); + } } static PyObject * @@ -1277,6 +1468,20 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info) goto error; } + if (info->pickled != NULL) { + PyObject *tbexc = NULL; + if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { + PyErr_Clear(); + } + else { + res = PyObject_SetAttrString(ns, "tbexc", tbexc); + Py_DECREF(tbexc); + if (res < 0) { + goto error; + } + } + } + return ns; error: @@ -1983,6 +2188,7 @@ _capture_current_exception(_PyXI_session *session) } else { failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION); + Py_DECREF(excval); if (failure == NULL && override != NULL) { err->code = errcode; } @@ -1997,18 +2203,6 @@ _capture_current_exception(_PyXI_session *session) err = NULL; } - // a temporary hack (famous last words) - if (excval != NULL) { - // XXX Store the traceback info (or rendered traceback) on - // _PyXI_excinfo, attach it to the exception when applied, - // and teach PyErr_Display() to print it. -#ifdef Py_DEBUG - // XXX Drop this once _Py_excinfo picks up the slack. - PyErr_Display(NULL, excval, NULL); -#endif - Py_DECREF(excval); - } - // Finished! assert(!PyErr_Occurred()); session->error = err; From a3c031884d2f16d84aacc3f733c047b3a6cae208 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 12 Dec 2023 19:20:21 -0500 Subject: [PATCH 219/442] gh-112723: Call `PyThreadState_Clear()` from the correct interpreter (#112776) The `PyThreadState_Clear()` function must only be called with the GIL held and must be called from the same interpreter as the passed in thread state. Otherwise, any Python objects on the thread state may be destroyed using the wrong interpreter, leading to memory corruption. This is also important for `Py_GIL_DISABLED` builds because free lists will be associated with PyThreadStates and cleared in `PyThreadState_Clear()`. This fixes two places that called `PyThreadState_Clear()` from the wrong interpreter and adds an assertion to `PyThreadState_Clear()`. --- Include/internal/pycore_ceval.h | 2 +- Include/internal/pycore_pylifecycle.h | 2 +- Modules/_xxsubinterpretersmodule.c | 2 + Python/ceval_gil.c | 4 +- Python/pylifecycle.c | 72 ++++++++++----------------- Python/pystate.c | 7 ++- 6 files changed, 34 insertions(+), 55 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 64fb4034669e19..a357bfa3a26064 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -124,7 +124,7 @@ _PyEval_Vector(PyThreadState *tstate, PyObject *kwnames); extern int _PyEval_ThreadsInitialized(void); -extern PyStatus _PyEval_InitGIL(PyThreadState *tstate, int own_gil); +extern void _PyEval_InitGIL(PyThreadState *tstate, int own_gil); extern void _PyEval_FiniGIL(PyInterpreterState *interp); extern void _PyEval_AcquireLock(PyThreadState *tstate); diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index daf7cb77dcc63a..c675098685764c 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -63,7 +63,7 @@ extern void _PyArg_Fini(void); extern void _Py_FinalizeAllocatedBlocks(_PyRuntimeState *); extern PyStatus _PyGILState_Init(PyInterpreterState *interp); -extern PyStatus _PyGILState_SetTstate(PyThreadState *tstate); +extern void _PyGILState_SetTstate(PyThreadState *tstate); extern void _PyGILState_Fini(PyInterpreterState *interp); extern void _PyGC_DumpShutdownStats(PyInterpreterState *interp); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 4bb54c93b0a61b..4e9e13457a9eb3 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -547,7 +547,9 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } + PyThreadState_Swap(tstate); PyThreadState_Clear(tstate); + PyThreadState_Swap(save_tstate); PyThreadState_Delete(tstate); _PyInterpreterState_RequireIDRef(interp, 1); diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 7581daa55b5e46..d70abbc27606b4 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -447,7 +447,7 @@ init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil) interp->ceval.own_gil = 1; } -PyStatus +void _PyEval_InitGIL(PyThreadState *tstate, int own_gil) { assert(tstate->interp->ceval.gil == NULL); @@ -466,8 +466,6 @@ _PyEval_InitGIL(PyThreadState *tstate, int own_gil) // Lock the GIL and mark the current thread as attached. _PyThreadState_Attach(tstate); - - return _PyStatus_OK(); } void diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index b5c7dc5da596de..0ec29846b0850b 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -576,44 +576,33 @@ init_interp_settings(PyInterpreterState *interp, interp->feature_flags |= Py_RTFLAGS_MULTI_INTERP_EXTENSIONS; } - /* We check "gil" in init_interp_create_gil(). */ + switch (config->gil) { + case PyInterpreterConfig_DEFAULT_GIL: break; + case PyInterpreterConfig_SHARED_GIL: break; + case PyInterpreterConfig_OWN_GIL: break; + default: + return _PyStatus_ERR("invalid interpreter config 'gil' value"); + } return _PyStatus_OK(); } -static PyStatus +static void init_interp_create_gil(PyThreadState *tstate, int gil) { - PyStatus status; - /* finalize_interp_delete() comment explains why _PyEval_FiniGIL() is only called here. */ // XXX This is broken with a per-interpreter GIL. _PyEval_FiniGIL(tstate->interp); /* Auto-thread-state API */ - status = _PyGILState_SetTstate(tstate); - if (_PyStatus_EXCEPTION(status)) { - return status; - } + _PyGILState_SetTstate(tstate); - int own_gil; - switch (gil) { - case PyInterpreterConfig_DEFAULT_GIL: own_gil = 0; break; - case PyInterpreterConfig_SHARED_GIL: own_gil = 0; break; - case PyInterpreterConfig_OWN_GIL: own_gil = 1; break; - default: - return _PyStatus_ERR("invalid interpreter config 'gil' value"); - } + int own_gil = (gil == PyInterpreterConfig_OWN_GIL); /* Create the GIL and take it */ - status = _PyEval_InitGIL(tstate, own_gil); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - - return _PyStatus_OK(); + _PyEval_InitGIL(tstate, own_gil); } @@ -657,10 +646,7 @@ pycore_create_interpreter(_PyRuntimeState *runtime, } _PyThreadState_Bind(tstate); - status = init_interp_create_gil(tstate, config.gil); - if (_PyStatus_EXCEPTION(status)) { - return status; - } + init_interp_create_gil(tstate, config.gil); *tstate_p = tstate; return _PyStatus_OK(); @@ -2099,28 +2085,21 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) return _PyStatus_OK(); } - PyThreadState *tstate = _PyThreadState_New(interp, - _PyThreadState_WHENCE_INTERP); - if (tstate == NULL) { - PyInterpreterState_Delete(interp); - *tstate_p = NULL; - return _PyStatus_OK(); - } - _PyThreadState_Bind(tstate); - + // XXX Might new_interpreter() have been called without the GIL held? PyThreadState *save_tstate = _PyThreadState_GET(); - int has_gil = 0; + PyThreadState *tstate = NULL; /* From this point until the init_interp_create_gil() call, we must not do anything that requires that the GIL be held (or otherwise exist). That applies whether or not the new interpreter has its own GIL (e.g. the main interpreter). */ + if (save_tstate != NULL) { + _PyThreadState_Detach(save_tstate); + } /* Copy the current interpreter config into the new interpreter */ const PyConfig *src_config; if (save_tstate != NULL) { - // XXX Might new_interpreter() have been called without the GIL held? - _PyThreadState_Detach(save_tstate); src_config = _PyInterpreterState_GetConfig(save_tstate->interp); } else @@ -2142,11 +2121,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) goto error; } - status = init_interp_create_gil(tstate, config->gil); - if (_PyStatus_EXCEPTION(status)) { + tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_INTERP); + if (tstate == NULL) { + status = _PyStatus_NO_MEMORY(); goto error; } - has_gil = 1; + + _PyThreadState_Bind(tstate); + init_interp_create_gil(tstate, config->gil); /* No objects have been created yet. */ @@ -2165,16 +2147,14 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) error: *tstate_p = NULL; - - /* Oops, it didn't work. Undo it all. */ - if (has_gil) { + if (tstate != NULL) { + PyThreadState_Clear(tstate); _PyThreadState_Detach(tstate); + PyThreadState_Delete(tstate); } if (save_tstate != NULL) { _PyThreadState_Attach(save_tstate); } - PyThreadState_Clear(tstate); - PyThreadState_Delete(tstate); PyInterpreterState_Delete(interp); return status; diff --git a/Python/pystate.c b/Python/pystate.c index f0c5259967d907..e18eb0186d0010 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1454,6 +1454,7 @@ void PyThreadState_Clear(PyThreadState *tstate) { assert(tstate->_status.initialized && !tstate->_status.cleared); + assert(current_fast_get(&_PyRuntime)->interp == tstate->interp); // XXX assert(!tstate->_status.bound || tstate->_status.unbound); tstate->_status.finalizing = 1; // just in case @@ -2150,7 +2151,7 @@ _PyGILState_Fini(PyInterpreterState *interp) // XXX Drop this. -PyStatus +void _PyGILState_SetTstate(PyThreadState *tstate) { /* must init with valid states */ @@ -2160,7 +2161,7 @@ _PyGILState_SetTstate(PyThreadState *tstate) if (!_Py_IsMainInterpreter(tstate->interp)) { /* Currently, PyGILState is shared by all interpreters. The main * interpreter is responsible to initialize it. */ - return _PyStatus_OK(); + return; } #ifndef NDEBUG @@ -2170,8 +2171,6 @@ _PyGILState_SetTstate(PyThreadState *tstate) assert(gilstate_tss_get(runtime) == tstate); assert(tstate->gilstate_counter == 1); #endif - - return _PyStatus_OK(); } PyInterpreterState * From 7e2d93f30b157e414924c32232bb748c8f66c828 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Dec 2023 14:29:21 -1000 Subject: [PATCH 220/442] gh-112989: asyncio: Reduce overhead to connect sockets with SelectorEventLoop (#112991) _ensure_fd_no_transport had a KeyError in the success path --- Lib/asyncio/selector_events.py | 14 +++++--------- .../2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index d521b4e2e255a9..dcd5e0aa345029 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -261,15 +261,11 @@ def _ensure_fd_no_transport(self, fd): except (AttributeError, TypeError, ValueError): # This code matches selectors._fileobj_to_fd function. raise ValueError(f"Invalid file object: {fd!r}") from None - try: - transport = self._transports[fileno] - except KeyError: - pass - else: - if not transport.is_closing(): - raise RuntimeError( - f'File descriptor {fd!r} is used by transport ' - f'{transport!r}') + transport = self._transports.get(fileno) + if transport and not transport.is_closing(): + raise RuntimeError( + f'File descriptor {fd!r} is used by transport ' + f'{transport!r}') def _add_reader(self, fd, callback, *args): self._check_closed() diff --git a/Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst b/Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst new file mode 100644 index 00000000000000..ceeab8cc7d6bec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst @@ -0,0 +1 @@ +Reduce overhead to connect sockets with :mod:`asyncio` SelectorEventLoop. From c6e614fd81d7dca436fe640d63a307c7dc9f6f3b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 12 Dec 2023 17:31:30 -0700 Subject: [PATCH 221/442] gh-76785: Avoid Pickled TracebackException for Propagated Subinterpreter Exceptions (gh-113036) We need the TracebackException of uncaught exceptions for a single purpose: the error display. Thus we only need to pass the formatted error display between interpreters. Passing a pickled TracebackException is overkill. --- Include/internal/pycore_crossinterp.h | 3 +- Lib/test/support/interpreters/__init__.py | 2 +- Python/crossinterp.c | 239 ++++++++-------------- 3 files changed, 90 insertions(+), 154 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 414e32b5155f62..d6e297a7e8e6db 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -188,8 +188,7 @@ typedef struct _excinfo { const char *module; } type; const char *msg; - const char *pickled; - Py_ssize_t pickled_len; + const char *errdisplay; } _PyXI_excinfo; diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index d619bea3e32f5d..15a908e9663593 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -56,7 +56,7 @@ def __init__(self, excinfo): def __str__(self): try: - formatted = ''.join(self.excinfo.tbexc.format()).rstrip() + formatted = self.excinfo.errdisplay except Exception: return super().__str__() else: diff --git a/Python/crossinterp.c b/Python/crossinterp.c index edd61cf99f3f52..c6ed7daeb1074a 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -945,113 +945,105 @@ _xidregistry_fini(struct _xidregistry *registry) /*************************/ static const char * -_copy_raw_string(const char *str, Py_ssize_t len) +_copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size) { - size_t size = len + 1; - if (len <= 0) { - size = strlen(str) + 1; - } - char *copied = PyMem_RawMalloc(size); - if (copied == NULL) { - return NULL; - } - if (len <= 0) { - strcpy(copied, str); - } - else { - memcpy(copied, str, size); - } - return copied; -} - -static const char * -_copy_string_obj_raw(PyObject *strobj) -{ - const char *str = PyUnicode_AsUTF8(strobj); + Py_ssize_t size = -1; + const char *str = PyUnicode_AsUTF8AndSize(strobj, &size); if (str == NULL) { return NULL; } - char *copied = PyMem_RawMalloc(strlen(str)+1); + char *copied = PyMem_RawMalloc(size+1); if (copied == NULL) { PyErr_NoMemory(); return NULL; } strcpy(copied, str); + if (p_size != NULL) { + *p_size = size; + } return copied; } static int -_pickle_object(PyObject *obj, const char **p_pickled, Py_ssize_t *p_len) +_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc) { - assert(!PyErr_Occurred()); - PyObject *picklemod = PyImport_ImportModule("_pickle"); - if (picklemod == NULL) { - PyErr_Clear(); - picklemod = PyImport_ImportModule("pickle"); - if (picklemod == NULL) { - return -1; - } + PyObject *args = NULL; + PyObject *kwargs = NULL; + PyObject *create = NULL; + + // This is inspired by _PyErr_Display(). + PyObject *tbmod = PyImport_ImportModule("traceback"); + if (tbmod == NULL) { + return -1; } - PyObject *dumps = PyObject_GetAttrString(picklemod, "dumps"); - Py_DECREF(picklemod); - if (dumps == NULL) { + PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException"); + Py_DECREF(tbmod); + if (tbexc_type == NULL) { return -1; } - PyObject *pickledobj = PyObject_CallOneArg(dumps, obj); - Py_DECREF(dumps); - if (pickledobj == NULL) { + create = PyObject_GetAttrString(tbexc_type, "from_exception"); + Py_DECREF(tbexc_type); + if (create == NULL) { return -1; } - char *pickled = NULL; - Py_ssize_t len = 0; - if (PyBytes_AsStringAndSize(pickledobj, &pickled, &len) < 0) { - Py_DECREF(pickledobj); - return -1; + args = PyTuple_Pack(1, exc); + if (args == NULL) { + goto error; } - const char *copied = _copy_raw_string(pickled, len); - Py_DECREF(pickledobj); - if (copied == NULL) { - return -1; + + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) { + goto error; + } + if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) { + goto error; + } + + PyObject *tbexc = PyObject_Call(create, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(create); + if (tbexc == NULL) { + goto error; } - *p_pickled = copied; - *p_len = len; + *p_tbexc = tbexc; return 0; + +error: + Py_XDECREF(args); + Py_XDECREF(kwargs); + Py_XDECREF(create); + return -1; } -static int -_unpickle_object(const char *pickled, Py_ssize_t size, PyObject **p_obj) + +static const char * +_format_TracebackException(PyObject *tbexc) { - assert(!PyErr_Occurred()); - PyObject *picklemod = PyImport_ImportModule("_pickle"); - if (picklemod == NULL) { - PyErr_Clear(); - picklemod = PyImport_ImportModule("pickle"); - if (picklemod == NULL) { - return -1; - } - } - PyObject *loads = PyObject_GetAttrString(picklemod, "loads"); - Py_DECREF(picklemod); - if (loads == NULL) { - return -1; - } - PyObject *pickledobj = PyBytes_FromStringAndSize(pickled, size); - if (pickledobj == NULL) { - Py_DECREF(loads); - return -1; + PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL); + if (lines == NULL) { + return NULL; } - PyObject *obj = PyObject_CallOneArg(loads, pickledobj); - Py_DECREF(loads); - Py_DECREF(pickledobj); - if (obj == NULL) { - return -1; + PyObject *formatted_obj = PyUnicode_Join(&_Py_STR(empty), lines); + Py_DECREF(lines); + if (formatted_obj == NULL) { + return NULL; } - *p_obj = obj; - return 0; + + Py_ssize_t size = -1; + const char *formatted = _copy_string_obj_raw(formatted_obj, &size); + Py_DECREF(formatted_obj); + // We remove trailing the newline added by TracebackException.format(). + assert(formatted[size-1] == '\n'); + ((char *)formatted)[size-1] = '\0'; + return formatted; } @@ -1101,7 +1093,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc) if (strobj == NULL) { return -1; } - info->name = _copy_string_obj_raw(strobj); + info->name = _copy_string_obj_raw(strobj, NULL); Py_DECREF(strobj); if (info->name == NULL) { return -1; @@ -1112,7 +1104,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc) if (strobj == NULL) { return -1; } - info->qualname = _copy_string_obj_raw(strobj); + info->qualname = _copy_string_obj_raw(strobj, NULL); Py_DECREF(strobj); if (info->name == NULL) { return -1; @@ -1123,7 +1115,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc) if (strobj == NULL) { return -1; } - info->module = _copy_string_obj_raw(strobj); + info->module = _copy_string_obj_raw(strobj, NULL); Py_DECREF(strobj); if (info->name == NULL) { return -1; @@ -1188,8 +1180,8 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info) if (info->msg != NULL) { PyMem_RawFree((void *)info->msg); } - if (info->pickled != NULL) { - PyMem_RawFree((void *)info->pickled); + if (info->errdisplay != NULL) { + PyMem_RawFree((void *)info->errdisplay); } *info = (_PyXI_excinfo){{NULL}}; } @@ -1226,63 +1218,6 @@ _PyXI_excinfo_format(_PyXI_excinfo *info) } } -static int -_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc) -{ - PyObject *args = NULL; - PyObject *kwargs = NULL; - PyObject *create = NULL; - - // This is inspired by _PyErr_Display(). - PyObject *tbmod = PyImport_ImportModule("traceback"); - if (tbmod == NULL) { - return -1; - } - PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException"); - Py_DECREF(tbmod); - if (tbexc_type == NULL) { - return -1; - } - create = PyObject_GetAttrString(tbexc_type, "from_exception"); - Py_DECREF(tbexc_type); - if (create == NULL) { - return -1; - } - - args = PyTuple_Pack(1, exc); - if (args == NULL) { - goto error; - } - - kwargs = PyDict_New(); - if (kwargs == NULL) { - goto error; - } - if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) { - goto error; - } - if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) { - goto error; - } - - PyObject *tbexc = PyObject_Call(create, args, kwargs); - Py_DECREF(args); - Py_DECREF(kwargs); - Py_DECREF(create); - if (tbexc == NULL) { - goto error; - } - - *p_tbexc = tbexc; - return 0; - -error: - Py_XDECREF(args); - Py_XDECREF(kwargs); - Py_XDECREF(create); - return -1; -} - static const char * _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) { @@ -1305,7 +1240,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) failure = "error while formatting exception"; goto error; } - info->msg = _copy_string_obj_raw(msgobj); + info->msg = _copy_string_obj_raw(msgobj, NULL); Py_DECREF(msgobj); if (info->msg == NULL) { failure = "error while copying exception message"; @@ -1321,13 +1256,14 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) PyErr_Clear(); } else { - if (_pickle_object(tbexc, &info->pickled, &info->pickled_len) < 0) { + info->errdisplay = _format_TracebackException(tbexc); + Py_DECREF(tbexc); + if (info->errdisplay == NULL) { #ifdef Py_DEBUG - PyErr_FormatUnraisable("Exception ignored while pickling TracebackException"); + PyErr_FormatUnraisable("Exception ignored while formating TracebackException"); #endif PyErr_Clear(); } - Py_DECREF(tbexc); } return NULL; @@ -1342,8 +1278,9 @@ static void _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) { PyObject *tbexc = NULL; - if (info->pickled != NULL) { - if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { + if (info->errdisplay != NULL) { + tbexc = PyUnicode_FromString(info->errdisplay); + if (tbexc == NULL) { PyErr_Clear(); } } @@ -1354,9 +1291,9 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) if (tbexc != NULL) { PyObject *exc = PyErr_GetRaisedException(); - if (PyObject_SetAttrString(exc, "_tbexc", tbexc) < 0) { + if (PyObject_SetAttrString(exc, "_errdisplay", tbexc) < 0) { #ifdef Py_DEBUG - PyErr_FormatUnraisable("Exception ignored when setting _tbexc"); + PyErr_FormatUnraisable("Exception ignored when setting _errdisplay"); #endif PyErr_Clear(); } @@ -1468,13 +1405,13 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info) goto error; } - if (info->pickled != NULL) { - PyObject *tbexc = NULL; - if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { + if (info->errdisplay != NULL) { + PyObject *tbexc = PyUnicode_FromString(info->errdisplay); + if (tbexc == NULL) { PyErr_Clear(); } else { - res = PyObject_SetAttrString(ns, "tbexc", tbexc); + res = PyObject_SetAttrString(ns, "errdisplay", tbexc); Py_DECREF(tbexc); if (res < 0) { goto error; @@ -1646,7 +1583,7 @@ _sharednsitem_is_initialized(_PyXI_namespace_item *item) static int _sharednsitem_init(_PyXI_namespace_item *item, PyObject *key) { - item->name = _copy_string_obj_raw(key); + item->name = _copy_string_obj_raw(key, NULL); if (item->name == NULL) { assert(!_sharednsitem_is_initialized(item)); return -1; From a3a1cb48456c809f7b1ab6a6ffe83e8b3f69be0f Mon Sep 17 00:00:00 2001 From: Jamie <101677823+ordinary-jamie@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:26:40 +1100 Subject: [PATCH 222/442] gh-112622: Pass name to loop create_task method (#112623) This affects task creation through either `asyncio.create_task()` or `TaskGroup.create_task()` -- the redundant call to `task.set_name()` is skipped. We still call `set_name()` when a task factory is involved, because the task factory call signature (unfortunately) doesn't take a `name` argument. --- Lib/asyncio/taskgroups.py | 6 +++--- Lib/asyncio/tasks.py | 5 ++--- .../Library/2023-12-03-01-01-52.gh-issue-112622.1Z8cpx.rst | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-03-01-01-52.gh-issue-112622.1Z8cpx.rst diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 91be0decc41c42..cb9c1ce4d7d1d2 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -158,10 +158,10 @@ def create_task(self, coro, *, name=None, context=None): if self._aborting: raise RuntimeError(f"TaskGroup {self!r} is shutting down") if context is None: - task = self._loop.create_task(coro) + task = self._loop.create_task(coro, name=name) else: - task = self._loop.create_task(coro, context=context) - task.set_name(name) + task = self._loop.create_task(coro, name=name, context=context) + # optimization: Immediately call the done callback if the task is # already done (e.g. if the coro was able to complete eagerly), # and skip scheduling a done callback diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index e84b21390557be..fafee3e738f6aa 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -404,11 +404,10 @@ def create_task(coro, *, name=None, context=None): loop = events.get_running_loop() if context is None: # Use legacy API if context is not needed - task = loop.create_task(coro) + task = loop.create_task(coro, name=name) else: - task = loop.create_task(coro, context=context) + task = loop.create_task(coro, name=name, context=context) - task.set_name(name) return task diff --git a/Misc/NEWS.d/next/Library/2023-12-03-01-01-52.gh-issue-112622.1Z8cpx.rst b/Misc/NEWS.d/next/Library/2023-12-03-01-01-52.gh-issue-112622.1Z8cpx.rst new file mode 100644 index 00000000000000..91c88bac334dcb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-03-01-01-52.gh-issue-112622.1Z8cpx.rst @@ -0,0 +1,2 @@ +Ensure ``name`` parameter is passed to event loop in +:func:`asyncio.create_task`. From 3aea6c4823e90172c9bc36cd20dc51b295d8a3c4 Mon Sep 17 00:00:00 2001 From: beavailable Date: Wed, 13 Dec 2023 11:23:29 +0800 Subject: [PATCH 223/442] gh-101336: Add keep_alive keyword arg for asyncio create_server() (#112485) --- Doc/library/asyncio-eventloop.rst | 8 ++++++++ Lib/asyncio/base_events.py | 4 ++++ Lib/asyncio/events.py | 4 ++++ .../2023-11-28-02-39-30.gh-issue-101336.ya433z.rst | 1 + 4 files changed, 17 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-11-28-02-39-30.gh-issue-101336.ya433z.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index ea1d146f06cf2b..828e506a72c937 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -671,6 +671,7 @@ Creating network servers flags=socket.AI_PASSIVE, \ sock=None, backlog=100, ssl=None, \ reuse_address=None, reuse_port=None, \ + keep_alive=None, \ ssl_handshake_timeout=None, \ ssl_shutdown_timeout=None, \ start_serving=True) @@ -735,6 +736,13 @@ Creating network servers set this flag when being created. This option is not supported on Windows. + * *keep_alive* set to ``True`` keeps connections active by enabling the + periodic transmission of messages. + + .. versionchanged:: 3.13 + + Added the *keep_alive* parameter. + * *ssl_handshake_timeout* is (for a TLS server) the time in seconds to wait for the TLS handshake to complete before aborting the connection. ``60.0`` seconds if ``None`` (default). diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 416c732298d9a9..a8870b636d1df5 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -1496,6 +1496,7 @@ async def create_server( ssl=None, reuse_address=None, reuse_port=None, + keep_alive=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True): @@ -1569,6 +1570,9 @@ async def create_server( socket.SOL_SOCKET, socket.SO_REUSEADDR, True) if reuse_port: _set_reuseport(sock) + if keep_alive: + sock.setsockopt( + socket.SOL_SOCKET, socket.SO_KEEPALIVE, True) # Disable IPv4/IPv6 dual stack support (enabled by # default on Linux) which makes a single socket # listen on both address families. diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 0ccf85105e6673..ebc3836bdc0c4d 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -316,6 +316,7 @@ async def create_server( *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, + keep_alive=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True): @@ -354,6 +355,9 @@ async def create_server( they all set this flag when being created. This option is not supported on Windows. + keep_alive set to True keeps connections active by enabling the + periodic transmission of messages. + ssl_handshake_timeout is the time in seconds that an SSL server will wait for completion of the SSL handshake before aborting the connection. Default is 60s. diff --git a/Misc/NEWS.d/next/Library/2023-11-28-02-39-30.gh-issue-101336.ya433z.rst b/Misc/NEWS.d/next/Library/2023-11-28-02-39-30.gh-issue-101336.ya433z.rst new file mode 100644 index 00000000000000..c222febae6b554 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-28-02-39-30.gh-issue-101336.ya433z.rst @@ -0,0 +1 @@ +Add ``keep_alive`` keyword parameter for :meth:`AbstractEventLoop.create_server` and :meth:`BaseEventLoop.create_server`. From 3531ea441b8b76bff90d2ecc062335da65fd3341 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 13 Dec 2023 09:24:55 +0200 Subject: [PATCH 224/442] gh-101100: Fix Sphinx warning in references with asterisks (#113029) Co-authored-by: Alex Waygood --- Doc/library/bdb.rst | 2 +- Doc/library/cmd.rst | 14 +++++++------- Doc/library/configparser.rst | 2 +- Doc/library/csv.rst | 6 ++++-- Doc/library/http.server.rst | 8 ++++---- Doc/library/locale.rst | 2 +- Doc/library/os.rst | 8 ++++---- Doc/library/resource.rst | 4 ++-- Doc/library/socket.rst | 8 ++++---- Doc/library/unittest.rst | 14 +++++++------- Doc/library/urllib.request.rst | 8 ++++---- Doc/library/xml.dom.rst | 2 +- Doc/whatsnew/2.3.rst | 2 +- Doc/whatsnew/2.4.rst | 2 +- Doc/whatsnew/2.5.rst | 4 ++-- Doc/whatsnew/2.7.rst | 2 +- Doc/whatsnew/3.4.rst | 2 +- Misc/NEWS.d/3.10.0a1.rst | 4 ++-- Misc/NEWS.d/3.12.0a1.rst | 2 +- Misc/NEWS.d/3.12.0b1.rst | 2 +- Misc/NEWS.d/3.6.0a2.rst | 2 +- Misc/NEWS.d/3.9.0a1.rst | 10 +++++----- 22 files changed, 56 insertions(+), 54 deletions(-) diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst index d201dc963b5995..4ce5c9bcde38ff 100644 --- a/Doc/library/bdb.rst +++ b/Doc/library/bdb.rst @@ -294,7 +294,7 @@ The :mod:`bdb` module also defines two classes: .. method:: set_quit() Set the :attr:`quitting` attribute to ``True``. This raises :exc:`BdbQuit` in - the next call to one of the :meth:`dispatch_\*` methods. + the next call to one of the :meth:`!dispatch_\*` methods. Derived classes and clients can call the following methods to manipulate diff --git a/Doc/library/cmd.rst b/Doc/library/cmd.rst index 1318ffe5a48d53..a79882ed1cca4f 100644 --- a/Doc/library/cmd.rst +++ b/Doc/library/cmd.rst @@ -83,7 +83,7 @@ A :class:`Cmd` instance has the following methods: This method will return when the :meth:`postcmd` method returns a true value. The *stop* argument to :meth:`postcmd` is the return value from the command's - corresponding :meth:`do_\*` method. + corresponding :meth:`!do_\*` method. If completion is enabled, completing commands will be done automatically, and completing of commands args is done by calling :meth:`complete_foo` with @@ -98,7 +98,7 @@ A :class:`Cmd` instance has the following methods: :meth:`help_bar`, and if that is not present, prints the docstring of :meth:`do_bar`, if available. With no argument, :meth:`do_help` lists all available help topics (that is, all commands with corresponding - :meth:`help_\*` methods or commands that have docstrings), and also lists any + :meth:`!help_\*` methods or commands that have docstrings), and also lists any undocumented commands. @@ -108,7 +108,7 @@ A :class:`Cmd` instance has the following methods: This may be overridden, but should not normally need to be; see the :meth:`precmd` and :meth:`postcmd` methods for useful execution hooks. The return value is a flag indicating whether interpretation of commands by the - interpreter should stop. If there is a :meth:`do_\*` method for the command + interpreter should stop. If there is a :meth:`!do_\*` method for the command *str*, the return value of that method is returned, otherwise the return value from the :meth:`default` method is returned. @@ -128,7 +128,7 @@ A :class:`Cmd` instance has the following methods: .. method:: Cmd.completedefault(text, line, begidx, endidx) Method called to complete an input line when no command-specific - :meth:`complete_\*` method is available. By default, it returns an empty list. + :meth:`!complete_\*` method is available. By default, it returns an empty list. .. method:: Cmd.columnize(list, displaywidth=80) @@ -209,14 +209,14 @@ Instances of :class:`Cmd` subclasses have some public instance variables: .. attribute:: Cmd.misc_header The header to issue if the help output has a section for miscellaneous help - topics (that is, there are :meth:`help_\*` methods without corresponding - :meth:`do_\*` methods). + topics (that is, there are :meth:`!help_\*` methods without corresponding + :meth:`!do_\*` methods). .. attribute:: Cmd.undoc_header The header to issue if the help output has a section for undocumented commands - (that is, there are :meth:`do_\*` methods without corresponding :meth:`help_\*` + (that is, there are :meth:`!do_\*` methods without corresponding :meth:`!help_\*` methods). diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index bb282428c5fffc..12eee47613d186 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -955,7 +955,7 @@ ConfigParser Objects When *converters* is given, it should be a dictionary where each key represents the name of a type converter and each value is a callable implementing the conversion from string to the desired datatype. Every - converter gets its own corresponding :meth:`get*()` method on the parser + converter gets its own corresponding :meth:`!get*()` method on the parser object and section proxies. .. versionchanged:: 3.1 diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index aba398b8ee1e54..4d52254e6d6db5 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -309,6 +309,8 @@ An example for :class:`Sniffer` use:: # ... process CSV file contents here ... +.. _csv-constants: + The :mod:`csv` module defines the following constants: .. data:: QUOTE_ALL @@ -432,8 +434,8 @@ Dialects support the following attributes: .. attribute:: Dialect.quoting Controls when quotes should be generated by the writer and recognised by the - reader. It can take on any of the :const:`QUOTE_\*` constants (see section - :ref:`csv-contents`) and defaults to :const:`QUOTE_MINIMAL`. + reader. It can take on any of the :ref:`QUOTE_\* constants ` + and defaults to :const:`QUOTE_MINIMAL`. .. attribute:: Dialect.skipinitialspace diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index 6f79b222790094..64bddd23f82933 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -65,10 +65,10 @@ provides three different variants: The handler will parse the request and the headers, then call a method specific to the request type. The method name is constructed from the - request. For example, for the request method ``SPAM``, the :meth:`do_SPAM` + request. For example, for the request method ``SPAM``, the :meth:`!do_SPAM` method will be called with no arguments. All of the relevant information is stored in instance variables of the handler. Subclasses should not need to - override or extend the :meth:`__init__` method. + override or extend the :meth:`!__init__` method. :class:`BaseHTTPRequestHandler` has the following instance variables: @@ -187,13 +187,13 @@ provides three different variants: Calls :meth:`handle_one_request` once (or, if persistent connections are enabled, multiple times) to handle incoming HTTP requests. You should - never need to override it; instead, implement appropriate :meth:`do_\*` + never need to override it; instead, implement appropriate :meth:`!do_\*` methods. .. method:: handle_one_request() This method will parse and dispatch the request to the appropriate - :meth:`do_\*` method. You should never need to override it. + :meth:`!do_\*` method. You should never need to override it. .. method:: handle_expect_100() diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index 0d48892fcdab97..a7201199191215 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -309,7 +309,7 @@ The :mod:`locale` module defines the following exception and functions: .. function:: getlocale(category=LC_CTYPE) Returns the current setting for the given locale category as sequence containing - *language code*, *encoding*. *category* may be one of the :const:`LC_\*` values + *language code*, *encoding*. *category* may be one of the :const:`!LC_\*` values except :const:`LC_ALL`. It defaults to :const:`LC_CTYPE`. Except for the code ``'C'``, the language code corresponds to :rfc:`1766`. diff --git a/Doc/library/os.rst b/Doc/library/os.rst index fe573f188ab066..e5ac9afc3c6fd9 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4170,7 +4170,7 @@ to be ignored. The "l" and "v" variants of the :func:`exec\* ` functions differ in how command-line arguments are passed. The "l" variants are perhaps the easiest to work with if the number of parameters is fixed when the code is written; the - individual parameters simply become additional parameters to the :func:`execl\*` + individual parameters simply become additional parameters to the :func:`!execl\*` functions. The "v" variants are good when the number of parameters is variable, with the arguments being passed in a list or tuple as the *args* parameter. In either case, the arguments to the child process should start with @@ -4708,7 +4708,7 @@ written in Python, such as a mail server's external command delivery program. command-line arguments are passed. The "l" variants are perhaps the easiest to work with if the number of parameters is fixed when the code is written; the individual parameters simply become additional parameters to the - :func:`spawnl\*` functions. The "v" variants are good when the number of + :func:`!spawnl\*` functions. The "v" variants are good when the number of parameters is variable, with the arguments being passed in a list or tuple as the *args* parameter. In either case, the arguments to the child process must start with the name of the command being run. @@ -4758,7 +4758,7 @@ written in Python, such as a mail server's external command delivery program. P_NOWAITO Possible values for the *mode* parameter to the :func:`spawn\* ` family of - functions. If either of these values is given, the :func:`spawn\*` functions + functions. If either of these values is given, the :func:`spawn\* ` functions will return as soon as the new process has been created, with the process id as the return value. @@ -4768,7 +4768,7 @@ written in Python, such as a mail server's external command delivery program. .. data:: P_WAIT Possible value for the *mode* parameter to the :func:`spawn\* ` family of - functions. If this is given as *mode*, the :func:`spawn\*` functions will not + functions. If this is given as *mode*, the :func:`spawn\* ` functions will not return until the new process has run to completion and will return the exit code of the process the run is successful, or ``-signal`` if a signal kills the process. diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst index ef65674d1b0a78..4e58b043f1da31 100644 --- a/Doc/library/resource.rst +++ b/Doc/library/resource.rst @@ -277,7 +277,7 @@ These functions are used to retrieve resource usage information: This function returns an object that describes the resources consumed by either the current process or its children, as specified by the *who* parameter. The - *who* parameter should be specified using one of the :const:`RUSAGE_\*` + *who* parameter should be specified using one of the :const:`!RUSAGE_\*` constants described below. A simple example:: @@ -353,7 +353,7 @@ These functions are used to retrieve resource usage information: Returns the number of bytes in a system page. (This need not be the same as the hardware page size.) -The following :const:`RUSAGE_\*` symbols are passed to the :func:`getrusage` +The following :const:`!RUSAGE_\*` symbols are passed to the :func:`getrusage` function to specify which processes information should be provided for. diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index e0a75304ef1606..4bfb0d8c2cfeac 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -311,7 +311,7 @@ Exceptions The accompanying value is a pair ``(error, string)`` representing an error returned by a library call. *string* represents the description of *error*, as returned by the :c:func:`gai_strerror` C function. The - numeric *error* value will match one of the :const:`EAI_\*` constants + numeric *error* value will match one of the :const:`!EAI_\*` constants defined in this module. .. versionchanged:: 3.3 @@ -1517,7 +1517,7 @@ to sockets. .. method:: socket.getsockopt(level, optname[, buflen]) Return the value of the given socket option (see the Unix man page - :manpage:`getsockopt(2)`). The needed symbolic constants (:const:`SO_\*` etc.) + :manpage:`getsockopt(2)`). The needed symbolic constants (:ref:`SO_\* etc. `) are defined in this module. If *buflen* is absent, an integer option is assumed and its integer value is returned by the function. If *buflen* is present, it specifies the maximum length of the buffer used to receive the option in, and @@ -1937,8 +1937,8 @@ to sockets. .. index:: pair: module; struct Set the value of the given socket option (see the Unix manual page - :manpage:`setsockopt(2)`). The needed symbolic constants are defined in the - :mod:`socket` module (:const:`SO_\*` etc.). The value can be an integer, + :manpage:`setsockopt(2)`). The needed symbolic constants are defined in this + module (:ref:`!SO_\* etc. `). The value can be an integer, ``None`` or a :term:`bytes-like object` representing a buffer. In the later case it is up to the caller to ensure that the bytestring contains the proper bits (see the optional built-in module :mod:`struct` for a way to diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 02b72cb9f6b8aa..c04e6c378cc3b1 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -390,8 +390,8 @@ testing code:: widget = Widget('The widget') self.assertEqual(widget.size(), (50, 50)) -Note that in order to test something, we use one of the :meth:`assert\*` -methods provided by the :class:`TestCase` base class. If the test fails, an +Note that in order to test something, we use one of the :ref:`assert\* methods ` +provided by the :class:`TestCase` base class. If the test fails, an exception will be raised with an explanatory message, and :mod:`unittest` will identify the test case as a :dfn:`failure`. Any other exceptions will be treated as :dfn:`errors`. @@ -1940,14 +1940,14 @@ Loading and running tests String giving the prefix of method names which will be interpreted as test methods. The default value is ``'test'``. - This affects :meth:`getTestCaseNames` and all the :meth:`loadTestsFrom\*` + This affects :meth:`getTestCaseNames` and all the ``loadTestsFrom*`` methods. .. attribute:: sortTestMethodsUsing Function to be used to compare method names when sorting them in - :meth:`getTestCaseNames` and all the :meth:`loadTestsFrom\*` methods. + :meth:`getTestCaseNames` and all the ``loadTestsFrom*`` methods. .. attribute:: suiteClass @@ -1956,7 +1956,7 @@ Loading and running tests methods on the resulting object are needed. The default value is the :class:`TestSuite` class. - This affects all the :meth:`loadTestsFrom\*` methods. + This affects all the ``loadTestsFrom*`` methods. .. attribute:: testNamePatterns @@ -1969,7 +1969,7 @@ Loading and running tests so unlike patterns passed to the ``-k`` option, simple substring patterns will have to be converted using ``*`` wildcards. - This affects all the :meth:`loadTestsFrom\*` methods. + This affects all the ``loadTestsFrom*`` methods. .. versionadded:: 3.7 @@ -2003,7 +2003,7 @@ Loading and running tests A list containing 2-tuples of :class:`TestCase` instances and strings holding formatted tracebacks. Each tuple represents a test where a failure - was explicitly signalled using the :meth:`TestCase.assert\*` methods. + was explicitly signalled using the :ref:`assert\* methods `. .. attribute:: skipped diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index bf3af1bef0714c..040e28e9124014 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -712,8 +712,8 @@ The following attribute and methods should only be used by classes derived from .. note:: The convention has been adopted that subclasses defining - :meth:`_request` or :meth:`_response` methods are named - :class:`\*Processor`; all others are named :class:`\*Handler`. + :meth:`!_request` or :meth:`!_response` methods are named + :class:`!\*Processor`; all others are named :class:`!\*Handler`. .. attribute:: BaseHandler.parent @@ -833,9 +833,9 @@ HTTPRedirectHandler Objects .. method:: HTTPRedirectHandler.redirect_request(req, fp, code, msg, hdrs, newurl) Return a :class:`Request` or ``None`` in response to a redirect. This is called - by the default implementations of the :meth:`http_error_30\*` methods when a + by the default implementations of the :meth:`!http_error_30\*` methods when a redirection is received from the server. If a redirection should take place, - return a new :class:`Request` to allow :meth:`http_error_30\*` to perform the + return a new :class:`Request` to allow :meth:`!http_error_30\*` to perform the redirect to *newurl*. Otherwise, raise :exc:`~urllib.error.HTTPError` if no other handler should try to handle this URL, or return ``None`` if you can't but another handler might. diff --git a/Doc/library/xml.dom.rst b/Doc/library/xml.dom.rst index b387240a3716cc..d0e1b248d595d1 100644 --- a/Doc/library/xml.dom.rst +++ b/Doc/library/xml.dom.rst @@ -734,7 +734,7 @@ NamedNodeMap Objects attribute node. Get its value with the :attr:`value` attribute. There are also experimental methods that give this class more mapping behavior. -You can use them or you can use the standardized :meth:`getAttribute\*` family +You can use them or you can use the standardized :meth:`!getAttribute\*` family of methods on the :class:`Element` objects. diff --git a/Doc/whatsnew/2.3.rst b/Doc/whatsnew/2.3.rst index c989e6d3fa5787..0c77b339a182c9 100644 --- a/Doc/whatsnew/2.3.rst +++ b/Doc/whatsnew/2.3.rst @@ -1362,7 +1362,7 @@ complete list of changes, or look through the CVS logs for all the details. :mod:`os` module. (Contributed by Gustavo Niemeyer, Geert Jansen, and Denis S. Otkidach.) -* In the :mod:`os` module, the :func:`\*stat` family of functions can now report +* In the :mod:`os` module, the :func:`!\*stat` family of functions can now report fractions of a second in a timestamp. Such time stamps are represented as floats, similar to the value returned by :func:`time.time`. diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index a7b0c6e5d76a6f..bc748dd44f5f8e 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -1164,7 +1164,7 @@ complete list of changes, or look through the CVS logs for all the details. * A number of functions were added to the :mod:`locale` module, such as :func:`bind_textdomain_codeset` to specify a particular encoding and a family of - :func:`l\*gettext` functions that return messages in the chosen encoding. + :func:`!l\*gettext` functions that return messages in the chosen encoding. (Contributed by Gustavo Niemeyer.) * Some keyword arguments were added to the :mod:`logging` package's diff --git a/Doc/whatsnew/2.5.rst b/Doc/whatsnew/2.5.rst index 64b951da3fd5b8..627c918dd6d8b4 100644 --- a/Doc/whatsnew/2.5.rst +++ b/Doc/whatsnew/2.5.rst @@ -1167,10 +1167,10 @@ marked in the following list. * It's now illegal to mix iterating over a file with ``for line in file`` and calling the file object's :meth:`read`/:meth:`readline`/:meth:`readlines` - methods. Iteration uses an internal buffer and the :meth:`read\*` methods + methods. Iteration uses an internal buffer and the :meth:`!read\*` methods don't use that buffer. Instead they would return the data following the buffer, causing the data to appear out of order. Mixing iteration and these - methods will now trigger a :exc:`ValueError` from the :meth:`read\*` method. + methods will now trigger a :exc:`ValueError` from the :meth:`!read\*` method. (Implemented by Thomas Wouters.) .. Patch 1397960 diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index cf6d26859bb6a2..5af700bd5d3506 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -1417,7 +1417,7 @@ changes, or look through the Subversion logs for all the details. :func:`~math.lgamma` for the natural log of the Gamma function. (Contributed by Mark Dickinson and nirinA raseliarison; :issue:`3366`.) -* The :mod:`multiprocessing` module's :class:`Manager*` classes +* The :mod:`multiprocessing` module's :class:`!Manager*` classes can now be passed a callable that will be called whenever a subprocess is started, along with a set of arguments that will be passed to the callable. diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 9f9cf7fafdc19e..72d12461d8f730 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -1936,7 +1936,7 @@ Other Improvements * The :ref:`python ` command has a new :ref:`option `, ``-I``, which causes it to run in "isolated mode", which means that :data:`sys.path` contains neither the script's directory nor - the user's ``site-packages`` directory, and all :envvar:`PYTHON*` environment + the user's ``site-packages`` directory, and all :envvar:`!PYTHON*` environment variables are ignored (it implies both ``-s`` and ``-E``). Other restrictions may also be applied in the future, with the goal being to isolate the execution of a script from the user's environment. This is diff --git a/Misc/NEWS.d/3.10.0a1.rst b/Misc/NEWS.d/3.10.0a1.rst index a9f25b482508ba..731eed3447d2bc 100644 --- a/Misc/NEWS.d/3.10.0a1.rst +++ b/Misc/NEWS.d/3.10.0a1.rst @@ -605,8 +605,8 @@ Opt out serialization/deserialization for _random.Random .. nonce: jxJ4yn .. section: Core and Builtins -Rename `PyPegen*` functions to `PyParser*`, so that we can remove the old -set of `PyParser*` functions that were using the old parser, but keep +Rename ``PyPegen*`` functions to ``PyParser*``, so that we can remove the old +set of ``PyParser*`` functions that were using the old parser, but keep everything backwards-compatible. .. diff --git a/Misc/NEWS.d/3.12.0a1.rst b/Misc/NEWS.d/3.12.0a1.rst index 633738de92bef7..29d04fa0e175bf 100644 --- a/Misc/NEWS.d/3.12.0a1.rst +++ b/Misc/NEWS.d/3.12.0a1.rst @@ -1913,7 +1913,7 @@ Stinner. .. nonce: Uxc9al .. section: Library -Allow :mod:`venv` to pass along :envvar:`PYTHON*` variables to ``ensurepip`` +Allow :mod:`venv` to pass along :envvar:`!PYTHON*` variables to ``ensurepip`` and ``pip`` when they do not impact path resolution .. diff --git a/Misc/NEWS.d/3.12.0b1.rst b/Misc/NEWS.d/3.12.0b1.rst index 0944dfd0e90ab9..007a6ad4ffd4d4 100644 --- a/Misc/NEWS.d/3.12.0b1.rst +++ b/Misc/NEWS.d/3.12.0b1.rst @@ -880,7 +880,7 @@ Update the ``repr`` of :class:`typing.Unpack` according to :pep:`692`. .. section: Library Make :mod:`dis` display the names of the args for -:opcode:`CALL_INTRINSIC_*`. +:opcode:`!CALL_INTRINSIC_*`. .. diff --git a/Misc/NEWS.d/3.6.0a2.rst b/Misc/NEWS.d/3.6.0a2.rst index 1b336d7bc5137a..05b3d9f0463c1c 100644 --- a/Misc/NEWS.d/3.6.0a2.rst +++ b/Misc/NEWS.d/3.6.0a2.rst @@ -603,7 +603,7 @@ configuring text widget colors to a new function. .. nonce: RbyFuV .. section: IDLE -Rename many `idlelib/*.py` and `idle_test/test_*.py` files. Edit files to +Rename many ``idlelib/*.py`` and ``idle_test/test_*.py`` files. Edit files to replace old names with new names when the old name referred to the module rather than the class it contained. See the issue and IDLE section in What's New in 3.6 for more. diff --git a/Misc/NEWS.d/3.9.0a1.rst b/Misc/NEWS.d/3.9.0a1.rst index 9818c17705074b..0444b53895b166 100644 --- a/Misc/NEWS.d/3.9.0a1.rst +++ b/Misc/NEWS.d/3.9.0a1.rst @@ -2534,7 +2534,7 @@ object when `self._spec_signature` exists. Patch by Elizabeth Uselton .. nonce: iXGuoi .. section: Library -Make `from tkinter import *` import only the expected objects. +Make ``from tkinter import *`` import only the expected objects. .. @@ -3117,9 +3117,9 @@ Ensure cookies with ``expires`` attribute are handled in .. section: Library Fix an unintended ValueError from :func:`subprocess.run` when checking for -conflicting `input` and `stdin` or `capture_output` and `stdout` or `stderr` -args when they were explicitly provided but with `None` values within a -passed in `**kwargs` dict rather than as passed directly by name. Patch +conflicting *input* and *stdin* or *capture_output* and *stdout* or *stderr* +args when they were explicitly provided but with ``None`` values within a +passed in ``**kwargs`` dict rather than as passed directly by name. Patch contributed by Rémi Lapeyre. .. @@ -3546,7 +3546,7 @@ Patch by Stein Karlsen. .. nonce: XaJDei .. section: Library -lib2to3 now recognizes expressions after ``*`` and `**` like in ``f(*[] or +lib2to3 now recognizes expressions after ``*`` and ``**`` like in ``f(*[] or [])``. .. From 428c9812cb4ff3521e5904719224fe63fba5370a Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:00:21 +0000 Subject: [PATCH 225/442] gh-112962: in dis module, put cache information in the Instruction instead of creating fake Instructions to represent it (#113016) --- Doc/library/dis.rst | 18 ++++- Lib/dis.py | 73 +++++++++++-------- Lib/test/support/bytecode_helper.py | 12 +++ Lib/test/test_code.py | 8 +- Lib/test/test_compile.py | 5 +- Lib/test/test_dis.py | 41 +++++++++-- ...-12-12-16-32-55.gh-issue-112962.ZZWXZn.rst | 3 + 7 files changed, 114 insertions(+), 46 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-12-16-32-55.gh-issue-112962.ZZWXZn.rst diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 0d93bc9f5da774..5647021d6a9ba6 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -328,13 +328,17 @@ operation is being performed, so the intermediate analysis object isn't useful: source line information (if any) is taken directly from the disassembled code object. - The *show_caches* and *adaptive* parameters work as they do in :func:`dis`. + The *adaptive* parameter works as it does in :func:`dis`. .. versionadded:: 3.4 .. versionchanged:: 3.11 Added the *show_caches* and *adaptive* parameters. + .. versionchanged:: 3.13 + The *show_caches* parameter is deprecated and has no effect. The *cache_info* + field of each instruction is populated regardless of its value. + .. function:: findlinestarts(code) @@ -482,6 +486,14 @@ details of bytecode instructions as :class:`Instruction` instances: :class:`dis.Positions` object holding the start and end locations that are covered by this instruction. + .. data::cache_info + + Information about the cache entries of this instruction, as + triplets of the form ``(name, size, data)``, where the ``name`` + and ``size`` describe the cache format and data is the contents + of the cache. ``cache_info`` is ``None`` if the instruction does not have + caches. + .. versionadded:: 3.4 .. versionchanged:: 3.11 @@ -493,8 +505,8 @@ details of bytecode instructions as :class:`Instruction` instances: Changed field ``starts_line``. Added fields ``start_offset``, ``cache_offset``, ``end_offset``, - ``baseopname``, ``baseopcode``, ``jump_target``, ``oparg``, and - ``line_number``. + ``baseopname``, ``baseopcode``, ``jump_target``, ``oparg``, + ``line_number`` and ``cache_info``. .. class:: Positions diff --git a/Lib/dis.py b/Lib/dis.py index efa935c5a6a0b6..183091cb0d6098 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -267,9 +267,10 @@ def show_code(co, *, file=None): 'starts_line', 'line_number', 'label', - 'positions' + 'positions', + 'cache_info', ], - defaults=[None, None] + defaults=[None, None, None] ) _Instruction.opname.__doc__ = "Human readable name for operation" @@ -286,6 +287,7 @@ def show_code(co, *, file=None): _Instruction.line_number.__doc__ = "source line number associated with this opcode (if any), otherwise None" _Instruction.label.__doc__ = "A label (int > 0) if this instruction is a jump target, otherwise None" _Instruction.positions.__doc__ = "dis.Positions object holding the span of source code covered by this instruction" +_Instruction.cache_info.__doc__ = "list of (name, size, data), one for each cache entry of the instruction" _ExceptionTableEntryBase = collections.namedtuple("_ExceptionTableEntryBase", "start end target depth lasti") @@ -334,6 +336,8 @@ class Instruction(_Instruction): label - A label if this instruction is a jump target, otherwise None positions - Optional dis.Positions object holding the span of source code covered by this instruction + cache_info - information about the format and content of the instruction's cache + entries (if any) """ @property @@ -570,7 +574,6 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): linestarts=linestarts, line_offset=line_offset, co_positions=co.co_positions(), - show_caches=show_caches, original_code=original_code, arg_resolver=arg_resolver) @@ -645,8 +648,7 @@ def _is_backward_jump(op): 'ENTER_EXECUTOR') def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, - show_caches=False, original_code=None, labels_map=None, - arg_resolver=None): + original_code=None, labels_map=None, arg_resolver=None): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -682,32 +684,28 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N else: argval, argrepr = arg, repr(arg) + instr = Instruction(_all_opname[op], op, arg, argval, argrepr, + offset, start_offset, starts_line, line_number, + labels_map.get(offset, None), positions) + + caches = _get_cache_size(_all_opname[deop]) + # Advance the co_positions iterator: + for _ in range(caches): + next(co_positions, ()) + + if caches: + cache_info = [] + for name, size in _cache_format[opname[deop]].items(): + data = code[offset + 2: offset + 2 + 2 * size] + cache_info.append((name, size, data)) + else: + cache_info = None + yield Instruction(_all_opname[op], op, arg, argval, argrepr, offset, start_offset, starts_line, line_number, - labels_map.get(offset, None), positions) + labels_map.get(offset, None), positions, cache_info) + - caches = _get_cache_size(_all_opname[deop]) - if not caches: - continue - if not show_caches: - # We still need to advance the co_positions iterator: - for _ in range(caches): - next(co_positions, ()) - continue - for name, size in _cache_format[opname[deop]].items(): - for i in range(size): - offset += 2 - # Only show the fancy argrepr for a CACHE instruction when it's - # the first entry for a particular cache value: - if i == 0: - data = code[offset: offset + 2 * size] - argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" - else: - argrepr = "" - yield Instruction( - "CACHE", CACHE, 0, None, argrepr, offset, offset, False, None, None, - Positions(*next(co_positions, ())) - ) def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, show_offsets=False): @@ -787,7 +785,6 @@ def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, instrs = _get_instructions_bytes(code, linestarts=linestarts, line_offset=line_offset, co_positions=co_positions, - show_caches=show_caches, original_code=original_code, labels_map=labels_map, arg_resolver=arg_resolver) @@ -805,6 +802,23 @@ def print_instructions(instrs, exception_entries, formatter, show_caches=False, is_current_instr = instr.offset <= lasti \ <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) formatter.print_instruction(instr, is_current_instr) + deop = _deoptop(instr.opcode) + if show_caches and instr.cache_info: + offset = instr.offset + for name, size, data in instr.cache_info: + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + formatter.print_instruction( + Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions), + is_current_instr) + formatter.print_exception_table(exception_entries) def _disassemble_str(source, **kwargs): @@ -952,7 +966,6 @@ def __iter__(self): linestarts=self._linestarts, line_offset=self._line_offset, co_positions=co.co_positions(), - show_caches=self.show_caches, original_code=original_code, labels_map=labels_map, arg_resolver=arg_resolver) diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index 388d1266773c8a..a4845065a5322e 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -7,6 +7,18 @@ _UNSPECIFIED = object() +def instructions_with_positions(instrs, co_positions): + # Return (instr, positions) pairs from the instrs list and co_positions + # iterator. The latter contains items for cache lines and the former + # doesn't, so those need to be skipped. + + co_positions = co_positions or iter(()) + for instr in instrs: + yield instr, next(co_positions, ()) + for _, size, _ in (instr.cache_info or ()): + for i in range(size): + next(co_positions, ()) + class BytecodeTestCase(unittest.TestCase): """Custom assertion methods for inspecting bytecode.""" diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index a961ddbe17a3d3..d8fb826edeb681 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -144,6 +144,8 @@ gc_collect) from test.support.script_helper import assert_python_ok from test.support import threading_helper +from test.support.bytecode_helper import (BytecodeTestCase, + instructions_with_positions) from opcode import opmap, opname COPY_FREE_VARS = opmap['COPY_FREE_VARS'] @@ -384,10 +386,8 @@ def test_co_positions_artificial_instructions(self): code = traceback.tb_frame.f_code artificial_instructions = [] - for instr, positions in zip( - dis.get_instructions(code, show_caches=True), - code.co_positions(), - strict=True + for instr, positions in instructions_with_positions( + dis.get_instructions(code), code.co_positions() ): # If any of the positions is None, then all have to # be None as well for the case above. There are still diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index df6e5e4b55f728..f681d125db7d7a 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -12,6 +12,7 @@ from test import support from test.support import (script_helper, requires_debug_ranges, requires_specialization, Py_C_RECURSION_LIMIT) +from test.support.bytecode_helper import instructions_with_positions from test.support.os_helper import FakePath class TestSpecifics(unittest.TestCase): @@ -1346,8 +1347,8 @@ def generic_visit(self, node): def assertOpcodeSourcePositionIs(self, code, opcode, line, end_line, column, end_column, occurrence=1): - for instr, position in zip( - dis.Bytecode(code, show_caches=True), code.co_positions(), strict=True + for instr, position in instructions_with_positions( + dis.Bytecode(code), code.co_positions() ): if instr.opname == opcode: occurrence -= 1 diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 0ea4dc4566a4a4..12e2c57e50b0ba 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -13,6 +13,7 @@ import opcode +CACHE = dis.opmap["CACHE"] def get_tb(): def _error(): @@ -1227,9 +1228,9 @@ def f(): else: # "copy" the code to un-quicken it: f.__code__ = f.__code__.replace() - for instruction in dis.get_instructions( + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( f, show_caches=True, adaptive=adaptive - ): + ), show_caches=True): if instruction.opname == "CACHE": yield instruction.argrepr @@ -1262,7 +1263,8 @@ def f(): # However, this might change in the future. So we explicitly try to find # a CACHE entry in the instructions. If we can't do that, fail the test - for inst in dis.get_instructions(f, show_caches=True): + for inst in _unroll_caches_as_Instructions( + dis.get_instructions(f, show_caches=True), show_caches=True): if inst.opname == "CACHE": op_offset = inst.offset - 2 cache_offset = inst.offset @@ -1775,8 +1777,8 @@ def simple(): pass class InstructionTestCase(BytecodeTestCase): def assertInstructionsEqual(self, instrs_1, instrs_2, /): - instrs_1 = [instr_1._replace(positions=None) for instr_1 in instrs_1] - instrs_2 = [instr_2._replace(positions=None) for instr_2 in instrs_2] + instrs_1 = [instr_1._replace(positions=None, cache_info=None) for instr_1 in instrs_1] + instrs_2 = [instr_2._replace(positions=None, cache_info=None) for instr_2 in instrs_2] self.assertEqual(instrs_1, instrs_2) class InstructionTests(InstructionTestCase): @@ -1890,9 +1892,9 @@ def roots(a, b, c): instruction.positions.col_offset, instruction.positions.end_col_offset, ) - for instruction in dis.get_instructions( + for instruction in _unroll_caches_as_Instructions(dis.get_instructions( code, adaptive=adaptive, show_caches=show_caches - ) + ), show_caches=show_caches) ] self.assertEqual(co_positions, dis_positions) @@ -2233,6 +2235,31 @@ def get_disassembly(self, tb): dis.distb(tb, file=output) return output.getvalue() +def _unroll_caches_as_Instructions(instrs, show_caches=False): + # Cache entries are no longer reported by dis as fake instructions, + # but some tests assume that do. We should rewrite the tests to assume + # the new API, but it will be clearer to keep the tests working as + # before and do that in a separate PR. + + for instr in instrs: + yield instr + if not show_caches: + continue + + offset = instr.offset + for name, size, data in (instr.cache_info or ()): + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + + yield Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2023-12-12-16-32-55.gh-issue-112962.ZZWXZn.rst b/Misc/NEWS.d/next/Library/2023-12-12-16-32-55.gh-issue-112962.ZZWXZn.rst new file mode 100644 index 00000000000000..b99e6bc90ae791 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-12-16-32-55.gh-issue-112962.ZZWXZn.rst @@ -0,0 +1,3 @@ +:mod:`dis` module functions add cache information to the +:class:`~dis.Instruction` instance rather than creating fake +:class:`~dis.Instruction` instances to represent the cache entries. From 2a3c37c273762d2dc40bfdae3899cb09b7c88d4a Mon Sep 17 00:00:00 2001 From: Sequew <88668176+KrySeyt@users.noreply.github.com> Date: Wed, 13 Dec 2023 15:04:17 +0300 Subject: [PATCH 226/442] [pprint]: Add docstring about `PrettyPrinter.underscore_numbers` parameter (#112963) Co-authored-by: Kirill Podoprigora --- Lib/pprint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/pprint.py b/Lib/pprint.py index 34ed12637e2288..9314701db340c7 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -128,6 +128,9 @@ def __init__(self, indent=1, width=80, depth=None, stream=None, *, sort_dicts If true, dict keys are sorted. + underscore_numbers + If true, digit groups are separated with underscores. + """ indent = int(indent) width = int(width) From 9263173280d7ce949911965efa5b745287c581b2 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 13 Dec 2023 12:31:41 +0000 Subject: [PATCH 227/442] Fix whitespace in generated code --- Python/executor_cases.c.h | 12 ++++++------ Tools/cases_generator/tier2_generator.py | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 9dda3c9a743258..14d9dd6e95e533 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1842,7 +1842,7 @@ PyObject *null = NULL; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); attr = Py_NewRef(descr); @@ -2546,7 +2546,7 @@ PyObject *self = NULL; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); /* Cached method object */ STAT_INC(LOAD_ATTR, hit); @@ -2566,7 +2566,7 @@ PyObject *self = NULL; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); assert(Py_TYPE(owner)->tp_dictoffset == 0); STAT_INC(LOAD_ATTR, hit); @@ -2585,7 +2585,7 @@ PyObject *attr; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); assert((oparg & 1) == 0); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); @@ -2601,7 +2601,7 @@ PyObject *attr; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); assert((oparg & 1) == 0); assert(Py_TYPE(owner)->tp_dictoffset == 0); STAT_INC(LOAD_ATTR, hit); @@ -2630,7 +2630,7 @@ PyObject *self = NULL; oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; - PyObject * descr = (PyObject *)CURRENT_OPERAND(); + PyObject *descr = (PyObject *)CURRENT_OPERAND(); assert(oparg & 1); STAT_INC(LOAD_ATTR, hit); assert(descr != NULL); diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index d2b503961aa7b9..a22fb6dd932503 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -123,10 +123,11 @@ def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: for cache in uop.caches: if cache.name != "unused": if cache.size == 4: - type = "PyObject *" + type = cast ="PyObject *" else: - type = f"uint{cache.size*16}_t" - out.emit(f"{type} {cache.name} = ({type})CURRENT_OPERAND();\n") + type = f"uint{cache.size*16}_t " + cast = f"uint{cache.size*16}_t" + out.emit(f"{type}{cache.name} = ({cast})CURRENT_OPERAND();\n") emit_tokens(out, uop, stack, None, TIER2_REPLACEMENT_FUNCTIONS) if uop.properties.stores_sp: for i, var in enumerate(uop.stack.outputs): From 498a096a51a215cd3084845131e619222b906b3e Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 13 Dec 2023 14:00:34 +0000 Subject: [PATCH 228/442] gh-112205: Support `@setter` annotation from AC (gh-112922) --------- Co-authored-by: Erlend E. Aasland Co-authored-by: Alex Waygood --- Lib/test/clinic.test.c | 34 ++++++++++++-- Lib/test/test_clinic.py | 52 +++++++++++++++++++++ Modules/_io/bufferedio.c | 18 ++++---- Modules/_io/clinic/bufferedio.c.h | 26 ++++++++--- Modules/_io/clinic/stringio.c.h | 26 ++++++++--- Modules/_io/clinic/textio.c.h | 46 ++++++++++++++++++- Modules/_io/stringio.c | 6 +-- Modules/_io/textio.c | 43 ++++++++---------- Tools/clinic/clinic.py | 75 +++++++++++++++++++++++++++---- 9 files changed, 262 insertions(+), 64 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index ee4a4228fd28be..a6a21664bb82a1 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4956,8 +4956,12 @@ Test_meth_coexist_impl(TestObj *self) Test.property [clinic start generated code]*/ -#define TEST_PROPERTY_GETTERDEF \ - {"property", (getter)Test_property_get, NULL, NULL}, +#if defined(TEST_PROPERTY_GETSETDEF) +# undef TEST_PROPERTY_GETSETDEF +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +#else +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, NULL, NULL}, +#endif static PyObject * Test_property_get_impl(TestObj *self); @@ -4970,8 +4974,32 @@ Test_property_get(TestObj *self, void *Py_UNUSED(context)) static PyObject * Test_property_get_impl(TestObj *self) -/*[clinic end generated code: output=892b6fb351ff85fd input=2d92b3449fbc7d2b]*/ +/*[clinic end generated code: output=af8140b692e0e2f1 input=2d92b3449fbc7d2b]*/ + +/*[clinic input] +@setter +Test.property +[clinic start generated code]*/ + +#if defined(TEST_PROPERTY_GETSETDEF) +# undef TEST_PROPERTY_GETSETDEF +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +#else +# define TEST_PROPERTY_GETSETDEF {"property", NULL, (setter)Test_property_set, NULL}, +#endif + +static int +Test_property_set_impl(TestObj *self, PyObject *value); + +static int +Test_property_set(TestObj *self, PyObject *value, void *Py_UNUSED(context)) +{ + return Test_property_set_impl(self, value); +} +static int +Test_property_set_impl(TestObj *self, PyObject *value) +/*[clinic end generated code: output=f3eba6487d7550e2 input=3bc3f46a23c83a88]*/ /*[clinic input] output push diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f53e9481083106..d3dbde88dd82a9 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2197,6 +2197,58 @@ class Foo "" "" expected_error = err_template.format(invalid_kind) self.expect_failure(block, expected_error, lineno=3) + def test_invalid_getset(self): + annotations = ["@getter", "@setter"] + for annotation in annotations: + with self.subTest(annotation=annotation): + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.property -> int + """ + expected_error = f"{annotation} method cannot define a return type" + self.expect_failure(block, expected_error, lineno=3) + + block = f""" + module foo + class Foo "" "" + {annotation} + Foo.property + obj: int + / + """ + expected_error = f"{annotation} method cannot define parameters" + self.expect_failure(block, expected_error) + + def test_duplicate_getset(self): + annotations = ["@getter", "@setter"] + for annotation in annotations: + with self.subTest(annotation=annotation): + block = f""" + module foo + class Foo "" "" + {annotation} + {annotation} + Foo.property -> int + """ + expected_error = f"Cannot apply {annotation} twice to the same function!" + self.expect_failure(block, expected_error, lineno=3) + + def test_getter_and_setter_disallowed_on_same_function(self): + dup_annotations = [("@getter", "@setter"), ("@setter", "@getter")] + for dup in dup_annotations: + with self.subTest(dup=dup): + block = f""" + module foo + class Foo "" "" + {dup[0]} + {dup[1]} + Foo.property -> int + """ + expected_error = "Cannot apply both @getter and @setter to the same function!" + self.expect_failure(block, expected_error, lineno=3) + def test_duplicate_coexist(self): err = "Called @coexist twice" block = """ diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 679626863c385c..f02207ace9f3d2 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -2526,9 +2526,9 @@ static PyMemberDef bufferedreader_members[] = { }; static PyGetSetDef bufferedreader_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; @@ -2586,9 +2586,9 @@ static PyMemberDef bufferedwriter_members[] = { }; static PyGetSetDef bufferedwriter_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; @@ -2704,9 +2704,9 @@ static PyMemberDef bufferedrandom_members[] = { }; static PyGetSetDef bufferedrandom_getset[] = { - _IO__BUFFERED_CLOSED_GETTERDEF - _IO__BUFFERED_NAME_GETTERDEF - _IO__BUFFERED_MODE_GETTERDEF + _IO__BUFFERED_CLOSED_GETSETDEF + _IO__BUFFERED_NAME_GETSETDEF + _IO__BUFFERED_MODE_GETSETDEF {NULL} }; diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index 69d28ad00c2ad5..ec46d5409a3d82 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -327,8 +327,12 @@ _io__Buffered_simple_flush(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } -#define _IO__BUFFERED_CLOSED_GETTERDEF \ - {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, +#if defined(_IO__BUFFERED_CLOSED_GETSETDEF) +# undef _IO__BUFFERED_CLOSED_GETSETDEF +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, (setter)_io__Buffered_closed_set, NULL}, +#else +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_closed_get_impl(buffered *self); @@ -460,8 +464,12 @@ _io__Buffered_writable(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } -#define _IO__BUFFERED_NAME_GETTERDEF \ - {"name", (getter)_io__Buffered_name_get, NULL, NULL}, +#if defined(_IO__BUFFERED_NAME_GETSETDEF) +# undef _IO__BUFFERED_NAME_GETSETDEF +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, (setter)_io__Buffered_name_set, NULL}, +#else +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_name_get_impl(buffered *self); @@ -478,8 +486,12 @@ _io__Buffered_name_get(buffered *self, void *Py_UNUSED(context)) return return_value; } -#define _IO__BUFFERED_MODE_GETTERDEF \ - {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, +#if defined(_IO__BUFFERED_MODE_GETSETDEF) +# undef _IO__BUFFERED_MODE_GETSETDEF +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, (setter)_io__Buffered_mode_set, NULL}, +#else +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, +#endif static PyObject * _io__Buffered_mode_get_impl(buffered *self); @@ -1218,4 +1230,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=f21ed03255032b43 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0999c33f666dc692 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h index ed505ae67589a8..fc2962d1c9c9a7 100644 --- a/Modules/_io/clinic/stringio.c.h +++ b/Modules/_io/clinic/stringio.c.h @@ -475,8 +475,12 @@ _io_StringIO___setstate__(stringio *self, PyObject *state) return return_value; } -#define _IO_STRINGIO_CLOSED_GETTERDEF \ - {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, +#if defined(_IO_STRINGIO_CLOSED_GETSETDEF) +# undef _IO_STRINGIO_CLOSED_GETSETDEF +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, (setter)_io_StringIO_closed_set, NULL}, +#else +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_closed_get_impl(stringio *self); @@ -493,8 +497,12 @@ _io_StringIO_closed_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -#define _IO_STRINGIO_LINE_BUFFERING_GETTERDEF \ - {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, +#if defined(_IO_STRINGIO_LINE_BUFFERING_GETSETDEF) +# undef _IO_STRINGIO_LINE_BUFFERING_GETSETDEF +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, (setter)_io_StringIO_line_buffering_set, NULL}, +#else +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_line_buffering_get_impl(stringio *self); @@ -511,8 +519,12 @@ _io_StringIO_line_buffering_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -#define _IO_STRINGIO_NEWLINES_GETTERDEF \ - {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, +#if defined(_IO_STRINGIO_NEWLINES_GETSETDEF) +# undef _IO_STRINGIO_NEWLINES_GETSETDEF +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, (setter)_io_StringIO_newlines_set, NULL}, +#else +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, +#endif static PyObject * _io_StringIO_newlines_get_impl(stringio *self); @@ -528,4 +540,4 @@ _io_StringIO_newlines_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -/*[clinic end generated code: output=3a92e8b6c322f61b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=27726751d98ab617 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index 675e0ed2eab75e..a492f340c74c0d 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1047,4 +1047,48 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored)) return return_value; } -/*[clinic end generated code: output=8781a91be6d99e2c input=a9049054013a1b77]*/ + +#if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) +# undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper__CHUNK_SIZE_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper__CHUNK_SIZE_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper__CHUNK_SIZE_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) +# undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", NULL, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +#endif + +static int +_io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value); + +static int +_io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper__CHUNK_SIZE_set_impl(self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} +/*[clinic end generated code: output=b312f2d2e2221580 input=a9049054013a1b77]*/ diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 74dcee23730306..06bc2679e8e227 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -1037,15 +1037,15 @@ static struct PyMethodDef stringio_methods[] = { }; static PyGetSetDef stringio_getset[] = { - _IO_STRINGIO_CLOSED_GETTERDEF - _IO_STRINGIO_NEWLINES_GETTERDEF + _IO_STRINGIO_CLOSED_GETSETDEF + _IO_STRINGIO_NEWLINES_GETSETDEF /* (following comments straight off of the original Python wrapper:) XXX Cruft to support the TextIOWrapper API. This would only be meaningful if StringIO supported the buffer attribute. Hopefully, a better solution, than adding these pseudo-attributes, will be found. */ - _IO_STRINGIO_LINE_BUFFERING_GETTERDEF + _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {NULL} }; diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 545f467b7f0257..c76d92cdd38b9a 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3238,33 +3238,37 @@ textiowrapper_errors_get(textio *self, void *context) return result; } +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper._CHUNK_SIZE +[clinic start generated code]*/ + static PyObject * -textiowrapper_chunk_size_get_impl(textio *self, void *context) +_io_TextIOWrapper__CHUNK_SIZE_get_impl(textio *self) +/*[clinic end generated code: output=039925cd2df375bc input=e9715b0e06ff0fa6]*/ { CHECK_ATTACHED(self); return PyLong_FromSsize_t(self->chunk_size); } -static PyObject * -textiowrapper_chunk_size_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_chunk_size_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@setter +_io.TextIOWrapper._CHUNK_SIZE +[clinic start generated code]*/ static int -textiowrapper_chunk_size_set_impl(textio *self, PyObject *arg, void *context) +_io_TextIOWrapper__CHUNK_SIZE_set_impl(textio *self, PyObject *value) +/*[clinic end generated code: output=edb86d2db660a5ab input=32fc99861db02a0a]*/ { Py_ssize_t n; CHECK_ATTACHED_INT(self); - if (arg == NULL) { + if (value == NULL) { PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); return -1; } - n = PyNumber_AsSsize_t(arg, PyExc_ValueError); + n = PyNumber_AsSsize_t(value, PyExc_ValueError); if (n == -1 && PyErr_Occurred()) return -1; if (n <= 0) { @@ -3276,16 +3280,6 @@ textiowrapper_chunk_size_set_impl(textio *self, PyObject *arg, void *context) return 0; } -static int -textiowrapper_chunk_size_set(textio *self, PyObject *arg, void *context) -{ - int result = 0; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_chunk_size_set_impl(self, arg, context); - Py_END_CRITICAL_SECTION(); - return result; -} - static PyMethodDef incrementalnewlinedecoder_methods[] = { _IO_INCREMENTALNEWLINEDECODER_DECODE_METHODDEF _IO_INCREMENTALNEWLINEDECODER_GETSTATE_METHODDEF @@ -3361,8 +3355,7 @@ static PyGetSetDef textiowrapper_getset[] = { */ {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, {"errors", (getter)textiowrapper_errors_get, NULL, NULL}, - {"_CHUNK_SIZE", (getter)textiowrapper_chunk_size_get, - (setter)textiowrapper_chunk_size_set, NULL}, + _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {NULL} }; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 816ce0e6efed61..5ec088765f3e01 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -850,6 +850,10 @@ class CLanguage(Language): static PyObject * {c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) """) + PARSER_PROTOTYPE_SETTER: Final[str] = normalize_snippet(""" + static int + {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context)) + """) METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" static PyObject * {c_basename}({impl_parameters}) @@ -870,8 +874,20 @@ class CLanguage(Language): {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, """) GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" - #define {getter_name} \ - {{"{name}", (getter){c_basename}, NULL, NULL}}, + #if defined({getset_name}_GETSETDEF) + # undef {getset_name}_GETSETDEF + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + #else + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, NULL}}, + #endif + """) + SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #if defined({getset_name}_GETSETDEF) + # undef {getset_name}_GETSETDEF + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + #else + # define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}}, + #endif """) METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" #ifndef {methoddef_name} @@ -1172,6 +1188,10 @@ def output_templates( elif f.kind is GETTER: methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE docstring_prototype = docstring_definition = '' + elif f.kind is SETTER: + return_value_declaration = "int {return_value};" + methoddef_define = self.SETTERDEF_PROTOTYPE_DEFINE + docstring_prototype = docstring_prototype = docstring_definition = '' else: docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR @@ -1226,12 +1246,19 @@ def parser_body( limited_capi = False parsearg: str | None + if f.kind in {GETTER, SETTER} and parameters: + fail(f"@{f.kind.name.lower()} method cannot define parameters") + if not parameters: parser_code: list[str] | None if f.kind is GETTER: flags = "" # This should end up unused parser_prototype = self.PARSER_PROTOTYPE_GETTER parser_code = [] + elif f.kind is SETTER: + flags = "" + parser_prototype = self.PARSER_PROTOTYPE_SETTER + parser_code = [] elif not requires_defining_class: # no parameters, METH_NOARGS flags = "METH_NOARGS" @@ -1944,9 +1971,16 @@ def render_function( full_name = f.full_name template_dict = {'full_name': full_name} template_dict['name'] = f.displayname - if f.kind is GETTER: - template_dict['getter_name'] = f.c_basename.upper() + "_GETTERDEF" - template_dict['c_basename'] = f.c_basename + "_get" + if f.kind in {GETTER, SETTER}: + template_dict['getset_name'] = f.c_basename.upper() + template_dict['getset_basename'] = f.c_basename + if f.kind is GETTER: + template_dict['c_basename'] = f.c_basename + "_get" + elif f.kind is SETTER: + template_dict['c_basename'] = f.c_basename + "_set" + # Implicitly add the setter value parameter. + data.impl_parameters.append("PyObject *value") + data.impl_arguments.append("value") else: template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" template_dict['c_basename'] = f.c_basename @@ -1959,7 +1993,11 @@ def render_function( converter.set_template_dict(template_dict) f.return_converter.render(f, data) - template_dict['impl_return_type'] = f.return_converter.type + if f.kind is SETTER: + # All setters return an int. + template_dict['impl_return_type'] = 'int' + else: + template_dict['impl_return_type'] = f.return_converter.type template_dict['declarations'] = format_escape("\n".join(data.declarations)) template_dict['initializers'] = "\n\n".join(data.initializers) @@ -2954,6 +2992,7 @@ class FunctionKind(enum.Enum): METHOD_INIT = enum.auto() METHOD_NEW = enum.auto() GETTER = enum.auto() + SETTER = enum.auto() @functools.cached_property def new_or_init(self) -> bool: @@ -2970,6 +3009,7 @@ def __repr__(self) -> str: METHOD_INIT: Final = FunctionKind.METHOD_INIT METHOD_NEW: Final = FunctionKind.METHOD_NEW GETTER: Final = FunctionKind.GETTER +SETTER: Final = FunctionKind.SETTER ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] @@ -3056,7 +3096,7 @@ def methoddef_flags(self) -> str | None: case FunctionKind.STATIC_METHOD: flags.append('METH_STATIC') case _ as kind: - acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER} + acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER} assert kind in acceptable_kinds, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') @@ -4702,7 +4742,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st def correct_name_for_self( f: Function ) -> tuple[str, str]: - if f.kind in {CALLABLE, METHOD_INIT, GETTER}: + if f.kind in {CALLABLE, METHOD_INIT, GETTER, SETTER}: if f.cls: return "PyObject *", "self" return "PyObject *", "module" @@ -5335,7 +5375,22 @@ def at_critical_section(self, *args: str) -> None: self.critical_section = True def at_getter(self) -> None: - self.kind = GETTER + match self.kind: + case FunctionKind.GETTER: + fail("Cannot apply @getter twice to the same function!") + case FunctionKind.SETTER: + fail("Cannot apply both @getter and @setter to the same function!") + case _: + self.kind = FunctionKind.GETTER + + def at_setter(self) -> None: + match self.kind: + case FunctionKind.SETTER: + fail("Cannot apply @setter twice to the same function!") + case FunctionKind.GETTER: + fail("Cannot apply both @getter and @setter to the same function!") + case _: + self.kind = FunctionKind.SETTER def at_staticmethod(self) -> None: if self.kind is not CALLABLE: @@ -5536,6 +5591,8 @@ def state_modulename_name(self, line: str) -> None: return_converter = None if returns: + if self.kind in {GETTER, SETTER}: + fail(f"@{self.kind.name.lower()} method cannot define a return type") ast_input = f"def x() -> {returns}: pass" try: module_node = ast.parse(ast_input) From 79dad03747fe17634136209f1bcaf346a8c10617 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 13 Dec 2023 15:38:45 +0000 Subject: [PATCH 229/442] gh-111650: Ensure pyconfig.h includes Py_GIL_DISABLED on Windows (GH-112778) --- .github/workflows/reusable-windows.yml | 6 ++-- Lib/sysconfig/__init__.py | 11 ++++++- Lib/test/test_sysconfig.py | 8 +++-- Lib/test/test_venv.py | 18 ++++++----- ...-12-05-22-56-30.gh-issue-111650.xlWmvM.rst | 3 ++ Modules/_ctypes/_ctypes_test.c | 2 -- Modules/_scproxy.c | 2 -- Modules/_stat.c | 2 -- Modules/_testcapi/heaptype_relative.c | 2 -- Modules/_testcapi/vectorcall_limited.c | 2 -- Modules/_testclinic_limited.c | 2 -- Modules/_testimportmultiple.c | 2 -- Modules/_uuidmodule.c | 2 -- Modules/errnomodule.c | 2 -- Modules/resource.c | 2 -- Modules/xxlimited.c | 2 -- Modules/xxlimited_35.c | 2 -- PC/layout/main.py | 8 +++-- PC/{pyconfig.h => pyconfig.h.in} | 3 ++ PC/winsound.c | 2 ++ PCbuild/_freeze_module.vcxproj | 30 ++++++++++++++++++ PCbuild/pyproject.props | 6 ++-- PCbuild/pythoncore.vcxproj | 31 ++++++++++++++++++- Tools/msi/dev/dev_files.wxs | 2 +- Tools/peg_generator/pegen/build.py | 4 +++ 25 files changed, 112 insertions(+), 44 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-05-22-56-30.gh-issue-111650.xlWmvM.rst rename PC/{pyconfig.h => pyconfig.h.in} (99%) diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 29e0a7e35b5450..47a3c10d2ca4c1 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build CPython - run: .\PCbuild\build.bat -e -d -p Win32 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p Win32 ${{ inputs.free-threaded && '--disable-gil' || '' }} - name: Display build info run: .\python.bat -m test.pythoninfo - name: Tests @@ -33,7 +33,7 @@ jobs: - name: Register MSVC problem matcher run: echo "::add-matcher::.github/problem-matchers/msvc.json" - name: Build CPython - run: .\PCbuild\build.bat -e -d -p x64 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p x64 ${{ inputs.free-threaded && '--disable-gil' || '' }} - name: Display build info run: .\python.bat -m test.pythoninfo - name: Tests @@ -50,4 +50,4 @@ jobs: - name: Register MSVC problem matcher run: echo "::add-matcher::.github/problem-matchers/msvc.json" - name: Build CPython - run: .\PCbuild\build.bat -e -d -p arm64 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p arm64 ${{ inputs.free-threaded && '--disable-gil' || '' }} diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 2a7fa45be079de..c60c9f3440615b 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -404,7 +404,16 @@ def get_config_h_filename(): """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": - inc_dir = os.path.join(_PROJECT_BASE, "PC") + # This ought to be as simple as dirname(sys._base_executable), but + # if a venv uses symlinks to a build in the source tree, then this + # fails. So instead we guess the subdirectory name from sys.winver + if sys.winver.endswith('-32'): + arch = 'win32' + elif sys.winver.endswith('-arm64'): + arch = 'arm64' + else: + arch = 'amd64' + inc_dir = os.path.join(_PROJECT_BASE, 'PCbuild', arch) else: inc_dir = _PROJECT_BASE else: diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index 2a6813f00bccc6..a19c04b1b2cde5 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -472,11 +472,15 @@ def test_srcdir(self): # should be a full source checkout. Python_h = os.path.join(srcdir, 'Include', 'Python.h') self.assertTrue(os.path.exists(Python_h), Python_h) - # /PC/pyconfig.h always exists even if unused on POSIX. - pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h') + # /PC/pyconfig.h.in always exists even if unused + pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h.in') self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in') self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in) + if os.name == 'nt': + # /pyconfig.h exists on Windows in a build tree + pyconfig_h = os.path.join(sys.executable, '..', 'pyconfig.h') + self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) elif os.name == 'posix': makefile_dir = os.path.dirname(sysconfig.get_makefile_filename()) # Issue #19340: srcdir has been realpath'ed already diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 890672c5d27eec..617d14dcb9c5fe 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -46,14 +46,18 @@ def check_output(cmd, encoding=None): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding=encoding) + stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode: if verbose and err: - print(err.decode('utf-8', 'backslashreplace')) + print(err.decode(encoding or 'utf-8', 'backslashreplace')) raise subprocess.CalledProcessError( p.returncode, cmd, out, err) + if encoding: + return ( + out.decode(encoding, 'backslashreplace'), + err.decode(encoding, 'backslashreplace'), + ) return out, err class BaseTest(unittest.TestCase): @@ -281,8 +285,8 @@ def test_sysconfig(self): ('get_config_h_filename()', sysconfig.get_config_h_filename())): with self.subTest(call): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call - out, err = check_output(cmd) - self.assertEqual(out.strip(), expected.encode(), err) + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) @requireVenvCreate @unittest.skipUnless(can_symlink(), 'Needs symlinks') @@ -303,8 +307,8 @@ def test_sysconfig_symlinks(self): ('get_config_h_filename()', sysconfig.get_config_h_filename())): with self.subTest(call): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call - out, err = check_output(cmd) - self.assertEqual(out.strip(), expected.encode(), err) + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) if sys.platform == 'win32': ENV_SUBDIRS = ( diff --git a/Misc/NEWS.d/next/Windows/2023-12-05-22-56-30.gh-issue-111650.xlWmvM.rst b/Misc/NEWS.d/next/Windows/2023-12-05-22-56-30.gh-issue-111650.xlWmvM.rst new file mode 100644 index 00000000000000..5a3493356e30be --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-05-22-56-30.gh-issue-111650.xlWmvM.rst @@ -0,0 +1,3 @@ +Ensures the ``Py_GIL_DISABLED`` preprocessor variable is defined in +:file:`pyconfig.h` so that extension modules written in C are able to use +it. diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index fc9fc131f6249a..2681b9c58ecb9d 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -1,6 +1,4 @@ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED diff --git a/Modules/_scproxy.c b/Modules/_scproxy.c index 7920d2c2b8739d..fe82e918677f9a 100644 --- a/Modules/_scproxy.c +++ b/Modules/_scproxy.c @@ -3,9 +3,7 @@ * using the SystemConfiguration framework. */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED diff --git a/Modules/_stat.c b/Modules/_stat.c index 1ef1e97f4b7dca..80f8a92668976b 100644 --- a/Modules/_stat.c +++ b/Modules/_stat.c @@ -11,9 +11,7 @@ * */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.13 for PyModule_Add() on Windows diff --git a/Modules/_testcapi/heaptype_relative.c b/Modules/_testcapi/heaptype_relative.c index 52286f05f7154c..52bda75736b316 100644 --- a/Modules/_testcapi/heaptype_relative.c +++ b/Modules/_testcapi/heaptype_relative.c @@ -1,6 +1,4 @@ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED #define Py_LIMITED_API 0x030c0000 // 3.12 diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testcapi/vectorcall_limited.c index 0a650f1b351d2d..d7b8d33b7f7162 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testcapi/vectorcall_limited.c @@ -1,8 +1,6 @@ /* Test Vectorcall in the limited API */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED #define Py_LIMITED_API 0x030c0000 // 3.12 diff --git a/Modules/_testclinic_limited.c b/Modules/_testclinic_limited.c index 61bc84134458da..ef595be0b626db 100644 --- a/Modules/_testclinic_limited.c +++ b/Modules/_testclinic_limited.c @@ -4,9 +4,7 @@ #undef Py_BUILD_CORE_MODULE #undef Py_BUILD_CORE_BUILTIN -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // For now, only limited C API 3.13 is supported diff --git a/Modules/_testimportmultiple.c b/Modules/_testimportmultiple.c index 245e81b2dce7f8..7e6556ad400cde 100644 --- a/Modules/_testimportmultiple.c +++ b/Modules/_testimportmultiple.c @@ -4,9 +4,7 @@ * foo, bar), only the first one is called the same as the compiled file. */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED #define Py_LIMITED_API 0x03020000 diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index d8b211c632eef1..4b6852c0d0ec73 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -3,9 +3,7 @@ * DCE compatible Universally Unique Identifier library. */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED diff --git a/Modules/errnomodule.c b/Modules/errnomodule.c index 8287edbfb47f6c..1100e9f6094352 100644 --- a/Modules/errnomodule.c +++ b/Modules/errnomodule.c @@ -1,8 +1,6 @@ /* Errno module */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED diff --git a/Modules/resource.c b/Modules/resource.c index a4b8f648c329e3..19020b8cc1b6db 100644 --- a/Modules/resource.c +++ b/Modules/resource.c @@ -1,6 +1,4 @@ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.13 for PySys_Audit() diff --git a/Modules/xxlimited.c b/Modules/xxlimited.c index 19f61216255cfa..0bb5e12d7c3dd9 100644 --- a/Modules/xxlimited.c +++ b/Modules/xxlimited.c @@ -62,9 +62,7 @@ pass */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED diff --git a/Modules/xxlimited_35.c b/Modules/xxlimited_35.c index 867820a6cb93fa..754a368f77e940 100644 --- a/Modules/xxlimited_35.c +++ b/Modules/xxlimited_35.c @@ -5,9 +5,7 @@ * See the xxlimited module for an extension module template. */ -#ifndef _MSC_VER #include "pyconfig.h" // Py_GIL_DISABLED -#endif #ifndef Py_GIL_DISABLED #define Py_LIMITED_API 0x03050000 diff --git a/PC/layout/main.py b/PC/layout/main.py index cb2e4878da26b1..accfd51dd978fb 100644 --- a/PC/layout/main.py +++ b/PC/layout/main.py @@ -73,7 +73,10 @@ def copy_if_modified(src, dest): ) if do_copy: - shutil.copy2(src, dest) + try: + shutil.copy2(src, dest) + except FileNotFoundError: + raise FileNotFoundError(src) from None def get_lib_layout(ns): @@ -208,8 +211,7 @@ def _c(d): for dest, src in rglob(ns.source / "Include", "**/*.h"): yield "include/{}".format(dest), src - src = ns.source / "PC" / "pyconfig.h" - yield "include/pyconfig.h", src + yield "include/pyconfig.h", ns.build / "pyconfig.h" for dest, src in get_tcltk_lib(ns): yield dest, src diff --git a/PC/pyconfig.h b/PC/pyconfig.h.in similarity index 99% rename from PC/pyconfig.h rename to PC/pyconfig.h.in index e6b368caffe280..d8f0a6be69c21a 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h.in @@ -739,4 +739,7 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */ /* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */ #define HAVE_X509_VERIFY_PARAM_SET1_HOST 1 +/* Define if you want to disable the GIL */ +#undef Py_GIL_DISABLED + #endif /* !Py_CONFIG_H */ diff --git a/PC/winsound.c b/PC/winsound.c index b0e416cfec4699..7e4ebd90f50c2e 100644 --- a/PC/winsound.c +++ b/PC/winsound.c @@ -35,6 +35,8 @@ winsound.PlaySound(None, 0) */ +#include "pyconfig.h" // Py_GIL_DISABLED + #ifndef Py_GIL_DISABLED // Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #define Py_LIMITED_API 0x030c0000 diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index a1c37e183f21c7..f8c5fafa561efa 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -89,6 +89,7 @@ Py_NO_ENABLE_SHARED;Py_BUILD_CORE;_CONSOLE;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) Disabled false @@ -257,6 +258,9 @@ + + + @@ -414,6 +418,32 @@ + + + + + + + + + + + @(PyConfigH->'%(FullPath)', ';') + $([System.IO.File]::ReadAllText($(PyConfigH))) + $([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h')) + + + $(PyConfigHText.Replace('#undef Py_GIL_DISABLED', '#define Py_GIL_DISABLED 1')) + + + + + diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props index 0acc7045c39a26..68c0550f7603b7 100644 --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -10,6 +10,8 @@ $(MSBuildThisFileDirectory)obj\ $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\$(ProjectName)\ $(IntDir.Replace(`\\`, `\`)) + + $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\pythoncore\ $(ProjectName) $(TargetName)$(PyDebugExt) false @@ -38,9 +40,8 @@ - $(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)Include\internal\mimalloc;$(PySourcePath)PC;$(IntDir);%(AdditionalIncludeDirectories) + $(PySourcePath)Include;$(PySourcePath)Include\internal;$(PySourcePath)Include\internal\mimalloc;$(GeneratedPyConfigDir);$(PySourcePath)PC;%(AdditionalIncludeDirectories) WIN32;$(_Py3NamePreprocessorDefinition);$(_PlatformPreprocessorDefinition)$(_DebugPreprocessorDefinition)$(_PydPreprocessorDefinition)%(PreprocessorDefinitions) - Py_GIL_DISABLED=1;%(PreprocessorDefinitions) _Py_USING_PGO=1;%(PreprocessorDefinitions) MaxSpeed @@ -60,6 +61,7 @@ -Wno-deprecated-non-prototype -Wno-unused-label -Wno-pointer-sign -Wno-incompatible-pointer-types-discards-qualifiers -Wno-unused-function %(AdditionalOptions) -flto %(AdditionalOptions) -d2pattern-opt-disable:-932189325 %(AdditionalOptions) + /sourceDependencies "$(IntDir.Trim(`\`))" %(AdditionalOptions) OnlyExplicitInline diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 778fc834c0db9c..90aa8cf28f8c5d 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -378,7 +378,7 @@ - + @@ -646,6 +646,35 @@ + + + + + + + + + + @(PyConfigH->'%(FullPath)', ';') + $([System.IO.File]::ReadAllText($(PyConfigH))) + $([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h')) + + + $(PyConfigHText.Replace('#undef Py_GIL_DISABLED', '#define Py_GIL_DISABLED 1')) + + + + + + + + + + + git diff --git a/Tools/msi/dev/dev_files.wxs b/Tools/msi/dev/dev_files.wxs index 21f9c848cc6be5..4357dc86d9d356 100644 --- a/Tools/msi/dev/dev_files.wxs +++ b/Tools/msi/dev/dev_files.wxs @@ -3,7 +3,7 @@ - + diff --git a/Tools/peg_generator/pegen/build.py b/Tools/peg_generator/pegen/build.py index 30bfb31471c7b2..7df39a3b0ae48e 100644 --- a/Tools/peg_generator/pegen/build.py +++ b/Tools/peg_generator/pegen/build.py @@ -143,6 +143,10 @@ def compile_c_extension( str(MOD_DIR.parent.parent.parent / "Parser" / "lexer"), str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer"), ] + if sys.platform == "win32": + # HACK: The location of pyconfig.h has moved within our build, and + # setuptools hasn't updated for it yet. So add the path manually for now + include_dirs.append(pathlib.Path(sysconfig.get_config_h_filename()).parent) extension = Extension( extension_name, sources=[generated_source_path], From 6644ca45cde9ca1b80513a90dacccfeea2d98620 Mon Sep 17 00:00:00 2001 From: Diego Russo Date: Wed, 13 Dec 2023 16:08:15 +0000 Subject: [PATCH 230/442] gh-110190: Fix ctypes structs with array on PPCLE64 (GH-112959) Fix the same issue of PR #112604 on PPC64LE platform Refactor tests to make easier to add more platfroms if needed. --- Lib/test/test_ctypes/test_structures.py | 232 +++++++----------- ...-12-11-14-12-46.gh-issue-110190.e0iEUa.rst | 1 + Modules/_ctypes/_ctypes_test.c | 138 ++++++++--- Modules/_ctypes/stgdict.c | 45 ++-- 4 files changed, 209 insertions(+), 207 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-11-14-12-46.gh-issue-110190.e0iEUa.rst diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py index 57ae9240b3c165..21039f04947507 100644 --- a/Lib/test/test_ctypes/test_structures.py +++ b/Lib/test/test_ctypes/test_structures.py @@ -9,6 +9,7 @@ c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double) from struct import calcsize +from collections import namedtuple from test import support @@ -474,36 +475,53 @@ class X(Structure): def test_array_in_struct(self): # See bpo-22273 + # Load the shared library + dll = CDLL(_ctypes_test.__file__) + # These should mirror the structures in Modules/_ctypes/_ctypes_test.c class Test2(Structure): _fields_ = [ ('data', c_ubyte * 16), ] - class Test3(Structure): + class Test3AParent(Structure): + _fields_ = [ + ('data', c_float * 2), + ] + + class Test3A(Test3AParent): + _fields_ = [ + ('more_data', c_float * 2), + ] + + class Test3B(Structure): _fields_ = [ ('data', c_double * 2), ] - class Test3A(Structure): + class Test3C(Structure): _fields_ = [ - ('data', c_float * 2), + ("data", c_double * 4) ] - class Test3B(Test3A): + class Test3D(Structure): _fields_ = [ - ('more_data', c_float * 2), + ("data", c_double * 8) + ] + + class Test3E(Structure): + _fields_ = [ + ("data", c_double * 9) ] - # Load the shared library - dll = CDLL(_ctypes_test.__file__) + # Tests for struct Test2 s = Test2() expected = 0 for i in range(16): s.data[i] = i expected += i - func = dll._testfunc_array_in_struct1 + func = dll._testfunc_array_in_struct2 func.restype = c_int func.argtypes = (Test2,) result = func(s) @@ -512,29 +530,16 @@ class Test3B(Test3A): for i in range(16): self.assertEqual(s.data[i], i) - s = Test3() - s.data[0] = 3.14159 - s.data[1] = 2.71828 - expected = 3.14159 + 2.71828 - func = dll._testfunc_array_in_struct2 - func.restype = c_double - func.argtypes = (Test3,) - result = func(s) - self.assertEqual(result, expected) - # check the passed-in struct hasn't changed - self.assertEqual(s.data[0], 3.14159) - self.assertEqual(s.data[1], 2.71828) - - s = Test3B() + # Tests for struct Test3A + s = Test3A() s.data[0] = 3.14159 s.data[1] = 2.71828 s.more_data[0] = -3.0 s.more_data[1] = -2.0 - - expected = 3.14159 + 2.71828 - 5.0 - func = dll._testfunc_array_in_struct2a + expected = 3.14159 + 2.71828 - 3.0 - 2.0 + func = dll._testfunc_array_in_struct3A func.restype = c_double - func.argtypes = (Test3B,) + func.argtypes = (Test3A,) result = func(s) self.assertAlmostEqual(result, expected, places=6) # check the passed-in struct hasn't changed @@ -543,129 +548,60 @@ class Test3B(Test3A): self.assertAlmostEqual(s.more_data[0], -3.0, places=6) self.assertAlmostEqual(s.more_data[1], -2.0, places=6) - @unittest.skipIf( - 'ppc64le' in platform.uname().machine, - "gh-110190: currently fails on ppc64le", - ) - def test_array_in_struct_registers(self): - dll = CDLL(_ctypes_test.__file__) - - class Test3C1(Structure): - _fields_ = [ - ("data", c_double * 4) - ] - - class DataType4(Array): - _type_ = c_double - _length_ = 4 - - class Test3C2(Structure): - _fields_ = [ - ("data", DataType4) - ] - - class Test3C3(Structure): - _fields_ = [ - ("x", c_double), - ("y", c_double), - ("z", c_double), - ("t", c_double) - ] - - class Test3D1(Structure): - _fields_ = [ - ("data", c_double * 5) - ] - - class DataType5(Array): - _type_ = c_double - _length_ = 5 - - class Test3D2(Structure): - _fields_ = [ - ("data", DataType5) - ] - - class Test3D3(Structure): - _fields_ = [ - ("x", c_double), - ("y", c_double), - ("z", c_double), - ("t", c_double), - ("u", c_double) - ] - - # Tests for struct Test3C - expected = (1.0, 2.0, 3.0, 4.0) - func = dll._testfunc_array_in_struct_set_defaults_3C - func.restype = Test3C1 - result = func() - # check the default values have been set properly - self.assertEqual( - (result.data[0], - result.data[1], - result.data[2], - result.data[3]), - expected + # Test3B, Test3C, Test3D, Test3E have the same logic with different + # sizes hence putting them in a loop. + StructCtype = namedtuple( + "StructCtype", + ["cls", "cfunc1", "cfunc2", "items"] ) - - func = dll._testfunc_array_in_struct_set_defaults_3C - func.restype = Test3C2 - result = func() - # check the default values have been set properly - self.assertEqual( - (result.data[0], - result.data[1], - result.data[2], - result.data[3]), - expected - ) - - func = dll._testfunc_array_in_struct_set_defaults_3C - func.restype = Test3C3 - result = func() - # check the default values have been set properly - self.assertEqual((result.x, result.y, result.z, result.t), expected) - - # Tests for struct Test3D - expected = (1.0, 2.0, 3.0, 4.0, 5.0) - func = dll._testfunc_array_in_struct_set_defaults_3D - func.restype = Test3D1 - result = func() - # check the default values have been set properly - self.assertEqual( - (result.data[0], - result.data[1], - result.data[2], - result.data[3], - result.data[4]), - expected - ) - - func = dll._testfunc_array_in_struct_set_defaults_3D - func.restype = Test3D2 - result = func() - # check the default values have been set properly - self.assertEqual( - (result.data[0], - result.data[1], - result.data[2], - result.data[3], - result.data[4]), - expected - ) - - func = dll._testfunc_array_in_struct_set_defaults_3D - func.restype = Test3D3 - result = func() - # check the default values have been set properly - self.assertEqual( - (result.x, - result.y, - result.z, - result.t, - result.u), - expected) + structs_to_test = [ + StructCtype( + Test3B, + dll._testfunc_array_in_struct3B, + dll._testfunc_array_in_struct3B_set_defaults, + 2), + StructCtype( + Test3C, + dll._testfunc_array_in_struct3C, + dll._testfunc_array_in_struct3C_set_defaults, + 4), + StructCtype( + Test3D, + dll._testfunc_array_in_struct3D, + dll._testfunc_array_in_struct3D_set_defaults, + 8), + StructCtype( + Test3E, + dll._testfunc_array_in_struct3E, + dll._testfunc_array_in_struct3E_set_defaults, + 9), + ] + + for sut in structs_to_test: + s = sut.cls() + + # Test for cfunc1 + expected = 0 + for i in range(sut.items): + float_i = float(i) + s.data[i] = float_i + expected += float_i + func = sut.cfunc1 + func.restype = c_double + func.argtypes = (sut.cls,) + result = func(s) + self.assertEqual(result, expected) + # check the passed-in struct hasn't changed + for i in range(sut.items): + self.assertEqual(s.data[i], float(i)) + + # Test for cfunc2 + func = sut.cfunc2 + func.restype = sut.cls + result = func() + # check if the default values have been set correctly + for i in range(sut.items): + self.assertEqual(result.data[i], float(i+1)) def test_38368(self): class U(Union): diff --git a/Misc/NEWS.d/next/Library/2023-12-11-14-12-46.gh-issue-110190.e0iEUa.rst b/Misc/NEWS.d/next/Library/2023-12-11-14-12-46.gh-issue-110190.e0iEUa.rst new file mode 100644 index 00000000000000..3bfed1e0f1dc91 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-11-14-12-46.gh-issue-110190.e0iEUa.rst @@ -0,0 +1 @@ +Fix ctypes structs with array on PPC64LE platform by setting ``MAX_STRUCT_SIZE`` to 64 in stgdict. Patch by Diego Russo. diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index 2681b9c58ecb9d..ecc60417790417 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -96,11 +96,10 @@ typedef struct { } Test2; EXPORT(int) -_testfunc_array_in_struct1(Test2 in) +_testfunc_array_in_struct2(Test2 in) { int result = 0; - - for (unsigned i = 0; i < 16; i++) + for (unsigned i = 0; i < sizeof(in.data)/sizeof(in.data[0]); i++) result += in.data[i]; /* As the structure is passed by value, changes to it shouldn't be * reflected in the caller. @@ -109,22 +108,25 @@ _testfunc_array_in_struct1(Test2 in) return result; } -typedef struct { - double data[2]; -} Test3; - +/* + * Test3A struct test the MAX_STRUCT_SIZE 16 with single precision floats. + * These structs should be passed via registers on all platforms and they are + * used for within bounds tests. + */ typedef struct { float data[2]; float more_data[2]; -} Test3B; +} Test3A; EXPORT(double) -_testfunc_array_in_struct2(Test3 in) +_testfunc_array_in_struct3A(Test3A in) { double result = 0; - for (unsigned i = 0; i < 2; i++) + for (unsigned i = 0; i < sizeof(in.data)/sizeof(in.data[0]); i++) result += in.data[i]; + for (unsigned i = 0; i < sizeof(in.more_data)/sizeof(in.more_data[0]); i++) + result += in.more_data[i]; /* As the structure is passed by value, changes to it shouldn't be * reflected in the caller. */ @@ -132,56 +134,116 @@ _testfunc_array_in_struct2(Test3 in) return result; } +/* The structs Test3B..Test3E use the same functions hence using the MACRO + * to define their implementation. + */ + +#define _TESTFUNC_ARRAY_IN_STRUCT_IMPL \ + double result = 0; \ + \ + for (unsigned i = 0; i < sizeof(in.data)/sizeof(in.data[0]); i++) \ + result += in.data[i]; \ + /* As the structure is passed by value, changes to it shouldn't be \ + * reflected in the caller. \ + */ \ + memset(in.data, 0, sizeof(in.data)); \ + return result; \ + +#define _TESTFUNC_ARRAY_IN_STRUCT_SET_DEFAULTS_IMPL \ + for (unsigned i = 0; i < sizeof(s.data)/sizeof(s.data[0]); i++) \ + s.data[i] = (double)i+1; \ + return s; \ + + +/* + * Test3B struct test the MAX_STRUCT_SIZE 16 with double precision floats. + * These structs should be passed via registers on all platforms and they are + * used for within bounds tests. + */ +typedef struct { + double data[2]; +} Test3B; + EXPORT(double) -_testfunc_array_in_struct2a(Test3B in) +_testfunc_array_in_struct3B(Test3B in) { - double result = 0; + _TESTFUNC_ARRAY_IN_STRUCT_IMPL +} - for (unsigned i = 0; i < 2; i++) - result += in.data[i]; - for (unsigned i = 0; i < 2; i++) - result += in.more_data[i]; - /* As the structure is passed by value, changes to it shouldn't be - * reflected in the caller. - */ - memset(in.data, 0, sizeof(in.data)); - return result; +EXPORT(Test3B) +_testfunc_array_in_struct3B_set_defaults(void) +{ + Test3B s; + _TESTFUNC_ARRAY_IN_STRUCT_SET_DEFAULTS_IMPL } /* - * See gh-110190. structs containing arrays of up to four floating point types - * (max 32 bytes) are passed in registers on Arm. + * Test3C struct tests the MAX_STRUCT_SIZE 32. Structs containing arrays of up + * to four floating point types are passed in registers on Arm platforms. + * This struct is used for within bounds test on Arm platfroms and for an + * out-of-bounds tests for platfroms where MAX_STRUCT_SIZE is less than 32. + * See gh-110190. */ - typedef struct { double data[4]; } Test3C; +EXPORT(double) +_testfunc_array_in_struct3C(Test3C in) +{ + _TESTFUNC_ARRAY_IN_STRUCT_IMPL +} + EXPORT(Test3C) -_testfunc_array_in_struct_set_defaults_3C(void) +_testfunc_array_in_struct3C_set_defaults(void) { Test3C s; - s.data[0] = 1.0; - s.data[1] = 2.0; - s.data[2] = 3.0; - s.data[3] = 4.0; - return s; + _TESTFUNC_ARRAY_IN_STRUCT_SET_DEFAULTS_IMPL } +/* + * Test3D struct tests the MAX_STRUCT_SIZE 64. Structs containing arrays of up + * to eight floating point types are passed in registers on PPC64LE platforms. + * This struct is used for within bounds test on PPC64LE platfroms and for an + * out-of-bounds tests for platfroms where MAX_STRUCT_SIZE is less than 64. + * See gh-110190. + */ typedef struct { - double data[5]; + double data[8]; } Test3D; +EXPORT(double) +_testfunc_array_in_struct3D(Test3D in) +{ + _TESTFUNC_ARRAY_IN_STRUCT_IMPL +} + EXPORT(Test3D) -_testfunc_array_in_struct_set_defaults_3D(void) +_testfunc_array_in_struct3D_set_defaults(void) { Test3D s; - s.data[0] = 1.0; - s.data[1] = 2.0; - s.data[2] = 3.0; - s.data[3] = 4.0; - s.data[4] = 5.0; - return s; + _TESTFUNC_ARRAY_IN_STRUCT_SET_DEFAULTS_IMPL +} + +/* + * Test3E struct tests the out-of-bounds for all architectures. + * See gh-110190. + */ +typedef struct { + double data[9]; +} Test3E; + +EXPORT(double) +_testfunc_array_in_struct3E(Test3E in) +{ + _TESTFUNC_ARRAY_IN_STRUCT_IMPL +} + +EXPORT(Test3E) +_testfunc_array_in_struct3E_set_defaults(void) +{ + Test3E s; + _TESTFUNC_ARRAY_IN_STRUCT_SET_DEFAULTS_IMPL } typedef union { diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 04dd9bae32cd5e..dfdb96b0e7258a 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -698,13 +698,12 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct stgdict->length = len; /* ADD ffi_ofs? */ /* - * On Arm platforms, structs with at most 4 elements of any floating point - * type values can be passed through registers. If the type is double the - * maximum size of the struct is 32 bytes. - * By Arm platforms it is meant both 32 and 64-bit. -*/ + * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. + */ #if defined(__aarch64__) || defined(__arm__) # define MAX_STRUCT_SIZE 32 +#elif defined(__powerpc64__) +# define MAX_STRUCT_SIZE 64 #else # define MAX_STRUCT_SIZE 16 #endif @@ -715,24 +714,29 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct * pointers, which is fine when an array name is being passed as * parameter, but not when passing structures by value that contain * arrays. - * On x86_64 Linux and Arm platforms, small structures passed by - * value are passed in registers, and in order to do this, libffi needs - * to know the true type of the array members of structs. Treating them - * as pointers breaks things. + * Small structures passed by value are passed in registers, and in + * order to do this, libffi needs to know the true type of the array + * members of structs. Treating them as pointers breaks things. + * + * Small structures have different sizes depending on the platform + * where Python is running on: + * + * * x86-64: 16 bytes or less + * * Arm platforms (both 32 and 64 bit): 32 bytes or less + * * PowerPC 64 Little Endian: 64 bytes or less * - * By small structures, we mean ones that are 16 bytes or less on - * x86-64 and 32 bytes or less on Arm. In that case, there can't be - * more than 16 or 32 elements after unrolling arrays, as we (will) - * disallow bitfields. So we can collect the true ffi_type values in - * a fixed-size local array on the stack and, if any arrays were seen, - * replace the ffi_type_pointer.elements with a more accurate set, - * to allow libffi to marshal them into registers correctly. + * In that case, there can't be more than 16, 32 or 64 elements after + * unrolling arrays, as we (will) disallow bitfields. + * So we can collect the true ffi_type values in a fixed-size local + * array on the stack and, if any arrays were seen, replace the + * ffi_type_pointer.elements with a more accurate set, to allow + * libffi to marshal them into registers correctly. * It means one more loop over the fields, but if we got here, * the structure is small, so there aren't too many of those. * - * Although the passing in registers is specific to x86_64 Linux - * and Arm platforms, the array-in-struct vs. pointer problem is - * general. But we restrict the type transformation to small structs + * Although the passing in registers is specific to the above + * platforms, the array-in-struct vs. pointer problem is general. + * But we restrict the type transformation to small structs * nonetheless. * * Note that although a union may be small in terms of memory usage, it @@ -759,8 +763,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct * struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6; * } * - * The same principle applies for a struct 32 bytes in size like in - * the case of Arm platforms. + * The same principle applies for a struct 32 or 64 bytes in size. * * So the struct/union needs setting up as follows: all non-array * elements copied across as is, and all array elements replaced with From 480b4b359d710c74f31e2ffd7fc51750dcec7012 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 13 Dec 2023 09:29:40 -0700 Subject: [PATCH 231/442] gh-76785: Fix CODEOWNERS (gh-113038) In gh-112982 I made some changes to .github/CODEOWNERS. Later, @ezio-melotti pointed out that some of those changes were unnecessary. --- .github/CODEOWNERS | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f8291d8689dd95..db28c2a231ae04 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,6 +40,7 @@ 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 # Exceptions Lib/traceback.py @iritkatriel @@ -78,11 +79,6 @@ Python/traceback.c @iritkatriel **/*importlib/resources/* @jaraco @warsaw @FFY00 **/importlib/metadata/* @jaraco @warsaw -# Subinterpreters -Lib/test/support/interpreters/** @ericsnowcurrently -Modules/_xx*interp*module.c @ericsnowcurrently -Lib/test/test_interpreters/** @ericsnowcurrently - # Dates and times **/*datetime* @pganssle @abalkin **/*str*time* @pganssle @abalkin @@ -153,15 +149,7 @@ Doc/c-api/stable.rst @encukou **/*itertools* @rhettinger **/*collections* @rhettinger **/*random* @rhettinger -Doc/**/*queue* @rhettinger -PCbuild/**/*queue* @rhettinger -Modules/_queuemodule.c @rhettinger -Lib/*queue*.py @rhettinger -Lib/asyncio/*queue*.py @rhettinger -Lib/multiprocessing/*queue*.py @rhettinger -Lib/test/*queue*.py @rhettinger -Lib/test_asyncio/*queue*.py @rhettinger -Lib/test_multiprocessing/*queue*.py @rhettinger +**/*queue* @rhettinger **/*bisect* @rhettinger **/*heapq* @rhettinger **/*functools* @rhettinger @@ -201,6 +189,11 @@ Lib/test_multiprocessing/*queue*.py @rhettinger /Lib/test/test_clinic.py @erlend-aasland @AlexWaygood Doc/howto/clinic.rst @erlend-aasland +# Subinterpreters +Lib/test/support/interpreters/ @ericsnowcurrently +Modules/_xx*interp*module.c @ericsnowcurrently +Lib/test/test_interpreters/ @ericsnowcurrently + # WebAssembly /Tools/wasm/ @brettcannon From 2111795d0c2dea0ade67d5d76f839102d68caa23 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 13 Dec 2023 12:11:52 -0600 Subject: [PATCH 232/442] Use match/case in grouper() recipe (gh-113059) Use match/case in grouper() reciper --- Doc/library/itertools.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 8a4254cf15ebe2..56c66f670c74dd 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -914,14 +914,15 @@ which incur interpreter overhead. # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF args = [iter(iterable)] * n - if incomplete == 'fill': - return zip_longest(*args, fillvalue=fillvalue) - elif incomplete == 'strict': - return zip(*args, strict=True) - elif incomplete == 'ignore': - return zip(*args) - else: - raise ValueError('Expected fill, strict, or ignore') + match incomplete: + case 'fill': + return zip_longest(*args, fillvalue=fillvalue) + case 'strict': + return zip(*args, strict=True) + case 'ignore': + return zip(*args) + case _: + raise ValueError('Expected fill, strict, or ignore') def sliding_window(iterable, n): # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG From d05a180350fe20d5fde56c7e525e394a0b282703 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 13 Dec 2023 18:59:36 +0000 Subject: [PATCH 233/442] gh-101100: Improve docs on exception attributes (GH-113057) * Improve docs on exception attributes * thanks sphinx-lint * fix doctests * argh, okay, give up on doctests * Various improvements --- Doc/c-api/exceptions.rst | 16 +++--- Doc/library/exceptions.rst | 94 +++++++++++++++++++++------------- Doc/library/traceback.rst | 30 ++++++----- Doc/reference/datamodel.rst | 3 +- Doc/reference/simple_stmts.rst | 28 +++++++--- Doc/whatsnew/3.0.rst | 11 ++-- 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index a3a63b38c432f2..284a9c71e420da 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -541,7 +541,8 @@ Querying the error indicator .. note:: - This function *does not* implicitly set the ``__traceback__`` + This function *does not* implicitly set the + :attr:`~BaseException.__traceback__` attribute on the exception value. If setting the traceback appropriately is desired, the following additional snippet is needed:: @@ -753,7 +754,8 @@ Exception Objects .. c:function:: PyObject* PyException_GetTraceback(PyObject *ex) Return the traceback associated with the exception as a new reference, as - accessible from Python through :attr:`__traceback__`. If there is no + accessible from Python through the :attr:`~BaseException.__traceback__` + attribute. If there is no traceback associated, this returns ``NULL``. @@ -767,8 +769,8 @@ Exception Objects Return the context (another exception instance during whose handling *ex* was raised) associated with the exception as a new reference, as accessible from - Python through :attr:`__context__`. If there is no context associated, this - returns ``NULL``. + Python through the :attr:`~BaseException.__context__` attribute. + If there is no context associated, this returns ``NULL``. .. c:function:: void PyException_SetContext(PyObject *ex, PyObject *ctx) @@ -782,7 +784,8 @@ Exception Objects Return the cause (either an exception instance, or ``None``, set by ``raise ... from ...``) associated with the exception as a new - reference, as accessible from Python through :attr:`__cause__`. + reference, as accessible from Python through the + :attr:`~BaseException.__cause__` attribute. .. c:function:: void PyException_SetCause(PyObject *ex, PyObject *cause) @@ -791,7 +794,8 @@ Exception Objects it. There is no type check to make sure that *cause* is either an exception instance or ``None``. This steals a reference to *cause*. - :attr:`__suppress_context__` is implicitly set to ``True`` by this function. + The :attr:`~BaseException.__suppress_context__` attribute is implicitly set + to ``True`` by this function. .. c:function:: PyObject* PyException_GetArgs(PyObject *ex) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index b67215b8b3a362..04686b6db0036c 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -38,36 +38,48 @@ information on defining exceptions is available in the Python Tutorial under Exception context ----------------- -When raising a new exception while another exception -is already being handled, the new exception's -:attr:`__context__` attribute is automatically set to the handled -exception. An exception may be handled when an :keyword:`except` or -:keyword:`finally` clause, or a :keyword:`with` statement, is used. - -This implicit exception context can be -supplemented with an explicit cause by using :keyword:`!from` with -:keyword:`raise`:: - - raise new_exc from original_exc - -The expression following :keyword:`from` must be an exception or ``None``. It -will be set as :attr:`__cause__` on the raised exception. Setting -:attr:`__cause__` also implicitly sets the :attr:`__suppress_context__` -attribute to ``True``, so that using ``raise new_exc from None`` -effectively replaces the old exception with the new one for display -purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`), while -leaving the old exception available in :attr:`__context__` for introspection -when debugging. - -The default traceback display code shows these chained exceptions in -addition to the traceback for the exception itself. An explicitly chained -exception in :attr:`__cause__` is always shown when present. An implicitly -chained exception in :attr:`__context__` is shown only if :attr:`__cause__` -is :const:`None` and :attr:`__suppress_context__` is false. - -In either case, the exception itself is always shown after any chained -exceptions so that the final line of the traceback always shows the last -exception that was raised. +.. index:: pair: exception; chaining + __cause__ (exception attribute) + __context__ (exception attribute) + __suppress_context__ (exception attribute) + +Three attributes on exception objects provide information about the context in +which an the exception was raised: + +.. attribute:: BaseException.__context__ + BaseException.__cause__ + BaseException.__suppress_context__ + + When raising a new exception while another exception + is already being handled, the new exception's + :attr:`!__context__` attribute is automatically set to the handled + exception. An exception may be handled when an :keyword:`except` or + :keyword:`finally` clause, or a :keyword:`with` statement, is used. + + This implicit exception context can be + supplemented with an explicit cause by using :keyword:`!from` with + :keyword:`raise`:: + + raise new_exc from original_exc + + The expression following :keyword:`from` must be an exception or ``None``. It + will be set as :attr:`!__cause__` on the raised exception. Setting + :attr:`!__cause__` also implicitly sets the :attr:`!__suppress_context__` + attribute to ``True``, so that using ``raise new_exc from None`` + effectively replaces the old exception with the new one for display + purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`), while + leaving the old exception available in :attr:`!__context__` for introspection + when debugging. + + The default traceback display code shows these chained exceptions in + addition to the traceback for the exception itself. An explicitly chained + exception in :attr:`!__cause__` is always shown when present. An implicitly + chained exception in :attr:`!__context__` is shown only if :attr:`!__cause__` + is :const:`None` and :attr:`!__suppress_context__` is false. + + In either case, the exception itself is always shown after any chained + exceptions so that the final line of the traceback always shows the last + exception that was raised. Inheriting from built-in exceptions @@ -126,6 +138,12 @@ The following exceptions are used mostly as base classes for other exceptions. tb = sys.exception().__traceback__ raise OtherException(...).with_traceback(tb) + .. attribute:: __traceback__ + + A writable field that holds the + :ref:`traceback object ` associated with this + exception. See also: :ref:`raise`. + .. method:: add_note(note) Add the string ``note`` to the exception's notes which appear in the standard @@ -929,8 +947,10 @@ their subgroups based on the types of the contained exceptions. true for the exceptions that should be in the subgroup. The nesting structure of the current exception is preserved in the result, - as are the values of its :attr:`message`, :attr:`__traceback__`, - :attr:`__cause__`, :attr:`__context__` and :attr:`__notes__` fields. + as are the values of its :attr:`message`, + :attr:`~BaseException.__traceback__`, :attr:`~BaseException.__cause__`, + :attr:`~BaseException.__context__` and + :attr:`~BaseException.__notes__` fields. Empty nested groups are omitted from the result. The condition is checked for all exceptions in the nested exception group, @@ -956,10 +976,14 @@ their subgroups based on the types of the contained exceptions. and :meth:`split` return instances of the subclass rather than :exc:`ExceptionGroup`. - :meth:`subgroup` and :meth:`split` copy the :attr:`__traceback__`, - :attr:`__cause__`, :attr:`__context__` and :attr:`__notes__` fields from + :meth:`subgroup` and :meth:`split` copy the + :attr:`~BaseException.__traceback__`, + :attr:`~BaseException.__cause__`, :attr:`~BaseException.__context__` and + :attr:`~BaseException.__notes__` fields from the original exception group to the one returned by :meth:`derive`, so - these fields do not need to be updated by :meth:`derive`. :: + these fields do not need to be updated by :meth:`derive`. + + .. doctest:: >>> class MyGroup(ExceptionGroup): ... def derive(self, excs): diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index 14cade690fc773..dfbd04de243a3c 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -67,7 +67,8 @@ The module defines the following functions: The optional *limit* argument has the same meaning as for :func:`print_tb`. If *chain* is true (the default), then chained exceptions (the - :attr:`__cause__` or :attr:`__context__` attributes of the exception) will be + :attr:`~BaseException.__cause__` or :attr:`~BaseException.__context__` + attributes of the exception) will be printed as well, like the interpreter itself does when printing an unhandled exception. @@ -234,10 +235,11 @@ capture data for later printing in a lightweight fashion. Capture an exception for later rendering. *limit*, *lookup_lines* and *capture_locals* are as for the :class:`StackSummary` class. - If *compact* is true, only data that is required by :class:`TracebackException`'s - ``format`` method is saved in the class attributes. In particular, the - ``__context__`` field is calculated only if ``__cause__`` is ``None`` and - ``__suppress_context__`` is false. + If *compact* is true, only data that is required by + :class:`!TracebackException`'s :meth:`format` method + is saved in the class attributes. In particular, the + :attr:`__context__` field is calculated only if :attr:`__cause__` is + ``None`` and :attr:`__suppress_context__` is false. Note that when locals are captured, they are also shown in the traceback. @@ -255,27 +257,31 @@ capture data for later printing in a lightweight fashion. .. attribute:: __cause__ - A :class:`TracebackException` of the original ``__cause__``. + A :class:`!TracebackException` of the original + :attr:`~BaseException.__cause__`. .. attribute:: __context__ - A :class:`TracebackException` of the original ``__context__``. + A :class:`!TracebackException` of the original + :attr:`~BaseException.__context__`. .. attribute:: exceptions If ``self`` represents an :exc:`ExceptionGroup`, this field holds a list of - :class:`TracebackException` instances representing the nested exceptions. + :class:`!TracebackException` instances representing the nested exceptions. Otherwise it is ``None``. .. versionadded:: 3.11 .. attribute:: __suppress_context__ - The ``__suppress_context__`` value from the original exception. + The :attr:`~BaseException.__suppress_context__` value from the original + exception. .. attribute:: __notes__ - The ``__notes__`` value from the original exception, or ``None`` + The :attr:`~BaseException.__notes__` value from the original exception, + or ``None`` if the exception does not have any notes. If it is not ``None`` is it formatted in the traceback after the exception string. @@ -349,8 +355,8 @@ capture data for later printing in a lightweight fashion. Format the exception. - If *chain* is not ``True``, ``__cause__`` and ``__context__`` will not - be formatted. + If *chain* is not ``True``, :attr:`__cause__` and :attr:`__context__` + will not be formatted. The return value is a generator of strings, each ending in a newline and some containing internal newlines. :func:`~traceback.print_exception` diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 16ee3e300c2b8b..5e3757e1f5c6f6 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1390,7 +1390,8 @@ unwinds the execution stack, at each unwound level a traceback object is inserted in front of the current traceback. When an exception handler is entered, the stack trace is made available to the program. (See section :ref:`try`.) It is accessible as the third item of the -tuple returned by :func:`sys.exc_info`, and as the ``__traceback__`` attribute +tuple returned by :func:`sys.exc_info`, and as the +:attr:`~BaseException.__traceback__` attribute of the caught exception. When the program contains no suitable diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index a9e65be1eda340..34c3a620b87223 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -578,7 +578,7 @@ The :dfn:`type` of the exception is the exception instance's class, the .. index:: pair: object; traceback A traceback object is normally created automatically when an exception is raised -and attached to it as the :attr:`__traceback__` attribute, which is writable. +and attached to it as the :attr:`~BaseException.__traceback__` attribute. You can create an exception and set your own traceback in one step using the :meth:`~BaseException.with_traceback` exception method (which returns the same exception instance, with its traceback set to its argument), like so:: @@ -592,11 +592,13 @@ same exception instance, with its traceback set to its argument), like so:: The ``from`` clause is used for exception chaining: if given, the second *expression* must be another exception class or instance. If the second expression is an exception instance, it will be attached to the raised -exception as the :attr:`__cause__` attribute (which is writable). If the +exception as the :attr:`~BaseException.__cause__` attribute (which is writable). If the expression is an exception class, the class will be instantiated and the resulting exception instance will be attached to the raised exception as the -:attr:`__cause__` attribute. If the raised exception is not handled, both -exceptions will be printed:: +:attr:`!__cause__` attribute. If the raised exception is not handled, both +exceptions will be printed: + +.. code-block:: pycon >>> try: ... print(1 / 0) @@ -605,19 +607,24 @@ exceptions will be printed:: ... Traceback (most recent call last): File "", line 2, in + print(1 / 0) + ~~^~~ ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 4, in + raise RuntimeError("Something bad happened") from exc RuntimeError: Something bad happened A similar mechanism works implicitly if a new exception is raised when an exception is already being handled. An exception may be handled when an :keyword:`except` or :keyword:`finally` clause, or a :keyword:`with` statement, is used. The previous exception is then -attached as the new exception's :attr:`__context__` attribute:: +attached as the new exception's :attr:`~BaseException.__context__` attribute: + +.. code-block:: pycon >>> try: ... print(1 / 0) @@ -626,16 +633,21 @@ attached as the new exception's :attr:`__context__` attribute:: ... Traceback (most recent call last): File "", line 2, in + print(1 / 0) + ~~^~~ ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "", line 4, in + raise RuntimeError("Something bad happened") RuntimeError: Something bad happened Exception chaining can be explicitly suppressed by specifying :const:`None` in -the ``from`` clause:: +the ``from`` clause: + +.. doctest:: >>> try: ... print(1 / 0) @@ -653,8 +665,8 @@ and information about handling exceptions is in section :ref:`try`. :const:`None` is now permitted as ``Y`` in ``raise X from Y``. .. versionadded:: 3.3 - The ``__suppress_context__`` attribute to suppress automatic display of the - exception context. + The :attr:`~BaseException.__suppress_context__` attribute to suppress + automatic display of the exception context. .. versionchanged:: 3.11 If the traceback of the active exception is modified in an :keyword:`except` diff --git a/Doc/whatsnew/3.0.rst b/Doc/whatsnew/3.0.rst index 5953b32c6aaa18..89e12062abaddd 100644 --- a/Doc/whatsnew/3.0.rst +++ b/Doc/whatsnew/3.0.rst @@ -711,7 +711,7 @@ new powerful features added: {Exception}({args})` instead of :samp:`raise {Exception}, {args}`. Additionally, you can no longer explicitly specify a traceback; instead, if you *have* to do this, you can assign directly to the - :attr:`__traceback__` attribute (see below). + :attr:`~BaseException.__traceback__` attribute (see below). * :pep:`3110`: Catching exceptions. You must now use :samp:`except {SomeException} as {variable}` instead @@ -725,7 +725,7 @@ new powerful features added: handler block. This usually happens due to a bug in the handler block; we call this a *secondary* exception. In this case, the original exception (that was being handled) is saved as the - :attr:`__context__` attribute of the secondary exception. + :attr:`~BaseException.__context__` attribute of the secondary exception. Explicit chaining is invoked with this syntax:: raise SecondaryException() from primary_exception @@ -733,14 +733,15 @@ new powerful features added: (where *primary_exception* is any expression that produces an exception object, probably an exception that was previously caught). In this case, the primary exception is stored on the - :attr:`__cause__` attribute of the secondary exception. The + :attr:`~BaseException.__cause__` attribute of the secondary exception. The traceback printed when an unhandled exception occurs walks the chain - of :attr:`__cause__` and :attr:`__context__` attributes and prints a + of :attr:`!__cause__` and :attr:`~BaseException.__context__` attributes and + prints a separate traceback for each component of the chain, with the primary exception at the top. (Java users may recognize this behavior.) * :pep:`3134`: Exception objects now store their traceback as the - :attr:`__traceback__` attribute. This means that an exception + :attr:`~BaseException.__traceback__` attribute. This means that an exception object now contains all the information pertaining to an exception, and there are fewer reasons to use :func:`sys.exc_info` (though the latter is not removed). From 85923cb377c4a13720c135da9ae3ed93465a81e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 13 Dec 2023 20:37:13 +0100 Subject: [PATCH 234/442] Docs: Fix external link to devguide.python.org (GH-112899) --- Doc/whatsnew/3.8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 4574702b1a600f..e4dcb9bf872e28 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -123,7 +123,7 @@ There is a new function parameter syntax ``/`` to indicate that some function parameters must be specified positionally and cannot be used as keyword arguments. This is the same notation shown by ``help()`` for C functions annotated with Larry Hastings' -`Argument Clinic `__ tool. +`Argument Clinic `__ tool. In the following example, parameters *a* and *b* are positional-only, while *c* or *d* can be positional or keyword, and *e* or *f* are From f14e3d59c955fb3cf89e5241727ec566164dcf42 Mon Sep 17 00:00:00 2001 From: Christoph Anton Mitterer Date: Wed, 13 Dec 2023 20:55:31 +0100 Subject: [PATCH 235/442] gh-107959: clarify Unix-availability of `os.lchmod()` (GH-107960) POSIX specifies that implementations are not required to support changing the file mode of symbolic links, but may do so. Consequently, `lchmod()` is not part of POSIX (but mentioned for implementations which do support the above). The current wording of the availability of `os.lchmod()` is rather vague and improved to clearly tell which POSIX/Unix/BSD-like support the function in general (those that support changing the file mode of symbolic links). Further, some examples of major implementations are added. Data for the BSDs taken from their online manpages. Signed-off-by: Christoph Anton Mitterer Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/os.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index e5ac9afc3c6fd9..9d2a3d65069253 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2160,9 +2160,12 @@ features: for possible values of *mode*. As of Python 3.3, this is equivalent to ``os.chmod(path, mode, follow_symlinks=False)``. + ``lchmod()`` is not part of POSIX, but Unix implementations may have it if + changing the mode of symbolic links is supported. + .. audit-event:: os.chmod path,mode,dir_fd os.lchmod - .. availability:: Unix. + .. availability:: Unix, not Linux, FreeBSD >= 1.3, NetBSD >= 1.3, not OpenBSD .. versionchanged:: 3.6 Accepts a :term:`path-like object`. From f5c05e015c178975f24b77e5a8975a22d694e019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20Babin=C4=8D=C3=A1k=E2=80=8F?= Date: Wed, 13 Dec 2023 21:23:13 +0100 Subject: [PATCH 236/442] bpo-40648: Test modes that file can get with chmod() on Windows (GH-20130) Order of tests matter second part makes testing file writable and possible to remove again. --- Lib/test/test_stat.py | 7 +++++++ .../next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst | 1 + 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 0eced2fcf98376..a0d0f61e5a192c 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -148,12 +148,19 @@ def test_mode(self): self.assertEqual(modestr, '-r--r--r--') self.assertEqual(self.statmod.S_IMODE(st_mode), 0o444) else: + os.chmod(TESTFN, 0o500) + st_mode, modestr = self.get_mode() + self.assertEqual(modestr[:3], '-r-') + self.assertS_IS("REG", st_mode) + self.assertEqual(self.statmod.S_IMODE(st_mode), 0o444) + os.chmod(TESTFN, 0o700) st_mode, modestr = self.get_mode() self.assertEqual(modestr[:3], '-rw') self.assertS_IS("REG", st_mode) self.assertEqual(self.statmod.S_IFMT(st_mode), self.statmod.S_IFREG) + self.assertEqual(self.statmod.S_IMODE(st_mode), 0o666) @os_helper.skip_unless_working_chmod def test_directory(self): diff --git a/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst b/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst new file mode 100644 index 00000000000000..8fbe42d263feb9 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2020-05-16-18-00-21.bpo-40648.p2uPqy.rst @@ -0,0 +1 @@ +Test modes that file can get with chmod() on Windows. From 41c18aacc7a6082fbd9b08697c4d6180a9b62265 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 13 Dec 2023 13:49:29 -0800 Subject: [PATCH 237/442] Move optimizer/executor tests to new file test_capi/test_opt.py (#113072) --- Lib/test/test_capi/test_misc.py | 535 ------------------------------- Lib/test/test_capi/test_opt.py | 544 ++++++++++++++++++++++++++++++++ 2 files changed, 544 insertions(+), 535 deletions(-) create mode 100644 Lib/test/test_capi/test_opt.py diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 776ee913a02216..123813b949fb7d 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -7,7 +7,6 @@ import importlib.machinery import importlib.util import json -import opcode import os import pickle import queue @@ -2483,540 +2482,6 @@ def func(): self.do_test(func, names) -@contextlib.contextmanager -def temporary_optimizer(opt): - old_opt = _testinternalcapi.get_optimizer() - _testinternalcapi.set_optimizer(opt) - try: - yield - finally: - _testinternalcapi.set_optimizer(old_opt) - - -@contextlib.contextmanager -def clear_executors(func): - # Clear executors in func before and after running a block - func.__code__ = func.__code__.replace() - try: - yield - finally: - func.__code__ = func.__code__.replace() - - -class TestOptimizerAPI(unittest.TestCase): - - def test_get_counter_optimizer_dealloc(self): - # See gh-108727 - def f(): - _testinternalcapi.get_counter_optimizer() - - f() - - def test_get_set_optimizer(self): - old = _testinternalcapi.get_optimizer() - opt = _testinternalcapi.get_counter_optimizer() - try: - _testinternalcapi.set_optimizer(opt) - self.assertEqual(_testinternalcapi.get_optimizer(), opt) - _testinternalcapi.set_optimizer(None) - self.assertEqual(_testinternalcapi.get_optimizer(), None) - finally: - _testinternalcapi.set_optimizer(old) - - - def test_counter_optimizer(self): - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def loop(): - for _ in range(1000): - pass - """), ns, ns) - loop = ns['loop'] - - for repeat in range(5): - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - self.assertEqual(opt.get_count(), 0) - with clear_executors(loop): - loop() - self.assertEqual(opt.get_count(), 1000) - - def test_long_loop(self): - "Check that we aren't confused by EXTENDED_ARG" - - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def nop(): - pass - - def long_loop(): - for _ in range(10): - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); - """), ns, ns) - long_loop = ns['long_loop'] - - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - self.assertEqual(opt.get_count(), 0) - long_loop() - self.assertEqual(opt.get_count(), 10) - - def test_code_restore_for_ENTER_EXECUTOR(self): - def testfunc(x): - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_counter_optimizer() - with temporary_optimizer(opt): - testfunc(1000) - code, replace_code = testfunc.__code__, testfunc.__code__.replace() - self.assertEqual(code, replace_code) - self.assertEqual(hash(code), hash(replace_code)) - - -def get_first_executor(func): - code = func.__code__ - co_code = code.co_code - JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] - for i in range(0, len(co_code), 2): - if co_code[i] == JUMP_BACKWARD: - try: - return _testinternalcapi.get_executor(code, i) - except ValueError: - pass - return None - - -class TestExecutorInvalidation(unittest.TestCase): - - def setUp(self): - self.old = _testinternalcapi.get_optimizer() - self.opt = _testinternalcapi.get_counter_optimizer() - _testinternalcapi.set_optimizer(self.opt) - - def tearDown(self): - _testinternalcapi.set_optimizer(self.old) - - def test_invalidate_object(self): - # Generate a new set of functions at each call - ns = {} - func_src = "\n".join( - f""" - def f{n}(): - for _ in range(1000): - pass - """ for n in range(5) - ) - exec(textwrap.dedent(func_src), ns, ns) - funcs = [ ns[f'f{n}'] for n in range(5)] - objects = [object() for _ in range(5)] - - for f in funcs: - f() - executors = [get_first_executor(f) for f in funcs] - # Set things up so each executor depends on the objects - # with an equal or lower index. - for i, exe in enumerate(executors): - self.assertTrue(exe.is_valid()) - for obj in objects[:i+1]: - _testinternalcapi.add_executor_dependency(exe, obj) - self.assertTrue(exe.is_valid()) - # Assert that the correct executors are invalidated - # and check that nothing crashes when we invalidate - # an executor mutliple times. - for i in (4,3,2,1,0): - _testinternalcapi.invalidate_executors(objects[i]) - for exe in executors[i:]: - self.assertFalse(exe.is_valid()) - for exe in executors[:i]: - self.assertTrue(exe.is_valid()) - - def test_uop_optimizer_invalidation(self): - # Generate a new function at each call - ns = {} - exec(textwrap.dedent(""" - def f(): - for i in range(1000): - pass - """), ns, ns) - f = ns['f'] - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - f() - exe = get_first_executor(f) - self.assertTrue(exe.is_valid()) - _testinternalcapi.invalidate_executors(f.__code__) - self.assertFalse(exe.is_valid()) - -class TestUops(unittest.TestCase): - - def test_basic_loop(self): - def testfunc(x): - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(1000) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_SET_IP", uops) - self.assertIn("LOAD_FAST", uops) - - def test_extended_arg(self): - "Check EXTENDED_ARG handling in superblock creation" - ns = {} - exec(textwrap.dedent(""" - def many_vars(): - # 260 vars, so z9 should have index 259 - a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = 42 - b0 = b1 = b2 = b3 = b4 = b5 = b6 = b7 = b8 = b9 = 42 - c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = 42 - d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 42 - e0 = e1 = e2 = e3 = e4 = e5 = e6 = e7 = e8 = e9 = 42 - f0 = f1 = f2 = f3 = f4 = f5 = f6 = f7 = f8 = f9 = 42 - g0 = g1 = g2 = g3 = g4 = g5 = g6 = g7 = g8 = g9 = 42 - h0 = h1 = h2 = h3 = h4 = h5 = h6 = h7 = h8 = h9 = 42 - i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = i9 = 42 - j0 = j1 = j2 = j3 = j4 = j5 = j6 = j7 = j8 = j9 = 42 - k0 = k1 = k2 = k3 = k4 = k5 = k6 = k7 = k8 = k9 = 42 - l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 42 - m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = 42 - n0 = n1 = n2 = n3 = n4 = n5 = n6 = n7 = n8 = n9 = 42 - o0 = o1 = o2 = o3 = o4 = o5 = o6 = o7 = o8 = o9 = 42 - p0 = p1 = p2 = p3 = p4 = p5 = p6 = p7 = p8 = p9 = 42 - q0 = q1 = q2 = q3 = q4 = q5 = q6 = q7 = q8 = q9 = 42 - r0 = r1 = r2 = r3 = r4 = r5 = r6 = r7 = r8 = r9 = 42 - s0 = s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = s9 = 42 - t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = 42 - u0 = u1 = u2 = u3 = u4 = u5 = u6 = u7 = u8 = u9 = 42 - v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = 42 - w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = 42 - x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42 - y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42 - z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42 - while z9 > 0: - z9 = z9 - 1 - """), ns, ns) - many_vars = ns["many_vars"] - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - ex = get_first_executor(many_vars) - self.assertIsNone(ex) - many_vars() - - ex = get_first_executor(many_vars) - self.assertIsNotNone(ex) - self.assertIn(("LOAD_FAST", 259, 0), list(ex)) - - def test_unspecialized_unpack(self): - # An example of an unspecialized opcode - def testfunc(x): - i = 0 - while i < x: - i += 1 - a, b = {1: 2, 3: 3} - assert a == 1 and b == 3 - i = 0 - while i < x: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_UNPACK_SEQUENCE", uops) - - def test_pop_jump_if_false(self): - def testfunc(n): - i = 0 - while i < n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_TRUE_POP", uops) - - def test_pop_jump_if_none(self): - def testfunc(a): - for x in a: - if x is None: - x = 0 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(range(20)) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) - - def test_pop_jump_if_not_none(self): - def testfunc(a): - for x in a: - x = None - if x is not None: - x = 0 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(range(20)) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_NONE_POP", uops) - - def test_pop_jump_if_true(self): - def testfunc(n): - i = 0 - while not i >= n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_FALSE_POP", uops) - - def test_jump_backward(self): - def testfunc(n): - i = 0 - while i < n: - i += 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_JUMP_TO_TOP", uops) - - def test_jump_forward(self): - def testfunc(n): - a = 0 - while a < n: - if a < 0: - a = -a - else: - a = +a - a += 1 - return a - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - # Since there is no JUMP_FORWARD instruction, - # look for indirect evidence: the += operator - self.assertIn("_BINARY_OP_ADD_INT", uops) - - def test_for_iter_range(self): - def testfunc(n): - total = 0 - for i in range(n): - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - total = testfunc(20) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_RANGE", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_for_iter_list(self): - def testfunc(a): - total = 0 - for i in a: - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = list(range(20)) - total = testfunc(a) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_LIST", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_for_iter_tuple(self): - def testfunc(a): - total = 0 - for i in a: - total += i - return total - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = tuple(range(20)) - total = testfunc(a) - self.assertEqual(total, 190) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - # for i, (opname, oparg) in enumerate(ex): - # print(f"{i:4d}: {opname:<20s} {oparg:3d}") - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_NOT_EXHAUSTED_TUPLE", uops) - # Verification that the jump goes past END_FOR - # is done by manual inspection of the output - - def test_list_edge_case(self): - def testfunc(it): - for x in it: - pass - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - a = [1, 2, 3] - it = iter(a) - testfunc(it) - a.append(4) - with self.assertRaises(StopIteration): - next(it) - - def test_call_py_exact_args(self): - def testfunc(n): - def dummy(x): - return x+1 - for i in range(n): - dummy(i) - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_PUSH_FRAME", uops) - self.assertIn("_BINARY_OP_ADD_INT", uops) - - def test_branch_taken(self): - def testfunc(n): - for i in range(n): - if i < 0: - i = 0 - else: - i = 1 - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(20) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_GUARD_IS_FALSE_POP", uops) - - def test_for_iter_tier_two(self): - class MyIter: - def __init__(self, n): - self.n = n - def __iter__(self): - return self - def __next__(self): - self.n -= 1 - if self.n < 0: - raise StopIteration - return self.n - - def testfunc(n, m): - x = 0 - for i in range(m): - for j in MyIter(n): - x += 1000*i + j - return x - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - x = testfunc(10, 10) - - self.assertEqual(x, sum(range(10)) * 10010) - - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - uops = {opname for opname, _, _ in ex} - self.assertIn("_FOR_ITER_TIER_TWO", uops) - - def test_confidence_score(self): - def testfunc(n): - bits = 0 - for i in range(n): - if i & 0x01: - bits += 1 - if i & 0x02: - bits += 1 - if i&0x04: - bits += 1 - if i&0x08: - bits += 1 - if i&0x10: - bits += 1 - if i&0x20: - bits += 1 - return bits - - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - x = testfunc(20) - - self.assertEqual(x, 40) - ex = get_first_executor(testfunc) - self.assertIsNotNone(ex) - ops = [opname for opname, _, _ in ex] - count = ops.count("_GUARD_IS_TRUE_POP") - # Because Each 'if' halves the score, the second branch is - # too much already. - self.assertEqual(count, 1) - - @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') class TestPyThreadId(unittest.TestCase): def test_py_thread_id(self): diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py new file mode 100644 index 00000000000000..9f4731103c9413 --- /dev/null +++ b/Lib/test/test_capi/test_opt.py @@ -0,0 +1,544 @@ +import contextlib +import opcode +import textwrap +import unittest + +import _testinternalcapi + + +@contextlib.contextmanager +def temporary_optimizer(opt): + old_opt = _testinternalcapi.get_optimizer() + _testinternalcapi.set_optimizer(opt) + try: + yield + finally: + _testinternalcapi.set_optimizer(old_opt) + + +@contextlib.contextmanager +def clear_executors(func): + # Clear executors in func before and after running a block + func.__code__ = func.__code__.replace() + try: + yield + finally: + func.__code__ = func.__code__.replace() + + +class TestOptimizerAPI(unittest.TestCase): + + def test_get_counter_optimizer_dealloc(self): + # See gh-108727 + def f(): + _testinternalcapi.get_counter_optimizer() + + f() + + def test_get_set_optimizer(self): + old = _testinternalcapi.get_optimizer() + opt = _testinternalcapi.get_counter_optimizer() + try: + _testinternalcapi.set_optimizer(opt) + self.assertEqual(_testinternalcapi.get_optimizer(), opt) + _testinternalcapi.set_optimizer(None) + self.assertEqual(_testinternalcapi.get_optimizer(), None) + finally: + _testinternalcapi.set_optimizer(old) + + + def test_counter_optimizer(self): + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def loop(): + for _ in range(1000): + pass + """), ns, ns) + loop = ns['loop'] + + for repeat in range(5): + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + self.assertEqual(opt.get_count(), 0) + with clear_executors(loop): + loop() + self.assertEqual(opt.get_count(), 1000) + + def test_long_loop(self): + "Check that we aren't confused by EXTENDED_ARG" + + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def nop(): + pass + + def long_loop(): + for _ in range(10): + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); + """), ns, ns) + long_loop = ns['long_loop'] + + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + self.assertEqual(opt.get_count(), 0) + long_loop() + self.assertEqual(opt.get_count(), 10) + + def test_code_restore_for_ENTER_EXECUTOR(self): + def testfunc(x): + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_counter_optimizer() + with temporary_optimizer(opt): + testfunc(1000) + code, replace_code = testfunc.__code__, testfunc.__code__.replace() + self.assertEqual(code, replace_code) + self.assertEqual(hash(code), hash(replace_code)) + + +def get_first_executor(func): + code = func.__code__ + co_code = code.co_code + JUMP_BACKWARD = opcode.opmap["JUMP_BACKWARD"] + for i in range(0, len(co_code), 2): + if co_code[i] == JUMP_BACKWARD: + try: + return _testinternalcapi.get_executor(code, i) + except ValueError: + pass + return None + + +class TestExecutorInvalidation(unittest.TestCase): + + def setUp(self): + self.old = _testinternalcapi.get_optimizer() + self.opt = _testinternalcapi.get_counter_optimizer() + _testinternalcapi.set_optimizer(self.opt) + + def tearDown(self): + _testinternalcapi.set_optimizer(self.old) + + def test_invalidate_object(self): + # Generate a new set of functions at each call + ns = {} + func_src = "\n".join( + f""" + def f{n}(): + for _ in range(1000): + pass + """ for n in range(5) + ) + exec(textwrap.dedent(func_src), ns, ns) + funcs = [ ns[f'f{n}'] for n in range(5)] + objects = [object() for _ in range(5)] + + for f in funcs: + f() + executors = [get_first_executor(f) for f in funcs] + # Set things up so each executor depends on the objects + # with an equal or lower index. + for i, exe in enumerate(executors): + self.assertTrue(exe.is_valid()) + for obj in objects[:i+1]: + _testinternalcapi.add_executor_dependency(exe, obj) + self.assertTrue(exe.is_valid()) + # Assert that the correct executors are invalidated + # and check that nothing crashes when we invalidate + # an executor mutliple times. + for i in (4,3,2,1,0): + _testinternalcapi.invalidate_executors(objects[i]) + for exe in executors[i:]: + self.assertFalse(exe.is_valid()) + for exe in executors[:i]: + self.assertTrue(exe.is_valid()) + + def test_uop_optimizer_invalidation(self): + # Generate a new function at each call + ns = {} + exec(textwrap.dedent(""" + def f(): + for i in range(1000): + pass + """), ns, ns) + f = ns['f'] + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + f() + exe = get_first_executor(f) + self.assertTrue(exe.is_valid()) + _testinternalcapi.invalidate_executors(f.__code__) + self.assertFalse(exe.is_valid()) + +class TestUops(unittest.TestCase): + + def test_basic_loop(self): + def testfunc(x): + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(1000) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_SET_IP", uops) + self.assertIn("LOAD_FAST", uops) + + def test_extended_arg(self): + "Check EXTENDED_ARG handling in superblock creation" + ns = {} + exec(textwrap.dedent(""" + def many_vars(): + # 260 vars, so z9 should have index 259 + a0 = a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = 42 + b0 = b1 = b2 = b3 = b4 = b5 = b6 = b7 = b8 = b9 = 42 + c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = 42 + d0 = d1 = d2 = d3 = d4 = d5 = d6 = d7 = d8 = d9 = 42 + e0 = e1 = e2 = e3 = e4 = e5 = e6 = e7 = e8 = e9 = 42 + f0 = f1 = f2 = f3 = f4 = f5 = f6 = f7 = f8 = f9 = 42 + g0 = g1 = g2 = g3 = g4 = g5 = g6 = g7 = g8 = g9 = 42 + h0 = h1 = h2 = h3 = h4 = h5 = h6 = h7 = h8 = h9 = 42 + i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = i9 = 42 + j0 = j1 = j2 = j3 = j4 = j5 = j6 = j7 = j8 = j9 = 42 + k0 = k1 = k2 = k3 = k4 = k5 = k6 = k7 = k8 = k9 = 42 + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 42 + m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = 42 + n0 = n1 = n2 = n3 = n4 = n5 = n6 = n7 = n8 = n9 = 42 + o0 = o1 = o2 = o3 = o4 = o5 = o6 = o7 = o8 = o9 = 42 + p0 = p1 = p2 = p3 = p4 = p5 = p6 = p7 = p8 = p9 = 42 + q0 = q1 = q2 = q3 = q4 = q5 = q6 = q7 = q8 = q9 = 42 + r0 = r1 = r2 = r3 = r4 = r5 = r6 = r7 = r8 = r9 = 42 + s0 = s1 = s2 = s3 = s4 = s5 = s6 = s7 = s8 = s9 = 42 + t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = 42 + u0 = u1 = u2 = u3 = u4 = u5 = u6 = u7 = u8 = u9 = 42 + v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = 42 + w0 = w1 = w2 = w3 = w4 = w5 = w6 = w7 = w8 = w9 = 42 + x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = x9 = 42 + y0 = y1 = y2 = y3 = y4 = y5 = y6 = y7 = y8 = y9 = 42 + z0 = z1 = z2 = z3 = z4 = z5 = z6 = z7 = z8 = z9 = 42 + while z9 > 0: + z9 = z9 - 1 + """), ns, ns) + many_vars = ns["many_vars"] + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + ex = get_first_executor(many_vars) + self.assertIsNone(ex) + many_vars() + + ex = get_first_executor(many_vars) + self.assertIsNotNone(ex) + self.assertIn(("LOAD_FAST", 259, 0), list(ex)) + + def test_unspecialized_unpack(self): + # An example of an unspecialized opcode + def testfunc(x): + i = 0 + while i < x: + i += 1 + a, b = {1: 2, 3: 3} + assert a == 1 and b == 3 + i = 0 + while i < x: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_UNPACK_SEQUENCE", uops) + + def test_pop_jump_if_false(self): + def testfunc(n): + i = 0 + while i < n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_TRUE_POP", uops) + + def test_pop_jump_if_none(self): + def testfunc(a): + for x in a: + if x is None: + x = 0 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(range(20)) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) + + def test_pop_jump_if_not_none(self): + def testfunc(a): + for x in a: + x = None + if x is not None: + x = 0 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(range(20)) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_NONE_POP", uops) + + def test_pop_jump_if_true(self): + def testfunc(n): + i = 0 + while not i >= n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_FALSE_POP", uops) + + def test_jump_backward(self): + def testfunc(n): + i = 0 + while i < n: + i += 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_JUMP_TO_TOP", uops) + + def test_jump_forward(self): + def testfunc(n): + a = 0 + while a < n: + if a < 0: + a = -a + else: + a = +a + a += 1 + return a + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + # Since there is no JUMP_FORWARD instruction, + # look for indirect evidence: the += operator + self.assertIn("_BINARY_OP_ADD_INT", uops) + + def test_for_iter_range(self): + def testfunc(n): + total = 0 + for i in range(n): + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + total = testfunc(20) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_RANGE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_list(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = list(range(20)) + total = testfunc(a) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_LIST", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_tuple(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = tuple(range(20)) + total = testfunc(a) + self.assertEqual(total, 190) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_NOT_EXHAUSTED_TUPLE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_list_edge_case(self): + def testfunc(it): + for x in it: + pass + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = [1, 2, 3] + it = iter(a) + testfunc(it) + a.append(4) + with self.assertRaises(StopIteration): + next(it) + + def test_call_py_exact_args(self): + def testfunc(n): + def dummy(x): + return x+1 + for i in range(n): + dummy(i) + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_PUSH_FRAME", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + + def test_branch_taken(self): + def testfunc(n): + for i in range(n): + if i < 0: + i = 0 + else: + i = 1 + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + testfunc(20) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_GUARD_IS_FALSE_POP", uops) + + def test_for_iter_tier_two(self): + class MyIter: + def __init__(self, n): + self.n = n + def __iter__(self): + return self + def __next__(self): + self.n -= 1 + if self.n < 0: + raise StopIteration + return self.n + + def testfunc(n, m): + x = 0 + for i in range(m): + for j in MyIter(n): + x += 1000*i + j + return x + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + x = testfunc(10, 10) + + self.assertEqual(x, sum(range(10)) * 10010) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + self.assertIn("_FOR_ITER_TIER_TWO", uops) + + def test_confidence_score(self): + def testfunc(n): + bits = 0 + for i in range(n): + if i & 0x01: + bits += 1 + if i & 0x02: + bits += 1 + if i&0x04: + bits += 1 + if i&0x08: + bits += 1 + if i&0x10: + bits += 1 + if i&0x20: + bits += 1 + return bits + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + x = testfunc(20) + + self.assertEqual(x, 40) + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + ops = [opname for opname, _, _ in ex] + count = ops.count("_GUARD_IS_TRUE_POP") + # Because Each 'if' halves the score, the second branch is + # too much already. + self.assertEqual(count, 1) + + +if __name__ == "__main__": + unittest.main() From fddc829236d7b29a522a2160e57b2d7ca23b9b95 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 13 Dec 2023 23:41:43 +0000 Subject: [PATCH 238/442] gh-86179: Implement realpath() on Windows for getpath.py calculations (GH-113033) --- Lib/sysconfig/__init__.py | 11 +----- Lib/test/test_venv.py | 23 ++++++++++- ...3-12-12-20-58-09.gh-issue-86179.YYSk_6.rst | 1 + Modules/getpath.c | 39 +++++++++++++++++++ 4 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index c60c9f3440615b..deb438c705f3a0 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -404,16 +404,7 @@ def get_config_h_filename(): """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": - # This ought to be as simple as dirname(sys._base_executable), but - # if a venv uses symlinks to a build in the source tree, then this - # fails. So instead we guess the subdirectory name from sys.winver - if sys.winver.endswith('-32'): - arch = 'win32' - elif sys.winver.endswith('-arm64'): - arch = 'arm64' - else: - arch = 'amd64' - inc_dir = os.path.join(_PROJECT_BASE, 'PCbuild', arch) + inc_dir = os.path.dirname(sys._base_executable) else: inc_dir = _PROJECT_BASE else: diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 617d14dcb9c5fe..8ecb23ff384362 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -46,7 +46,8 @@ def check_output(cmd, encoding=None): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + env={**os.environ, "PYTHONHOME": ""}) out, err = p.communicate() if p.returncode: if verbose and err: @@ -287,6 +288,16 @@ def test_sysconfig(self): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) @requireVenvCreate @unittest.skipUnless(can_symlink(), 'Needs symlinks') @@ -309,6 +320,16 @@ def test_sysconfig_symlinks(self): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) if sys.platform == 'win32': ENV_SUBDIRS = ( diff --git a/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst new file mode 100644 index 00000000000000..c1d96792bdae0b --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst @@ -0,0 +1 @@ +Fixes path calculations when launching Python on Windows through a symlink. diff --git a/Modules/getpath.c b/Modules/getpath.c index 6c1078b8914522..422056b1fb6de4 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -502,6 +502,45 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) PyMem_Free((void *)path); PyMem_Free((void *)narrow); return r; +#elif defined(MS_WINDOWS) + HANDLE hFile; + wchar_t resolved[MAXPATHLEN+1]; + int len = 0, err; + PyObject *result; + + wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + if (!path) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + len = GetFinalPathNameByHandleW(hFile, resolved, MAXPATHLEN, VOLUME_NAME_DOS); + err = len ? 0 : GetLastError(); + CloseHandle(hFile); + } else { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + + if (err) { + PyErr_SetFromWindowsErr(err); + result = NULL; + } else if (len <= MAXPATHLEN) { + const wchar_t *p = resolved; + if (0 == wcsncmp(p, L"\\\\?\\", 4)) { + if (GetFileAttributesW(&p[4]) != INVALID_FILE_ATTRIBUTES) { + p += 4; + len -= 4; + } + } + result = PyUnicode_FromWideChar(p, len); + } else { + result = Py_NewRef(pathobj); + } + PyMem_Free(path); + return result; #endif return Py_NewRef(pathobj); From b4f2c89118d5a48ce6c495ba931d7f6493422029 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 09:16:06 +0200 Subject: [PATCH 239/442] gh-113086: Add tests for os.chmod() and os.lchmod() (GH-113087) Also make test_copymode_symlink_to_symlink in test_shutil more strict. --- Lib/test/test_posix.py | 116 ++++++++++++++++++++++++++++++++++++++++ Lib/test/test_shutil.py | 3 +- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 1722c84727bbd8..4eb3cacc23768f 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -935,6 +935,122 @@ def test_utime(self): posix.utime(os_helper.TESTFN, (int(now), int(now))) posix.utime(os_helper.TESTFN, (now, now)) + def check_chmod(self, chmod_func, target, **kwargs): + mode = os.stat(target).st_mode + try: + new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + try: + with open(target, 'wb+'): + pass + except PermissionError: + pass + new_mode = mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(target, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + if stat.S_ISREG(mode): + with open(target, 'wb+'): + pass + finally: + posix.chmod(target, mode) + + def test_chmod_file(self): + self.check_chmod(posix.chmod, os_helper.TESTFN) + + def tempdir(self): + target = os_helper.TESTFN + 'd' + posix.mkdir(target) + self.addCleanup(posix.rmdir, target) + return target + + def test_chmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.chmod, target) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_file(self): + self.check_chmod(posix.lchmod, os_helper.TESTFN) + self.check_chmod(posix.chmod, os_helper.TESTFN, follow_symlinks=False) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + def test_lchmod_dir(self): + target = self.tempdir() + self.check_chmod(posix.lchmod, target) + self.check_chmod(posix.chmod, target, follow_symlinks=False) + + def check_chmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + try: + new_mode = target_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + new_mode = target_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, new_mode) + self.assertEqual(os.lstat(link).st_mode, link_mode) + finally: + posix.chmod(target, target_mode) + + def check_lchmod_link(self, chmod_func, target, link, **kwargs): + target_mode = os.stat(target).st_mode + link_mode = os.lstat(link).st_mode + new_mode = link_mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + new_mode = link_mode | (stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) + chmod_func(link, new_mode, **kwargs) + self.assertEqual(os.stat(target).st_mode, target_mode) + self.assertEqual(os.lstat(link).st_mode, new_mode) + + @os_helper.skip_unless_symlink + def test_chmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @os_helper.skip_unless_symlink + def test_chmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link, target_is_directory=True) + self.addCleanup(posix.unlink, link) + if os.name == 'nt': + self.check_lchmod_link(posix.chmod, target, link) + else: + self.check_chmod_link(posix.chmod, target, link) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_file_symlink(self): + target = os_helper.TESTFN + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') + @os_helper.skip_unless_symlink + def test_lchmod_dir_symlink(self): + target = self.tempdir() + link = os_helper.TESTFN + '-link' + os.symlink(target, link) + self.addCleanup(posix.unlink, link) + self.check_lchmod_link(posix.chmod, target, link, follow_symlinks=False) + self.check_lchmod_link(posix.lchmod, target, link) + def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs): st = os.stat(target_file) self.assertTrue(hasattr(st, 'st_flags')) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index b29d316352f219..5ce8e5d77fbbf3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1132,10 +1132,11 @@ def test_copymode_symlink_to_symlink(self): os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG) # link to link os.lchmod(dst_link, stat.S_IRWXO) + old_mode = os.stat(dst).st_mode shutil.copymode(src_link, dst_link, follow_symlinks=False) self.assertEqual(os.lstat(src_link).st_mode, os.lstat(dst_link).st_mode) - self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + self.assertEqual(os.stat(dst).st_mode, old_mode) # src link - use chmod os.lchmod(dst_link, stat.S_IRWXO) shutil.copymode(src_link, dst, follow_symlinks=False) From 4d5d9acb22ee8c6908ebd4fdc3d17472f8b286e3 Mon Sep 17 00:00:00 2001 From: Stephen Gildea Date: Wed, 13 Dec 2023 23:53:08 -0800 Subject: [PATCH 240/442] gh-90890: Reorder mailbox.Maildir method documentation (GH-113071) When new mailbox.Maildir methods were added for 3.13.0a2, their documentation was added at the end of the mailbox.Maildir section instead of grouping them with other methods Maildir adds to Mailbox. This commit moves the new methods' documentation adjacent to documentation for existing Maildir-specific methods, so that the "special remarks" for common methods remains at the end. --- Doc/library/mailbox.rst | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index 05ffaf6c9b336e..fd60d163378f07 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -383,46 +383,6 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. last 36 hours. The Maildir specification says that mail-reading programs should do this occasionally. - Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special - remarks: - - - .. method:: add(message) - __setitem__(key, message) - update(arg) - - .. warning:: - - These methods generate unique file names based upon the current process - ID. When using multiple threads, undetected name clashes may occur and - cause corruption of the mailbox unless threads are coordinated to avoid - using these methods to manipulate the same mailbox simultaneously. - - - .. method:: flush() - - All changes to Maildir mailboxes are immediately applied, so this method - does nothing. - - - .. method:: lock() - unlock() - - Maildir mailboxes do not support (or require) locking, so these methods do - nothing. - - - .. method:: close() - - :class:`Maildir` instances do not keep any open files and the underlying - mailboxes do not support locking, so this method does nothing. - - - .. method:: get_file(key) - - Depending upon the host platform, it may not be possible to modify or - remove the underlying message while the returned file remains open. - .. method:: get_flags(key) @@ -525,6 +485,46 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. versionadded:: 3.13 + Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special + remarks: + + + .. method:: add(message) + __setitem__(key, message) + update(arg) + + .. warning:: + + These methods generate unique file names based upon the current process + ID. When using multiple threads, undetected name clashes may occur and + cause corruption of the mailbox unless threads are coordinated to avoid + using these methods to manipulate the same mailbox simultaneously. + + + .. method:: flush() + + All changes to Maildir mailboxes are immediately applied, so this method + does nothing. + + + .. method:: lock() + unlock() + + Maildir mailboxes do not support (or require) locking, so these methods do + nothing. + + + .. method:: close() + + :class:`Maildir` instances do not keep any open files and the underlying + mailboxes do not support locking, so this method does nothing. + + + .. method:: get_file(key) + + Depending upon the host platform, it may not be possible to modify or + remove the underlying message while the returned file remains open. + .. seealso:: From bb36f72efcc6a656e0907ffa83620a1e44044895 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 12:04:23 +0200 Subject: [PATCH 241/442] gh-111049: Fix crash during garbage collection of the BytesIO buffer object (GH-111221) --- Lib/test/test_memoryio.py | 21 +++++++++++++++++++ ...-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst | 2 ++ Modules/_io/bytesio.c | 14 ++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 731299294e6877..8192502a40791b 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -6,10 +6,12 @@ import unittest from test import support +import gc import io import _pyio as pyio import pickle import sys +import weakref class IntLike: def __init__(self, num): @@ -477,6 +479,25 @@ def test_getbuffer_empty(self): buf2.release() memio.write(b'x') + def test_getbuffer_gc_collect(self): + memio = self.ioclass(b"1234567890") + buf = memio.getbuffer() + memiowr = weakref.ref(memio) + bufwr = weakref.ref(buf) + # Create a reference loop. + a = [buf] + a.append(a) + # The Python implementation emits an unraisable exception. + with support.catch_unraisable_exception(): + del memio + del buf + del a + # The C implementation emits an unraisable exception. + with support.catch_unraisable_exception(): + gc.collect() + self.assertIsNone(memiowr()) + self.assertIsNone(bufwr()) + def test_read1(self): buf = self.buftype("1234567890") self.assertEqual(self.ioclass(buf).read1(), buf) diff --git a/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst b/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst new file mode 100644 index 00000000000000..b1de348bea0a58 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-23-18-42-26.gh-issue-111049.Ys7-o_.rst @@ -0,0 +1,2 @@ +Fix crash during garbage collection of the :class:`io.BytesIO` buffer +object. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 16b8ac600ace79..4a15c8e841f25f 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -990,7 +990,9 @@ static int bytesio_clear(bytesio *self) { Py_CLEAR(self->dict); - Py_CLEAR(self->buf); + if (self->exports == 0) { + Py_CLEAR(self->buf); + } return 0; } @@ -1095,13 +1097,6 @@ bytesiobuf_releasebuffer(bytesiobuf *obj, Py_buffer *view) b->exports--; } -static int -bytesiobuf_clear(bytesiobuf *self) -{ - Py_CLEAR(self->source); - return 0; -} - static int bytesiobuf_traverse(bytesiobuf *self, visitproc visit, void *arg) { @@ -1116,7 +1111,7 @@ bytesiobuf_dealloc(bytesiobuf *self) PyTypeObject *tp = Py_TYPE(self); /* bpo-31095: UnTrack is needed before calling any callbacks */ PyObject_GC_UnTrack(self); - (void)bytesiobuf_clear(self); + Py_CLEAR(self->source); tp->tp_free(self); Py_DECREF(tp); } @@ -1124,7 +1119,6 @@ bytesiobuf_dealloc(bytesiobuf *self) static PyType_Slot bytesiobuf_slots[] = { {Py_tp_dealloc, bytesiobuf_dealloc}, {Py_tp_traverse, bytesiobuf_traverse}, - {Py_tp_clear, bytesiobuf_clear}, // Buffer protocol {Py_bf_getbuffer, bytesiobuf_getbuffer}, From b3c21265fa48a137a4c6a35e425b2b7f94f96cf4 Mon Sep 17 00:00:00 2001 From: Daniel Wysocki Date: Thu, 14 Dec 2023 04:07:37 -0600 Subject: [PATCH 242/442] Fixing typo in DocTestRunner docs (GH-112326) --- Doc/library/doctest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index fa1b850c531346..8c28e4478bb70e 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -1502,7 +1502,7 @@ DocTestRunner objects :attr:`failures` and :attr:`skips` attributes. The :meth:`run` and :meth:`summarize` methods return a :class:`TestResults` instance. - :class:`DocTestParser` defines the following methods: + :class:`DocTestRunner` defines the following methods: .. method:: report_start(out, test, example) From 23a5711100271e5a8b9dd9ab48b10807627ef4fb Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 14 Dec 2023 10:26:46 +0000 Subject: [PATCH 243/442] gh-112205: Update textio module to use `@getter` as possible. (gh-113095) --- Modules/_io/clinic/textio.c.h | 90 ++++++++++++++++++++++++++++++++++- Modules/_io/textio.c | 84 ++++++++++++++------------------ 2 files changed, 125 insertions(+), 49 deletions(-) diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index a492f340c74c0d..f24f65f0c1d4f9 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -1048,6 +1048,94 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#if defined(_IO_TEXTIOWRAPPER_NAME_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_NAME_GETSETDEF +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, (setter)_io_TextIOWrapper_name_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_name_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_name_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_name_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_CLOSED_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, (setter)_io_TextIOWrapper_closed_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_closed_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_closed_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_closed_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, (setter)_io_TextIOWrapper_newlines_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_newlines_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_newlines_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_newlines_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if defined(_IO_TEXTIOWRAPPER_ERRORS_GETSETDEF) +# undef _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, (setter)_io_TextIOWrapper_errors_set, NULL}, +#else +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, NULL, NULL}, +#endif + +static PyObject * +_io_TextIOWrapper_errors_get_impl(textio *self); + +static PyObject * +_io_TextIOWrapper_errors_get(textio *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _io_TextIOWrapper_errors_get_impl(self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + #if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) # undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF # define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, @@ -1091,4 +1179,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED return return_value; } -/*[clinic end generated code: output=b312f2d2e2221580 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=7af87bf848a5d3f3 input=a9049054013a1b77]*/ diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index c76d92cdd38b9a..702336ca2aeb06 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1475,7 +1475,7 @@ textiowrapper_traverse(textio *self, visitproc visit, void *arg) } static PyObject * -textiowrapper_closed_get(textio *self, void *context); +_io_TextIOWrapper_closed_get_impl(textio *self); /* This macro takes some shortcuts to make the common case faster. */ #define CHECK_CLOSED(self) \ @@ -1486,7 +1486,7 @@ textiowrapper_closed_get(textio *self, void *context); if (self->raw != NULL) \ r = _PyFileIO_closed(self->raw); \ else { \ - _res = textiowrapper_closed_get(self, NULL); \ + _res = _io_TextIOWrapper_closed_get_impl(self); \ if (_res == NULL) \ return NULL; \ r = PyObject_IsTrue(_res); \ @@ -3090,7 +3090,7 @@ _io_TextIOWrapper_close_impl(textio *self) int r; CHECK_ATTACHED(self); - res = textiowrapper_closed_get(self, NULL); + res = _io_TextIOWrapper_closed_get_impl(self); if (res == NULL) return NULL; r = PyObject_IsTrue(res); @@ -3164,42 +3164,43 @@ textiowrapper_iternext(textio *self) return line; } +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.name +[clinic start generated code]*/ + static PyObject * -textiowrapper_name_get_impl(textio *self, void *context) +_io_TextIOWrapper_name_get_impl(textio *self) +/*[clinic end generated code: output=8c2f1d6d8756af40 input=26ecec9b39e30e07]*/ { CHECK_ATTACHED(self); return PyObject_GetAttr(self->buffer, &_Py_ID(name)); } -static PyObject * -textiowrapper_name_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_name_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.closed +[clinic start generated code]*/ static PyObject * -textiowrapper_closed_get_impl(textio *self, void *context) +_io_TextIOWrapper_closed_get_impl(textio *self) +/*[clinic end generated code: output=b49b68f443a85e3c input=7dfcf43f63c7003d]*/ { CHECK_ATTACHED(self); return PyObject_GetAttr(self->buffer, &_Py_ID(closed)); } -static PyObject * -textiowrapper_closed_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_closed_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.newlines +[clinic start generated code]*/ static PyObject * -textiowrapper_newlines_get_impl(textio *self, void *context) +_io_TextIOWrapper_newlines_get_impl(textio *self) +/*[clinic end generated code: output=53aa03ac35573180 input=610df647e514b3e8]*/ { PyObject *res; CHECK_ATTACHED(self); @@ -3211,33 +3212,20 @@ textiowrapper_newlines_get_impl(textio *self, void *context) return res; } -static PyObject * -textiowrapper_newlines_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_newlines_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} +/*[clinic input] +@critical_section +@getter +_io.TextIOWrapper.errors +[clinic start generated code]*/ static PyObject * -textiowrapper_errors_get_impl(textio *self, void *context) +_io_TextIOWrapper_errors_get_impl(textio *self) +/*[clinic end generated code: output=dca3a3ef21b09484 input=b45f983e6d43c4d8]*/ { CHECK_INITIALIZED(self); return Py_NewRef(self->errors); } -static PyObject * -textiowrapper_errors_get(textio *self, void *context) -{ - PyObject *result = NULL; - Py_BEGIN_CRITICAL_SECTION(self); - result = textiowrapper_errors_get_impl(self, context); - Py_END_CRITICAL_SECTION(); - return result; -} - /*[clinic input] @critical_section @getter @@ -3349,12 +3337,12 @@ static PyMemberDef textiowrapper_members[] = { }; static PyGetSetDef textiowrapper_getset[] = { - {"name", (getter)textiowrapper_name_get, NULL, NULL}, - {"closed", (getter)textiowrapper_closed_get, NULL, NULL}, + _IO_TEXTIOWRAPPER_NAME_GETSETDEF + _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF /* {"mode", (getter)TextIOWrapper_mode_get, NULL, NULL}, */ - {"newlines", (getter)textiowrapper_newlines_get, NULL, NULL}, - {"errors", (getter)textiowrapper_errors_get, NULL, NULL}, + _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF + _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {NULL} }; From c6e953be125c491226adc1a5bc9c804ce256aa17 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 13:27:43 +0200 Subject: [PATCH 244/442] gh-113090: Fix test.support.os_support.can_chmod() on Windows (GH-113091) --- Lib/test/support/os_helper.py | 10 +++++++--- Lib/test/test_os.py | 2 +- Lib/test/test_posix.py | 4 +++- Lib/test/test_tarfile.py | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 7a67d87fb9e846..20f38fd36a8876 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -247,15 +247,15 @@ def can_chmod(): global _can_chmod if _can_chmod is not None: return _can_chmod - if not hasattr(os, "chown"): + if not hasattr(os, "chmod"): _can_chmod = False return _can_chmod try: with open(TESTFN, "wb") as f: try: - os.chmod(TESTFN, 0o777) + os.chmod(TESTFN, 0o555) mode1 = os.stat(TESTFN).st_mode - os.chmod(TESTFN, 0o666) + os.chmod(TESTFN, 0o777) mode2 = os.stat(TESTFN).st_mode except OSError as e: can = False @@ -302,6 +302,10 @@ def can_dac_override(): else: _can_dac_override = True finally: + try: + os.chmod(TESTFN, 0o700) + except OSError: + pass unlink(TESTFN) return _can_dac_override diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index d4680ef0f0e03f..c66c5797471413 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1743,7 +1743,7 @@ def tearDown(self): os.removedirs(path) -@os_helper.skip_unless_working_chmod +@unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") class ChownFileTests(unittest.TestCase): @classmethod diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 4eb3cacc23768f..887420f8caccfb 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -791,7 +791,7 @@ def check_stat(uid, gid): self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) check_stat(uid, gid) - @os_helper.skip_unless_working_chmod + @unittest.skipUnless(hasattr(os, "chown"), "requires os.chown()") @unittest.skipIf(support.is_emscripten, "getgid() is a stub") def test_chown(self): # raise an OSError if the file does not exist @@ -956,6 +956,7 @@ def check_chmod(self, chmod_func, target, **kwargs): finally: posix.chmod(target, mode) + @os_helper.skip_unless_working_chmod def test_chmod_file(self): self.check_chmod(posix.chmod, os_helper.TESTFN) @@ -965,6 +966,7 @@ def tempdir(self): self.addCleanup(posix.rmdir, target) return target + @os_helper.skip_unless_working_chmod def test_chmod_dir(self): target = self.tempdir() self.check_chmod(posix.chmod, target) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 761560bfbf8b53..edfeac6d6a5edf 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -3452,7 +3452,7 @@ def expect_file(self, name, type=None, symlink_to=None, mode=None, path = pathlib.Path(os.path.normpath(self.destdir / name)) self.assertIn(path, self.expected_paths) self.expected_paths.remove(path) - if mode is not None and os_helper.can_chmod(): + if mode is not None and os_helper.can_chmod() and os.name != 'nt': got = stat.filemode(stat.S_IMODE(path.stat().st_mode)) self.assertEqual(got, mode) if type is None and isinstance(name, str) and name.endswith('/'): From 29f7eb4859bfc27a4c93f36449ca7d810e13288b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 13:28:37 +0200 Subject: [PATCH 245/442] gh-59616: Support os.chmod(follow_symlinks=True) and os.lchmod() on Windows (GH-113049) --- Doc/library/os.rst | 9 +- Doc/whatsnew/3.13.rst | 6 ++ Lib/os.py | 1 + Lib/tempfile.py | 2 +- Lib/test/test_posix.py | 4 +- ...3-12-13-17-08-21.gh-issue-59616.JNlWSs.rst | 3 + Modules/clinic/posixmodule.c.h | 11 +-- Modules/posixmodule.c | 83 +++++++++++++++---- Tools/clinic/clinic.py | 2 +- 9 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 9d2a3d65069253..f4566a6684e14c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2062,6 +2062,7 @@ features: Although Windows supports :func:`chmod`, you can only set the file's read-only flag with it (via the ``stat.S_IWRITE`` and ``stat.S_IREAD`` constants or a corresponding integer value). All other bits are ignored. + The default value of *follow_symlinks* is ``False`` on Windows. The function is limited on Emscripten and WASI, see :ref:`wasm-availability` for more information. @@ -2075,6 +2076,9 @@ features: .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.13 + Added support for the *follow_symlinks* argument on Windows. + .. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True) @@ -2165,11 +2169,14 @@ features: .. audit-event:: os.chmod path,mode,dir_fd os.lchmod - .. availability:: Unix, not Linux, FreeBSD >= 1.3, NetBSD >= 1.3, not OpenBSD + .. availability:: Unix, Windows, not Linux, FreeBSD >= 1.3, NetBSD >= 1.3, not OpenBSD .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.13 + Added support on Windows. + .. function:: lchown(path, uid, gid) Change the owner and group id of *path* to the numeric *uid* and *gid*. This diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index d599ba9ae6fac8..bd2ae653100a43 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -261,6 +261,12 @@ os CPU resources of a container system without having to modify the container (application code). (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`) + pathlib ------- diff --git a/Lib/os.py b/Lib/os.py index a17946750ea7e7..8c4b93250918eb 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -171,6 +171,7 @@ def _add(str, fn): _add("HAVE_FSTATAT", "stat") _add("HAVE_LCHFLAGS", "chflags") _add("HAVE_LCHMOD", "chmod") + _add("MS_WINDOWS", "chmod") if _exists("lchown"): # mac os x10.3 _add("HAVE_LCHOWN", "chown") _add("HAVE_LINKAT", "link") diff --git a/Lib/tempfile.py b/Lib/tempfile.py index cbfc172a789b25..b5a15f7b72c872 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -273,7 +273,7 @@ def _dont_follow_symlinks(func, path, *args): # Pass follow_symlinks=False, unless not supported on this platform. if func in _os.supports_follow_symlinks: func(path, *args, follow_symlinks=False) - elif _os.name == 'nt' or not _os.path.islink(path): + elif not _os.path.islink(path): func(path, *args) def _resetperms(path): diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 887420f8caccfb..55cc5e4c6e4f03 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1019,7 +1019,7 @@ def test_chmod_file_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @os_helper.skip_unless_symlink def test_chmod_dir_symlink(self): @@ -1031,7 +1031,7 @@ def test_chmod_dir_symlink(self): self.check_lchmod_link(posix.chmod, target, link) else: self.check_chmod_link(posix.chmod, target, link) - self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) + self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True) @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') @os_helper.skip_unless_symlink diff --git a/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst b/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst new file mode 100644 index 00000000000000..793ae63b4c1ff5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-13-17-08-21.gh-issue-59616.JNlWSs.rst @@ -0,0 +1,3 @@ +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. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 9d6cd337f4a2f4..f36872a1eb7a0f 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -493,7 +493,8 @@ os_fchdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k #endif /* defined(HAVE_FCHDIR) */ PyDoc_STRVAR(os_chmod__doc__, -"chmod($module, /, path, mode, *, dir_fd=None, follow_symlinks=True)\n" +"chmod($module, /, path, mode, *, dir_fd=None,\n" +" follow_symlinks=(os.name != \'nt\'))\n" "--\n" "\n" "Change the access permissions of a file.\n" @@ -562,7 +563,7 @@ os_chmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw path_t path = PATH_T_INITIALIZE("chmod", "path", 0, PATH_HAVE_FCHMOD); int mode; int dir_fd = DEFAULT_DIR_FD; - int follow_symlinks = 1; + int follow_symlinks = CHMOD_DEFAULT_FOLLOW_SYMLINKS; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { @@ -677,7 +678,7 @@ os_fchmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k #endif /* defined(HAVE_FCHMOD) */ -#if defined(HAVE_LCHMOD) +#if (defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) PyDoc_STRVAR(os_lchmod__doc__, "lchmod($module, /, path, mode)\n" @@ -747,7 +748,7 @@ os_lchmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k return return_value; } -#endif /* defined(HAVE_LCHMOD) */ +#endif /* (defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) */ #if defined(HAVE_CHFLAGS) @@ -12421,4 +12422,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=ff0ec3371de19904 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1be15e60a553b40d input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ddbb4cd43babfc..b464a28e63b8ac 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3309,6 +3309,29 @@ os_fchdir_impl(PyObject *module, int fd) } #endif /* HAVE_FCHDIR */ +#ifdef MS_WINDOWS +# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 0 +#else +# define CHMOD_DEFAULT_FOLLOW_SYMLINKS 1 +#endif + +#ifdef MS_WINDOWS +static int +win32_lchmod(LPCWSTR path, int mode) +{ + DWORD attr = GetFileAttributesW(path); + if (attr == INVALID_FILE_ATTRIBUTES) { + return 0; + } + if (mode & _S_IWRITE) { + attr &= ~FILE_ATTRIBUTE_READONLY; + } + else { + attr |= FILE_ATTRIBUTE_READONLY; + } + return SetFileAttributesW(path, attr); +} +#endif /*[clinic input] os.chmod @@ -3331,7 +3354,8 @@ os.chmod and path should be relative; path will then be relative to that directory. - follow_symlinks: bool = True + follow_symlinks: bool(c_default="CHMOD_DEFAULT_FOLLOW_SYMLINKS", \ + py_default="(os.name != 'nt')") = CHMOD_DEFAULT_FOLLOW_SYMLINKS If False, and the last element of the path is a symbolic link, chmod will modify the symbolic link itself instead of the file the link points to. @@ -3348,20 +3372,16 @@ dir_fd and follow_symlinks may not be implemented on your platform. static PyObject * os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, int follow_symlinks) -/*[clinic end generated code: output=5cf6a94915cc7bff input=674a14bc998de09d]*/ +/*[clinic end generated code: output=5cf6a94915cc7bff input=fcf115d174b9f3d8]*/ { int result; -#ifdef MS_WINDOWS - DWORD attr; -#endif - #ifdef HAVE_FCHMODAT int fchmodat_nofollow_unsupported = 0; int fchmodat_unsupported = 0; #endif -#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD)) +#if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) if (follow_symlinks_specified("chmod", follow_symlinks)) return NULL; #endif @@ -3372,19 +3392,36 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, } #ifdef MS_WINDOWS + result = 0; Py_BEGIN_ALLOW_THREADS - attr = GetFileAttributesW(path->wide); - if (attr == INVALID_FILE_ATTRIBUTES) - result = 0; + if (follow_symlinks) { + HANDLE hfile; + FILE_BASIC_INFO info; + + hfile = CreateFileW(path->wide, + FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES, + 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, + &info, sizeof(info))) + { + if (mode & _S_IWRITE) { + info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + } + else { + info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + } + result = SetFileInformationByHandle(hfile, FileBasicInfo, + &info, sizeof(info)); + } + (void)CloseHandle(hfile); + } + } else { - if (mode & _S_IWRITE) - attr &= ~FILE_ATTRIBUTE_READONLY; - else - attr |= FILE_ATTRIBUTE_READONLY; - result = SetFileAttributesW(path->wide, attr); + result = win32_lchmod(path->wide, mode); } Py_END_ALLOW_THREADS - if (!result) { return path_error(path); } @@ -3514,7 +3551,7 @@ os_fchmod_impl(PyObject *module, int fd, int mode) #endif /* HAVE_FCHMOD */ -#ifdef HAVE_LCHMOD +#if defined(HAVE_LCHMOD) || defined(MS_WINDOWS) /*[clinic input] os.lchmod @@ -3535,6 +3572,15 @@ os_lchmod_impl(PyObject *module, path_t *path, int mode) if (PySys_Audit("os.chmod", "Oii", path->object, mode, -1) < 0) { return NULL; } +#ifdef MS_WINDOWS + Py_BEGIN_ALLOW_THREADS + res = win32_lchmod(path->wide, mode); + Py_END_ALLOW_THREADS + if (!res) { + path_error(path); + return NULL; + } +#else /* MS_WINDOWS */ Py_BEGIN_ALLOW_THREADS res = lchmod(path->narrow, mode); Py_END_ALLOW_THREADS @@ -3542,9 +3588,10 @@ os_lchmod_impl(PyObject *module, path_t *path, int mode) path_error(path); return NULL; } +#endif /* MS_WINDOWS */ Py_RETURN_NONE; } -#endif /* HAVE_LCHMOD */ +#endif /* HAVE_LCHMOD || MS_WINDOWS */ #ifdef HAVE_CHFLAGS diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 5ec088765f3e01..a9bf110291eadd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -3737,7 +3737,7 @@ def converter_init(self, *, accept: TypeSet = {object}) -> None: self.format_unit = 'i' elif accept != {object}: fail(f"bool_converter: illegal 'accept' argument {accept!r}") - if self.default is not unspecified: + if self.default is not unspecified and self.default is not unknown: self.default = bool(self.default) self.c_default = str(int(self.default)) From 12f0bbd6e08bcc1e7165f2641716f7685c1db35c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Dec 2023 13:36:48 +0200 Subject: [PATCH 246/442] gh-112730: Update docs for colour env vars (#112837) --- Doc/using/cmdline.rst | 5 ++++- Doc/whatsnew/3.13.rst | 3 ++- Misc/python.man | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index dac4956b551dd3..e032a1971bc6d6 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -610,7 +610,9 @@ Miscellaneous options .. versionadded:: 3.13 The ``-X presite`` option. -Controlling Color +.. _using-on-controlling-color: + +Controlling color ~~~~~~~~~~~~~~~~~ The Python interpreter is configured by default to use colors to highlight @@ -1133,6 +1135,7 @@ conflict. If this variable is set to ``1``, the interpreter will colorize various kinds of output. Setting it to ``0`` deactivates this behavior. + See also :ref:`using-on-controlling-color`. .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index bd2ae653100a43..e22257853d8333 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -91,7 +91,8 @@ Improved Error Messages * The interpreter now colorizes error messages when displaying tracebacks by default. This feature can be controlled via the new :envvar:`PYTHON_COLORS` environment variable as well as the canonical ``NO_COLOR`` and ``FORCE_COLOR`` environment - variables. (Contributed by Pablo Galindo Salgado in :gh:`112730`.) + variables. See also :ref:`using-on-controlling-color`. + (Contributed by Pablo Galindo Salgado in :gh:`112730`.) Other Language Changes ====================== diff --git a/Misc/python.man b/Misc/python.man index 9f89c94adf5028..14cbd85c60bfa9 100644 --- a/Misc/python.man +++ b/Misc/python.man @@ -601,6 +601,9 @@ show how long each import takes. This is exactly equivalent to setting .IP PYTHONBREAKPOINT If this environment variable is set to 0, it disables the default debugger. It can be set to the callable of your debugger of choice. +.IP PYTHON_COLORS +If this variable is set to 1, the interpreter will colorize various kinds of +output. Setting it to 0 deactivates this behavior. .SS Debug-mode variables Setting these variables only has an effect in a debug build of Python, that is, if Python was configured with the From 1161c14e8c68296fc465cd48970b32be9bee012e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 14 Dec 2023 14:24:24 +0200 Subject: [PATCH 247/442] gh-112716: Fix SystemError when __builtins__ is not a dict (GH-112770) It was raised in two cases: * in the import statement when looking up __import__ * in pickling some builtin type when looking up built-ins iter, getattr, etc. --- Lib/test/test_builtin.py | 26 +++++++++++++++++++ ...-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst | 2 ++ Python/ceval.c | 4 +-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 5e66d58fd2cb18..e15492783aeec1 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -837,6 +837,32 @@ class customdict(dict): # this one should not do anything fancy self.assertRaisesRegex(NameError, "name 'superglobal' is not defined", exec, code, {'__builtins__': customdict()}) + def test_eval_builtins_mapping(self): + code = compile("superglobal", "test", "eval") + # works correctly + ns = {'__builtins__': types.MappingProxyType({'superglobal': 1})} + self.assertEqual(eval(code, ns), 1) + # custom builtins mapping is missing key + ns = {'__builtins__': types.MappingProxyType({})} + self.assertRaisesRegex(NameError, "name 'superglobal' is not defined", + eval, code, ns) + + def test_exec_builtins_mapping_import(self): + code = compile("import foo.bar", "test", "exec") + ns = {'__builtins__': types.MappingProxyType({})} + self.assertRaisesRegex(ImportError, "__import__ not found", exec, code, ns) + ns = {'__builtins__': types.MappingProxyType({'__import__': lambda *args: args})} + exec(code, ns) + self.assertEqual(ns['foo'], ('foo.bar', ns, ns, None, 0)) + + def test_eval_builtins_mapping_reduce(self): + # list_iterator.__reduce__() calls _PyEval_GetBuiltin("iter") + code = compile("x.__reduce__()", "test", "eval") + ns = {'__builtins__': types.MappingProxyType({}), 'x': iter([1, 2])} + self.assertRaisesRegex(AttributeError, "iter", eval, code, ns) + ns = {'__builtins__': types.MappingProxyType({'iter': iter}), 'x': iter([1, 2])} + self.assertEqual(eval(code, ns), (iter, ([1, 2],), 0)) + def test_exec_redirected(self): savestdout = sys.stdout sys.stdout = None # Whatever that cannot flush() diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst new file mode 100644 index 00000000000000..44d63269c5424a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-05-20-41-58.gh-issue-112716.hOcx0Y.rst @@ -0,0 +1,2 @@ +Fix SystemError in the ``import`` statement and in ``__reduce__()`` methods +of builtin types when ``__builtins__`` is not a dict. diff --git a/Python/ceval.c b/Python/ceval.c index d92ab926f84963..8e0be7056919ea 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2417,7 +2417,7 @@ PyObject * _PyEval_GetBuiltin(PyObject *name) { PyObject *attr; - if (PyDict_GetItemRef(PyEval_GetBuiltins(), name, &attr) == 0) { + if (PyMapping_GetOptionalItem(PyEval_GetBuiltins(), name, &attr) == 0) { PyErr_SetObject(PyExc_AttributeError, name); } return attr; @@ -2570,7 +2570,7 @@ import_name(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *name, PyObject *fromlist, PyObject *level) { PyObject *import_func; - if (PyDict_GetItemRef(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) { + if (PyMapping_GetOptionalItem(frame->f_builtins, &_Py_ID(__import__), &import_func) < 0) { return NULL; } if (import_func == NULL) { From 4b3cb082da82da744f5db0b7315aa80558c51557 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 13:30:57 +0000 Subject: [PATCH 248/442] gh-101100: Fix Sphinx nitpicks in `library/inspect.rst` and `reference/simple_stmts.rst` (#113107) --- Doc/conf.py | 4 ++++ Doc/reference/simple_stmts.rst | 6 +++--- Doc/tools/.nitignore | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index f2d36fdc70430c..0d7c0b553eaa74 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -245,6 +245,10 @@ # be resolved, as the method is currently undocumented. For context, see # https://github.com/python/cpython/pull/103289. ('py:meth', '_SubParsersAction.add_parser'), + # Attributes that definitely should be documented better, + # but are deferred for now: + ('py:attr', '__annotations__'), + ('py:attr', '__wrapped__'), ] # gh-106948: Copy standard C types declared in the "c:type" domain to the diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 34c3a620b87223..04132c78ce77a6 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -214,7 +214,7 @@ Assignment of an object to a single target is recursively defined as follows. object. This can either replace an existing key/value pair with the same key value, or insert a new key/value pair (if no key with the same value existed). - For user-defined objects, the :meth:`__setitem__` method is called with + For user-defined objects, the :meth:`~object.__setitem__` method is called with appropriate arguments. .. index:: pair: slicing; assignment @@ -351,7 +351,7 @@ If the right hand side is present, an annotated assignment performs the actual assignment before evaluating annotations (where applicable). If the right hand side is not present for an expression target, then the interpreter evaluates the target except for the last -:meth:`__setitem__` or :meth:`__setattr__` call. +:meth:`~object.__setitem__` or :meth:`~object.__setattr__` call. .. seealso:: @@ -932,7 +932,7 @@ That is not a future statement; it's an ordinary import statement with no special semantics or syntax restrictions. Code compiled by calls to the built-in functions :func:`exec` and :func:`compile` -that occur in a module :mod:`M` containing a future statement will, by default, +that occur in a module :mod:`!M` containing a future statement will, by default, use the new syntax or semantics associated with the future statement. This can be controlled by optional arguments to :func:`compile` --- see the documentation of that function for details. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 75d50fee33a064..c49fcf71dd3326 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -59,7 +59,6 @@ Doc/library/http.client.rst Doc/library/http.cookiejar.rst Doc/library/http.server.rst Doc/library/importlib.rst -Doc/library/inspect.rst Doc/library/locale.rst Doc/library/logging.config.rst Doc/library/logging.handlers.rst @@ -116,7 +115,6 @@ Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst Doc/reference/expressions.rst Doc/reference/import.rst -Doc/reference/simple_stmts.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst Doc/whatsnew/2.0.rst From d9e1b5794a8fade21773d18f91a07f80b55c839c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 14:10:35 +0000 Subject: [PATCH 249/442] gh-101100: Fix Sphinx nitpicks in `library/traceback.rst` (#113106) --- Doc/library/exceptions.rst | 2 +- Doc/library/traceback.rst | 110 +++++++++++++++++++++++++------------ Doc/tools/.nitignore | 1 - 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 04686b6db0036c..f7891f2732bdb1 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -44,7 +44,7 @@ Exception context __suppress_context__ (exception attribute) Three attributes on exception objects provide information about the context in -which an the exception was raised: +which the exception was raised: .. attribute:: BaseException.__context__ BaseException.__cause__ diff --git a/Doc/library/traceback.rst b/Doc/library/traceback.rst index dfbd04de243a3c..ab83e0df10b709 100644 --- a/Doc/library/traceback.rst +++ b/Doc/library/traceback.rst @@ -18,7 +18,8 @@ interpreter. The module uses :ref:`traceback objects ` --- these are objects of type :class:`types.TracebackType`, -which are assigned to the ``__traceback__`` field of :class:`BaseException` instances. +which are assigned to the :attr:`~BaseException.__traceback__` field of +:class:`BaseException` instances. .. seealso:: @@ -32,11 +33,13 @@ The module defines the following functions: .. function:: print_tb(tb, limit=None, file=None) - Print up to *limit* stack trace entries from traceback object *tb* (starting + Print up to *limit* stack trace entries from + :ref:`traceback object ` *tb* (starting from the caller's frame) if *limit* is positive. Otherwise, print the last ``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries are printed. If *file* is omitted or ``None``, the output goes to - ``sys.stderr``; otherwise it should be an open file or file-like object to + :data:`sys.stderr`; otherwise it should be an open + :term:`file ` or :term:`file-like object` to receive the output. .. versionchanged:: 3.5 @@ -46,7 +49,8 @@ The module defines the following functions: .. function:: print_exception(exc, /[, value, tb], limit=None, \ file=None, chain=True) - Print exception information and stack trace entries from traceback object + Print exception information and stack trace entries from + :ref:`traceback object ` *tb* to *file*. This differs from :func:`print_tb` in the following ways: @@ -98,7 +102,8 @@ The module defines the following functions: Print up to *limit* stack trace entries (starting from the invocation point) if *limit* is positive. Otherwise, print the last ``abs(limit)`` entries. If *limit* is omitted or ``None``, all entries are printed. - The optional *f* argument can be used to specify an alternate stack frame + The optional *f* argument can be used to specify an alternate + :ref:`stack frame ` to start. The optional *file* argument has the same meaning as for :func:`print_tb`. @@ -109,20 +114,20 @@ The module defines the following functions: .. function:: extract_tb(tb, limit=None) Return a :class:`StackSummary` object representing a list of "pre-processed" - stack trace entries extracted from the traceback object *tb*. It is useful + stack trace entries extracted from the + :ref:`traceback object ` *tb*. It is useful for alternate formatting of stack traces. The optional *limit* argument has the same meaning as for :func:`print_tb`. A "pre-processed" stack trace entry is a :class:`FrameSummary` object containing attributes :attr:`~FrameSummary.filename`, :attr:`~FrameSummary.lineno`, :attr:`~FrameSummary.name`, and :attr:`~FrameSummary.line` representing the - information that is usually printed for a stack trace. The - :attr:`~FrameSummary.line` is a string with leading and trailing - whitespace stripped; if the source is not available it is ``None``. + information that is usually printed for a stack trace. .. function:: extract_stack(f=None, limit=None) - Extract the raw traceback from the current stack frame. The return value has + Extract the raw traceback from the current + :ref:`stack frame `. The return value has the same format as for :func:`extract_tb`. The optional *f* and *limit* arguments have the same meaning as for :func:`print_stack`. @@ -140,7 +145,7 @@ The module defines the following functions: .. function:: format_exception_only(exc, /[, value], *, show_group=False) Format the exception part of a traceback using an exception value such as - given by ``sys.last_value``. The return value is a list of strings, each + given by :data:`sys.last_value`. The return value is a list of strings, each ending in a newline. The list contains the exception's message, which is normally a single string; however, for :exc:`SyntaxError` exceptions, it contains several lines that (when printed) display detailed information @@ -160,7 +165,8 @@ The module defines the following functions: positional-only. .. versionchanged:: 3.11 - The returned list now includes any notes attached to the exception. + The returned list now includes any + :attr:`notes ` attached to the exception. .. versionchanged:: 3.13 *show_group* parameter was added. @@ -199,14 +205,17 @@ The module defines the following functions: .. function:: clear_frames(tb) - Clears the local variables of all the stack frames in a traceback *tb* - by calling the :meth:`clear` method of each frame object. + Clears the local variables of all the stack frames in a + :ref:`traceback ` *tb* + by calling the :meth:`~frame.clear` method of each + :ref:`frame object `. .. versionadded:: 3.4 .. function:: walk_stack(f) - Walk a stack following ``f.f_back`` from the given frame, yielding the frame + Walk a stack following :attr:`f.f_back ` from the given frame, + yielding the frame and line number for each frame. If *f* is ``None``, the current stack is used. This helper is used with :meth:`StackSummary.extract`. @@ -222,12 +231,12 @@ The module defines the following functions: The module also defines the following classes: -:class:`TracebackException` Objects ------------------------------------ +:class:`!TracebackException` Objects +------------------------------------ .. versionadded:: 3.5 -:class:`TracebackException` objects are created from actual exceptions to +:class:`!TracebackException` objects are created from actual exceptions to capture data for later printing in a lightweight fashion. .. class:: TracebackException(exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, compact=False, max_group_width=15, max_group_depth=10) @@ -379,33 +388,34 @@ capture data for later printing in a lightweight fashion. well, recursively, with indentation relative to their nesting depth. .. versionchanged:: 3.11 - The exception's notes are now included in the output. + The exception's :attr:`notes ` are now + included in the output. .. versionchanged:: 3.13 Added the *show_group* parameter. -:class:`StackSummary` Objects ------------------------------ +:class:`!StackSummary` Objects +------------------------------ .. versionadded:: 3.5 -:class:`StackSummary` objects represent a call stack ready for formatting. +:class:`!StackSummary` objects represent a call stack ready for formatting. .. class:: StackSummary .. classmethod:: extract(frame_gen, *, limit=None, lookup_lines=True, capture_locals=False) - Construct a :class:`StackSummary` object from a frame generator (such as + Construct a :class:`!StackSummary` object from a frame generator (such as is returned by :func:`~traceback.walk_stack` or :func:`~traceback.walk_tb`). If *limit* is supplied, only this many frames are taken from *frame_gen*. If *lookup_lines* is ``False``, the returned :class:`FrameSummary` objects will not have read their lines in yet, making the cost of - creating the :class:`StackSummary` cheaper (which may be valuable if it + creating the :class:`!StackSummary` cheaper (which may be valuable if it may not actually get formatted). If *capture_locals* is ``True`` the - local variables in each :class:`FrameSummary` are captured as object + local variables in each :class:`!FrameSummary` are captured as object representations. .. versionchanged:: 3.12 @@ -414,14 +424,16 @@ capture data for later printing in a lightweight fashion. .. classmethod:: from_list(a_list) - Construct a :class:`StackSummary` object from a supplied list of + Construct a :class:`!StackSummary` object from a supplied list of :class:`FrameSummary` objects or old-style list of tuples. Each tuple - should be a 4-tuple with filename, lineno, name, line as the elements. + should be a 4-tuple with *filename*, *lineno*, *name*, *line* as the + elements. .. method:: format() Returns a list of strings ready for printing. Each string in the - resulting list corresponds to a single frame from the stack. + resulting list corresponds to a single :ref:`frame ` from + the stack. Each string ends in a newline; the strings may contain internal newlines as well, for those items with source text lines. @@ -434,7 +446,8 @@ capture data for later printing in a lightweight fashion. .. method:: format_frame_summary(frame_summary) - Returns a string for printing one of the frames involved in the stack. + Returns a string for printing one of the :ref:`frames ` + involved in the stack. This method is called for each :class:`FrameSummary` object to be printed by :meth:`StackSummary.format`. If it returns ``None``, the frame is omitted from the output. @@ -442,25 +455,50 @@ capture data for later printing in a lightweight fashion. .. versionadded:: 3.11 -:class:`FrameSummary` Objects ------------------------------ +:class:`!FrameSummary` Objects +------------------------------ .. versionadded:: 3.5 -A :class:`FrameSummary` object represents a single frame in a traceback. +A :class:`!FrameSummary` object represents a single :ref:`frame ` +in a :ref:`traceback `. .. class:: FrameSummary(filename, lineno, name, lookup_line=True, locals=None, line=None) - Represent a single frame in the traceback or stack that is being formatted - or printed. It may optionally have a stringified version of the frames + Represents a single :ref:`frame ` in the + :ref:`traceback ` or stack that is being formatted + or printed. It may optionally have a stringified version of the frame's locals included in it. If *lookup_line* is ``False``, the source code is not - looked up until the :class:`FrameSummary` has the :attr:`~FrameSummary.line` - attribute accessed (which also happens when casting it to a tuple). + looked up until the :class:`!FrameSummary` has the :attr:`~FrameSummary.line` + attribute accessed (which also happens when casting it to a :class:`tuple`). :attr:`~FrameSummary.line` may be directly provided, and will prevent line lookups happening at all. *locals* is an optional local variable dictionary, and if supplied the variable representations are stored in the summary for later display. + :class:`!FrameSummary` instances have the following attributes: + + .. attribute:: FrameSummary.filename + + The filename of the source code for this frame. Equivalent to accessing + :attr:`f.f_code.co_filename ` on a + :ref:`frame object ` *f*. + + .. attribute:: FrameSummary.lineno + + The line number of the source code for this frame. + + .. attribute:: FrameSummary.name + + Equivalent to accessing :attr:`f.f_code.co_name ` on + a :ref:`frame object ` *f*. + + .. attribute:: FrameSummary.line + + A string representing the source code for this frame, with leading and + trailing whitespace stripped. + If the source is not available, it is ``None``. + .. _traceback-example: Traceback Examples diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index c49fcf71dd3326..4d1d31d44fcf75 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -96,7 +96,6 @@ Doc/library/test.rst Doc/library/tkinter.rst Doc/library/tkinter.scrolledtext.rst Doc/library/tkinter.ttk.rst -Doc/library/traceback.rst Doc/library/unittest.mock.rst Doc/library/unittest.rst Doc/library/urllib.parse.rst From 6873555955497e9adbc72fc0e38df5261844aafd Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 14 Dec 2023 14:26:44 +0000 Subject: [PATCH 250/442] GH-112354: Treat _EXIT_TRACE like an unconditional side exit (GH-113104) --- Include/internal/pycore_opcode_metadata.h | 2 +- Python/bytecodes.c | 2 +- Python/ceval.c | 19 ++----------------- Python/ceval_macros.h | 2 -- Python/executor_cases.c.h | 2 +- 5 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 2c512d97c421c9..4670c34a833963 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1689,7 +1689,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [_JUMP_TO_TOP] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG }, [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [_SAVE_RETURN_OFFSET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_EXIT_TRACE] = { true, INSTR_FMT_IX, 0 }, + [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, }; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 1ae83422730f8f..68bb15c2b536eb 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4027,7 +4027,7 @@ dummy_func( op(_EXIT_TRACE, (--)) { TIER_TWO_ONLY - GOTO_TIER_ONE(); + DEOPT_IF(1); } op(_INSERT, (unused[oparg], top -- top, unused[oparg])) { diff --git a/Python/ceval.c b/Python/ceval.c index 8e0be7056919ea..27304d31e27949 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1063,31 +1063,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int // Jump here from DEOPT_IF() deoptimize: - // On DEOPT_IF we just repeat the last instruction. - // This presumes nothing was popped from the stack (nor pushed). - frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); + next_instr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); DPRINTF(2, "DEOPT: [UOp %d (%s), oparg %d, operand %" PRIu64 ", target %d @ %d -> %s]\n", uopcode, _PyUOpName(uopcode), next_uop[-1].oparg, next_uop[-1].operand, next_uop[-1].target, (int)(next_uop - current_executor->trace - 1), _PyOpcode_OpName[frame->instr_ptr->op.code]); OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); UOP_STAT_INC(uopcode, miss); - frame->return_offset = 0; // Dispatch to frame->instr_ptr - _PyFrame_SetStackPointer(frame, stack_pointer); Py_DECREF(current_executor); - // Fall through -// Jump here from ENTER_EXECUTOR -enter_tier_one: - next_instr = frame->instr_ptr; - goto resume_frame; + DISPATCH(); -// Jump here from _EXIT_TRACE -exit_trace: - _PyFrame_SetStackPointer(frame, stack_pointer); - frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame)); - Py_DECREF(current_executor); - OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); - goto enter_tier_one; } #if defined(__GNUC__) # pragma GCC diagnostic pop diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index ac44aecae046d8..a3606b17b71c62 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -392,8 +392,6 @@ stack_pointer = _PyFrame_GetStackPointer(frame); #define GOTO_TIER_TWO() goto enter_tier_two; -#define GOTO_TIER_ONE() goto exit_trace; - #define CURRENT_OPARG() (next_uop[-1].oparg) #define CURRENT_OPERAND() (next_uop[-1].operand) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 14d9dd6e95e533..2519a4ee546a5e 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -3516,7 +3516,7 @@ case _EXIT_TRACE: { TIER_TWO_ONLY - GOTO_TIER_ONE(); + if (1) goto deoptimize; break; } From fd81afc624402816e3455fd8e932ac7702e4d988 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 14 Dec 2023 15:16:39 +0000 Subject: [PATCH 251/442] gh-86179: Avoid making case-only changes when calculating realpath() during initialization (GH-113077) --- Modules/getpath.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/getpath.c b/Modules/getpath.c index 422056b1fb6de4..afa9273ebc59b7 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -506,12 +506,17 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) HANDLE hFile; wchar_t resolved[MAXPATHLEN+1]; int len = 0, err; + Py_ssize_t pathlen; PyObject *result; - wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + wchar_t *path = PyUnicode_AsWideCharString(pathobj, &pathlen); if (!path) { return NULL; } + if (wcslen(path) != pathlen) { + PyErr_SetString(PyExc_ValueError, "path contains embedded nulls"); + return NULL; + } Py_BEGIN_ALLOW_THREADS hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -535,7 +540,11 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args) len -= 4; } } - result = PyUnicode_FromWideChar(p, len); + if (CompareStringOrdinal(path, (int)pathlen, p, len, TRUE) == CSTR_EQUAL) { + result = Py_NewRef(pathobj); + } else { + result = PyUnicode_FromWideChar(p, len); + } } else { result = Py_NewRef(pathobj); } From fb4cb7ce310e28e25d3adf33b29cb97c637097ed Mon Sep 17 00:00:00 2001 From: jeremy-dolan <129558107+jeremy-dolan@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:40:24 -0500 Subject: [PATCH 252/442] gh-113113: doc: use less ambiguously named variable (gh-113114) --- Doc/tutorial/controlflow.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/tutorial/controlflow.rst b/Doc/tutorial/controlflow.rst index aa9caa101da40a..77444f9cb8358d 100644 --- a/Doc/tutorial/controlflow.rst +++ b/Doc/tutorial/controlflow.rst @@ -559,10 +559,10 @@ defined to allow. For example:: def ask_ok(prompt, retries=4, reminder='Please try again!'): while True: - ok = input(prompt) - if ok in ('y', 'ye', 'yes'): + reply = input(prompt) + if reply in {'y', 'ye', 'yes'}: return True - if ok in ('n', 'no', 'nop', 'nope'): + if reply in {'n', 'no', 'nop', 'nope'}: return False retries = retries - 1 if retries < 0: From e24eccbc1cf5f22743cd5cda733cd04891155d54 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 14 Dec 2023 16:41:52 +0000 Subject: [PATCH 253/442] GH-111485: Sort metadata tables for easier checking of future diffs (GH-113101) --- Include/internal/pycore_opcode_metadata.h | 3042 ++++++++++----------- Tools/cases_generator/generate_cases.py | 14 +- 2 files changed, 1529 insertions(+), 1527 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 4670c34a833963..36b6cd52d2b272 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -31,636 +31,636 @@ extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); #ifdef NEED_OPCODE_METADATA int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { switch(opcode) { - case NOP: - return 0; - case RESUME: - return 0; - case RESUME_CHECK: - return 0; - case INSTRUMENTED_RESUME: - return 0; - case LOAD_CLOSURE: - return 0; - case LOAD_FAST_CHECK: - return 0; - case LOAD_FAST: - return 0; - case LOAD_FAST_AND_CLEAR: - return 0; - case LOAD_FAST_LOAD_FAST: - return 0; - case LOAD_CONST: - return 0; - case STORE_FAST: - return 1; - case STORE_FAST_MAYBE_NULL: - return 1; - case STORE_FAST_LOAD_FAST: + case BEFORE_ASYNC_WITH: return 1; - case STORE_FAST_STORE_FAST: - return 2; - case POP_TOP: + case BEFORE_WITH: return 1; - case PUSH_NULL: - return 0; - case END_FOR: - return 2; - case INSTRUMENTED_END_FOR: - return 2; - case END_SEND: + case BINARY_OP: return 2; - case INSTRUMENTED_END_SEND: + case BINARY_OP_ADD_FLOAT: return 2; - case UNARY_NEGATIVE: - return 1; - case UNARY_NOT: - return 1; - case _SPECIALIZE_TO_BOOL: - return 1; - case _TO_BOOL: - return 1; - case TO_BOOL: - return 1; - case TO_BOOL_BOOL: - return 1; - case TO_BOOL_INT: - return 1; - case TO_BOOL_LIST: - return 1; - case TO_BOOL_NONE: - return 1; - case TO_BOOL_STR: - return 1; - case TO_BOOL_ALWAYS_TRUE: - return 1; - case UNARY_INVERT: - return 1; - case _GUARD_BOTH_INT: + case BINARY_OP_ADD_INT: return 2; - case _BINARY_OP_MULTIPLY_INT: + case BINARY_OP_ADD_UNICODE: return 2; - case _BINARY_OP_ADD_INT: + case BINARY_OP_INPLACE_ADD_UNICODE: return 2; - case _BINARY_OP_SUBTRACT_INT: + case BINARY_OP_MULTIPLY_FLOAT: return 2; case BINARY_OP_MULTIPLY_INT: return 2; - case BINARY_OP_ADD_INT: + case BINARY_OP_SUBTRACT_FLOAT: return 2; case BINARY_OP_SUBTRACT_INT: return 2; - case _GUARD_BOTH_FLOAT: - return 2; - case _BINARY_OP_MULTIPLY_FLOAT: - return 2; - case _BINARY_OP_ADD_FLOAT: - return 2; - case _BINARY_OP_SUBTRACT_FLOAT: - return 2; - case BINARY_OP_MULTIPLY_FLOAT: + case BINARY_SLICE: + return 3; + case BINARY_SUBSCR: return 2; - case BINARY_OP_ADD_FLOAT: + case BINARY_SUBSCR_DICT: return 2; - case BINARY_OP_SUBTRACT_FLOAT: + case BINARY_SUBSCR_GETITEM: return 2; - case _GUARD_BOTH_UNICODE: + case BINARY_SUBSCR_LIST_INT: return 2; - case _BINARY_OP_ADD_UNICODE: + case BINARY_SUBSCR_STR_INT: return 2; - case BINARY_OP_ADD_UNICODE: + case BINARY_SUBSCR_TUPLE_INT: return 2; - case _BINARY_OP_INPLACE_ADD_UNICODE: + case BUILD_CONST_KEY_MAP: + return oparg + 1; + case BUILD_LIST: + return oparg; + case BUILD_MAP: + return oparg*2; + case BUILD_SET: + return oparg; + case BUILD_SLICE: + return ((oparg == 3) ? 1 : 0) + 2; + case BUILD_STRING: + return oparg; + case BUILD_TUPLE: + return oparg; + case CACHE: + return 0; + case CALL: + return oparg + 2; + case CALL_ALLOC_AND_ENTER_INIT: + return oparg + 2; + case CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case CALL_BUILTIN_CLASS: + return oparg + 2; + case CALL_BUILTIN_FAST: + return oparg + 2; + case CALL_BUILTIN_FAST_WITH_KEYWORDS: + return oparg + 2; + case CALL_BUILTIN_O: + return oparg + 2; + case CALL_FUNCTION_EX: + return ((oparg & 1) ? 1 : 0) + 3; + case CALL_INTRINSIC_1: + return 1; + case CALL_INTRINSIC_2: return 2; - case BINARY_OP_INPLACE_ADD_UNICODE: + case CALL_ISINSTANCE: + return oparg + 2; + case CALL_KW: + return oparg + 3; + case CALL_LEN: + return oparg + 2; + case CALL_LIST_APPEND: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_FAST: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_NOARGS: + return oparg + 2; + case CALL_METHOD_DESCRIPTOR_O: + return oparg + 2; + case CALL_PY_EXACT_ARGS: + return oparg + 2; + case CALL_PY_WITH_DEFAULTS: + return oparg + 2; + case CALL_STR_1: + return oparg + 2; + case CALL_TUPLE_1: + return oparg + 2; + case CALL_TYPE_1: + return oparg + 2; + case CHECK_EG_MATCH: return 2; - case _SPECIALIZE_BINARY_SUBSCR: + case CHECK_EXC_MATCH: return 2; - case _BINARY_SUBSCR: + case CLEANUP_THROW: + return 3; + case COMPARE_OP: return 2; - case BINARY_SUBSCR: + case COMPARE_OP_FLOAT: return 2; - case BINARY_SLICE: - return 3; - case STORE_SLICE: - return 4; - case BINARY_SUBSCR_LIST_INT: + case COMPARE_OP_INT: return 2; - case BINARY_SUBSCR_STR_INT: + case COMPARE_OP_STR: return 2; - case BINARY_SUBSCR_TUPLE_INT: + case CONTAINS_OP: return 2; - case BINARY_SUBSCR_DICT: + case CONVERT_VALUE: + return 1; + case COPY: + return (oparg-1) + 1; + case COPY_FREE_VARS: + return 0; + case DELETE_ATTR: + return 1; + case DELETE_DEREF: + return 0; + case DELETE_FAST: + return 0; + case DELETE_GLOBAL: + return 0; + case DELETE_NAME: + return 0; + case DELETE_SUBSCR: return 2; - case BINARY_SUBSCR_GETITEM: + case DICT_MERGE: + return (oparg - 1) + 5; + case DICT_UPDATE: + return (oparg - 1) + 2; + case END_ASYNC_FOR: return 2; - case LIST_APPEND: - return (oparg-1) + 2; - case SET_ADD: - return (oparg-1) + 2; - case _SPECIALIZE_STORE_SUBSCR: + case END_FOR: return 2; - case _STORE_SUBSCR: - return 3; - case STORE_SUBSCR: - return 3; - case STORE_SUBSCR_LIST_INT: - return 3; - case STORE_SUBSCR_DICT: - return 3; - case DELETE_SUBSCR: + case END_SEND: return 2; - case CALL_INTRINSIC_1: + case ENTER_EXECUTOR: + return 0; + case EXIT_INIT_CHECK: return 1; - case CALL_INTRINSIC_2: + case EXTENDED_ARG: + return 0; + case FORMAT_SIMPLE: + return 1; + case FORMAT_WITH_SPEC: return 2; - case RAISE_VARARGS: - return oparg; - case INTERPRETER_EXIT: + case FOR_ITER: return 1; - case _POP_FRAME: + case FOR_ITER_GEN: return 1; - case RETURN_VALUE: + case FOR_ITER_LIST: return 1; - case INSTRUMENTED_RETURN_VALUE: + case FOR_ITER_RANGE: + return 1; + case FOR_ITER_TUPLE: return 1; - case RETURN_CONST: - return 0; - case INSTRUMENTED_RETURN_CONST: - return 0; case GET_AITER: return 1; case GET_ANEXT: return 1; case GET_AWAITABLE: return 1; - case _SPECIALIZE_SEND: - return 2; - case _SEND: - return 2; - case SEND: - return 2; - case SEND_GEN: - return 2; - case INSTRUMENTED_YIELD_VALUE: + case GET_ITER: return 1; - case YIELD_VALUE: + case GET_LEN: return 1; - case POP_EXCEPT: + case GET_YIELD_FROM_ITER: return 1; - case RERAISE: - return oparg + 1; - case END_ASYNC_FOR: + case IMPORT_FROM: + return 1; + case IMPORT_NAME: return 2; - case CLEANUP_THROW: + case INSTRUMENTED_CALL: + return 0; + case INSTRUMENTED_CALL_FUNCTION_EX: + return 0; + case INSTRUMENTED_CALL_KW: + return 0; + case INSTRUMENTED_END_FOR: + return 2; + case INSTRUMENTED_END_SEND: + return 2; + case INSTRUMENTED_FOR_ITER: + return 0; + case INSTRUMENTED_INSTRUCTION: + return 0; + case INSTRUMENTED_JUMP_BACKWARD: + return 0; + case INSTRUMENTED_JUMP_FORWARD: + return 0; + case INSTRUMENTED_LOAD_SUPER_ATTR: return 3; - case LOAD_ASSERTION_ERROR: + case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; - case LOAD_BUILD_CLASS: + case INSTRUMENTED_POP_JUMP_IF_NONE: return 0; - case STORE_NAME: + case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: + return 0; + case INSTRUMENTED_POP_JUMP_IF_TRUE: + return 0; + case INSTRUMENTED_RESUME: + return 0; + case INSTRUMENTED_RETURN_CONST: + return 0; + case INSTRUMENTED_RETURN_VALUE: return 1; - case DELETE_NAME: + case INSTRUMENTED_YIELD_VALUE: + return 1; + case INTERPRETER_EXIT: + return 1; + case IS_OP: + return 2; + case JUMP: return 0; - case _SPECIALIZE_UNPACK_SEQUENCE: + case JUMP_BACKWARD: + return 0; + case JUMP_BACKWARD_NO_INTERRUPT: + return 0; + case JUMP_FORWARD: + return 0; + case JUMP_NO_INTERRUPT: + return 0; + case LIST_APPEND: + return (oparg-1) + 2; + case LIST_EXTEND: + return (oparg-1) + 2; + case LOAD_ASSERTION_ERROR: + return 0; + case LOAD_ATTR: return 1; - case _UNPACK_SEQUENCE: + case LOAD_ATTR_CLASS: return 1; - case UNPACK_SEQUENCE: + case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: return 1; - case UNPACK_SEQUENCE_TWO_TUPLE: + case LOAD_ATTR_INSTANCE_VALUE: return 1; - case UNPACK_SEQUENCE_TUPLE: + case LOAD_ATTR_METHOD_LAZY_DICT: return 1; - case UNPACK_SEQUENCE_LIST: + case LOAD_ATTR_METHOD_NO_DICT: return 1; - case UNPACK_EX: + case LOAD_ATTR_METHOD_WITH_VALUES: return 1; - case _SPECIALIZE_STORE_ATTR: + case LOAD_ATTR_MODULE: return 1; - case _STORE_ATTR: - return 2; - case STORE_ATTR: - return 2; - case DELETE_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case STORE_GLOBAL: + case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case DELETE_GLOBAL: - return 0; - case LOAD_LOCALS: - return 0; - case LOAD_FROM_DICT_OR_GLOBALS: + case LOAD_ATTR_PROPERTY: return 1; - case LOAD_NAME: + case LOAD_ATTR_SLOT: + return 1; + case LOAD_ATTR_WITH_HINT: + return 1; + case LOAD_BUILD_CLASS: return 0; - case _SPECIALIZE_LOAD_GLOBAL: + case LOAD_CLOSURE: return 0; - case _LOAD_GLOBAL: + case LOAD_CONST: return 0; - case LOAD_GLOBAL: + case LOAD_DEREF: return 0; - case _GUARD_GLOBALS_VERSION: + case LOAD_FAST: return 0; - case _GUARD_BUILTINS_VERSION: + case LOAD_FAST_AND_CLEAR: return 0; - case _LOAD_GLOBAL_MODULE: + case LOAD_FAST_CHECK: return 0; - case _LOAD_GLOBAL_BUILTINS: + case LOAD_FAST_LOAD_FAST: return 0; - case LOAD_GLOBAL_MODULE: + case LOAD_FROM_DICT_OR_DEREF: + return 1; + case LOAD_FROM_DICT_OR_GLOBALS: + return 1; + case LOAD_GLOBAL: return 0; case LOAD_GLOBAL_BUILTIN: return 0; - case DELETE_FAST: - return 0; - case MAKE_CELL: - return 0; - case DELETE_DEREF: + case LOAD_GLOBAL_MODULE: return 0; - case LOAD_FROM_DICT_OR_DEREF: - return 1; - case LOAD_DEREF: + case LOAD_LOCALS: return 0; - case STORE_DEREF: + case LOAD_METHOD: return 1; - case COPY_FREE_VARS: - return 0; - case BUILD_STRING: - return oparg; - case BUILD_TUPLE: - return oparg; - case BUILD_LIST: - return oparg; - case LIST_EXTEND: - return (oparg-1) + 2; - case SET_UPDATE: - return (oparg-1) + 2; - case BUILD_SET: - return oparg; - case BUILD_MAP: - return oparg*2; - case SETUP_ANNOTATIONS: + case LOAD_NAME: return 0; - case BUILD_CONST_KEY_MAP: - return oparg + 1; - case DICT_UPDATE: - return (oparg - 1) + 2; - case DICT_MERGE: - return (oparg - 1) + 5; - case MAP_ADD: - return (oparg - 1) + 3; - case INSTRUMENTED_LOAD_SUPER_ATTR: - return 3; - case _SPECIALIZE_LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR: return 3; - case _LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR_ATTR: return 3; - case LOAD_SUPER_ATTR: + case LOAD_SUPER_ATTR_METHOD: return 3; case LOAD_SUPER_METHOD: return 3; - case LOAD_ZERO_SUPER_METHOD: - return 3; case LOAD_ZERO_SUPER_ATTR: return 3; - case LOAD_SUPER_ATTR_ATTR: - return 3; - case LOAD_SUPER_ATTR_METHOD: + case LOAD_ZERO_SUPER_METHOD: return 3; - case _SPECIALIZE_LOAD_ATTR: + case MAKE_CELL: + return 0; + case MAKE_FUNCTION: return 1; - case _LOAD_ATTR: + case MAP_ADD: + return (oparg - 1) + 3; + case MATCH_CLASS: + return 3; + case MATCH_KEYS: + return 2; + case MATCH_MAPPING: return 1; - case LOAD_ATTR: + case MATCH_SEQUENCE: return 1; - case LOAD_METHOD: + case NOP: + return 0; + case POP_BLOCK: + return 0; + case POP_EXCEPT: return 1; - case _GUARD_TYPE_VERSION: + case POP_JUMP_IF_FALSE: return 1; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: + case POP_JUMP_IF_NONE: return 1; - case _LOAD_ATTR_INSTANCE_VALUE: + case POP_JUMP_IF_NOT_NONE: return 1; - case LOAD_ATTR_INSTANCE_VALUE: + case POP_JUMP_IF_TRUE: return 1; - case _CHECK_ATTR_MODULE: + case POP_TOP: return 1; - case _LOAD_ATTR_MODULE: + case PUSH_EXC_INFO: return 1; - case LOAD_ATTR_MODULE: + case PUSH_NULL: + return 0; + case RAISE_VARARGS: + return oparg; + case RERAISE: + return oparg + 1; + case RESERVED: + return 0; + case RESUME: + return 0; + case RESUME_CHECK: + return 0; + case RETURN_CONST: + return 0; + case RETURN_GENERATOR: + return 0; + case RETURN_VALUE: return 1; - case _CHECK_ATTR_WITH_HINT: + case SEND: + return 2; + case SEND_GEN: + return 2; + case SETUP_ANNOTATIONS: + return 0; + case SETUP_CLEANUP: + return 0; + case SETUP_FINALLY: + return 0; + case SETUP_WITH: + return 0; + case SET_ADD: + return (oparg-1) + 2; + case SET_FUNCTION_ATTRIBUTE: + return 2; + case SET_UPDATE: + return (oparg-1) + 2; + case STORE_ATTR: + return 2; + case STORE_ATTR_INSTANCE_VALUE: + return 2; + case STORE_ATTR_SLOT: + return 2; + case STORE_ATTR_WITH_HINT: + return 2; + case STORE_DEREF: return 1; - case _LOAD_ATTR_WITH_HINT: + case STORE_FAST: return 1; - case LOAD_ATTR_WITH_HINT: + case STORE_FAST_LOAD_FAST: return 1; - case _LOAD_ATTR_SLOT: + case STORE_FAST_MAYBE_NULL: return 1; - case LOAD_ATTR_SLOT: + case STORE_FAST_STORE_FAST: + return 2; + case STORE_GLOBAL: return 1; - case _CHECK_ATTR_CLASS: + case STORE_NAME: return 1; - case _LOAD_ATTR_CLASS: + case STORE_SLICE: + return 4; + case STORE_SUBSCR: + return 3; + case STORE_SUBSCR_DICT: + return 3; + case STORE_SUBSCR_LIST_INT: + return 3; + case SWAP: + return (oparg-2) + 2; + case TO_BOOL: return 1; - case LOAD_ATTR_CLASS: + case TO_BOOL_ALWAYS_TRUE: return 1; - case LOAD_ATTR_PROPERTY: + case TO_BOOL_BOOL: return 1; - case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + case TO_BOOL_INT: return 1; - case _GUARD_DORV_VALUES: + case TO_BOOL_LIST: return 1; - case _STORE_ATTR_INSTANCE_VALUE: - return 2; - case STORE_ATTR_INSTANCE_VALUE: - return 2; - case STORE_ATTR_WITH_HINT: - return 2; - case _STORE_ATTR_SLOT: - return 2; - case STORE_ATTR_SLOT: - return 2; - case _SPECIALIZE_COMPARE_OP: - return 2; - case _COMPARE_OP: + case TO_BOOL_NONE: + return 1; + case TO_BOOL_STR: + return 1; + case UNARY_INVERT: + return 1; + case UNARY_NEGATIVE: + return 1; + case UNARY_NOT: + return 1; + case UNPACK_EX: + return 1; + case UNPACK_SEQUENCE: + return 1; + case UNPACK_SEQUENCE_LIST: + return 1; + case UNPACK_SEQUENCE_TUPLE: + return 1; + case UNPACK_SEQUENCE_TWO_TUPLE: + return 1; + case WITH_EXCEPT_START: + return 4; + case YIELD_VALUE: + return 1; + case _BINARY_OP: return 2; - case COMPARE_OP: + case _BINARY_OP_ADD_FLOAT: return 2; - case COMPARE_OP_FLOAT: + case _BINARY_OP_ADD_INT: return 2; - case COMPARE_OP_INT: + case _BINARY_OP_ADD_UNICODE: return 2; - case COMPARE_OP_STR: + case _BINARY_OP_INPLACE_ADD_UNICODE: return 2; - case IS_OP: + case _BINARY_OP_MULTIPLY_FLOAT: return 2; - case CONTAINS_OP: + case _BINARY_OP_MULTIPLY_INT: return 2; - case CHECK_EG_MATCH: + case _BINARY_OP_SUBTRACT_FLOAT: return 2; - case CHECK_EXC_MATCH: + case _BINARY_OP_SUBTRACT_INT: return 2; - case IMPORT_NAME: + case _BINARY_SUBSCR: return 2; - case IMPORT_FROM: - return 1; - case JUMP_FORWARD: - return 0; - case JUMP_BACKWARD: - return 0; - case JUMP: - return 0; - case JUMP_NO_INTERRUPT: - return 0; - case ENTER_EXECUTOR: - return 0; - case _POP_JUMP_IF_FALSE: - return 1; - case _POP_JUMP_IF_TRUE: - return 1; - case _IS_NONE: + case _CALL: + return oparg + 2; + case _CHECK_ATTR_CLASS: return 1; - case POP_JUMP_IF_TRUE: + case _CHECK_ATTR_METHOD_LAZY_DICT: return 1; - case POP_JUMP_IF_FALSE: + case _CHECK_ATTR_MODULE: return 1; - case POP_JUMP_IF_NONE: + case _CHECK_ATTR_WITH_HINT: return 1; - case POP_JUMP_IF_NOT_NONE: + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _CHECK_FUNCTION_EXACT_ARGS: + return oparg + 2; + case _CHECK_MANAGED_OBJECT_HAS_VALUES: return 1; - case JUMP_BACKWARD_NO_INTERRUPT: + case _CHECK_PEP_523: return 0; - case GET_LEN: - return 1; - case MATCH_CLASS: - return 3; - case MATCH_MAPPING: - return 1; - case MATCH_SEQUENCE: - return 1; - case MATCH_KEYS: + case _CHECK_STACK_SPACE: + return oparg + 2; + case _CHECK_VALIDITY: + return 0; + case _COMPARE_OP: return 2; - case GET_ITER: - return 1; - case GET_YIELD_FROM_ITER: - return 1; - case _SPECIALIZE_FOR_ITER: - return 1; + case _EXIT_TRACE: + return 0; case _FOR_ITER: return 1; case _FOR_ITER_TIER_TWO: return 1; - case FOR_ITER: + case _GUARD_BOTH_FLOAT: + return 2; + case _GUARD_BOTH_INT: + return 2; + case _GUARD_BOTH_UNICODE: + return 2; + case _GUARD_BUILTINS_VERSION: + return 0; + case _GUARD_DORV_VALUES: return 1; - case INSTRUMENTED_FOR_ITER: + case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + return 1; + case _GUARD_GLOBALS_VERSION: return 0; - case _ITER_CHECK_LIST: + case _GUARD_IS_FALSE_POP: return 1; - case _ITER_JUMP_LIST: + case _GUARD_IS_NONE_POP: return 1; - case _GUARD_NOT_EXHAUSTED_LIST: + case _GUARD_IS_NOT_NONE_POP: return 1; - case _ITER_NEXT_LIST: + case _GUARD_IS_TRUE_POP: return 1; - case FOR_ITER_LIST: + case _GUARD_KEYS_VERSION: return 1; - case _ITER_CHECK_TUPLE: + case _GUARD_NOT_EXHAUSTED_LIST: return 1; - case _ITER_JUMP_TUPLE: + case _GUARD_NOT_EXHAUSTED_RANGE: return 1; case _GUARD_NOT_EXHAUSTED_TUPLE: return 1; - case _ITER_NEXT_TUPLE: + case _GUARD_TYPE_VERSION: return 1; - case FOR_ITER_TUPLE: + case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _INIT_CALL_PY_EXACT_ARGS: + return oparg + 2; + case _INSERT: + return oparg + 1; + case _IS_NONE: + return 1; + case _ITER_CHECK_LIST: return 1; case _ITER_CHECK_RANGE: return 1; - case _ITER_JUMP_RANGE: + case _ITER_CHECK_TUPLE: return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: + case _ITER_JUMP_LIST: return 1; - case _ITER_NEXT_RANGE: + case _ITER_JUMP_RANGE: return 1; - case FOR_ITER_RANGE: + case _ITER_JUMP_TUPLE: return 1; - case FOR_ITER_GEN: + case _ITER_NEXT_LIST: return 1; - case BEFORE_ASYNC_WITH: + case _ITER_NEXT_RANGE: return 1; - case BEFORE_WITH: + case _ITER_NEXT_TUPLE: return 1; - case WITH_EXCEPT_START: - return 4; - case SETUP_FINALLY: - return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_WITH: - return 0; - case POP_BLOCK: + case _JUMP_TO_TOP: return 0; - case PUSH_EXC_INFO: - return 1; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + case _LOAD_ATTR: return 1; - case _GUARD_KEYS_VERSION: + case _LOAD_ATTR_CLASS: return 1; - case _LOAD_ATTR_METHOD_WITH_VALUES: + case _LOAD_ATTR_INSTANCE_VALUE: return 1; - case LOAD_ATTR_METHOD_WITH_VALUES: + case _LOAD_ATTR_METHOD_LAZY_DICT: return 1; case _LOAD_ATTR_METHOD_NO_DICT: return 1; - case LOAD_ATTR_METHOD_NO_DICT: + case _LOAD_ATTR_METHOD_WITH_VALUES: + return 1; + case _LOAD_ATTR_MODULE: + return 1; + case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _LOAD_ATTR_SLOT: return 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _LOAD_ATTR_WITH_HINT: return 1; - case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _LOAD_GLOBAL: + return 0; + case _LOAD_GLOBAL_BUILTINS: + return 0; + case _LOAD_GLOBAL_MODULE: + return 0; + case _LOAD_SUPER_ATTR: + return 3; + case _POP_FRAME: return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: + case _POP_JUMP_IF_FALSE: return 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: + case _POP_JUMP_IF_TRUE: return 1; - case LOAD_ATTR_METHOD_LAZY_DICT: + case _PUSH_FRAME: return 1; - case INSTRUMENTED_CALL: + case _SAVE_RETURN_OFFSET: return 0; + case _SEND: + return 2; + case _SET_IP: + return 0; + case _SPECIALIZE_BINARY_OP: + return 2; + case _SPECIALIZE_BINARY_SUBSCR: + return 2; case _SPECIALIZE_CALL: return oparg + 2; - case _CALL: - return oparg + 2; - case CALL: - return oparg + 2; - case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _CHECK_PEP_523: - return 0; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; - case _CHECK_STACK_SPACE: - return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return oparg + 2; - case _PUSH_FRAME: + case _SPECIALIZE_COMPARE_OP: + return 2; + case _SPECIALIZE_FOR_ITER: return 1; - case CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case CALL_PY_EXACT_ARGS: - return oparg + 2; - case CALL_PY_WITH_DEFAULTS: - return oparg + 2; - case CALL_TYPE_1: - return oparg + 2; - case CALL_STR_1: - return oparg + 2; - case CALL_TUPLE_1: - return oparg + 2; - case CALL_ALLOC_AND_ENTER_INIT: - return oparg + 2; - case EXIT_INIT_CHECK: + case _SPECIALIZE_LOAD_ATTR: return 1; - case CALL_BUILTIN_CLASS: - return oparg + 2; - case CALL_BUILTIN_O: - return oparg + 2; - case CALL_BUILTIN_FAST: - return oparg + 2; - case CALL_BUILTIN_FAST_WITH_KEYWORDS: - return oparg + 2; - case CALL_LEN: - return oparg + 2; - case CALL_ISINSTANCE: - return oparg + 2; - case CALL_LIST_APPEND: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_O: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_NOARGS: - return oparg + 2; - case CALL_METHOD_DESCRIPTOR_FAST: - return oparg + 2; - case INSTRUMENTED_CALL_KW: - return 0; - case CALL_KW: - return oparg + 3; - case INSTRUMENTED_CALL_FUNCTION_EX: + case _SPECIALIZE_LOAD_GLOBAL: return 0; - case CALL_FUNCTION_EX: - return ((oparg & 1) ? 1 : 0) + 3; - case MAKE_FUNCTION: + case _SPECIALIZE_LOAD_SUPER_ATTR: + return 3; + case _SPECIALIZE_SEND: + return 2; + case _SPECIALIZE_STORE_ATTR: return 1; - case SET_FUNCTION_ATTRIBUTE: + case _SPECIALIZE_STORE_SUBSCR: return 2; - case RETURN_GENERATOR: - return 0; - case BUILD_SLICE: - return ((oparg == 3) ? 1 : 0) + 2; - case CONVERT_VALUE: + case _SPECIALIZE_TO_BOOL: return 1; - case FORMAT_SIMPLE: + case _SPECIALIZE_UNPACK_SEQUENCE: return 1; - case FORMAT_WITH_SPEC: - return 2; - case COPY: - return (oparg-1) + 1; - case _SPECIALIZE_BINARY_OP: + case _STORE_ATTR: return 2; - case _BINARY_OP: + case _STORE_ATTR_INSTANCE_VALUE: return 2; - case BINARY_OP: + case _STORE_ATTR_SLOT: return 2; - case SWAP: - return (oparg-2) + 2; - case INSTRUMENTED_INSTRUCTION: - return 0; - case INSTRUMENTED_JUMP_FORWARD: - return 0; - case INSTRUMENTED_JUMP_BACKWARD: - return 0; - case INSTRUMENTED_POP_JUMP_IF_TRUE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_FALSE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NONE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: - return 0; - case EXTENDED_ARG: - return 0; - case CACHE: - return 0; - case RESERVED: - return 0; - case _GUARD_IS_TRUE_POP: - return 1; - case _GUARD_IS_FALSE_POP: - return 1; - case _GUARD_IS_NONE_POP: + case _STORE_SUBSCR: + return 3; + case _TO_BOOL: return 1; - case _GUARD_IS_NOT_NONE_POP: + case _UNPACK_SEQUENCE: return 1; - case _JUMP_TO_TOP: - return 0; - case _SET_IP: - return 0; - case _SAVE_RETURN_OFFSET: - return 0; - case _EXIT_TRACE: - return 0; - case _INSERT: - return oparg + 1; - case _CHECK_VALIDITY: - return 0; default: return -1; } @@ -671,636 +671,636 @@ extern int _PyOpcode_num_pushed(int opcode, int oparg, bool jump); #ifdef NEED_OPCODE_METADATA int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { switch(opcode) { - case NOP: - return 0; - case RESUME: - return 0; - case RESUME_CHECK: - return 0; - case INSTRUMENTED_RESUME: - return 0; - case LOAD_CLOSURE: - return 1; - case LOAD_FAST_CHECK: + case BEFORE_ASYNC_WITH: + return 2; + case BEFORE_WITH: + return 2; + case BINARY_OP: return 1; - case LOAD_FAST: + case BINARY_OP_ADD_FLOAT: return 1; - case LOAD_FAST_AND_CLEAR: + case BINARY_OP_ADD_INT: return 1; - case LOAD_FAST_LOAD_FAST: - return 2; - case LOAD_CONST: + case BINARY_OP_ADD_UNICODE: return 1; - case STORE_FAST: - return 0; - case STORE_FAST_MAYBE_NULL: + case BINARY_OP_INPLACE_ADD_UNICODE: return 0; - case STORE_FAST_LOAD_FAST: + case BINARY_OP_MULTIPLY_FLOAT: return 1; - case STORE_FAST_STORE_FAST: - return 0; - case POP_TOP: - return 0; - case PUSH_NULL: + case BINARY_OP_MULTIPLY_INT: return 1; - case END_FOR: - return 0; - case INSTRUMENTED_END_FOR: - return 0; - case END_SEND: + case BINARY_OP_SUBTRACT_FLOAT: return 1; - case INSTRUMENTED_END_SEND: + case BINARY_OP_SUBTRACT_INT: return 1; - case UNARY_NEGATIVE: + case BINARY_SLICE: return 1; - case UNARY_NOT: + case BINARY_SUBSCR: return 1; - case _SPECIALIZE_TO_BOOL: + case BINARY_SUBSCR_DICT: return 1; - case _TO_BOOL: + case BINARY_SUBSCR_GETITEM: return 1; - case TO_BOOL: + case BINARY_SUBSCR_LIST_INT: return 1; - case TO_BOOL_BOOL: + case BINARY_SUBSCR_STR_INT: return 1; - case TO_BOOL_INT: + case BINARY_SUBSCR_TUPLE_INT: return 1; - case TO_BOOL_LIST: + case BUILD_CONST_KEY_MAP: return 1; - case TO_BOOL_NONE: + case BUILD_LIST: return 1; - case TO_BOOL_STR: + case BUILD_MAP: return 1; - case TO_BOOL_ALWAYS_TRUE: + case BUILD_SET: return 1; - case UNARY_INVERT: + case BUILD_SLICE: return 1; - case _GUARD_BOTH_INT: - return 2; - case _BINARY_OP_MULTIPLY_INT: + case BUILD_STRING: return 1; - case _BINARY_OP_ADD_INT: + case BUILD_TUPLE: return 1; - case _BINARY_OP_SUBTRACT_INT: + case CACHE: + return 0; + case CALL: return 1; - case BINARY_OP_MULTIPLY_INT: + case CALL_ALLOC_AND_ENTER_INIT: return 1; - case BINARY_OP_ADD_INT: + case CALL_BOUND_METHOD_EXACT_ARGS: + return 0; + case CALL_BUILTIN_CLASS: return 1; - case BINARY_OP_SUBTRACT_INT: + case CALL_BUILTIN_FAST: return 1; - case _GUARD_BOTH_FLOAT: - return 2; - case _BINARY_OP_MULTIPLY_FLOAT: + case CALL_BUILTIN_FAST_WITH_KEYWORDS: return 1; - case _BINARY_OP_ADD_FLOAT: + case CALL_BUILTIN_O: return 1; - case _BINARY_OP_SUBTRACT_FLOAT: + case CALL_FUNCTION_EX: return 1; - case BINARY_OP_MULTIPLY_FLOAT: + case CALL_INTRINSIC_1: return 1; - case BINARY_OP_ADD_FLOAT: + case CALL_INTRINSIC_2: return 1; - case BINARY_OP_SUBTRACT_FLOAT: + case CALL_ISINSTANCE: return 1; - case _GUARD_BOTH_UNICODE: - return 2; - case _BINARY_OP_ADD_UNICODE: + case CALL_KW: return 1; - case BINARY_OP_ADD_UNICODE: + case CALL_LEN: return 1; - case _BINARY_OP_INPLACE_ADD_UNICODE: - return 0; - case BINARY_OP_INPLACE_ADD_UNICODE: - return 0; - case _SPECIALIZE_BINARY_SUBSCR: - return 2; - case _BINARY_SUBSCR: + case CALL_LIST_APPEND: return 1; - case BINARY_SUBSCR: + case CALL_METHOD_DESCRIPTOR_FAST: return 1; - case BINARY_SLICE: + case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: return 1; - case STORE_SLICE: - return 0; - case BINARY_SUBSCR_LIST_INT: + case CALL_METHOD_DESCRIPTOR_NOARGS: return 1; - case BINARY_SUBSCR_STR_INT: + case CALL_METHOD_DESCRIPTOR_O: return 1; - case BINARY_SUBSCR_TUPLE_INT: + case CALL_PY_EXACT_ARGS: + return 0; + case CALL_PY_WITH_DEFAULTS: return 1; - case BINARY_SUBSCR_DICT: + case CALL_STR_1: return 1; - case BINARY_SUBSCR_GETITEM: + case CALL_TUPLE_1: return 1; - case LIST_APPEND: - return (oparg-1) + 1; - case SET_ADD: - return (oparg-1) + 1; - case _SPECIALIZE_STORE_SUBSCR: + case CALL_TYPE_1: + return 1; + case CHECK_EG_MATCH: return 2; - case _STORE_SUBSCR: + case CHECK_EXC_MATCH: + return 2; + case CLEANUP_THROW: + return 2; + case COMPARE_OP: + return 1; + case COMPARE_OP_FLOAT: + return 1; + case COMPARE_OP_INT: + return 1; + case COMPARE_OP_STR: + return 1; + case CONTAINS_OP: + return 1; + case CONVERT_VALUE: + return 1; + case COPY: + return (oparg-1) + 2; + case COPY_FREE_VARS: return 0; - case STORE_SUBSCR: + case DELETE_ATTR: return 0; - case STORE_SUBSCR_LIST_INT: + case DELETE_DEREF: return 0; - case STORE_SUBSCR_DICT: + case DELETE_FAST: return 0; - case DELETE_SUBSCR: + case DELETE_GLOBAL: return 0; - case CALL_INTRINSIC_1: - return 1; - case CALL_INTRINSIC_2: - return 1; - case RAISE_VARARGS: + case DELETE_NAME: return 0; - case INTERPRETER_EXIT: + case DELETE_SUBSCR: return 0; - case _POP_FRAME: + case DICT_MERGE: + return (oparg - 1) + 4; + case DICT_UPDATE: + return (oparg - 1) + 1; + case END_ASYNC_FOR: return 0; - case RETURN_VALUE: + case END_FOR: return 0; - case INSTRUMENTED_RETURN_VALUE: + case END_SEND: + return 1; + case ENTER_EXECUTOR: return 0; - case RETURN_CONST: + case EXIT_INIT_CHECK: return 0; - case INSTRUMENTED_RETURN_CONST: + case EXTENDED_ARG: return 0; - case GET_AITER: + case FORMAT_SIMPLE: return 1; - case GET_ANEXT: - return 2; - case GET_AWAITABLE: + case FORMAT_WITH_SPEC: return 1; - case _SPECIALIZE_SEND: + case FOR_ITER: return 2; - case _SEND: + case FOR_ITER_GEN: return 2; - case SEND: + case FOR_ITER_LIST: return 2; - case SEND_GEN: + case FOR_ITER_RANGE: return 2; - case INSTRUMENTED_YIELD_VALUE: + case FOR_ITER_TUPLE: + return 2; + case GET_AITER: return 1; - case YIELD_VALUE: + case GET_ANEXT: + return 2; + case GET_AWAITABLE: return 1; - case POP_EXCEPT: - return 0; - case RERAISE: - return oparg; - case END_ASYNC_FOR: - return 0; - case CLEANUP_THROW: + case GET_ITER: + return 1; + case GET_LEN: return 2; - case LOAD_ASSERTION_ERROR: + case GET_YIELD_FROM_ITER: return 1; - case LOAD_BUILD_CLASS: + case IMPORT_FROM: + return 2; + case IMPORT_NAME: return 1; - case STORE_NAME: + case INSTRUMENTED_CALL: return 0; - case DELETE_NAME: + case INSTRUMENTED_CALL_FUNCTION_EX: return 0; - case _SPECIALIZE_UNPACK_SEQUENCE: - return 1; - case _UNPACK_SEQUENCE: - return oparg; - case UNPACK_SEQUENCE: - return oparg; - case UNPACK_SEQUENCE_TWO_TUPLE: - return oparg; - case UNPACK_SEQUENCE_TUPLE: - return oparg; - case UNPACK_SEQUENCE_LIST: - return oparg; - case UNPACK_EX: - return (oparg & 0xFF) + (oparg >> 8) + 1; - case _SPECIALIZE_STORE_ATTR: - return 1; - case _STORE_ATTR: + case INSTRUMENTED_CALL_KW: return 0; - case STORE_ATTR: + case INSTRUMENTED_END_FOR: return 0; - case DELETE_ATTR: + case INSTRUMENTED_END_SEND: + return 1; + case INSTRUMENTED_FOR_ITER: return 0; - case STORE_GLOBAL: + case INSTRUMENTED_INSTRUCTION: return 0; - case DELETE_GLOBAL: + case INSTRUMENTED_JUMP_BACKWARD: return 0; - case LOAD_LOCALS: - return 1; - case LOAD_FROM_DICT_OR_GLOBALS: - return 1; - case LOAD_NAME: - return 1; - case _SPECIALIZE_LOAD_GLOBAL: + case INSTRUMENTED_JUMP_FORWARD: return 0; - case _LOAD_GLOBAL: + case INSTRUMENTED_LOAD_SUPER_ATTR: return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_GLOBAL: - return (oparg & 1 ? 1 : 0) + 1; - case _GUARD_GLOBALS_VERSION: + case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; - case _GUARD_BUILTINS_VERSION: + case INSTRUMENTED_POP_JUMP_IF_NONE: return 0; - case _LOAD_GLOBAL_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_GLOBAL_BUILTINS: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_GLOBAL_MODULE: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_GLOBAL_BUILTIN: - return (oparg & 1 ? 1 : 0) + 1; - case DELETE_FAST: + case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: return 0; - case MAKE_CELL: + case INSTRUMENTED_POP_JUMP_IF_TRUE: return 0; - case DELETE_DEREF: + case INSTRUMENTED_RESUME: return 0; - case LOAD_FROM_DICT_OR_DEREF: - return 1; - case LOAD_DEREF: - return 1; - case STORE_DEREF: + case INSTRUMENTED_RETURN_CONST: return 0; - case COPY_FREE_VARS: + case INSTRUMENTED_RETURN_VALUE: return 0; - case BUILD_STRING: - return 1; - case BUILD_TUPLE: + case INSTRUMENTED_YIELD_VALUE: return 1; - case BUILD_LIST: + case INTERPRETER_EXIT: + return 0; + case IS_OP: return 1; - case LIST_EXTEND: + case JUMP: + return 0; + case JUMP_BACKWARD: + return 0; + case JUMP_BACKWARD_NO_INTERRUPT: + return 0; + case JUMP_FORWARD: + return 0; + case JUMP_NO_INTERRUPT: + return 0; + case LIST_APPEND: return (oparg-1) + 1; - case SET_UPDATE: + case LIST_EXTEND: return (oparg-1) + 1; - case BUILD_SET: - return 1; - case BUILD_MAP: - return 1; - case SETUP_ANNOTATIONS: - return 0; - case BUILD_CONST_KEY_MAP: + case LOAD_ASSERTION_ERROR: return 1; - case DICT_UPDATE: - return (oparg - 1) + 1; - case DICT_MERGE: - return (oparg - 1) + 4; - case MAP_ADD: - return (oparg - 1) + 1; - case INSTRUMENTED_LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case _SPECIALIZE_LOAD_SUPER_ATTR: - return 3; - case _LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_SUPER_ATTR: + case LOAD_ATTR: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_SUPER_METHOD: + case LOAD_ATTR_CLASS: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_METHOD: + case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + return 1; + case LOAD_ATTR_INSTANCE_VALUE: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_ATTR: + case LOAD_ATTR_METHOD_LAZY_DICT: + return 2; + case LOAD_ATTR_METHOD_NO_DICT: + return 2; + case LOAD_ATTR_METHOD_WITH_VALUES: + return 2; + case LOAD_ATTR_MODULE: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_SUPER_ATTR_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case LOAD_SUPER_ATTR_METHOD: - return 2; - case _SPECIALIZE_LOAD_ATTR: + case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case _LOAD_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR: + case LOAD_ATTR_PROPERTY: + return 1; + case LOAD_ATTR_SLOT: return (oparg & 1 ? 1 : 0) + 1; - case LOAD_METHOD: + case LOAD_ATTR_WITH_HINT: return (oparg & 1 ? 1 : 0) + 1; - case _GUARD_TYPE_VERSION: + case LOAD_BUILD_CLASS: return 1; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: + case LOAD_CLOSURE: return 1; - case _LOAD_ATTR_INSTANCE_VALUE: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_INSTANCE_VALUE: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_MODULE: + case LOAD_CONST: return 1; - case _LOAD_ATTR_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_MODULE: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_WITH_HINT: - return 1; - case _LOAD_ATTR_WITH_HINT: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_WITH_HINT: - return (oparg & 1 ? 1 : 0) + 1; - case _LOAD_ATTR_SLOT: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_SLOT: - return (oparg & 1 ? 1 : 0) + 1; - case _CHECK_ATTR_CLASS: + case LOAD_DEREF: return 1; - case _LOAD_ATTR_CLASS: - return ((oparg & 1) ? 1 : 0) + 1; - case LOAD_ATTR_CLASS: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ATTR_PROPERTY: + case LOAD_FAST: return 1; - case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: + case LOAD_FAST_AND_CLEAR: return 1; - case _GUARD_DORV_VALUES: + case LOAD_FAST_CHECK: return 1; - case _STORE_ATTR_INSTANCE_VALUE: - return 0; - case STORE_ATTR_INSTANCE_VALUE: - return 0; - case STORE_ATTR_WITH_HINT: - return 0; - case _STORE_ATTR_SLOT: - return 0; - case STORE_ATTR_SLOT: - return 0; - case _SPECIALIZE_COMPARE_OP: + case LOAD_FAST_LOAD_FAST: return 2; - case _COMPARE_OP: + case LOAD_FROM_DICT_OR_DEREF: return 1; - case COMPARE_OP: + case LOAD_FROM_DICT_OR_GLOBALS: return 1; - case COMPARE_OP_FLOAT: + case LOAD_GLOBAL: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_GLOBAL_BUILTIN: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_GLOBAL_MODULE: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_LOCALS: return 1; - case COMPARE_OP_INT: + case LOAD_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_NAME: return 1; - case COMPARE_OP_STR: + case LOAD_SUPER_ATTR: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_SUPER_ATTR_ATTR: return 1; - case IS_OP: + case LOAD_SUPER_ATTR_METHOD: + return 2; + case LOAD_SUPER_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_ZERO_SUPER_ATTR: + return (oparg & 1 ? 1 : 0) + 1; + case LOAD_ZERO_SUPER_METHOD: + return (oparg & 1 ? 1 : 0) + 1; + case MAKE_CELL: + return 0; + case MAKE_FUNCTION: return 1; - case CONTAINS_OP: + case MAP_ADD: + return (oparg - 1) + 1; + case MATCH_CLASS: return 1; - case CHECK_EG_MATCH: - return 2; - case CHECK_EXC_MATCH: + case MATCH_KEYS: + return 3; + case MATCH_MAPPING: return 2; - case IMPORT_NAME: - return 1; - case IMPORT_FROM: + case MATCH_SEQUENCE: return 2; - case JUMP_FORWARD: + case NOP: return 0; - case JUMP_BACKWARD: + case POP_BLOCK: return 0; - case JUMP: + case POP_EXCEPT: return 0; - case JUMP_NO_INTERRUPT: + case POP_JUMP_IF_FALSE: return 0; - case ENTER_EXECUTOR: + case POP_JUMP_IF_NONE: return 0; - case _POP_JUMP_IF_FALSE: + case POP_JUMP_IF_NOT_NONE: return 0; - case _POP_JUMP_IF_TRUE: + case POP_JUMP_IF_TRUE: return 0; - case _IS_NONE: + case POP_TOP: + return 0; + case PUSH_EXC_INFO: + return 2; + case PUSH_NULL: return 1; - case POP_JUMP_IF_TRUE: + case RAISE_VARARGS: return 0; - case POP_JUMP_IF_FALSE: + case RERAISE: + return oparg; + case RESERVED: return 0; - case POP_JUMP_IF_NONE: + case RESUME: return 0; - case POP_JUMP_IF_NOT_NONE: + case RESUME_CHECK: return 0; - case JUMP_BACKWARD_NO_INTERRUPT: + case RETURN_CONST: return 0; - case GET_LEN: - return 2; - case MATCH_CLASS: - return 1; - case MATCH_MAPPING: + case RETURN_GENERATOR: + return 0; + case RETURN_VALUE: + return 0; + case SEND: return 2; - case MATCH_SEQUENCE: + case SEND_GEN: return 2; - case MATCH_KEYS: - return 3; - case GET_ITER: - return 1; - case GET_YIELD_FROM_ITER: + case SETUP_ANNOTATIONS: + return 0; + case SETUP_CLEANUP: + return 0; + case SETUP_FINALLY: + return 0; + case SETUP_WITH: + return 0; + case SET_ADD: + return (oparg-1) + 1; + case SET_FUNCTION_ATTRIBUTE: return 1; - case _SPECIALIZE_FOR_ITER: + case SET_UPDATE: + return (oparg-1) + 1; + case STORE_ATTR: + return 0; + case STORE_ATTR_INSTANCE_VALUE: + return 0; + case STORE_ATTR_SLOT: + return 0; + case STORE_ATTR_WITH_HINT: + return 0; + case STORE_DEREF: + return 0; + case STORE_FAST: + return 0; + case STORE_FAST_LOAD_FAST: return 1; - case _FOR_ITER: - return 2; - case _FOR_ITER_TIER_TWO: - return 2; - case FOR_ITER: - return 2; - case INSTRUMENTED_FOR_ITER: + case STORE_FAST_MAYBE_NULL: return 0; - case _ITER_CHECK_LIST: + case STORE_FAST_STORE_FAST: + return 0; + case STORE_GLOBAL: + return 0; + case STORE_NAME: + return 0; + case STORE_SLICE: + return 0; + case STORE_SUBSCR: + return 0; + case STORE_SUBSCR_DICT: + return 0; + case STORE_SUBSCR_LIST_INT: + return 0; + case SWAP: + return (oparg-2) + 2; + case TO_BOOL: return 1; - case _ITER_JUMP_LIST: + case TO_BOOL_ALWAYS_TRUE: return 1; - case _GUARD_NOT_EXHAUSTED_LIST: + case TO_BOOL_BOOL: return 1; - case _ITER_NEXT_LIST: - return 2; - case FOR_ITER_LIST: - return 2; - case _ITER_CHECK_TUPLE: + case TO_BOOL_INT: return 1; - case _ITER_JUMP_TUPLE: + case TO_BOOL_LIST: return 1; - case _GUARD_NOT_EXHAUSTED_TUPLE: + case TO_BOOL_NONE: return 1; - case _ITER_NEXT_TUPLE: - return 2; - case FOR_ITER_TUPLE: - return 2; - case _ITER_CHECK_RANGE: + case TO_BOOL_STR: return 1; - case _ITER_JUMP_RANGE: + case UNARY_INVERT: return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: + case UNARY_NEGATIVE: return 1; - case _ITER_NEXT_RANGE: - return 2; - case FOR_ITER_RANGE: - return 2; - case FOR_ITER_GEN: - return 2; - case BEFORE_ASYNC_WITH: - return 2; - case BEFORE_WITH: - return 2; + case UNARY_NOT: + return 1; + case UNPACK_EX: + return (oparg & 0xFF) + (oparg >> 8) + 1; + case UNPACK_SEQUENCE: + return oparg; + case UNPACK_SEQUENCE_LIST: + return oparg; + case UNPACK_SEQUENCE_TUPLE: + return oparg; + case UNPACK_SEQUENCE_TWO_TUPLE: + return oparg; case WITH_EXCEPT_START: return 5; - case SETUP_FINALLY: - return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_WITH: - return 0; - case POP_BLOCK: - return 0; - case PUSH_EXC_INFO: - return 2; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: + case YIELD_VALUE: return 1; - case _GUARD_KEYS_VERSION: + case _BINARY_OP: return 1; - case _LOAD_ATTR_METHOD_WITH_VALUES: - return 2; - case LOAD_ATTR_METHOD_WITH_VALUES: - return 2; - case _LOAD_ATTR_METHOD_NO_DICT: - return 2; - case LOAD_ATTR_METHOD_NO_DICT: - return 2; - case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _BINARY_OP_ADD_FLOAT: return 1; - case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: + case _BINARY_OP_ADD_INT: return 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _BINARY_OP_ADD_UNICODE: return 1; - case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: + case _BINARY_OP_INPLACE_ADD_UNICODE: + return 0; + case _BINARY_OP_MULTIPLY_FLOAT: return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: + case _BINARY_OP_MULTIPLY_INT: + return 1; + case _BINARY_OP_SUBTRACT_FLOAT: + return 1; + case _BINARY_OP_SUBTRACT_INT: + return 1; + case _BINARY_SUBSCR: return 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: - return 2; - case LOAD_ATTR_METHOD_LAZY_DICT: - return 2; - case INSTRUMENTED_CALL: - return 0; - case _SPECIALIZE_CALL: - return oparg + 2; case _CALL: return 1; - case CALL: + case _CHECK_ATTR_CLASS: + return 1; + case _CHECK_ATTR_METHOD_LAZY_DICT: + return 1; + case _CHECK_ATTR_MODULE: + return 1; + case _CHECK_ATTR_WITH_HINT: return 1; case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: return oparg + 2; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + case _CHECK_FUNCTION_EXACT_ARGS: return oparg + 2; + case _CHECK_MANAGED_OBJECT_HAS_VALUES: + return 1; case _CHECK_PEP_523: return 0; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; case _CHECK_STACK_SPACE: return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return 1; - case _PUSH_FRAME: + case _CHECK_VALIDITY: return 0; - case CALL_BOUND_METHOD_EXACT_ARGS: + case _COMPARE_OP: + return 1; + case _EXIT_TRACE: return 0; - case CALL_PY_EXACT_ARGS: + case _FOR_ITER: + return 2; + case _FOR_ITER_TIER_TWO: + return 2; + case _GUARD_BOTH_FLOAT: + return 2; + case _GUARD_BOTH_INT: + return 2; + case _GUARD_BOTH_UNICODE: + return 2; + case _GUARD_BUILTINS_VERSION: return 0; - case CALL_PY_WITH_DEFAULTS: - return 1; - case CALL_TYPE_1: - return 1; - case CALL_STR_1: - return 1; - case CALL_TUPLE_1: + case _GUARD_DORV_VALUES: return 1; - case CALL_ALLOC_AND_ENTER_INIT: + case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: return 1; - case EXIT_INIT_CHECK: + case _GUARD_GLOBALS_VERSION: return 0; - case CALL_BUILTIN_CLASS: - return 1; - case CALL_BUILTIN_O: - return 1; - case CALL_BUILTIN_FAST: + case _GUARD_IS_FALSE_POP: + return 0; + case _GUARD_IS_NONE_POP: + return 0; + case _GUARD_IS_NOT_NONE_POP: + return 0; + case _GUARD_IS_TRUE_POP: + return 0; + case _GUARD_KEYS_VERSION: return 1; - case CALL_BUILTIN_FAST_WITH_KEYWORDS: + case _GUARD_NOT_EXHAUSTED_LIST: return 1; - case CALL_LEN: + case _GUARD_NOT_EXHAUSTED_RANGE: return 1; - case CALL_ISINSTANCE: + case _GUARD_NOT_EXHAUSTED_TUPLE: return 1; - case CALL_LIST_APPEND: + case _GUARD_TYPE_VERSION: return 1; - case CALL_METHOD_DESCRIPTOR_O: + case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: + return oparg + 2; + case _INIT_CALL_PY_EXACT_ARGS: return 1; - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: + case _INSERT: + return oparg + 1; + case _IS_NONE: return 1; - case CALL_METHOD_DESCRIPTOR_NOARGS: + case _ITER_CHECK_LIST: return 1; - case CALL_METHOD_DESCRIPTOR_FAST: + case _ITER_CHECK_RANGE: return 1; - case INSTRUMENTED_CALL_KW: - return 0; - case CALL_KW: + case _ITER_CHECK_TUPLE: return 1; - case INSTRUMENTED_CALL_FUNCTION_EX: - return 0; - case CALL_FUNCTION_EX: + case _ITER_JUMP_LIST: return 1; - case MAKE_FUNCTION: + case _ITER_JUMP_RANGE: return 1; - case SET_FUNCTION_ATTRIBUTE: + case _ITER_JUMP_TUPLE: return 1; - case RETURN_GENERATOR: + case _ITER_NEXT_LIST: + return 2; + case _ITER_NEXT_RANGE: + return 2; + case _ITER_NEXT_TUPLE: + return 2; + case _JUMP_TO_TOP: return 0; - case BUILD_SLICE: - return 1; - case CONVERT_VALUE: - return 1; - case FORMAT_SIMPLE: - return 1; - case FORMAT_WITH_SPEC: - return 1; - case COPY: - return (oparg-1) + 2; - case _SPECIALIZE_BINARY_OP: + case _LOAD_ATTR: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_CLASS: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_INSTANCE_VALUE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_METHOD_LAZY_DICT: return 2; - case _BINARY_OP: + case _LOAD_ATTR_METHOD_NO_DICT: + return 2; + case _LOAD_ATTR_METHOD_WITH_VALUES: + return 2; + case _LOAD_ATTR_MODULE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: return 1; - case BINARY_OP: + case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: return 1; - case SWAP: - return (oparg-2) + 2; - case INSTRUMENTED_INSTRUCTION: - return 0; - case INSTRUMENTED_JUMP_FORWARD: - return 0; - case INSTRUMENTED_JUMP_BACKWARD: - return 0; - case INSTRUMENTED_POP_JUMP_IF_TRUE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_FALSE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NONE: - return 0; - case INSTRUMENTED_POP_JUMP_IF_NOT_NONE: - return 0; - case EXTENDED_ARG: - return 0; - case CACHE: + case _LOAD_ATTR_SLOT: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_ATTR_WITH_HINT: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL_BUILTINS: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_GLOBAL_MODULE: + return ((oparg & 1) ? 1 : 0) + 1; + case _LOAD_SUPER_ATTR: + return ((oparg & 1) ? 1 : 0) + 1; + case _POP_FRAME: return 0; - case RESERVED: + case _POP_JUMP_IF_FALSE: return 0; - case _GUARD_IS_TRUE_POP: + case _POP_JUMP_IF_TRUE: return 0; - case _GUARD_IS_FALSE_POP: + case _PUSH_FRAME: return 0; - case _GUARD_IS_NONE_POP: + case _SAVE_RETURN_OFFSET: return 0; - case _GUARD_IS_NOT_NONE_POP: + case _SEND: + return 2; + case _SET_IP: return 0; - case _JUMP_TO_TOP: + case _SPECIALIZE_BINARY_OP: + return 2; + case _SPECIALIZE_BINARY_SUBSCR: + return 2; + case _SPECIALIZE_CALL: + return oparg + 2; + case _SPECIALIZE_COMPARE_OP: + return 2; + case _SPECIALIZE_FOR_ITER: + return 1; + case _SPECIALIZE_LOAD_ATTR: + return 1; + case _SPECIALIZE_LOAD_GLOBAL: return 0; - case _SET_IP: + case _SPECIALIZE_LOAD_SUPER_ATTR: + return 3; + case _SPECIALIZE_SEND: + return 2; + case _SPECIALIZE_STORE_ATTR: + return 1; + case _SPECIALIZE_STORE_SUBSCR: + return 2; + case _SPECIALIZE_TO_BOOL: + return 1; + case _SPECIALIZE_UNPACK_SEQUENCE: + return 1; + case _STORE_ATTR: return 0; - case _SAVE_RETURN_OFFSET: + case _STORE_ATTR_INSTANCE_VALUE: return 0; - case _EXIT_TRACE: + case _STORE_ATTR_SLOT: return 0; - case _INSERT: - return oparg + 1; - case _CHECK_VALIDITY: + case _STORE_SUBSCR: return 0; + case _TO_BOOL: + return 1; + case _UNPACK_SEQUENCE: + return oparg; default: return -1; } @@ -1377,484 +1377,484 @@ struct opcode_macro_expansion { extern const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE]; #ifdef NEED_OPCODE_METADATA const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { - [NOP] = { true, INSTR_FMT_IX, 0 }, - [RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RESUME_CHECK] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_CLOSURE] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, - [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, - [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_MAYBE_NULL] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [POP_TOP] = { true, INSTR_FMT_IX, 0 }, - [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, - [END_FOR] = { true, INSTR_FMT_IX, 0 }, - [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [END_SEND] = { true, INSTR_FMT_IX, 0 }, - [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_NOT] = { true, INSTR_FMT_IX, 0 }, - [_SPECIALIZE_TO_BOOL] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_TO_BOOL] = { true, INSTR_FMT_IXC0, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, - [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_BOTH_INT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [_GUARD_BOTH_FLOAT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_BOTH_UNICODE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_BINARY_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BINARY_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BINARY_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [BINARY_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BINARY_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_STORE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_CONST_KEY_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CACHE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_ALLOC_AND_ENTER_INIT] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_CLASS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [CALL_BUILTIN_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_BUILTIN_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_INTRINSIC_1] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_INTRINSIC_2] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [_POP_FRAME] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [INSTRUMENTED_RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, + [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_METHOD_DESCRIPTOR_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_PY_WITH_DEFAULTS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CALL_STR_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_TUPLE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CALL_TYPE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [CHECK_EG_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CHECK_EXC_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, + [DELETE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DELETE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DICT_MERGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [END_ASYNC_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [END_FOR] = { true, INSTR_FMT_IX, 0 }, + [END_SEND] = { true, INSTR_FMT_IX, 0 }, + [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [FORMAT_SIMPLE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FORMAT_WITH_SPEC] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, + [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, [GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [GET_ANEXT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [GET_AWAITABLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_SEND] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SEND] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [GET_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [GET_LEN] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [GET_YIELD_FROM_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [IMPORT_FROM] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [IMPORT_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, 0 }, + [INSTRUMENTED_CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_INSTRUCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG }, + [INSTRUMENTED_JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [INSTRUMENTED_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, + [INSTRUMENTED_RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INSTRUMENTED_RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [INSTRUMENTED_YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [END_ASYNC_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ASSERTION_ERROR] = { true, INSTR_FMT_IX, 0 }, + [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_BUILD_CLASS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_UNPACK_SEQUENCE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_LOCALS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_CLOSURE] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, + [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, + [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FROM_DICT_OR_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_FROM_DICT_OR_GLOBALS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_GLOBAL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_GLOBAL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_GLOBAL] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_GLOBALS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_BUILTINS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_GLOBAL_BUILTINS] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_GLOBAL_BUILTIN] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [DELETE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG }, - [MAKE_CELL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_FROM_DICT_OR_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, - [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_CONST_KEY_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [DICT_MERGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [_SPECIALIZE_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_LOCALS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_ATTR] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_METHOD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR] = { true, INSTR_FMT_IBC0000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_TYPE_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_MODULE] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_WITH_HINT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_CLASS] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [LOAD_ATTR_PROPERTY] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_DORV_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_COMPARE_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CHECK_EG_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CHECK_EXC_MATCH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [IMPORT_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [IMPORT_FROM] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, - [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [GET_LEN] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_ATTR] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAKE_CELL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_CLASS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, - [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [GET_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [GET_YIELD_FROM_ITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_FOR_ITER] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_FOR_ITER_TIER_TWO] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_ITER_CHECK_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_LIST] = { true, INSTR_FMT_IX, 0 }, - [FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, - [_ITER_CHECK_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_TUPLE] = { true, INSTR_FMT_IX, 0 }, - [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, - [_ITER_CHECK_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_RANGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_GUARD_NOT_EXHAUSTED_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_NEXT_RANGE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, - [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, - [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, + [NOP] = { true, INSTR_FMT_IX, 0 }, [POP_BLOCK] = { true, 0, 0 }, + [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [POP_TOP] = { true, INSTR_FMT_IX, 0 }, [PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 }, - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_KEYS_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_CALL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, + [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RESERVED] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [RESUME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RESUME_CHECK] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [RETURN_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG | HAS_ESCAPES_FLAG }, + [RETURN_GENERATOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [RETURN_VALUE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [SEND] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, + [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, + [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, + [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_MAYBE_NULL] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_FAST_STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [STORE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_INT] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_DEOPT_FLAG }, + [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_NOT] = { true, INSTR_FMT_IX, 0 }, + [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNPACK_SEQUENCE_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [UNPACK_SEQUENCE_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [_BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, 0 }, + [_BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, + [_BINARY_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [_CALL] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_CHECK_ATTR_CLASS] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_MODULE] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_CHECK_ATTR_WITH_HINT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_CHECK_PEP_523] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_CHECK_FUNCTION_EXACT_ARGS] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_PEP_523] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_CHECK_STACK_SPACE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_INIT_CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_PUSH_FRAME] = { true, INSTR_FMT_IX, 0 }, - [CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_PY_WITH_DEFAULTS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [CALL_TYPE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [CALL_STR_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_TUPLE_1] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_ALLOC_AND_ENTER_INIT] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_CLASS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [CALL_BUILTIN_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LEN] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_ISINSTANCE] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_LIST_APPEND] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, - [CALL_METHOD_DESCRIPTOR_O] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_METHOD_DESCRIPTOR_FAST] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, 0 }, - [CALL_FUNCTION_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [RETURN_GENERATOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [FORMAT_SIMPLE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [FORMAT_WITH_SPEC] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_SPECIALIZE_BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [INSTRUMENTED_INSTRUCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [INSTRUMENTED_JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [INSTRUMENTED_JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG }, - [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [CACHE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [RESERVED] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [_GUARD_IS_TRUE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_COMPARE_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_FOR_ITER] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_FOR_ITER_TIER_TWO] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_GUARD_BOTH_FLOAT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BOTH_INT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BOTH_UNICODE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_BUILTINS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, + [_GUARD_DORV_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_GLOBALS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [_GUARD_IS_FALSE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_GUARD_IS_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, [_GUARD_IS_NOT_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_IS_TRUE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_KEYS_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_NOT_EXHAUSTED_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_GUARD_TYPE_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [_INIT_CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, + [_ITER_CHECK_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_CHECK_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_CHECK_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_ITER_JUMP_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_JUMP_RANGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_JUMP_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_ITER_NEXT_LIST] = { true, INSTR_FMT_IX, 0 }, + [_ITER_NEXT_RANGE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_ITER_NEXT_TUPLE] = { true, INSTR_FMT_IX, 0 }, [_JUMP_TO_TOP] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG }, - [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR] = { true, INSTR_FMT_IBC0000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, + [_LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_GLOBAL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_LOAD_GLOBAL_BUILTINS] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_POP_FRAME] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [_PUSH_FRAME] = { true, INSTR_FMT_IX, 0 }, [_SAVE_RETURN_OFFSET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, + [_SEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_CALL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_GLOBAL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_SEND] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_STORE_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, + [_SPECIALIZE_STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_TO_BOOL] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_SPECIALIZE_UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, + [_STORE_ATTR] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, + [_STORE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_TO_BOOL] = { true, INSTR_FMT_IXC0, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [_UNPACK_SEQUENCE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, }; #endif // NEED_OPCODE_METADATA extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE]; #ifdef NEED_OPCODE_METADATA const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE] = { - [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, - [RESUME_CHECK] = { .nuops = 1, .uops = { { RESUME_CHECK, 0, 0 } } }, - [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, - [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, - [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, - [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { LOAD_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, - [STORE_FAST] = { .nuops = 1, .uops = { { STORE_FAST, 0, 0 } } }, - [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { STORE_FAST, 6, 0 } } }, - [POP_TOP] = { .nuops = 1, .uops = { { POP_TOP, 0, 0 } } }, - [PUSH_NULL] = { .nuops = 1, .uops = { { PUSH_NULL, 0, 0 } } }, - [END_FOR] = { .nuops = 2, .uops = { { POP_TOP, 0, 0 }, { POP_TOP, 0, 0 } } }, - [END_SEND] = { .nuops = 1, .uops = { { END_SEND, 0, 0 } } }, - [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { UNARY_NEGATIVE, 0, 0 } } }, - [UNARY_NOT] = { .nuops = 1, .uops = { { UNARY_NOT, 0, 0 } } }, - [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, - [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { TO_BOOL_BOOL, 0, 0 } } }, - [TO_BOOL_INT] = { .nuops = 1, .uops = { { TO_BOOL_INT, 0, 0 } } }, - [TO_BOOL_LIST] = { .nuops = 1, .uops = { { TO_BOOL_LIST, 0, 0 } } }, - [TO_BOOL_NONE] = { .nuops = 1, .uops = { { TO_BOOL_NONE, 0, 0 } } }, - [TO_BOOL_STR] = { .nuops = 1, .uops = { { TO_BOOL_STR, 0, 0 } } }, - [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, - [UNARY_INVERT] = { .nuops = 1, .uops = { { UNARY_INVERT, 0, 0 } } }, - [BINARY_OP_MULTIPLY_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_MULTIPLY_INT, 0, 0 } } }, + [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { BEFORE_ASYNC_WITH, 0, 0 } } }, + [BEFORE_WITH] = { .nuops = 1, .uops = { { BEFORE_WITH, 0, 0 } } }, + [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, 0, 0 } } }, + [BINARY_OP_ADD_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_ADD_FLOAT, 0, 0 } } }, [BINARY_OP_ADD_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_ADD_INT, 0, 0 } } }, - [BINARY_OP_SUBTRACT_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_SUBTRACT_INT, 0, 0 } } }, + [BINARY_OP_ADD_UNICODE] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _BINARY_OP_ADD_UNICODE, 0, 0 } } }, [BINARY_OP_MULTIPLY_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_MULTIPLY_FLOAT, 0, 0 } } }, - [BINARY_OP_ADD_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_ADD_FLOAT, 0, 0 } } }, + [BINARY_OP_MULTIPLY_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_MULTIPLY_INT, 0, 0 } } }, [BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, 0, 0 } } }, - [BINARY_OP_ADD_UNICODE] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _BINARY_OP_ADD_UNICODE, 0, 0 } } }, - [BINARY_SUBSCR] = { .nuops = 1, .uops = { { _BINARY_SUBSCR, 0, 0 } } }, + [BINARY_OP_SUBTRACT_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_SUBTRACT_INT, 0, 0 } } }, [BINARY_SLICE] = { .nuops = 1, .uops = { { BINARY_SLICE, 0, 0 } } }, - [STORE_SLICE] = { .nuops = 1, .uops = { { STORE_SLICE, 0, 0 } } }, + [BINARY_SUBSCR] = { .nuops = 1, .uops = { { _BINARY_SUBSCR, 0, 0 } } }, + [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_DICT, 0, 0 } } }, [BINARY_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_LIST_INT, 0, 0 } } }, [BINARY_SUBSCR_STR_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_STR_INT, 0, 0 } } }, [BINARY_SUBSCR_TUPLE_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_TUPLE_INT, 0, 0 } } }, - [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_DICT, 0, 0 } } }, - [LIST_APPEND] = { .nuops = 1, .uops = { { LIST_APPEND, 0, 0 } } }, - [SET_ADD] = { .nuops = 1, .uops = { { SET_ADD, 0, 0 } } }, - [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, 0, 0 } } }, - [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { STORE_SUBSCR_LIST_INT, 0, 0 } } }, - [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { STORE_SUBSCR_DICT, 0, 0 } } }, - [DELETE_SUBSCR] = { .nuops = 1, .uops = { { DELETE_SUBSCR, 0, 0 } } }, - [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { CALL_INTRINSIC_1, 0, 0 } } }, - [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { CALL_INTRINSIC_2, 0, 0 } } }, - [RETURN_VALUE] = { .nuops = 1, .uops = { { _POP_FRAME, 0, 0 } } }, - [RETURN_CONST] = { .nuops = 2, .uops = { { LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, - [GET_AITER] = { .nuops = 1, .uops = { { GET_AITER, 0, 0 } } }, - [GET_ANEXT] = { .nuops = 1, .uops = { { GET_ANEXT, 0, 0 } } }, - [GET_AWAITABLE] = { .nuops = 1, .uops = { { GET_AWAITABLE, 0, 0 } } }, - [POP_EXCEPT] = { .nuops = 1, .uops = { { POP_EXCEPT, 0, 0 } } }, - [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { LOAD_ASSERTION_ERROR, 0, 0 } } }, - [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { LOAD_BUILD_CLASS, 0, 0 } } }, - [STORE_NAME] = { .nuops = 1, .uops = { { STORE_NAME, 0, 0 } } }, - [DELETE_NAME] = { .nuops = 1, .uops = { { DELETE_NAME, 0, 0 } } }, - [UNPACK_SEQUENCE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE, 0, 0 } } }, - [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, - [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, - [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_LIST, 0, 0 } } }, - [UNPACK_EX] = { .nuops = 1, .uops = { { UNPACK_EX, 0, 0 } } }, - [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, 0, 0 } } }, - [DELETE_ATTR] = { .nuops = 1, .uops = { { DELETE_ATTR, 0, 0 } } }, - [STORE_GLOBAL] = { .nuops = 1, .uops = { { STORE_GLOBAL, 0, 0 } } }, - [DELETE_GLOBAL] = { .nuops = 1, .uops = { { DELETE_GLOBAL, 0, 0 } } }, - [LOAD_LOCALS] = { .nuops = 1, .uops = { { LOAD_LOCALS, 0, 0 } } }, - [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, - [LOAD_NAME] = { .nuops = 1, .uops = { { LOAD_NAME, 0, 0 } } }, - [LOAD_GLOBAL] = { .nuops = 1, .uops = { { _LOAD_GLOBAL, 0, 0 } } }, - [LOAD_GLOBAL_MODULE] = { .nuops = 2, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _LOAD_GLOBAL_MODULE, 1, 3 } } }, - [LOAD_GLOBAL_BUILTIN] = { .nuops = 3, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _GUARD_BUILTINS_VERSION, 1, 2 }, { _LOAD_GLOBAL_BUILTINS, 1, 3 } } }, - [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, - [MAKE_CELL] = { .nuops = 1, .uops = { { MAKE_CELL, 0, 0 } } }, - [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, - [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, - [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, - [STORE_DEREF] = { .nuops = 1, .uops = { { STORE_DEREF, 0, 0 } } }, - [COPY_FREE_VARS] = { .nuops = 1, .uops = { { COPY_FREE_VARS, 0, 0 } } }, - [BUILD_STRING] = { .nuops = 1, .uops = { { BUILD_STRING, 0, 0 } } }, - [BUILD_TUPLE] = { .nuops = 1, .uops = { { BUILD_TUPLE, 0, 0 } } }, + [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { BUILD_CONST_KEY_MAP, 0, 0 } } }, [BUILD_LIST] = { .nuops = 1, .uops = { { BUILD_LIST, 0, 0 } } }, - [LIST_EXTEND] = { .nuops = 1, .uops = { { LIST_EXTEND, 0, 0 } } }, - [SET_UPDATE] = { .nuops = 1, .uops = { { SET_UPDATE, 0, 0 } } }, - [BUILD_SET] = { .nuops = 1, .uops = { { BUILD_SET, 0, 0 } } }, [BUILD_MAP] = { .nuops = 1, .uops = { { BUILD_MAP, 0, 0 } } }, - [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { SETUP_ANNOTATIONS, 0, 0 } } }, - [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { BUILD_CONST_KEY_MAP, 0, 0 } } }, - [DICT_UPDATE] = { .nuops = 1, .uops = { { DICT_UPDATE, 0, 0 } } }, - [DICT_MERGE] = { .nuops = 1, .uops = { { DICT_MERGE, 0, 0 } } }, - [MAP_ADD] = { .nuops = 1, .uops = { { MAP_ADD, 0, 0 } } }, - [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, - [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, - [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, - [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, - [LOAD_ATTR_MODULE] = { .nuops = 2, .uops = { { _CHECK_ATTR_MODULE, 2, 1 }, { _LOAD_ATTR_MODULE, 1, 3 } } }, - [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_WITH_HINT, 0, 0 }, { _LOAD_ATTR_WITH_HINT, 1, 3 } } }, - [LOAD_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 } } }, - [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, - [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES, 0, 0 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 } } }, - [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, + [BUILD_SET] = { .nuops = 1, .uops = { { BUILD_SET, 0, 0 } } }, + [BUILD_SLICE] = { .nuops = 1, .uops = { { BUILD_SLICE, 0, 0 } } }, + [BUILD_STRING] = { .nuops = 1, .uops = { { BUILD_STRING, 0, 0 } } }, + [BUILD_TUPLE] = { .nuops = 1, .uops = { { BUILD_TUPLE, 0, 0 } } }, + [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, + [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { CALL_BUILTIN_CLASS, 0, 0 } } }, + [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST, 0, 0 } } }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { CALL_BUILTIN_O, 0, 0 } } }, + [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { CALL_INTRINSIC_1, 0, 0 } } }, + [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { CALL_INTRINSIC_2, 0, 0 } } }, + [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { CALL_ISINSTANCE, 0, 0 } } }, + [CALL_LEN] = { .nuops = 1, .uops = { { CALL_LEN, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, + [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, + [CALL_STR_1] = { .nuops = 1, .uops = { { CALL_STR_1, 0, 0 } } }, + [CALL_TUPLE_1] = { .nuops = 1, .uops = { { CALL_TUPLE_1, 0, 0 } } }, + [CALL_TYPE_1] = { .nuops = 1, .uops = { { CALL_TYPE_1, 0, 0 } } }, + [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { CHECK_EG_MATCH, 0, 0 } } }, + [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { CHECK_EXC_MATCH, 0, 0 } } }, [COMPARE_OP] = { .nuops = 1, .uops = { { _COMPARE_OP, 0, 0 } } }, [COMPARE_OP_FLOAT] = { .nuops = 1, .uops = { { COMPARE_OP_FLOAT, 0, 0 } } }, [COMPARE_OP_INT] = { .nuops = 1, .uops = { { COMPARE_OP_INT, 0, 0 } } }, [COMPARE_OP_STR] = { .nuops = 1, .uops = { { COMPARE_OP_STR, 0, 0 } } }, - [IS_OP] = { .nuops = 1, .uops = { { IS_OP, 0, 0 } } }, [CONTAINS_OP] = { .nuops = 1, .uops = { { CONTAINS_OP, 0, 0 } } }, - [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { CHECK_EG_MATCH, 0, 0 } } }, - [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { CHECK_EXC_MATCH, 0, 0 } } }, - [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [GET_LEN] = { .nuops = 1, .uops = { { GET_LEN, 0, 0 } } }, - [MATCH_CLASS] = { .nuops = 1, .uops = { { MATCH_CLASS, 0, 0 } } }, - [MATCH_MAPPING] = { .nuops = 1, .uops = { { MATCH_MAPPING, 0, 0 } } }, - [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { MATCH_SEQUENCE, 0, 0 } } }, - [MATCH_KEYS] = { .nuops = 1, .uops = { { MATCH_KEYS, 0, 0 } } }, - [GET_ITER] = { .nuops = 1, .uops = { { GET_ITER, 0, 0 } } }, - [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { GET_YIELD_FROM_ITER, 0, 0 } } }, + [CONVERT_VALUE] = { .nuops = 1, .uops = { { CONVERT_VALUE, 0, 0 } } }, + [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, + [COPY_FREE_VARS] = { .nuops = 1, .uops = { { COPY_FREE_VARS, 0, 0 } } }, + [DELETE_ATTR] = { .nuops = 1, .uops = { { DELETE_ATTR, 0, 0 } } }, + [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, + [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, + [DELETE_GLOBAL] = { .nuops = 1, .uops = { { DELETE_GLOBAL, 0, 0 } } }, + [DELETE_NAME] = { .nuops = 1, .uops = { { DELETE_NAME, 0, 0 } } }, + [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_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 } } }, + [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { FORMAT_WITH_SPEC, 0, 0 } } }, [FOR_ITER] = { .nuops = 1, .uops = { { _FOR_ITER, 0, 0 } } }, [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, 0, 0 }, { _ITER_JUMP_LIST, 0, 0 }, { _ITER_NEXT_LIST, 0, 0 } } }, - [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 0, 0 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, [FOR_ITER_RANGE] = { .nuops = 3, .uops = { { _ITER_CHECK_RANGE, 0, 0 }, { _ITER_JUMP_RANGE, 0, 0 }, { _ITER_NEXT_RANGE, 0, 0 } } }, - [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { BEFORE_ASYNC_WITH, 0, 0 } } }, - [BEFORE_WITH] = { .nuops = 1, .uops = { { BEFORE_WITH, 0, 0 } } }, - [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { WITH_EXCEPT_START, 0, 0 } } }, - [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { PUSH_EXC_INFO, 0, 0 } } }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, + [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 0, 0 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, + [GET_AITER] = { .nuops = 1, .uops = { { GET_AITER, 0, 0 } } }, + [GET_ANEXT] = { .nuops = 1, .uops = { { GET_ANEXT, 0, 0 } } }, + [GET_AWAITABLE] = { .nuops = 1, .uops = { { GET_AWAITABLE, 0, 0 } } }, + [GET_ITER] = { .nuops = 1, .uops = { { GET_ITER, 0, 0 } } }, + [GET_LEN] = { .nuops = 1, .uops = { { GET_LEN, 0, 0 } } }, + [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { GET_YIELD_FROM_ITER, 0, 0 } } }, + [IS_OP] = { .nuops = 1, .uops = { { IS_OP, 0, 0 } } }, + [LIST_APPEND] = { .nuops = 1, .uops = { { LIST_APPEND, 0, 0 } } }, + [LIST_EXTEND] = { .nuops = 1, .uops = { { LIST_EXTEND, 0, 0 } } }, + [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { LOAD_ASSERTION_ERROR, 0, 0 } } }, + [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, + [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, + [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 0, 0 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, [LOAD_ATTR_METHOD_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_MODULE] = { .nuops = 2, .uops = { { _CHECK_ATTR_MODULE, 2, 1 }, { _LOAD_ATTR_MODULE, 1, 3 } } }, [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_NONDESCRIPTOR_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 0, 0 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, - [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_TYPE_1] = { .nuops = 1, .uops = { { CALL_TYPE_1, 0, 0 } } }, - [CALL_STR_1] = { .nuops = 1, .uops = { { CALL_STR_1, 0, 0 } } }, - [CALL_TUPLE_1] = { .nuops = 1, .uops = { { CALL_TUPLE_1, 0, 0 } } }, - [EXIT_INIT_CHECK] = { .nuops = 1, .uops = { { EXIT_INIT_CHECK, 0, 0 } } }, - [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { CALL_BUILTIN_CLASS, 0, 0 } } }, - [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { CALL_BUILTIN_O, 0, 0 } } }, - [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST, 0, 0 } } }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_LEN] = { .nuops = 1, .uops = { { CALL_LEN, 0, 0 } } }, - [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { CALL_ISINSTANCE, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 } } }, + [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_WITH_HINT, 0, 0 }, { _LOAD_ATTR_WITH_HINT, 1, 3 } } }, + [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { LOAD_BUILD_CLASS, 0, 0 } } }, + [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, + [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, + [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, + [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, + [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, + [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { LOAD_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, + [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, + [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, + [LOAD_GLOBAL] = { .nuops = 1, .uops = { { _LOAD_GLOBAL, 0, 0 } } }, + [LOAD_GLOBAL_BUILTIN] = { .nuops = 3, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _GUARD_BUILTINS_VERSION, 1, 2 }, { _LOAD_GLOBAL_BUILTINS, 1, 3 } } }, + [LOAD_GLOBAL_MODULE] = { .nuops = 2, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _LOAD_GLOBAL_MODULE, 1, 3 } } }, + [LOAD_LOCALS] = { .nuops = 1, .uops = { { LOAD_LOCALS, 0, 0 } } }, + [LOAD_NAME] = { .nuops = 1, .uops = { { LOAD_NAME, 0, 0 } } }, + [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, + [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, + [MAKE_CELL] = { .nuops = 1, .uops = { { MAKE_CELL, 0, 0 } } }, [MAKE_FUNCTION] = { .nuops = 1, .uops = { { MAKE_FUNCTION, 0, 0 } } }, + [MAP_ADD] = { .nuops = 1, .uops = { { MAP_ADD, 0, 0 } } }, + [MATCH_CLASS] = { .nuops = 1, .uops = { { MATCH_CLASS, 0, 0 } } }, + [MATCH_KEYS] = { .nuops = 1, .uops = { { MATCH_KEYS, 0, 0 } } }, + [MATCH_MAPPING] = { .nuops = 1, .uops = { { MATCH_MAPPING, 0, 0 } } }, + [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { MATCH_SEQUENCE, 0, 0 } } }, + [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, + [POP_EXCEPT] = { .nuops = 1, .uops = { { POP_EXCEPT, 0, 0 } } }, + [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 0, 0 } } }, + [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 0, 0 } } }, + [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 0, 0 } } }, + [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 0, 0 } } }, + [POP_TOP] = { .nuops = 1, .uops = { { POP_TOP, 0, 0 } } }, + [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { PUSH_EXC_INFO, 0, 0 } } }, + [PUSH_NULL] = { .nuops = 1, .uops = { { PUSH_NULL, 0, 0 } } }, + [RESUME_CHECK] = { .nuops = 1, .uops = { { RESUME_CHECK, 0, 0 } } }, + [RETURN_CONST] = { .nuops = 2, .uops = { { LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, + [RETURN_VALUE] = { .nuops = 1, .uops = { { _POP_FRAME, 0, 0 } } }, + [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { SETUP_ANNOTATIONS, 0, 0 } } }, + [SET_ADD] = { .nuops = 1, .uops = { { SET_ADD, 0, 0 } } }, [SET_FUNCTION_ATTRIBUTE] = { .nuops = 1, .uops = { { SET_FUNCTION_ATTRIBUTE, 0, 0 } } }, - [BUILD_SLICE] = { .nuops = 1, .uops = { { BUILD_SLICE, 0, 0 } } }, - [CONVERT_VALUE] = { .nuops = 1, .uops = { { CONVERT_VALUE, 0, 0 } } }, - [FORMAT_SIMPLE] = { .nuops = 1, .uops = { { FORMAT_SIMPLE, 0, 0 } } }, - [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { FORMAT_WITH_SPEC, 0, 0 } } }, - [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, - [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, 0, 0 } } }, + [SET_UPDATE] = { .nuops = 1, .uops = { { SET_UPDATE, 0, 0 } } }, + [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, 0, 0 } } }, + [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES, 0, 0 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 } } }, + [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, + [STORE_DEREF] = { .nuops = 1, .uops = { { STORE_DEREF, 0, 0 } } }, + [STORE_FAST] = { .nuops = 1, .uops = { { STORE_FAST, 0, 0 } } }, + [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, + [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { STORE_FAST, 6, 0 } } }, + [STORE_GLOBAL] = { .nuops = 1, .uops = { { STORE_GLOBAL, 0, 0 } } }, + [STORE_NAME] = { .nuops = 1, .uops = { { STORE_NAME, 0, 0 } } }, + [STORE_SLICE] = { .nuops = 1, .uops = { { STORE_SLICE, 0, 0 } } }, + [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, 0, 0 } } }, + [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { STORE_SUBSCR_DICT, 0, 0 } } }, + [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { STORE_SUBSCR_LIST_INT, 0, 0 } } }, [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } }, + [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, + [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, + [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { TO_BOOL_BOOL, 0, 0 } } }, + [TO_BOOL_INT] = { .nuops = 1, .uops = { { TO_BOOL_INT, 0, 0 } } }, + [TO_BOOL_LIST] = { .nuops = 1, .uops = { { TO_BOOL_LIST, 0, 0 } } }, + [TO_BOOL_NONE] = { .nuops = 1, .uops = { { TO_BOOL_NONE, 0, 0 } } }, + [TO_BOOL_STR] = { .nuops = 1, .uops = { { TO_BOOL_STR, 0, 0 } } }, + [UNARY_INVERT] = { .nuops = 1, .uops = { { UNARY_INVERT, 0, 0 } } }, + [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { UNARY_NEGATIVE, 0, 0 } } }, + [UNARY_NOT] = { .nuops = 1, .uops = { { UNARY_NOT, 0, 0 } } }, + [UNPACK_EX] = { .nuops = 1, .uops = { { UNPACK_EX, 0, 0 } } }, + [UNPACK_SEQUENCE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE, 0, 0 } } }, + [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_LIST, 0, 0 } } }, + [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, + [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, + [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { WITH_EXCEPT_START, 0, 0 } } }, }; #endif // NEED_OPCODE_METADATA @@ -1863,153 +1863,124 @@ extern const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]; const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE] = { [_EXIT_TRACE] = "_EXIT_TRACE", [_SET_IP] = "_SET_IP", - [_SPECIALIZE_TO_BOOL] = "_SPECIALIZE_TO_BOOL", - [_TO_BOOL] = "_TO_BOOL", - [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", - [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", - [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", - [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", - [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", - [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP] = "_BINARY_OP", [_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT", - [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", - [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", [_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE", - [_SPECIALIZE_BINARY_SUBSCR] = "_SPECIALIZE_BINARY_SUBSCR", + [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", + [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", + [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", [_BINARY_SUBSCR] = "_BINARY_SUBSCR", - [_SPECIALIZE_STORE_SUBSCR] = "_SPECIALIZE_STORE_SUBSCR", - [_STORE_SUBSCR] = "_STORE_SUBSCR", - [_POP_FRAME] = "_POP_FRAME", - [_SPECIALIZE_SEND] = "_SPECIALIZE_SEND", - [_SEND] = "_SEND", - [_SPECIALIZE_UNPACK_SEQUENCE] = "_SPECIALIZE_UNPACK_SEQUENCE", - [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", - [_SPECIALIZE_STORE_ATTR] = "_SPECIALIZE_STORE_ATTR", - [_STORE_ATTR] = "_STORE_ATTR", - [_SPECIALIZE_LOAD_GLOBAL] = "_SPECIALIZE_LOAD_GLOBAL", - [_LOAD_GLOBAL] = "_LOAD_GLOBAL", - [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", - [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", - [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", - [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", - [_SPECIALIZE_LOAD_SUPER_ATTR] = "_SPECIALIZE_LOAD_SUPER_ATTR", - [_LOAD_SUPER_ATTR] = "_LOAD_SUPER_ATTR", - [_SPECIALIZE_LOAD_ATTR] = "_SPECIALIZE_LOAD_ATTR", - [_LOAD_ATTR] = "_LOAD_ATTR", - [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", - [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_CALL] = "_CALL", + [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", + [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", [_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE", - [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", [_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT", - [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", - [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", - [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", - [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", - [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", - [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", - [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", - [_SPECIALIZE_COMPARE_OP] = "_SPECIALIZE_COMPARE_OP", + [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", + [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", + [_CHECK_PEP_523] = "_CHECK_PEP_523", + [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", + [_CHECK_VALIDITY] = "_CHECK_VALIDITY", [_COMPARE_OP] = "_COMPARE_OP", - [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", - [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", - [_IS_NONE] = "_IS_NONE", - [_SPECIALIZE_FOR_ITER] = "_SPECIALIZE_FOR_ITER", [_FOR_ITER] = "_FOR_ITER", [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", - [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", - [_ITER_JUMP_LIST] = "_ITER_JUMP_LIST", + [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", + [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", + [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", + [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", + [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", + [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", + [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", + [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", + [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", + [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", [_GUARD_NOT_EXHAUSTED_LIST] = "_GUARD_NOT_EXHAUSTED_LIST", - [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", - [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", - [_ITER_JUMP_TUPLE] = "_ITER_JUMP_TUPLE", + [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", [_GUARD_NOT_EXHAUSTED_TUPLE] = "_GUARD_NOT_EXHAUSTED_TUPLE", - [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", + [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", + [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_INSERT] = "_INSERT", + [_IS_NONE] = "_IS_NONE", + [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", + [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", + [_ITER_JUMP_LIST] = "_ITER_JUMP_LIST", [_ITER_JUMP_RANGE] = "_ITER_JUMP_RANGE", - [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", + [_ITER_JUMP_TUPLE] = "_ITER_JUMP_TUPLE", + [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", - [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", - [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", + [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", + [_JUMP_TO_TOP] = "_JUMP_TO_TOP", + [_LOAD_ATTR] = "_LOAD_ATTR", + [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", + [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", [_LOAD_ATTR_METHOD_NO_DICT] = "_LOAD_ATTR_METHOD_NO_DICT", - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", + [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", - [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", - [_SPECIALIZE_CALL] = "_SPECIALIZE_CALL", - [_CALL] = "_CALL", - [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", - [_CHECK_PEP_523] = "_CHECK_PEP_523", - [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", - [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", - [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", + [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", + [_LOAD_GLOBAL] = "_LOAD_GLOBAL", + [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", + [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", + [_LOAD_SUPER_ATTR] = "_LOAD_SUPER_ATTR", + [_POP_FRAME] = "_POP_FRAME", + [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", + [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", [_PUSH_FRAME] = "_PUSH_FRAME", - [_SPECIALIZE_BINARY_OP] = "_SPECIALIZE_BINARY_OP", - [_BINARY_OP] = "_BINARY_OP", - [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", - [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", - [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", - [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", - [_JUMP_TO_TOP] = "_JUMP_TO_TOP", [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", - [_INSERT] = "_INSERT", - [_CHECK_VALIDITY] = "_CHECK_VALIDITY", -}; -#endif // NEED_OPCODE_METADATA - -extern const char *const _PyOpcode_OpName[268]; -#ifdef NEED_OPCODE_METADATA -const char *const _PyOpcode_OpName[268] = { - [CACHE] = "CACHE", - [RESERVED] = "RESERVED", - [RESUME] = "RESUME", - [BEFORE_ASYNC_WITH] = "BEFORE_ASYNC_WITH", - [BEFORE_WITH] = "BEFORE_WITH", - [BINARY_OP_INPLACE_ADD_UNICODE] = "BINARY_OP_INPLACE_ADD_UNICODE", - [BINARY_SLICE] = "BINARY_SLICE", - [BINARY_SUBSCR] = "BINARY_SUBSCR", - [CHECK_EG_MATCH] = "CHECK_EG_MATCH", - [CHECK_EXC_MATCH] = "CHECK_EXC_MATCH", - [CLEANUP_THROW] = "CLEANUP_THROW", - [DELETE_SUBSCR] = "DELETE_SUBSCR", - [END_ASYNC_FOR] = "END_ASYNC_FOR", - [END_FOR] = "END_FOR", - [END_SEND] = "END_SEND", - [EXIT_INIT_CHECK] = "EXIT_INIT_CHECK", - [FORMAT_SIMPLE] = "FORMAT_SIMPLE", - [FORMAT_WITH_SPEC] = "FORMAT_WITH_SPEC", - [GET_AITER] = "GET_AITER", - [GET_ANEXT] = "GET_ANEXT", - [GET_ITER] = "GET_ITER", - [GET_LEN] = "GET_LEN", - [GET_YIELD_FROM_ITER] = "GET_YIELD_FROM_ITER", - [INTERPRETER_EXIT] = "INTERPRETER_EXIT", - [LOAD_ASSERTION_ERROR] = "LOAD_ASSERTION_ERROR", - [LOAD_BUILD_CLASS] = "LOAD_BUILD_CLASS", - [LOAD_LOCALS] = "LOAD_LOCALS", - [MAKE_FUNCTION] = "MAKE_FUNCTION", - [MATCH_KEYS] = "MATCH_KEYS", - [MATCH_MAPPING] = "MATCH_MAPPING", - [MATCH_SEQUENCE] = "MATCH_SEQUENCE", - [NOP] = "NOP", - [POP_EXCEPT] = "POP_EXCEPT", - [POP_TOP] = "POP_TOP", - [PUSH_EXC_INFO] = "PUSH_EXC_INFO", - [PUSH_NULL] = "PUSH_NULL", - [RETURN_GENERATOR] = "RETURN_GENERATOR", - [RETURN_VALUE] = "RETURN_VALUE", - [SETUP_ANNOTATIONS] = "SETUP_ANNOTATIONS", - [STORE_SLICE] = "STORE_SLICE", - [STORE_SUBSCR] = "STORE_SUBSCR", - [TO_BOOL] = "TO_BOOL", - [UNARY_INVERT] = "UNARY_INVERT", - [UNARY_NEGATIVE] = "UNARY_NEGATIVE", - [UNARY_NOT] = "UNARY_NOT", - [WITH_EXCEPT_START] = "WITH_EXCEPT_START", + [_SEND] = "_SEND", + [_SPECIALIZE_BINARY_OP] = "_SPECIALIZE_BINARY_OP", + [_SPECIALIZE_BINARY_SUBSCR] = "_SPECIALIZE_BINARY_SUBSCR", + [_SPECIALIZE_CALL] = "_SPECIALIZE_CALL", + [_SPECIALIZE_COMPARE_OP] = "_SPECIALIZE_COMPARE_OP", + [_SPECIALIZE_FOR_ITER] = "_SPECIALIZE_FOR_ITER", + [_SPECIALIZE_LOAD_ATTR] = "_SPECIALIZE_LOAD_ATTR", + [_SPECIALIZE_LOAD_GLOBAL] = "_SPECIALIZE_LOAD_GLOBAL", + [_SPECIALIZE_LOAD_SUPER_ATTR] = "_SPECIALIZE_LOAD_SUPER_ATTR", + [_SPECIALIZE_SEND] = "_SPECIALIZE_SEND", + [_SPECIALIZE_STORE_ATTR] = "_SPECIALIZE_STORE_ATTR", + [_SPECIALIZE_STORE_SUBSCR] = "_SPECIALIZE_STORE_SUBSCR", + [_SPECIALIZE_TO_BOOL] = "_SPECIALIZE_TO_BOOL", + [_SPECIALIZE_UNPACK_SEQUENCE] = "_SPECIALIZE_UNPACK_SEQUENCE", + [_STORE_ATTR] = "_STORE_ATTR", + [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", + [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", + [_STORE_SUBSCR] = "_STORE_SUBSCR", + [_TO_BOOL] = "_TO_BOOL", + [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", +}; +#endif // NEED_OPCODE_METADATA + +extern const char *const _PyOpcode_OpName[268]; +#ifdef NEED_OPCODE_METADATA +const char *const _PyOpcode_OpName[268] = { + [BEFORE_ASYNC_WITH] = "BEFORE_ASYNC_WITH", + [BEFORE_WITH] = "BEFORE_WITH", [BINARY_OP] = "BINARY_OP", + [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT", + [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT", + [BINARY_OP_ADD_UNICODE] = "BINARY_OP_ADD_UNICODE", + [BINARY_OP_INPLACE_ADD_UNICODE] = "BINARY_OP_INPLACE_ADD_UNICODE", + [BINARY_OP_MULTIPLY_FLOAT] = "BINARY_OP_MULTIPLY_FLOAT", + [BINARY_OP_MULTIPLY_INT] = "BINARY_OP_MULTIPLY_INT", + [BINARY_OP_SUBTRACT_FLOAT] = "BINARY_OP_SUBTRACT_FLOAT", + [BINARY_OP_SUBTRACT_INT] = "BINARY_OP_SUBTRACT_INT", + [BINARY_SLICE] = "BINARY_SLICE", + [BINARY_SUBSCR] = "BINARY_SUBSCR", + [BINARY_SUBSCR_DICT] = "BINARY_SUBSCR_DICT", + [BINARY_SUBSCR_GETITEM] = "BINARY_SUBSCR_GETITEM", + [BINARY_SUBSCR_LIST_INT] = "BINARY_SUBSCR_LIST_INT", + [BINARY_SUBSCR_STR_INT] = "BINARY_SUBSCR_STR_INT", + [BINARY_SUBSCR_TUPLE_INT] = "BINARY_SUBSCR_TUPLE_INT", [BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP", [BUILD_LIST] = "BUILD_LIST", [BUILD_MAP] = "BUILD_MAP", @@ -2017,12 +1988,37 @@ const char *const _PyOpcode_OpName[268] = { [BUILD_SLICE] = "BUILD_SLICE", [BUILD_STRING] = "BUILD_STRING", [BUILD_TUPLE] = "BUILD_TUPLE", + [CACHE] = "CACHE", [CALL] = "CALL", + [CALL_ALLOC_AND_ENTER_INIT] = "CALL_ALLOC_AND_ENTER_INIT", + [CALL_BOUND_METHOD_EXACT_ARGS] = "CALL_BOUND_METHOD_EXACT_ARGS", + [CALL_BUILTIN_CLASS] = "CALL_BUILTIN_CLASS", + [CALL_BUILTIN_FAST] = "CALL_BUILTIN_FAST", + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = "CALL_BUILTIN_FAST_WITH_KEYWORDS", + [CALL_BUILTIN_O] = "CALL_BUILTIN_O", [CALL_FUNCTION_EX] = "CALL_FUNCTION_EX", [CALL_INTRINSIC_1] = "CALL_INTRINSIC_1", [CALL_INTRINSIC_2] = "CALL_INTRINSIC_2", + [CALL_ISINSTANCE] = "CALL_ISINSTANCE", [CALL_KW] = "CALL_KW", + [CALL_LEN] = "CALL_LEN", + [CALL_LIST_APPEND] = "CALL_LIST_APPEND", + [CALL_METHOD_DESCRIPTOR_FAST] = "CALL_METHOD_DESCRIPTOR_FAST", + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + [CALL_METHOD_DESCRIPTOR_NOARGS] = "CALL_METHOD_DESCRIPTOR_NOARGS", + [CALL_METHOD_DESCRIPTOR_O] = "CALL_METHOD_DESCRIPTOR_O", + [CALL_PY_EXACT_ARGS] = "CALL_PY_EXACT_ARGS", + [CALL_PY_WITH_DEFAULTS] = "CALL_PY_WITH_DEFAULTS", + [CALL_STR_1] = "CALL_STR_1", + [CALL_TUPLE_1] = "CALL_TUPLE_1", + [CALL_TYPE_1] = "CALL_TYPE_1", + [CHECK_EG_MATCH] = "CHECK_EG_MATCH", + [CHECK_EXC_MATCH] = "CHECK_EXC_MATCH", + [CLEANUP_THROW] = "CLEANUP_THROW", [COMPARE_OP] = "COMPARE_OP", + [COMPARE_OP_FLOAT] = "COMPARE_OP_FLOAT", + [COMPARE_OP_INT] = "COMPARE_OP_INT", + [COMPARE_OP_STR] = "COMPARE_OP_STR", [CONTAINS_OP] = "CONTAINS_OP", [CONVERT_VALUE] = "CONVERT_VALUE", [COPY] = "COPY", @@ -2032,21 +2028,74 @@ const char *const _PyOpcode_OpName[268] = { [DELETE_FAST] = "DELETE_FAST", [DELETE_GLOBAL] = "DELETE_GLOBAL", [DELETE_NAME] = "DELETE_NAME", + [DELETE_SUBSCR] = "DELETE_SUBSCR", [DICT_MERGE] = "DICT_MERGE", [DICT_UPDATE] = "DICT_UPDATE", + [END_ASYNC_FOR] = "END_ASYNC_FOR", + [END_FOR] = "END_FOR", + [END_SEND] = "END_SEND", [ENTER_EXECUTOR] = "ENTER_EXECUTOR", + [EXIT_INIT_CHECK] = "EXIT_INIT_CHECK", [EXTENDED_ARG] = "EXTENDED_ARG", + [FORMAT_SIMPLE] = "FORMAT_SIMPLE", + [FORMAT_WITH_SPEC] = "FORMAT_WITH_SPEC", [FOR_ITER] = "FOR_ITER", + [FOR_ITER_GEN] = "FOR_ITER_GEN", + [FOR_ITER_LIST] = "FOR_ITER_LIST", + [FOR_ITER_RANGE] = "FOR_ITER_RANGE", + [FOR_ITER_TUPLE] = "FOR_ITER_TUPLE", + [GET_AITER] = "GET_AITER", + [GET_ANEXT] = "GET_ANEXT", [GET_AWAITABLE] = "GET_AWAITABLE", + [GET_ITER] = "GET_ITER", + [GET_LEN] = "GET_LEN", + [GET_YIELD_FROM_ITER] = "GET_YIELD_FROM_ITER", [IMPORT_FROM] = "IMPORT_FROM", [IMPORT_NAME] = "IMPORT_NAME", + [INSTRUMENTED_CALL] = "INSTRUMENTED_CALL", + [INSTRUMENTED_CALL_FUNCTION_EX] = "INSTRUMENTED_CALL_FUNCTION_EX", + [INSTRUMENTED_CALL_KW] = "INSTRUMENTED_CALL_KW", + [INSTRUMENTED_END_FOR] = "INSTRUMENTED_END_FOR", + [INSTRUMENTED_END_SEND] = "INSTRUMENTED_END_SEND", + [INSTRUMENTED_FOR_ITER] = "INSTRUMENTED_FOR_ITER", + [INSTRUMENTED_INSTRUCTION] = "INSTRUMENTED_INSTRUCTION", + [INSTRUMENTED_JUMP_BACKWARD] = "INSTRUMENTED_JUMP_BACKWARD", + [INSTRUMENTED_JUMP_FORWARD] = "INSTRUMENTED_JUMP_FORWARD", + [INSTRUMENTED_LINE] = "INSTRUMENTED_LINE", + [INSTRUMENTED_LOAD_SUPER_ATTR] = "INSTRUMENTED_LOAD_SUPER_ATTR", + [INSTRUMENTED_POP_JUMP_IF_FALSE] = "INSTRUMENTED_POP_JUMP_IF_FALSE", + [INSTRUMENTED_POP_JUMP_IF_NONE] = "INSTRUMENTED_POP_JUMP_IF_NONE", + [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = "INSTRUMENTED_POP_JUMP_IF_NOT_NONE", + [INSTRUMENTED_POP_JUMP_IF_TRUE] = "INSTRUMENTED_POP_JUMP_IF_TRUE", + [INSTRUMENTED_RESUME] = "INSTRUMENTED_RESUME", + [INSTRUMENTED_RETURN_CONST] = "INSTRUMENTED_RETURN_CONST", + [INSTRUMENTED_RETURN_VALUE] = "INSTRUMENTED_RETURN_VALUE", + [INSTRUMENTED_YIELD_VALUE] = "INSTRUMENTED_YIELD_VALUE", + [INTERPRETER_EXIT] = "INTERPRETER_EXIT", [IS_OP] = "IS_OP", + [JUMP] = "JUMP", [JUMP_BACKWARD] = "JUMP_BACKWARD", [JUMP_BACKWARD_NO_INTERRUPT] = "JUMP_BACKWARD_NO_INTERRUPT", [JUMP_FORWARD] = "JUMP_FORWARD", + [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", [LIST_APPEND] = "LIST_APPEND", [LIST_EXTEND] = "LIST_EXTEND", + [LOAD_ASSERTION_ERROR] = "LOAD_ASSERTION_ERROR", [LOAD_ATTR] = "LOAD_ATTR", + [LOAD_ATTR_CLASS] = "LOAD_ATTR_CLASS", + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + [LOAD_ATTR_INSTANCE_VALUE] = "LOAD_ATTR_INSTANCE_VALUE", + [LOAD_ATTR_METHOD_LAZY_DICT] = "LOAD_ATTR_METHOD_LAZY_DICT", + [LOAD_ATTR_METHOD_NO_DICT] = "LOAD_ATTR_METHOD_NO_DICT", + [LOAD_ATTR_METHOD_WITH_VALUES] = "LOAD_ATTR_METHOD_WITH_VALUES", + [LOAD_ATTR_MODULE] = "LOAD_ATTR_MODULE", + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [LOAD_ATTR_PROPERTY] = "LOAD_ATTR_PROPERTY", + [LOAD_ATTR_SLOT] = "LOAD_ATTR_SLOT", + [LOAD_ATTR_WITH_HINT] = "LOAD_ATTR_WITH_HINT", + [LOAD_BUILD_CLASS] = "LOAD_BUILD_CLASS", + [LOAD_CLOSURE] = "LOAD_CLOSURE", [LOAD_CONST] = "LOAD_CONST", [LOAD_DEREF] = "LOAD_DEREF", [LOAD_FAST] = "LOAD_FAST", @@ -2056,133 +2105,84 @@ const char *const _PyOpcode_OpName[268] = { [LOAD_FROM_DICT_OR_DEREF] = "LOAD_FROM_DICT_OR_DEREF", [LOAD_FROM_DICT_OR_GLOBALS] = "LOAD_FROM_DICT_OR_GLOBALS", [LOAD_GLOBAL] = "LOAD_GLOBAL", + [LOAD_GLOBAL_BUILTIN] = "LOAD_GLOBAL_BUILTIN", + [LOAD_GLOBAL_MODULE] = "LOAD_GLOBAL_MODULE", + [LOAD_LOCALS] = "LOAD_LOCALS", + [LOAD_METHOD] = "LOAD_METHOD", [LOAD_NAME] = "LOAD_NAME", [LOAD_SUPER_ATTR] = "LOAD_SUPER_ATTR", + [LOAD_SUPER_ATTR_ATTR] = "LOAD_SUPER_ATTR_ATTR", + [LOAD_SUPER_ATTR_METHOD] = "LOAD_SUPER_ATTR_METHOD", + [LOAD_SUPER_METHOD] = "LOAD_SUPER_METHOD", + [LOAD_ZERO_SUPER_ATTR] = "LOAD_ZERO_SUPER_ATTR", + [LOAD_ZERO_SUPER_METHOD] = "LOAD_ZERO_SUPER_METHOD", [MAKE_CELL] = "MAKE_CELL", + [MAKE_FUNCTION] = "MAKE_FUNCTION", [MAP_ADD] = "MAP_ADD", [MATCH_CLASS] = "MATCH_CLASS", + [MATCH_KEYS] = "MATCH_KEYS", + [MATCH_MAPPING] = "MATCH_MAPPING", + [MATCH_SEQUENCE] = "MATCH_SEQUENCE", + [NOP] = "NOP", + [POP_BLOCK] = "POP_BLOCK", + [POP_EXCEPT] = "POP_EXCEPT", [POP_JUMP_IF_FALSE] = "POP_JUMP_IF_FALSE", [POP_JUMP_IF_NONE] = "POP_JUMP_IF_NONE", [POP_JUMP_IF_NOT_NONE] = "POP_JUMP_IF_NOT_NONE", [POP_JUMP_IF_TRUE] = "POP_JUMP_IF_TRUE", + [POP_TOP] = "POP_TOP", + [PUSH_EXC_INFO] = "PUSH_EXC_INFO", + [PUSH_NULL] = "PUSH_NULL", [RAISE_VARARGS] = "RAISE_VARARGS", [RERAISE] = "RERAISE", + [RESERVED] = "RESERVED", + [RESUME] = "RESUME", + [RESUME_CHECK] = "RESUME_CHECK", [RETURN_CONST] = "RETURN_CONST", + [RETURN_GENERATOR] = "RETURN_GENERATOR", + [RETURN_VALUE] = "RETURN_VALUE", [SEND] = "SEND", + [SEND_GEN] = "SEND_GEN", + [SETUP_ANNOTATIONS] = "SETUP_ANNOTATIONS", + [SETUP_CLEANUP] = "SETUP_CLEANUP", + [SETUP_FINALLY] = "SETUP_FINALLY", + [SETUP_WITH] = "SETUP_WITH", [SET_ADD] = "SET_ADD", [SET_FUNCTION_ATTRIBUTE] = "SET_FUNCTION_ATTRIBUTE", [SET_UPDATE] = "SET_UPDATE", [STORE_ATTR] = "STORE_ATTR", + [STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE", + [STORE_ATTR_SLOT] = "STORE_ATTR_SLOT", + [STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT", [STORE_DEREF] = "STORE_DEREF", [STORE_FAST] = "STORE_FAST", [STORE_FAST_LOAD_FAST] = "STORE_FAST_LOAD_FAST", + [STORE_FAST_MAYBE_NULL] = "STORE_FAST_MAYBE_NULL", [STORE_FAST_STORE_FAST] = "STORE_FAST_STORE_FAST", [STORE_GLOBAL] = "STORE_GLOBAL", [STORE_NAME] = "STORE_NAME", - [SWAP] = "SWAP", - [UNPACK_EX] = "UNPACK_EX", - [UNPACK_SEQUENCE] = "UNPACK_SEQUENCE", - [YIELD_VALUE] = "YIELD_VALUE", - [BINARY_OP_ADD_FLOAT] = "BINARY_OP_ADD_FLOAT", - [BINARY_OP_ADD_INT] = "BINARY_OP_ADD_INT", - [BINARY_OP_ADD_UNICODE] = "BINARY_OP_ADD_UNICODE", - [BINARY_OP_MULTIPLY_FLOAT] = "BINARY_OP_MULTIPLY_FLOAT", - [BINARY_OP_MULTIPLY_INT] = "BINARY_OP_MULTIPLY_INT", - [BINARY_OP_SUBTRACT_FLOAT] = "BINARY_OP_SUBTRACT_FLOAT", - [BINARY_OP_SUBTRACT_INT] = "BINARY_OP_SUBTRACT_INT", - [BINARY_SUBSCR_DICT] = "BINARY_SUBSCR_DICT", - [BINARY_SUBSCR_GETITEM] = "BINARY_SUBSCR_GETITEM", - [BINARY_SUBSCR_LIST_INT] = "BINARY_SUBSCR_LIST_INT", - [BINARY_SUBSCR_STR_INT] = "BINARY_SUBSCR_STR_INT", - [BINARY_SUBSCR_TUPLE_INT] = "BINARY_SUBSCR_TUPLE_INT", - [CALL_ALLOC_AND_ENTER_INIT] = "CALL_ALLOC_AND_ENTER_INIT", - [CALL_BOUND_METHOD_EXACT_ARGS] = "CALL_BOUND_METHOD_EXACT_ARGS", - [CALL_BUILTIN_CLASS] = "CALL_BUILTIN_CLASS", - [CALL_BUILTIN_FAST] = "CALL_BUILTIN_FAST", - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = "CALL_BUILTIN_FAST_WITH_KEYWORDS", - [CALL_BUILTIN_O] = "CALL_BUILTIN_O", - [CALL_ISINSTANCE] = "CALL_ISINSTANCE", - [CALL_LEN] = "CALL_LEN", - [CALL_LIST_APPEND] = "CALL_LIST_APPEND", - [CALL_METHOD_DESCRIPTOR_FAST] = "CALL_METHOD_DESCRIPTOR_FAST", - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - [CALL_METHOD_DESCRIPTOR_NOARGS] = "CALL_METHOD_DESCRIPTOR_NOARGS", - [CALL_METHOD_DESCRIPTOR_O] = "CALL_METHOD_DESCRIPTOR_O", - [CALL_PY_EXACT_ARGS] = "CALL_PY_EXACT_ARGS", - [CALL_PY_WITH_DEFAULTS] = "CALL_PY_WITH_DEFAULTS", - [CALL_STR_1] = "CALL_STR_1", - [CALL_TUPLE_1] = "CALL_TUPLE_1", - [CALL_TYPE_1] = "CALL_TYPE_1", - [COMPARE_OP_FLOAT] = "COMPARE_OP_FLOAT", - [COMPARE_OP_INT] = "COMPARE_OP_INT", - [COMPARE_OP_STR] = "COMPARE_OP_STR", - [FOR_ITER_GEN] = "FOR_ITER_GEN", - [FOR_ITER_LIST] = "FOR_ITER_LIST", - [FOR_ITER_RANGE] = "FOR_ITER_RANGE", - [FOR_ITER_TUPLE] = "FOR_ITER_TUPLE", - [LOAD_ATTR_CLASS] = "LOAD_ATTR_CLASS", - [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - [LOAD_ATTR_INSTANCE_VALUE] = "LOAD_ATTR_INSTANCE_VALUE", - [LOAD_ATTR_METHOD_LAZY_DICT] = "LOAD_ATTR_METHOD_LAZY_DICT", - [LOAD_ATTR_METHOD_NO_DICT] = "LOAD_ATTR_METHOD_NO_DICT", - [LOAD_ATTR_METHOD_WITH_VALUES] = "LOAD_ATTR_METHOD_WITH_VALUES", - [LOAD_ATTR_MODULE] = "LOAD_ATTR_MODULE", - [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - [LOAD_ATTR_PROPERTY] = "LOAD_ATTR_PROPERTY", - [LOAD_ATTR_SLOT] = "LOAD_ATTR_SLOT", - [LOAD_ATTR_WITH_HINT] = "LOAD_ATTR_WITH_HINT", - [LOAD_GLOBAL_BUILTIN] = "LOAD_GLOBAL_BUILTIN", - [LOAD_GLOBAL_MODULE] = "LOAD_GLOBAL_MODULE", - [LOAD_SUPER_ATTR_ATTR] = "LOAD_SUPER_ATTR_ATTR", - [LOAD_SUPER_ATTR_METHOD] = "LOAD_SUPER_ATTR_METHOD", - [RESUME_CHECK] = "RESUME_CHECK", - [SEND_GEN] = "SEND_GEN", - [STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE", - [STORE_ATTR_SLOT] = "STORE_ATTR_SLOT", - [STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT", + [STORE_SLICE] = "STORE_SLICE", + [STORE_SUBSCR] = "STORE_SUBSCR", [STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT", [STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT", + [SWAP] = "SWAP", + [TO_BOOL] = "TO_BOOL", [TO_BOOL_ALWAYS_TRUE] = "TO_BOOL_ALWAYS_TRUE", [TO_BOOL_BOOL] = "TO_BOOL_BOOL", [TO_BOOL_INT] = "TO_BOOL_INT", [TO_BOOL_LIST] = "TO_BOOL_LIST", [TO_BOOL_NONE] = "TO_BOOL_NONE", [TO_BOOL_STR] = "TO_BOOL_STR", + [UNARY_INVERT] = "UNARY_INVERT", + [UNARY_NEGATIVE] = "UNARY_NEGATIVE", + [UNARY_NOT] = "UNARY_NOT", + [UNPACK_EX] = "UNPACK_EX", + [UNPACK_SEQUENCE] = "UNPACK_SEQUENCE", [UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST", [UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE", [UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE", - [INSTRUMENTED_RESUME] = "INSTRUMENTED_RESUME", - [INSTRUMENTED_END_FOR] = "INSTRUMENTED_END_FOR", - [INSTRUMENTED_END_SEND] = "INSTRUMENTED_END_SEND", - [INSTRUMENTED_RETURN_VALUE] = "INSTRUMENTED_RETURN_VALUE", - [INSTRUMENTED_RETURN_CONST] = "INSTRUMENTED_RETURN_CONST", - [INSTRUMENTED_YIELD_VALUE] = "INSTRUMENTED_YIELD_VALUE", - [INSTRUMENTED_LOAD_SUPER_ATTR] = "INSTRUMENTED_LOAD_SUPER_ATTR", - [INSTRUMENTED_FOR_ITER] = "INSTRUMENTED_FOR_ITER", - [INSTRUMENTED_CALL] = "INSTRUMENTED_CALL", - [INSTRUMENTED_CALL_KW] = "INSTRUMENTED_CALL_KW", - [INSTRUMENTED_CALL_FUNCTION_EX] = "INSTRUMENTED_CALL_FUNCTION_EX", - [INSTRUMENTED_INSTRUCTION] = "INSTRUMENTED_INSTRUCTION", - [INSTRUMENTED_JUMP_FORWARD] = "INSTRUMENTED_JUMP_FORWARD", - [INSTRUMENTED_JUMP_BACKWARD] = "INSTRUMENTED_JUMP_BACKWARD", - [INSTRUMENTED_POP_JUMP_IF_TRUE] = "INSTRUMENTED_POP_JUMP_IF_TRUE", - [INSTRUMENTED_POP_JUMP_IF_FALSE] = "INSTRUMENTED_POP_JUMP_IF_FALSE", - [INSTRUMENTED_POP_JUMP_IF_NONE] = "INSTRUMENTED_POP_JUMP_IF_NONE", - [INSTRUMENTED_POP_JUMP_IF_NOT_NONE] = "INSTRUMENTED_POP_JUMP_IF_NOT_NONE", - [INSTRUMENTED_LINE] = "INSTRUMENTED_LINE", - [JUMP] = "JUMP", - [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", - [LOAD_CLOSURE] = "LOAD_CLOSURE", - [LOAD_METHOD] = "LOAD_METHOD", - [LOAD_SUPER_METHOD] = "LOAD_SUPER_METHOD", - [LOAD_ZERO_SUPER_ATTR] = "LOAD_ZERO_SUPER_ATTR", - [LOAD_ZERO_SUPER_METHOD] = "LOAD_ZERO_SUPER_METHOD", - [POP_BLOCK] = "POP_BLOCK", - [SETUP_CLEANUP] = "SETUP_CLEANUP", - [SETUP_FINALLY] = "SETUP_FINALLY", - [SETUP_WITH] = "SETUP_WITH", - [STORE_FAST_MAYBE_NULL] = "STORE_FAST_MAYBE_NULL", + [WITH_EXCEPT_START] = "WITH_EXCEPT_START", + [YIELD_VALUE] = "YIELD_VALUE", }; #endif // NEED_OPCODE_METADATA diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index c6ed5911b846bf..50bc14a57fc584 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -209,8 +209,9 @@ def write_function( "", ): with self.out.block("switch(opcode)"): - for instr, effect in data: - self.out.emit(f"case {instr.name}:") + effects = [(instr.name, effect) for instr, effect in data] + for name, effect in sorted(effects): + self.out.emit(f"case {name}:") self.out.emit(f" return {effect};") self.out.emit("default:") self.out.emit(" return -1;") @@ -433,7 +434,8 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No ";", ): # Write metadata for each instruction - for thing in self.everything: + sorted_things = sorted(self.everything, key = lambda t:t.name) + for thing in sorted_things: match thing: case parsing.InstDef(): self.write_metadata_for_inst(self.instrs[thing.name]) @@ -456,7 +458,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No ";", ): # Write macro expansion for each non-pseudo instruction - for mac in self.macro_instrs.values(): + for mac in sorted(self.macro_instrs.values(), key=lambda t: t.name): if is_super_instruction(mac): # Special-case the heck out of super-instructions self.write_super_expansions(mac.name) @@ -475,7 +477,7 @@ def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> No "=", ";", ): - for name in self.opmap: + for name in sorted(self.opmap): self.out.emit(f'[{name}] = "{name}",') with self.metadata_item( @@ -589,7 +591,7 @@ def add(name: str) -> None: add("_EXIT_TRACE") add("_SET_IP") - for instr in self.instrs.values(): + for instr in sorted(self.instrs.values(), key=lambda t:t.name): # Skip ops that are also macros -- those are desugared inst()s if instr.name not in self.macros: add(instr.name) From a723a13bf135306cdc5999a959596bfb487e8f4f Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Thu, 14 Dec 2023 11:06:53 -0800 Subject: [PATCH 254/442] bpo-36796: Clean the error handling in _testcapimodule.c (GH-13085) --- Modules/_testcapi/datetime.c | 46 +++++++++++++++++++++++++++++------- Modules/_testcapimodule.c | 18 +++++++++----- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Modules/_testcapi/datetime.c b/Modules/_testcapi/datetime.c index 88f992915fa8c1..b1796039f0d83a 100644 --- a/Modules/_testcapi/datetime.c +++ b/Modules/_testcapi/datetime.c @@ -85,17 +85,25 @@ make_timezones_capi(PyObject *self, PyObject *args) { PyObject *offset = PyDelta_FromDSU(0, -18000, 0); PyObject *name = PyUnicode_FromString("EST"); + if (offset == NULL || name == NULL) { + Py_XDECREF(offset); + Py_XDECREF(name); + return NULL; + } PyObject *est_zone_capi = PyDateTimeAPI->TimeZone_FromTimeZone(offset, name); PyObject *est_zone_macro = PyTimeZone_FromOffsetAndName(offset, name); PyObject *est_zone_macro_noname = PyTimeZone_FromOffset(offset); - - Py_DecRef(offset); - Py_DecRef(name); - + Py_DECREF(offset); + Py_DECREF(name); + if (est_zone_capi == NULL || est_zone_macro == NULL || + est_zone_macro_noname == NULL) + { + goto error; + } PyObject *rv = PyTuple_New(3); if (rv == NULL) { - return NULL; + goto error; } PyTuple_SET_ITEM(rv, 0, est_zone_capi); @@ -103,6 +111,11 @@ make_timezones_capi(PyObject *self, PyObject *args) PyTuple_SET_ITEM(rv, 2, est_zone_macro_noname); return rv; +error: + Py_XDECREF(est_zone_capi); + Py_XDECREF(est_zone_macro); + Py_XDECREF(est_zone_macro_noname); + return NULL; } static PyObject * @@ -110,6 +123,11 @@ get_timezones_offset_zero(PyObject *self, PyObject *args) { PyObject *offset = PyDelta_FromDSU(0, 0, 0); PyObject *name = PyUnicode_FromString(""); + if (offset == NULL || name == NULL) { + Py_XDECREF(offset); + Py_XDECREF(name); + return NULL; + } // These two should return the UTC singleton PyObject *utc_singleton_0 = PyTimeZone_FromOffset(offset); @@ -117,16 +135,28 @@ get_timezones_offset_zero(PyObject *self, PyObject *args) // This one will return +00:00 zone, but not the UTC singleton PyObject *non_utc_zone = PyTimeZone_FromOffsetAndName(offset, name); - - Py_DecRef(offset); - Py_DecRef(name); + Py_DECREF(offset); + Py_DECREF(name); + if (utc_singleton_0 == NULL || utc_singleton_1 == NULL || + non_utc_zone == NULL) + { + goto error; + } PyObject *rv = PyTuple_New(3); + if (rv == NULL) { + goto error; + } PyTuple_SET_ITEM(rv, 0, utc_singleton_0); PyTuple_SET_ITEM(rv, 1, utc_singleton_1); PyTuple_SET_ITEM(rv, 2, non_utc_zone); return rv; +error: + Py_XDECREF(utc_singleton_0); + Py_XDECREF(utc_singleton_1); + Py_XDECREF(non_utc_zone); + return NULL; } static PyObject * diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b3ddfae58e6fc0..3527dfa77279ac 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -196,11 +196,11 @@ test_dict_inner(int count) for (i = 0; i < count; i++) { v = PyLong_FromLong(i); if (v == NULL) { - return -1; + goto error; } if (PyDict_SetItem(dict, v, v) < 0) { Py_DECREF(v); - return -1; + goto error; } Py_DECREF(v); } @@ -214,11 +214,12 @@ test_dict_inner(int count) assert(v != UNINITIALIZED_PTR); i = PyLong_AS_LONG(v) + 1; o = PyLong_FromLong(i); - if (o == NULL) - return -1; + if (o == NULL) { + goto error; + } if (PyDict_SetItem(dict, k, o) < 0) { Py_DECREF(o); - return -1; + goto error; } Py_DECREF(o); k = v = UNINITIALIZED_PTR; @@ -236,6 +237,9 @@ test_dict_inner(int count) } else { return 0; } +error: + Py_DECREF(dict); + return -1; } @@ -1556,7 +1560,9 @@ test_structseq_newtype_doesnt_leak(PyObject *Py_UNUSED(self), descr.n_in_sequence = 1; PyTypeObject* structseq_type = PyStructSequence_NewType(&descr); - assert(structseq_type != NULL); + if (structseq_type == NULL) { + return NULL; + } assert(PyType_Check(structseq_type)); assert(PyType_FastSubclass(structseq_type, Py_TPFLAGS_TUPLE_SUBCLASS)); Py_DECREF(structseq_type); From 22511f77c2818a138a252e6ddae89725d082f8b0 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 14 Dec 2023 20:14:50 +0100 Subject: [PATCH 255/442] gh-105912: document gotcha with using os.fork on macOS (#112871) * gh-105912: document gotcha with using os.fork on macOS Using ``fork(2)`` on macOS when also using higher-level system APIs in the parent proces can crash on macOS because those system APIs are not written to handle this usage pattern. There's nothing we can do about this other than documenting the problem. Co-authored-by: Carol Willing --- Doc/library/os.rst | 10 ++++++++++ Doc/library/pty.rst | 3 +++ Doc/library/urllib.request.rst | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index f4566a6684e14c..2ff0b73560ac4c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4383,6 +4383,11 @@ written in Python, such as a mail server's external command delivery program. If you use TLS sockets in an application calling ``fork()``, see the warning in the :mod:`ssl` documentation. + .. warning:: + + On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. versionchanged:: 3.8 Calling ``fork()`` in a subinterpreter is no longer supported (:exc:`RuntimeError` is raised). @@ -4422,6 +4427,11 @@ written in Python, such as a mail server's external command delivery program. .. audit-event:: os.forkpty "" os.forkpty + .. warning:: + + On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. versionchanged:: 3.12 If Python is able to detect that your process has multiple threads, this now raises a :exc:`DeprecationWarning`. See the diff --git a/Doc/library/pty.rst b/Doc/library/pty.rst index af9378464edb9f..bd2f5ed45cb8b4 100644 --- a/Doc/library/pty.rst +++ b/Doc/library/pty.rst @@ -33,6 +33,9 @@ The :mod:`pty` module defines the following functions: file descriptor connected to the child's controlling terminal (and also to the child's standard input and output). + .. warning:: On macOS the use of this function is unsafe when mixed with using + higher-level system APIs, and that includes using :mod:`urllib.request`. + .. function:: openpty() diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index 040e28e9124014..0e18db73280a63 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -21,6 +21,14 @@ authentication, redirections, cookies and more. The `Requests package `_ is recommended for a higher-level HTTP client interface. +.. warning:: + + On macOS it is unsafe to use this module in programs using + :func:`os.fork` because the :func:`getproxies` implementation for + macOS uses a higher-level system API. Set the environment variable + ``no_proxy`` to ``*`` to avoid this problem + (e.g. ``os.environ["no_proxy"] = "*"``). + .. include:: ../includes/wasm-notavail.rst The :mod:`urllib.request` module defines the following functions: From 93cf7358d996e0e296046526bf9bc44755d597d1 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 13:15:29 -0600 Subject: [PATCH 256/442] Add recipe for totient() to demonstrate unique_justseen() and factor(). (gh-113131) --- Doc/library/itertools.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 56c66f670c74dd..83e2a9fdb7b464 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1128,6 +1128,14 @@ The following recipes have a more mathematical flavor: if n > 1: yield n + def totient(n): + "Count of natural numbers up to n that are coprime to n." + # https://mathworld.wolfram.com/TotientFunction.html + # totient(12) --> 4 because len([1, 5, 7, 11]) == 4 + for p in unique_justseen(factor(n)): + n = n // p * (p - 1) + return n + def nth_combination(iterable, r, index): "Equivalent to list(combinations(iterable, r))[index]" pool = tuple(iterable) @@ -1429,6 +1437,25 @@ The following recipes have a more mathematical flavor: >>> all(list(factor(n)) == sorted(factor(n)) for n in range(2_000)) True + >>> totient(0) # https://www.wolframalpha.com/input?i=totient+0 + 0 + >>> first_totients = [1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, + ... 18, 8, 12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, + ... 24, 16, 40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, + ... 28, 58, 16, 60, 30, 36, 32, 48, 20, 66, 32, 44] # https://oeis.org/A000010 + ... + >>> list(map(totient, range(1, 70))) == first_totients + True + >>> reference_totient = lambda n: sum(math.gcd(t, n) == 1 for t in range(1, n+1)) + >>> all(totient(n) == reference_totient(n) for n in range(1000)) + True + >>> totient(128_884_753_939) == 128_884_753_938 # large prime + True + >>> totient(999953 * 999983) == 999952 * 999982 # large semiprime + True + >>> totient(6 ** 20) == 1 * 2**19 * 2 * 3**19 # repeated primes + True + >>> list(flatten([('a', 'b'), (), ('c', 'd', 'e'), ('f',), ('g', 'h', 'i')])) ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] From 006355b2a966060541166608ecfec4c957b85f55 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 19:25:55 +0000 Subject: [PATCH 257/442] gh-101100: Fix Sphinx nitpicks in `library/collections.abc.rst` (#113116) --- Doc/conf.py | 3 +- Doc/library/collections.abc.rst | 80 +++++++++++++++++---------------- Doc/tools/.nitignore | 1 - 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 0d7c0b553eaa74..4077eaf5a139b0 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -245,10 +245,11 @@ # be resolved, as the method is currently undocumented. For context, see # https://github.com/python/cpython/pull/103289. ('py:meth', '_SubParsersAction.add_parser'), - # Attributes that definitely should be documented better, + # Attributes/methods/etc. that definitely should be documented better, # but are deferred for now: ('py:attr', '__annotations__'), ('py:attr', '__wrapped__'), + ('py:meth', 'index'), # list.index, tuple.index, etc. ] # gh-106948: Copy standard C types declared in the "c:type" domain to the diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index edc078953290d7..e0c72ff9249ee7 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -22,7 +22,7 @@ This module provides :term:`abstract base classes ` that can be used to test whether a class provides a particular interface; for -example, whether it is :term:`hashable` or whether it is a mapping. +example, whether it is :term:`hashable` or whether it is a :term:`mapping`. An :func:`issubclass` or :func:`isinstance` test for an interface works in one of three ways. @@ -73,7 +73,7 @@ of the API: >>> isinstance(D(), Sequence) True -In this example, class :class:`D` does not need to define +In this example, class :class:`!D` does not need to define ``__contains__``, ``__iter__``, and ``__reversed__`` because the :ref:`in-operator `, the :term:`iteration ` logic, and the :func:`reversed` function automatically fall back to @@ -183,14 +183,14 @@ ABC Inherits from Abstract Methods Mi .. rubric:: Footnotes -.. [1] These ABCs override :meth:`object.__subclasshook__` to support +.. [1] These ABCs override :meth:`~abc.ABCMeta.__subclasshook__` to support testing an interface by verifying the required methods are present and have not been set to :const:`None`. This only works for simple interfaces. More complex interfaces require registration or direct subclassing. .. [2] Checking ``isinstance(obj, Iterable)`` detects classes that are - registered as :class:`Iterable` or that have an :meth:`__iter__` + registered as :class:`Iterable` or that have an :meth:`~container.__iter__` method, but it does not detect classes that iterate with the :meth:`~object.__getitem__` method. The only reliable way to determine whether an object is :term:`iterable` is to call ``iter(obj)``. @@ -202,26 +202,27 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Container - ABC for classes that provide the :meth:`__contains__` method. + ABC for classes that provide the :meth:`~object.__contains__` method. .. class:: Hashable - ABC for classes that provide the :meth:`__hash__` method. + ABC for classes that provide the :meth:`~object.__hash__` method. .. class:: Sized - ABC for classes that provide the :meth:`__len__` method. + ABC for classes that provide the :meth:`~object.__len__` method. .. class:: Callable - ABC for classes that provide the :meth:`__call__` method. + ABC for classes that provide the :meth:`~object.__call__` method. .. class:: Iterable - ABC for classes that provide the :meth:`__iter__` method. + ABC for classes that provide the :meth:`~container.__iter__` method. Checking ``isinstance(obj, Iterable)`` detects classes that are registered - as :class:`Iterable` or that have an :meth:`__iter__` method, but it does + as :class:`Iterable` or that have an :meth:`~container.__iter__` method, + but it does not detect classes that iterate with the :meth:`~object.__getitem__` method. The only reliable way to determine whether an object is :term:`iterable` is to call ``iter(obj)``. @@ -240,17 +241,17 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Reversible - ABC for iterable classes that also provide the :meth:`__reversed__` + ABC for iterable classes that also provide the :meth:`~object.__reversed__` method. .. versionadded:: 3.6 .. class:: Generator - ABC for generator classes that implement the protocol defined in - :pep:`342` that extends iterators with the :meth:`~generator.send`, + ABC for :term:`generator` classes that implement the protocol defined in + :pep:`342` that extends :term:`iterators ` with the + :meth:`~generator.send`, :meth:`~generator.throw` and :meth:`~generator.close` methods. - See also the definition of :term:`generator`. .. versionadded:: 3.5 @@ -261,7 +262,7 @@ Collections Abstract Base Classes -- Detailed Descriptions ABCs for read-only and mutable :term:`sequences `. Implementation note: Some of the mixin methods, such as - :meth:`__iter__`, :meth:`__reversed__` and :meth:`index`, make + :meth:`~container.__iter__`, :meth:`~object.__reversed__` and :meth:`index`, make repeated calls to the underlying :meth:`~object.__getitem__` method. Consequently, if :meth:`~object.__getitem__` is implemented with constant access speed, the mixin methods will have linear performance; @@ -282,7 +283,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Set MutableSet - ABCs for read-only and mutable sets. + ABCs for read-only and mutable :ref:`sets `. .. class:: Mapping MutableMapping @@ -299,16 +300,16 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Awaitable ABC for :term:`awaitable` objects, which can be used in :keyword:`await` - expressions. Custom implementations must provide the :meth:`__await__` - method. + expressions. Custom implementations must provide the + :meth:`~object.__await__` method. :term:`Coroutine ` objects and instances of the :class:`~collections.abc.Coroutine` ABC are all instances of this ABC. .. note:: - In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine`) are - *awaitables*, even though they do not have an :meth:`__await__` method. + In CPython, generator-based coroutines (:term:`generators ` + decorated with :func:`@types.coroutine `) are + *awaitables*, even though they do not have an :meth:`~object.__await__` method. Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. @@ -316,17 +317,17 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: Coroutine - ABC for coroutine compatible classes. These implement the + ABC for :term:`coroutine` compatible classes. These implement the following methods, defined in :ref:`coroutine-objects`: :meth:`~coroutine.send`, :meth:`~coroutine.throw`, and :meth:`~coroutine.close`. Custom implementations must also implement - :meth:`__await__`. All :class:`Coroutine` instances are also instances of - :class:`Awaitable`. See also the definition of :term:`coroutine`. + :meth:`~object.__await__`. All :class:`Coroutine` instances are also + instances of :class:`Awaitable`. .. note:: - In CPython, generator-based coroutines (generators decorated with - :func:`types.coroutine`) are - *awaitables*, even though they do not have an :meth:`__await__` method. + In CPython, generator-based coroutines (:term:`generators ` + decorated with :func:`@types.coroutine `) are + *awaitables*, even though they do not have an :meth:`~object.__await__` method. Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``. Use :func:`inspect.isawaitable` to detect them. @@ -334,7 +335,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: AsyncIterable - ABC for classes that provide ``__aiter__`` method. See also the + ABC for classes that provide an ``__aiter__`` method. See also the definition of :term:`asynchronous iterable`. .. versionadded:: 3.5 @@ -348,7 +349,7 @@ Collections Abstract Base Classes -- Detailed Descriptions .. class:: AsyncGenerator - ABC for asynchronous generator classes that implement the protocol + ABC for :term:`asynchronous generator` classes that implement the protocol defined in :pep:`525` and :pep:`492`. .. versionadded:: 3.6 @@ -373,9 +374,9 @@ particular functionality, for example:: Several of the ABCs are also useful as mixins that make it easier to develop classes supporting container APIs. For example, to write a class supporting the full :class:`Set` API, it is only necessary to supply the three underlying -abstract methods: :meth:`__contains__`, :meth:`__iter__`, and :meth:`__len__`. -The ABC supplies the remaining methods such as :meth:`__and__` and -:meth:`isdisjoint`:: +abstract methods: :meth:`~object.__contains__`, :meth:`~container.__iter__`, and +:meth:`~object.__len__`. The ABC supplies the remaining methods such as +:meth:`!__and__` and :meth:`~frozenset.isdisjoint`:: class ListBasedSet(collections.abc.Set): ''' Alternate set implementation favoring space over speed @@ -403,23 +404,24 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin: (1) Since some set operations create new sets, the default mixin methods need - a way to create new instances from an iterable. The class constructor is + a way to create new instances from an :term:`iterable`. The class constructor is assumed to have a signature in the form ``ClassName(iterable)``. - That assumption is factored-out to an internal classmethod called - :meth:`_from_iterable` which calls ``cls(iterable)`` to produce a new set. + That assumption is factored-out to an internal :class:`classmethod` called + :meth:`!_from_iterable` which calls ``cls(iterable)`` to produce a new set. If the :class:`Set` mixin is being used in a class with a different - constructor signature, you will need to override :meth:`_from_iterable` + constructor signature, you will need to override :meth:`!_from_iterable` with a classmethod or regular method that can construct new instances from an iterable argument. (2) To override the comparisons (presumably for speed, as the - semantics are fixed), redefine :meth:`__le__` and :meth:`__ge__`, + semantics are fixed), redefine :meth:`~object.__le__` and + :meth:`~object.__ge__`, then the other operations will automatically follow suit. (3) - The :class:`Set` mixin provides a :meth:`_hash` method to compute a hash value - for the set; however, :meth:`__hash__` is not defined because not all sets + The :class:`Set` mixin provides a :meth:`!_hash` method to compute a hash value + for the set; however, :meth:`~object.__hash__` is not defined because not all sets are :term:`hashable` or immutable. To add set hashability using mixins, inherit from both :meth:`Set` and :meth:`Hashable`, then define ``__hash__ = Set._hash``. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 4d1d31d44fcf75..ca0cb84d850928 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -35,7 +35,6 @@ Doc/library/bdb.rst Doc/library/bisect.rst Doc/library/calendar.rst Doc/library/cmd.rst -Doc/library/collections.abc.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst Doc/library/configparser.rst From becad9a2a1b5f3deaad24759daec95014218e0db Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 14:36:40 -0600 Subject: [PATCH 258/442] Remove itertool recipe with low pedagogical value (gh-113138) --- Doc/library/itertools.rst | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 83e2a9fdb7b464..36cea9a835f302 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1136,24 +1136,6 @@ The following recipes have a more mathematical flavor: n = n // p * (p - 1) return n - def nth_combination(iterable, r, index): - "Equivalent to list(combinations(iterable, r))[index]" - pool = tuple(iterable) - n = len(pool) - c = math.comb(n, r) - if index < 0: - index += c - if index < 0 or index >= c: - raise IndexError - result = [] - while r: - c, n, r = c*r//n, n-1, r-1 - while index >= c: - index -= c - c, n = c*(n-r)//n, n-1 - result.append(pool[-1-n]) - return tuple(result) - .. doctest:: :hide: @@ -1577,20 +1559,6 @@ The following recipes have a more mathematical flavor: >>> first_true('ABC0DEF1', '9', str.isdigit) '0' - >>> population = 'ABCDEFGH' - >>> for r in range(len(population) + 1): - ... seq = list(combinations(population, r)) - ... for i in range(len(seq)): - ... assert nth_combination(population, r, i) == seq[i] - ... for i in range(-len(seq), 0): - ... assert nth_combination(population, r, i) == seq[i] - - >>> iterable = 'abcde' - >>> r = 3 - >>> combos = list(combinations(iterable, r)) - >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) - True - .. testcode:: :hide: @@ -1617,6 +1585,24 @@ The following recipes have a more mathematical flavor: for (a, _), (b, c) in pairwise(pairwise(iterable)): yield a, b, c + def nth_combination(iterable, r, index): + "Equivalent to list(combinations(iterable, r))[index]" + pool = tuple(iterable) + n = len(pool) + c = math.comb(n, r) + if index < 0: + index += c + if index < 0 or index >= c: + raise IndexError + result = [] + while r: + c, n, r = c*r//n, n-1, r-1 + while index >= c: + index -= c + c, n = c*(n-r)//n, n-1 + result.append(pool[-1-n]) + return tuple(result) + .. doctest:: :hide: @@ -1632,3 +1618,17 @@ The following recipes have a more mathematical flavor: >>> list(triplewise('ABCDEFG')) [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E'), ('D', 'E', 'F'), ('E', 'F', 'G')] + + >>> population = 'ABCDEFGH' + >>> for r in range(len(population) + 1): + ... seq = list(combinations(population, r)) + ... for i in range(len(seq)): + ... assert nth_combination(population, r, i) == seq[i] + ... for i in range(-len(seq), 0): + ... assert nth_combination(population, r, i) == seq[i] + + >>> iterable = 'abcde' + >>> r = 3 + >>> combos = list(combinations(iterable, r)) + >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) + True From 961f1043a022ddeff314f63480a232335a598d6a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Dec 2023 22:48:36 +0200 Subject: [PATCH 259/442] gh-101100: Fix Sphinx warnings in `whatsnew/2.3.rst` (#112373) --- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.3.rst | 276 +++++++++++++++++++++---------------------- 2 files changed, 138 insertions(+), 139 deletions(-) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index ca0cb84d850928..20580f78e07b5d 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -118,7 +118,6 @@ Doc/using/windows.rst Doc/whatsnew/2.0.rst Doc/whatsnew/2.1.rst Doc/whatsnew/2.2.rst -Doc/whatsnew/2.3.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/whatsnew/2.3.rst b/Doc/whatsnew/2.3.rst index 0c77b339a182c9..8ebcbfaf248551 100644 --- a/Doc/whatsnew/2.3.rst +++ b/Doc/whatsnew/2.3.rst @@ -40,10 +40,10 @@ new feature. PEP 218: A Standard Set Datatype ================================ -The new :mod:`sets` module contains an implementation of a set datatype. The +The new :mod:`!sets` module contains an implementation of a set datatype. The :class:`Set` class is for mutable sets, sets that can have members added and -removed. The :class:`ImmutableSet` class is for sets that can't be modified, -and instances of :class:`ImmutableSet` can therefore be used as dictionary keys. +removed. The :class:`!ImmutableSet` class is for sets that can't be modified, +and instances of :class:`!ImmutableSet` can therefore be used as dictionary keys. Sets are built on top of dictionaries, so the elements within a set must be hashable. @@ -63,10 +63,10 @@ Here's a simple example:: Set([1, 2, 5]) >>> -The union and intersection of sets can be computed with the :meth:`union` and -:meth:`intersection` methods; an alternative notation uses the bitwise operators +The union and intersection of sets can be computed with the :meth:`~frozenset.union` and +:meth:`~frozenset.intersection` methods; an alternative notation uses the bitwise operators ``&`` and ``|``. Mutable sets also have in-place versions of these methods, -:meth:`union_update` and :meth:`intersection_update`. :: +:meth:`!union_update` and :meth:`~frozenset.intersection_update`. :: >>> S1 = sets.Set([1,2,3]) >>> S2 = sets.Set([4,5,6]) @@ -87,7 +87,7 @@ It's also possible to take the symmetric difference of two sets. This is the set of all elements in the union that aren't in the intersection. Another way of putting it is that the symmetric difference contains all elements that are in exactly one set. Again, there's an alternative notation (``^``), and an -in-place version with the ungainly name :meth:`symmetric_difference_update`. :: +in-place version with the ungainly name :meth:`~frozenset.symmetric_difference_update`. :: >>> S1 = sets.Set([1,2,3,4]) >>> S2 = sets.Set([3,4,5,6]) @@ -97,7 +97,7 @@ in-place version with the ungainly name :meth:`symmetric_difference_update`. :: Set([1, 2, 5, 6]) >>> -There are also :meth:`issubset` and :meth:`issuperset` methods for checking +There are also :meth:`!issubset` and :meth:`!issuperset` methods for checking whether one set is a subset or superset of another:: >>> S1 = sets.Set([1,2,3]) @@ -166,7 +166,7 @@ statement isn't 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 @@ -227,7 +227,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 @@ -345,7 +345,7 @@ Python now allows using arbitrary Unicode strings (within the limitations of the file system) for all functions that expect file names, most notably the :func:`open` built-in function. If a Unicode string is passed to :func:`os.listdir`, Python now returns a list of Unicode strings. A new -function, :func:`os.getcwdu`, returns the current directory as a Unicode string. +function, :func:`!os.getcwdu`, returns the current directory as a Unicode string. Byte strings still work as file names, and on Windows Python will transparently convert them to Unicode using the ``mbcs`` encoding. @@ -386,10 +386,10 @@ one followed by the platform on which Python is running. Opening a file with the mode ``'U'`` or ``'rU'`` will open a file for reading in :term:`universal newlines` mode. All three line ending conventions will be translated to a ``'\n'`` in the strings returned by the various file methods such as -:meth:`read` and :meth:`readline`. +:meth:`!read` and :meth:`!readline`. Universal newline support is also used when importing modules and when executing -a file with the :func:`execfile` function. This means that Python modules can +a file with the :func:`!execfile` function. This means that Python modules can be shared between all three operating systems without needing to convert the line-endings. @@ -450,16 +450,16 @@ records to standard error or to a file or socket, send them to the system log, or even e-mail them to a particular address; of course, it's also possible to write your own handler classes. -The :class:`Logger` class is the primary class. Most application code will deal -with one or more :class:`Logger` objects, each one used by a particular -subsystem of the application. Each :class:`Logger` is identified by a name, and +The :class:`~logging.Logger` class is the primary class. Most application code will deal +with one or more :class:`~logging.Logger` objects, each one used by a particular +subsystem of the application. Each :class:`~logging.Logger` is identified by a name, and names are organized into a hierarchy using ``.`` as the component separator. -For example, you might have :class:`Logger` instances named ``server``, +For example, you might have :class:`~logging.Logger` instances named ``server``, ``server.auth`` and ``server.network``. The latter two instances are below ``server`` in the hierarchy. This means that if you turn up the verbosity for ``server`` or direct ``server`` messages to a different handler, the changes will also apply to records logged to ``server.auth`` and ``server.network``. -There's also a root :class:`Logger` that's the parent of all other loggers. +There's also a root :class:`~logging.Logger` that's the parent of all other loggers. For simple uses, the :mod:`logging` package contains some convenience functions that always use the root log:: @@ -480,14 +480,14 @@ This produces the following output:: In the default configuration, informational and debugging messages are suppressed and the output is sent to standard error. You can enable the display -of informational and debugging messages by calling the :meth:`setLevel` method +of informational and debugging messages by calling the :meth:`~logging.Logger.setLevel` method on the root logger. -Notice the :func:`warning` call's use of string formatting operators; all of the +Notice the :func:`~logging.warning` call's use of string formatting operators; all of the functions for logging messages take the arguments ``(msg, arg1, arg2, ...)`` and log the string resulting from ``msg % (arg1, arg2, ...)``. -There's also an :func:`exception` function that records the most recent +There's also an :func:`~logging.exception` function that records the most recent traceback. Any of the other functions will also record the traceback if you specify a true value for the keyword argument *exc_info*. :: @@ -517,16 +517,16 @@ it if it doesn't exist yet. ``getLogger(None)`` returns the root logger. :: ... Log records are usually propagated up the hierarchy, so a message logged to -``server.auth`` is also seen by ``server`` and ``root``, but a :class:`Logger` -can prevent this by setting its :attr:`propagate` attribute to :const:`False`. +``server.auth`` is also seen by ``server`` and ``root``, but a :class:`~logging.Logger` +can prevent this by setting its :attr:`~logging.Logger.propagate` attribute to :const:`False`. There are more classes provided by the :mod:`logging` package that can be -customized. When a :class:`Logger` instance is told to log a message, it -creates a :class:`LogRecord` instance that is sent to any number of different -:class:`Handler` instances. Loggers and handlers can also have an attached list -of filters, and each filter can cause the :class:`LogRecord` to be ignored or +customized. When a :class:`~logging.Logger` instance is told to log a message, it +creates a :class:`~logging.LogRecord` instance that is sent to any number of different +:class:`~logging.Handler` instances. Loggers and handlers can also have an attached list +of filters, and each filter can cause the :class:`~logging.LogRecord` to be ignored or can modify the record before passing it along. When they're finally output, -:class:`LogRecord` instances are converted to text by a :class:`Formatter` +:class:`~logging.LogRecord` instances are converted to text by a :class:`~logging.Formatter` class. All of these classes can be replaced by your own specially written classes. @@ -550,7 +550,7 @@ PEP 285: A Boolean Type ======================= A Boolean type was added to Python 2.3. Two new constants were added to the -:mod:`__builtin__` module, :const:`True` and :const:`False`. (:const:`True` and +:mod:`!__builtin__` module, :const:`True` and :const:`False`. (:const:`True` and :const:`False` constants were added to the built-ins in Python 2.2.1, but the 2.2.1 versions are simply set to integer values of 1 and 0 and aren't a different type.) @@ -662,7 +662,7 @@ a central catalog server. The resulting catalog is available from https://pypi.org. To make the catalog a bit more useful, a new optional *classifiers* keyword -argument has been added to the Distutils :func:`setup` function. A list of +argument has been added to the Distutils :func:`!setup` function. A list of `Trove `_-style strings can be supplied to help classify the software. @@ -703,14 +703,14 @@ PEP 302: New Import Hooks ========================= While it's been possible to write custom import hooks ever since the -:mod:`ihooks` module was introduced in Python 1.3, no one has ever been really +:mod:`!ihooks` module was introduced in Python 1.3, no one has ever been really happy with it because writing new import hooks is difficult and messy. There -have been various proposed alternatives such as the :mod:`imputil` and :mod:`iu` +have been various proposed alternatives such as the :mod:`!imputil` and :mod:`!iu` modules, but none of them has ever gained much acceptance, and none of them were easily usable from C code. :pep:`302` borrows ideas from its predecessors, especially from Gordon -McMillan's :mod:`iu` module. Three new items are added to the :mod:`sys` +McMillan's :mod:`!iu` module. Three new items are added to the :mod:`sys` module: * ``sys.path_hooks`` is a list of callable objects; most often they'll be @@ -790,7 +790,7 @@ package is much simpler:: for line in reader: print line -The :func:`reader` function takes a number of different options. The field +The :func:`~csv.reader` function takes a number of different options. The field separator isn't limited to the comma and can be changed to any character, and so can the quoting and line-ending characters. @@ -814,7 +814,7 @@ of tuples or lists, quoting strings that contain the delimiter. PEP 307: Pickle Enhancements ============================ -The :mod:`pickle` and :mod:`cPickle` modules received some attention during the +The :mod:`pickle` and :mod:`!cPickle` modules received some attention during the 2.3 development cycle. In 2.2, new-style classes could be pickled without difficulty, but they weren't pickled very compactly; :pep:`307` quotes a trivial example where a new-style class results in a pickled string three times longer @@ -829,13 +829,13 @@ fanciest protocol available. Unpickling is no longer considered a safe operation. 2.2's :mod:`pickle` provided hooks for trying to prevent unsafe classes from being unpickled -(specifically, a :attr:`__safe_for_unpickling__` attribute), but none of this +(specifically, a :attr:`!__safe_for_unpickling__` attribute), but none of this code was ever audited and therefore it's all been ripped out in 2.3. You should not unpickle untrusted data in any version of Python. To reduce the pickling overhead for new-style classes, a new interface for customizing pickling was added using three special methods: -:meth:`__getstate__`, :meth:`__setstate__`, and :meth:`__getnewargs__`. Consult +:meth:`~object.__getstate__`, :meth:`~object.__setstate__`, and :meth:`~object.__getnewargs__`. Consult :pep:`307` for the full semantics of these methods. As a way to compress pickles yet further, it's now possible to use integer codes @@ -939,7 +939,7 @@ Or use slice objects directly in subscripts:: To simplify implementing sequences that support extended slicing, slice objects now have a method ``indices(length)`` which, given the length of a sequence, returns a ``(start, stop, step)`` tuple that can be passed directly to -:func:`range`. :meth:`indices` handles omitted and out-of-bounds indices in a +:func:`range`. :meth:`!indices` handles omitted and out-of-bounds indices in a manner consistent with regular slices (and this innocuous phrase hides a welter of confusing details!). The method is intended to be used like this:: @@ -1042,7 +1042,7 @@ Here are all of the changes that Python 2.3 makes to the core Python language. execute any assertions. * Most type objects are now callable, so you can use them to create new objects - such as functions, classes, and modules. (This means that the :mod:`new` module + such as functions, classes, and modules. (This means that the :mod:`!new` module can be deprecated in a future Python version, because you can now use the type objects available in the :mod:`types` module.) For example, you can create a new module object with the following code: @@ -1069,11 +1069,11 @@ Here are all of the changes that Python 2.3 makes to the core Python language. * Using ``None`` as a variable name will now result in a :exc:`SyntaxWarning` warning. In a future version of Python, ``None`` may finally become a keyword. -* The :meth:`xreadlines` method of file objects, introduced in Python 2.1, is no +* The :meth:`!xreadlines` method of file objects, introduced in Python 2.1, is no longer necessary because files now behave as their own iterator. - :meth:`xreadlines` was originally introduced as a faster way to loop over all + :meth:`!xreadlines` was originally introduced as a faster way to loop over all the lines in a file, but now you can simply write ``for line in file_obj``. - File objects also have a new read-only :attr:`encoding` attribute that gives the + File objects also have a new read-only :attr:`!encoding` attribute that gives the encoding used by the file; Unicode strings written to the file will be automatically converted to bytes using the given encoding. @@ -1096,12 +1096,12 @@ Here are all of the changes that Python 2.3 makes to the core Python language. switching overhead. Some multithreaded applications may suffer slower response time, but that's easily fixed by setting the limit back to a lower number using ``sys.setcheckinterval(N)``. The limit can be retrieved with the new - :func:`sys.getcheckinterval` function. + :func:`!sys.getcheckinterval` function. * One minor but far-reaching change is that the names of extension types defined by the modules included with Python now contain the module and a ``'.'`` in front of the type name. For example, in Python 2.2, if you created a socket and - printed its :attr:`__class__`, you'd get this output:: + printed its :attr:`!__class__`, you'd get this output:: >>> s = socket.socket() >>> s.__class__ @@ -1138,9 +1138,9 @@ String Changes True Note that this doesn't tell you where the substring starts; if you need that - information, use the :meth:`find` string method. + information, use the :meth:`~str.find` string method. -* The :meth:`strip`, :meth:`lstrip`, and :meth:`rstrip` string methods now have +* The :meth:`~str.strip`, :meth:`~str.lstrip`, and :meth:`~str.rstrip` string methods now have an optional argument for specifying the characters to strip. The default is still to remove all whitespace characters:: @@ -1156,13 +1156,13 @@ String Changes (Suggested by Simon Brunning and implemented by Walter Dörwald.) -* The :meth:`startswith` and :meth:`endswith` string methods now accept negative +* The :meth:`~str.startswith` and :meth:`~str.endswith` string methods now accept negative numbers for the *start* and *end* parameters. -* Another new string method is :meth:`zfill`, originally a function in the - :mod:`string` module. :meth:`zfill` pads a numeric string with zeros on the +* Another new string method is :meth:`~str.zfill`, originally a function in the + :mod:`string` module. :meth:`~str.zfill` pads a numeric string with zeros on the left until it's the specified width. Note that the ``%`` operator is still more - flexible and powerful than :meth:`zfill`. :: + flexible and powerful than :meth:`~str.zfill`. :: >>> '45'.zfill(4) '0045' @@ -1173,10 +1173,10 @@ String Changes (Contributed by Walter Dörwald.) -* A new type object, :class:`basestring`, has been added. Both 8-bit strings and +* A new type object, :class:`!basestring`, has been added. Both 8-bit strings and Unicode strings inherit from this type, so ``isinstance(obj, basestring)`` will return :const:`True` for either kind of string. It's a completely abstract - type, so you can't create :class:`basestring` instances. + type, so you can't create :class:`!basestring` instances. * Interned strings are no longer immortal and will now be garbage-collected in the usual way when the only reference to them is from the internal dictionary of @@ -1191,7 +1191,7 @@ Optimizations * The creation of new-style class instances has been made much faster; they're now faster than classic classes! -* The :meth:`sort` method of list objects has been extensively rewritten by Tim +* The :meth:`~list.sort` method of list objects has been extensively rewritten by Tim Peters, and the implementation is significantly faster. * Multiplication of large long integers is now much faster thanks to an @@ -1203,7 +1203,7 @@ Optimizations increase, depending on your compiler's idiosyncrasies. See section :ref:`23section-other` for a longer explanation. (Removed by Michael Hudson.) -* :func:`xrange` objects now have their own iterator, making ``for i in +* :func:`!xrange` objects now have their own iterator, making ``for i in xrange(n)`` slightly faster than ``for i in range(n)``. (Patch by Raymond Hettinger.) @@ -1230,21 +1230,21 @@ complete list of changes, or look through the CVS logs for all the details. operator to add another array's contents, and the ``*=`` assignment operator to repeat an array. (Contributed by Jason Orendorff.) -* The :mod:`bsddb` module has been replaced by version 4.1.6 of the `PyBSDDB +* The :mod:`!bsddb` module has been replaced by version 4.1.6 of the `PyBSDDB `_ package, providing a more complete interface to the transactional features of the BerkeleyDB library. - The old version of the module has been renamed to :mod:`bsddb185` and is no + The old version of the module has been renamed to :mod:`!bsddb185` and is no longer built automatically; you'll have to edit :file:`Modules/Setup` to enable - it. Note that the new :mod:`bsddb` package is intended to be compatible with + it. Note that the new :mod:`!bsddb` package is intended to be compatible with the old module, so be sure to file bugs if you discover any incompatibilities. When upgrading to Python 2.3, if the new interpreter is compiled with a new version of the underlying BerkeleyDB library, you will almost certainly have to convert your database files to the new version. You can do this fairly easily with the new scripts :file:`db2pickle.py` and :file:`pickle2db.py` which you will find in the distribution's :file:`Tools/scripts` directory. If you've - already been using the PyBSDDB package and importing it as :mod:`bsddb3`, you - will have to change your ``import`` statements to import it as :mod:`bsddb`. + already been using the PyBSDDB package and importing it as :mod:`!bsddb3`, you + will have to change your ``import`` statements to import it as :mod:`!bsddb`. * The new :mod:`bz2` module is an interface to the bz2 data compression library. bz2-compressed data is usually smaller than corresponding @@ -1253,11 +1253,11 @@ complete list of changes, or look through the CVS logs for all the details. * A set of standard date/time types has been added in the new :mod:`datetime` module. See the following section for more details. -* The Distutils :class:`Extension` class now supports an extra constructor +* The Distutils :class:`!Extension` class now supports an extra constructor argument named *depends* for listing additional source files that an extension depends on. This lets Distutils recompile the module if any of the dependency files are modified. For example, if :file:`sampmodule.c` includes the header - file :file:`sample.h`, you would create the :class:`Extension` object like + file :file:`sample.h`, you would create the :class:`!Extension` object like this:: ext = Extension("samp", @@ -1268,21 +1268,21 @@ complete list of changes, or look through the CVS logs for all the details. (Contributed by Jeremy Hylton.) * Other minor changes to Distutils: it now checks for the :envvar:`CC`, - :envvar:`CFLAGS`, :envvar:`CPP`, :envvar:`LDFLAGS`, and :envvar:`CPPFLAGS` + :envvar:`CFLAGS`, :envvar:`!CPP`, :envvar:`LDFLAGS`, and :envvar:`CPPFLAGS` environment variables, using them to override the settings in Python's configuration (contributed by Robert Weber). * Previously the :mod:`doctest` module would only search the docstrings of public methods and functions for test cases, but it now also examines private - ones as well. The :func:`DocTestSuite` function creates a + ones as well. The :func:`~doctest.DocTestSuite` function creates a :class:`unittest.TestSuite` object from a set of :mod:`doctest` tests. * The new ``gc.get_referents(object)`` function returns a list of all the objects referenced by *object*. -* The :mod:`getopt` module gained a new function, :func:`gnu_getopt`, that - supports the same arguments as the existing :func:`getopt` function but uses - GNU-style scanning mode. The existing :func:`getopt` stops processing options as +* The :mod:`getopt` module gained a new function, :func:`~getopt.gnu_getopt`, that + supports the same arguments as the existing :func:`~getopt.getopt` function but uses + GNU-style scanning mode. The existing :func:`~getopt.getopt` stops processing options as soon as a non-option argument is encountered, but in GNU-style mode processing continues, meaning that options and arguments can be mixed. For example:: @@ -1311,7 +1311,7 @@ complete list of changes, or look through the CVS logs for all the details. O(lg n). (See https://xlinux.nist.gov/dads//HTML/priorityque.html for more information about the priority queue data structure.) - The :mod:`heapq` module provides :func:`heappush` and :func:`heappop` functions + The :mod:`heapq` module provides :func:`~heapq.heappush` and :func:`~heapq.heappop` functions for adding and removing items while maintaining the heap property on top of some other mutable Python sequence type. Here's an example that uses a Python list:: @@ -1343,7 +1343,7 @@ complete list of changes, or look through the CVS logs for all the details. * The :mod:`itertools` contains a number of useful functions for use with iterators, inspired by various functions provided by the ML and Haskell languages. For example, ``itertools.ifilter(predicate, iterator)`` returns all - elements in the iterator for which the function :func:`predicate` returns + elements in the iterator for which the function :func:`!predicate` returns :const:`True`, and ``itertools.repeat(obj, N)`` returns ``obj`` *N* times. There are a number of other functions in the module; see the package's reference documentation for details. @@ -1356,9 +1356,9 @@ complete list of changes, or look through the CVS logs for all the details. was added to :func:`math.log` to make it easier to compute logarithms for bases other than ``e`` and ``10``. (Contributed by Raymond Hettinger.) -* Several new POSIX functions (:func:`getpgid`, :func:`killpg`, :func:`lchown`, - :func:`loadavg`, :func:`major`, :func:`makedev`, :func:`minor`, and - :func:`mknod`) were added to the :mod:`posix` module that underlies the +* Several new POSIX functions (:func:`!getpgid`, :func:`!killpg`, :func:`!lchown`, + :func:`!loadavg`, :func:`!major`, :func:`!makedev`, :func:`!minor`, and + :func:`!mknod`) were added to the :mod:`posix` module that underlies the :mod:`os` module. (Contributed by Gustavo Niemeyer, Geert Jansen, and Denis S. Otkidach.) @@ -1368,9 +1368,9 @@ complete list of changes, or look through the CVS logs for all the details. During testing, it was found that some applications will break if time stamps are floats. For compatibility, when using the tuple interface of the - :class:`stat_result` time stamps will be represented as integers. When using + :class:`~os.stat_result` time stamps will be represented as integers. When using named fields (a feature first introduced in Python 2.2), time stamps are still - represented as integers, unless :func:`os.stat_float_times` is invoked to enable + represented as integers, unless :func:`!os.stat_float_times` is invoked to enable float return values:: >>> os.stat("/tmp").st_mtime @@ -1391,7 +1391,7 @@ complete list of changes, or look through the CVS logs for all the details. automatically generate a usage message. See the following section for more details. -* The old and never-documented :mod:`linuxaudiodev` module has been deprecated, +* The old and never-documented :mod:`!linuxaudiodev` module has been deprecated, and a new version named :mod:`!ossaudiodev` has been added. The module was renamed because the OSS sound drivers can be used on platforms other than Linux, and the interface has also been tidied and brought up to date in various ways. @@ -1402,14 +1402,14 @@ complete list of changes, or look through the CVS logs for all the details. functions for getting the architecture, CPU type, the Windows OS version, and even the Linux distribution version. (Contributed by Marc-André Lemburg.) -* The parser objects provided by the :mod:`pyexpat` module can now optionally +* The parser objects provided by the :mod:`pyexpat ` module can now optionally buffer character data, resulting in fewer calls to your character data handler and therefore faster performance. Setting the parser object's - :attr:`buffer_text` attribute to :const:`True` will enable buffering. + :attr:`~xml.parsers.expat.xmlparser.buffer_text` attribute to :const:`True` will enable buffering. * The ``sample(population, k)`` function was added to the :mod:`random` - module. *population* is a sequence or :class:`xrange` object containing the - elements of a population, and :func:`sample` chooses *k* elements from the + module. *population* is a sequence or :class:`!xrange` object containing the + elements of a population, and :func:`~random.sample` chooses *k* elements from the population without replacing chosen elements. *k* can be any value up to ``len(population)``. For example:: @@ -1436,20 +1436,20 @@ complete list of changes, or look through the CVS logs for all the details. (All changes contributed by Raymond Hettinger.) * The :mod:`readline` module also gained a number of new functions: - :func:`get_history_item`, :func:`get_current_history_length`, and - :func:`redisplay`. + :func:`~readline.get_history_item`, :func:`~readline.get_current_history_length`, and + :func:`~readline.redisplay`. -* The :mod:`rexec` and :mod:`Bastion` modules have been declared dead, and +* The :mod:`!rexec` and :mod:`!Bastion` modules have been declared dead, and attempts to import them will fail with a :exc:`RuntimeError`. New-style classes provide new ways to break out of the restricted execution environment provided - by :mod:`rexec`, and no one has interest in fixing them or time to do so. If - you have applications using :mod:`rexec`, rewrite them to use something else. + by :mod:`!rexec`, and no one has interest in fixing them or time to do so. If + you have applications using :mod:`!rexec`, rewrite them to use something else. (Sticking with Python 2.2 or 2.1 will not make your applications any safer - because there are known bugs in the :mod:`rexec` module in those versions. To - repeat: if you're using :mod:`rexec`, stop using it immediately.) + because there are known bugs in the :mod:`!rexec` module in those versions. To + repeat: if you're using :mod:`!rexec`, stop using it immediately.) -* The :mod:`rotor` module has been deprecated because the algorithm it uses for +* The :mod:`!rotor` module has been deprecated because the algorithm it uses for encryption is not believed to be secure. If you need encryption, use one of the several AES Python modules that are available separately. @@ -1474,9 +1474,9 @@ complete list of changes, or look through the CVS logs for all the details. * On Windows, the :mod:`socket` module now ships with Secure Sockets Layer (SSL) support. -* The value of the C :c:macro:`PYTHON_API_VERSION` macro is now exposed at the +* The value of the C :c:macro:`!PYTHON_API_VERSION` macro is now exposed at the Python level as ``sys.api_version``. The current exception can be cleared by - calling the new :func:`sys.exc_clear` function. + calling the new :func:`!sys.exc_clear` function. * The new :mod:`tarfile` module allows reading from and writing to :program:`tar`\ -format archive files. (Contributed by Lars Gustäbel.) @@ -1486,7 +1486,7 @@ complete list of changes, or look through the CVS logs for all the details. string and returns a list containing the text split into lines of no more than the chosen width. The ``fill(text, width)`` function returns a single string, reformatted to fit into lines no longer than the chosen width. (As you - can guess, :func:`fill` is built on top of :func:`wrap`. For example:: + can guess, :func:`~textwrap.fill` is built on top of :func:`~textwrap.wrap`. For example:: >>> import textwrap >>> paragraph = "Not a whit, we defy augury: ... more text ..." @@ -1503,15 +1503,15 @@ complete list of changes, or look through the CVS logs for all the details. it will come: the readiness is all. >>> - The module also contains a :class:`TextWrapper` class that actually implements - the text wrapping strategy. Both the :class:`TextWrapper` class and the - :func:`wrap` and :func:`fill` functions support a number of additional keyword + The module also contains a :class:`~textwrap.TextWrapper` class that actually implements + the text wrapping strategy. Both the :class:`~textwrap.TextWrapper` class and the + :func:`~textwrap.wrap` and :func:`~textwrap.fill` functions support a number of additional keyword arguments for fine-tuning the formatting; consult the module's documentation for details. (Contributed by Greg Ward.) -* The :mod:`thread` and :mod:`threading` modules now have companion modules, - :mod:`dummy_thread` and :mod:`dummy_threading`, that provide a do-nothing - implementation of the :mod:`thread` module's interface for platforms where +* The :mod:`!thread` and :mod:`threading` modules now have companion modules, + :mod:`!dummy_thread` and :mod:`!dummy_threading`, that provide a do-nothing + implementation of the :mod:`!thread` module's interface for platforms where threads are not supported. The intention is to simplify thread-aware modules (ones that *don't* rely on threads to run) by putting the following code at the top:: @@ -1521,26 +1521,26 @@ complete list of changes, or look through the CVS logs for all the details. except ImportError: import dummy_threading as _threading - In this example, :mod:`_threading` is used as the module name to make it clear + In this example, :mod:`!_threading` is used as the module name to make it clear that the module being used is not necessarily the actual :mod:`threading` - module. Code can call functions and use classes in :mod:`_threading` whether or + module. Code can call functions and use classes in :mod:`!_threading` whether or not threads are supported, avoiding an :keyword:`if` statement and making the code slightly clearer. This module will not magically make multithreaded code run without threads; code that waits for another thread to return or to do something will simply hang forever. -* The :mod:`time` module's :func:`strptime` function has long been an annoyance - because it uses the platform C library's :func:`strptime` implementation, and +* The :mod:`time` module's :func:`~time.strptime` function has long been an annoyance + because it uses the platform C library's :func:`~time.strptime` implementation, and different platforms sometimes have odd bugs. Brett Cannon contributed a portable implementation that's written in pure Python and should behave identically on all platforms. * The new :mod:`timeit` module helps measure how long snippets of Python code take to execute. The :file:`timeit.py` file can be run directly from the - command line, or the module's :class:`Timer` class can be imported and used + command line, or the module's :class:`~timeit.Timer` class can be imported and used directly. Here's a short example that figures out whether it's faster to convert an 8-bit string to Unicode by appending an empty Unicode string to it or - by using the :func:`unicode` function:: + by using the :func:`!unicode` function:: import timeit @@ -1558,46 +1558,46 @@ complete list of changes, or look through the CVS logs for all the details. * The :mod:`!Tix` module has received various bug fixes and updates for the current version of the Tix package. -* The :mod:`Tkinter` module now works with a thread-enabled version of Tcl. +* The :mod:`!Tkinter` module now works with a thread-enabled version of Tcl. Tcl's threading model requires that widgets only be accessed from the thread in which they're created; accesses from another thread can cause Tcl to panic. For - certain Tcl interfaces, :mod:`Tkinter` will now automatically avoid this when a + certain Tcl interfaces, :mod:`!Tkinter` will now automatically avoid this when a widget is accessed from a different thread by marshalling a command, passing it to the correct thread, and waiting for the results. Other interfaces can't be - handled automatically but :mod:`Tkinter` will now raise an exception on such an + handled automatically but :mod:`!Tkinter` will now raise an exception on such an access so that you can at least find out about the problem. See https://mail.python.org/pipermail/python-dev/2002-December/031107.html for a more detailed explanation of this change. (Implemented by Martin von Löwis.) -* Calling Tcl methods through :mod:`_tkinter` no longer returns only strings. +* Calling Tcl methods through :mod:`!_tkinter` no longer returns only strings. Instead, if Tcl returns other objects those objects are converted to their - Python equivalent, if one exists, or wrapped with a :class:`_tkinter.Tcl_Obj` + Python equivalent, if one exists, or wrapped with a :class:`!_tkinter.Tcl_Obj` object if no Python equivalent exists. This behavior can be controlled through - the :meth:`wantobjects` method of :class:`tkapp` objects. + the :meth:`!wantobjects` method of :class:`!tkapp` objects. - When using :mod:`_tkinter` through the :mod:`Tkinter` module (as most Tkinter + When using :mod:`!_tkinter` through the :mod:`!Tkinter` module (as most Tkinter applications will), this feature is always activated. It should not cause compatibility problems, since Tkinter would always convert string results to Python types where possible. If any incompatibilities are found, the old behavior can be restored by setting - the :attr:`wantobjects` variable in the :mod:`Tkinter` module to false before - creating the first :class:`tkapp` object. :: + the :attr:`!wantobjects` variable in the :mod:`!Tkinter` module to false before + creating the first :class:`!tkapp` object. :: import Tkinter Tkinter.wantobjects = 0 Any breakage caused by this change should be reported as a bug. -* The :mod:`UserDict` module has a new :class:`DictMixin` class which defines +* The :mod:`!UserDict` module has a new :class:`!DictMixin` class which defines all dictionary methods for classes that already have a minimum mapping interface. This greatly simplifies writing classes that need to be substitutable for dictionaries, such as the classes in the :mod:`shelve` module. Adding the mix-in as a superclass provides the full dictionary interface - whenever the class defines :meth:`~object.__getitem__`, :meth:`__setitem__`, - :meth:`__delitem__`, and :meth:`keys`. For example:: + whenever the class defines :meth:`~object.__getitem__`, :meth:`~object.__setitem__`, + :meth:`~object.__delitem__`, and :meth:`!keys`. For example:: >>> import UserDict >>> class SeqDict(UserDict.DictMixin): @@ -1640,15 +1640,15 @@ complete list of changes, or look through the CVS logs for all the details. * The DOM implementation in :mod:`xml.dom.minidom` can now generate XML output in a particular encoding by providing an optional encoding argument to the - :meth:`toxml` and :meth:`toprettyxml` methods of DOM nodes. + :meth:`~xml.dom.minidom.Node.toxml` and :meth:`~xml.dom.minidom.Node.toprettyxml` methods of DOM nodes. -* The :mod:`xmlrpclib` module now supports an XML-RPC extension for handling nil +* The :mod:`!xmlrpclib` module now supports an XML-RPC extension for handling nil data values such as Python's ``None``. Nil values are always supported on unmarshalling an XML-RPC response. To generate requests containing ``None``, you must supply a true value for the *allow_none* parameter when creating a - :class:`Marshaller` instance. + :class:`!Marshaller` instance. -* The new :mod:`DocXMLRPCServer` module allows writing self-documenting XML-RPC +* The new :mod:`!DocXMLRPCServer` module allows writing self-documenting XML-RPC servers. Run it in demo mode (as a program) to see it in action. Pointing the web browser to the RPC server produces pydoc-style documentation; pointing xmlrpclib to the server allows invoking the actual methods. (Contributed by @@ -1663,8 +1663,8 @@ complete list of changes, or look through the CVS logs for all the details. The :mod:`socket` module has also been extended to transparently convert Unicode hostnames to the ACE version before passing them to the C library. - Modules that deal with hostnames such as :mod:`httplib` and :mod:`ftplib`) - also support Unicode host names; :mod:`httplib` also sends HTTP ``Host`` + Modules that deal with hostnames such as :mod:`!httplib` and :mod:`ftplib`) + also support Unicode host names; :mod:`!httplib` also sends HTTP ``Host`` headers using the ACE version of the domain name. :mod:`urllib` supports Unicode URLs with non-ASCII host names as long as the ``path`` part of the URL is ASCII only. @@ -1682,17 +1682,17 @@ Date and time types suitable for expressing timestamps were added as the :mod:`datetime` module. The types don't support different calendars or many fancy features, and just stick to the basics of representing time. -The three primary types are: :class:`date`, representing a day, month, and year; +The three primary types are: :class:`~datetime.date`, representing a day, month, and year; :class:`~datetime.time`, consisting of hour, minute, and second; and :class:`~datetime.datetime`, -which contains all the attributes of both :class:`date` and :class:`~datetime.time`. -There's also a :class:`timedelta` class representing differences between two +which contains all the attributes of both :class:`~datetime.date` and :class:`~datetime.time`. +There's also a :class:`~datetime.timedelta` class representing differences between two points in time, and time zone logic is implemented by classes inheriting from -the abstract :class:`tzinfo` class. +the abstract :class:`~datetime.tzinfo` class. -You can create instances of :class:`date` and :class:`~datetime.time` by either supplying +You can create instances of :class:`~datetime.date` and :class:`~datetime.time` by either supplying keyword arguments to the appropriate constructor, e.g. ``datetime.date(year=1972, month=10, day=15)``, or by using one of a number of -class methods. For example, the :meth:`date.today` class method returns the +class methods. For example, the :meth:`~datetime.date.today` class method returns the current local date. Once created, instances of the date/time classes are all immutable. There are a @@ -1707,8 +1707,8 @@ number of methods for producing formatted strings from objects:: >>> now.strftime('%Y %d %b') '2002 30 Dec' -The :meth:`replace` method allows modifying one or more fields of a -:class:`date` or :class:`~datetime.datetime` instance, returning a new instance:: +The :meth:`~datetime.datetime.replace` method allows modifying one or more fields of a +:class:`~datetime.date` or :class:`~datetime.datetime` instance, returning a new instance:: >>> d = datetime.datetime.now() >>> d @@ -1718,10 +1718,10 @@ The :meth:`replace` method allows modifying one or more fields of a >>> Instances can be compared, hashed, and converted to strings (the result is the -same as that of :meth:`isoformat`). :class:`date` and :class:`~datetime.datetime` -instances can be subtracted from each other, and added to :class:`timedelta` +same as that of :meth:`~datetime.datetime.isoformat`). :class:`~datetime.date` and :class:`~datetime.datetime` +instances can be subtracted from each other, and added to :class:`~datetime.timedelta` instances. The largest missing feature is that there's no standard library -support for parsing strings and getting back a :class:`date` or +support for parsing strings and getting back a :class:`~datetime.date` or :class:`~datetime.datetime`. For more information, refer to the module's reference documentation. @@ -1739,7 +1739,7 @@ command-line parsing that follows the Unix conventions, automatically creates the output for :option:`!--help`, and can perform different actions for different options. -You start by creating an instance of :class:`OptionParser` and telling it what +You start by creating an instance of :class:`~optparse.OptionParser` and telling it what your program's options are. :: import sys @@ -1753,7 +1753,7 @@ your program's options are. :: action='store', type='int', dest='length', help='set maximum length of output') -Parsing a command line is then done by calling the :meth:`parse_args` method. :: +Parsing a command line is then done by calling the :meth:`~optparse.OptionParser.parse_args` method. :: options, args = op.parse_args(sys.argv[1:]) print options @@ -1925,7 +1925,7 @@ Changes to Python's build process and to the C API include: dependence on a system version or local installation of Expat. * If you dynamically allocate type objects in your extension, you should be - aware of a change in the rules relating to the :attr:`__module__` and + aware of a change in the rules relating to the :attr:`!__module__` and :attr:`~definition.__name__` attributes. In summary, you will want to ensure the type's dictionary contains a ``'__module__'`` key; making the module name the part of the type name leading up to the final period will no longer have the desired @@ -1940,7 +1940,7 @@ Port-Specific Changes Support for a port to IBM's OS/2 using the EMX runtime environment was merged into the main Python source tree. EMX is a POSIX emulation layer over the OS/2 system APIs. The Python port for EMX tries to support all the POSIX-like -capability exposed by the EMX runtime, and mostly succeeds; :func:`fork` and +capability exposed by the EMX runtime, and mostly succeeds; :func:`!fork` and :func:`fcntl` are restricted by the limitations of the underlying emulation layer. The standard OS/2 port, which uses IBM's Visual Age compiler, also gained support for case-sensitive import semantics as part of the integration of @@ -2031,9 +2031,9 @@ code: the file's encoding (UTF-8, Latin-1, or whatever) by adding a comment to the top of the file. See section :ref:`section-encodings` for more information. -* Calling Tcl methods through :mod:`_tkinter` no longer returns only strings. +* Calling Tcl methods through :mod:`!_tkinter` no longer returns only strings. Instead, if Tcl returns other objects those objects are converted to their - Python equivalent, if one exists, or wrapped with a :class:`_tkinter.Tcl_Obj` + Python equivalent, if one exists, or wrapped with a :class:`!_tkinter.Tcl_Obj` object if no Python equivalent exists. * Large octal and hex literals such as ``0xffffffff`` now trigger a @@ -2049,10 +2049,10 @@ code: * You can no longer disable assertions by assigning to ``__debug__``. -* The Distutils :func:`setup` function has gained various new keyword arguments +* The Distutils :func:`!setup` function has gained various new keyword arguments such as *depends*. Old versions of the Distutils will abort if passed unknown keywords. A solution is to check for the presence of the new - :func:`get_distutil_options` function in your :file:`setup.py` and only uses the + :func:`!get_distutil_options` function in your :file:`setup.py` and only uses the new keywords with a version of the Distutils that supports them:: from distutils import core From 25061f5c98a47691fdb70f550943167bda77f6e0 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 14 Dec 2023 21:10:26 +0000 Subject: [PATCH 260/442] gh-101100: Cleanup `mailbox` docs (#113124) --- Doc/library/mailbox.rst | 261 +++++++++++++++++++++------------------- Doc/tools/.nitignore | 1 - 2 files changed, 139 insertions(+), 123 deletions(-) diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index fd60d163378f07..c98496d1fff993 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -13,8 +13,8 @@ This module defines two classes, :class:`Mailbox` and :class:`Message`, for accessing and manipulating on-disk mailboxes and the messages they contain. -:class:`Mailbox` offers a dictionary-like mapping from keys to messages. -:class:`Message` extends the :mod:`email.message` module's +:class:`!Mailbox` offers a dictionary-like mapping from keys to messages. +:class:`!Message` extends the :mod:`email.message` module's :class:`~email.message.Message` class with format-specific state and behavior. Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. @@ -27,37 +27,38 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-objects: -:class:`Mailbox` objects ------------------------- +:class:`!Mailbox` objects +------------------------- .. class:: Mailbox A mailbox, which may be inspected and modified. - The :class:`Mailbox` class defines an interface and is not intended to be + The :class:`!Mailbox` class defines an interface and is not intended to be instantiated. Instead, format-specific subclasses should inherit from - :class:`Mailbox` and your code should instantiate a particular subclass. + :class:`!Mailbox` and your code should instantiate a particular subclass. - The :class:`Mailbox` interface is dictionary-like, with small keys - corresponding to messages. Keys are issued by the :class:`Mailbox` instance - with which they will be used and are only meaningful to that :class:`Mailbox` + The :class:`!Mailbox` interface is dictionary-like, with small keys + corresponding to messages. Keys are issued by the :class:`!Mailbox` instance + with which they will be used and are only meaningful to that :class:`!Mailbox` instance. A key continues to identify a message even if the corresponding message is modified, such as by replacing it with another message. - Messages may be added to a :class:`Mailbox` instance using the set-like + Messages may be added to a :class:`!Mailbox` instance using the set-like method :meth:`add` and removed using a ``del`` statement or the set-like methods :meth:`remove` and :meth:`discard`. - :class:`Mailbox` interface semantics differ from dictionary semantics in some + :class:`!Mailbox` interface semantics differ from dictionary semantics in some noteworthy ways. Each time a message is requested, a new representation (typically a :class:`Message` instance) is generated based upon the current state of the mailbox. Similarly, when a message is added to a - :class:`Mailbox` instance, the provided message representation's contents are + :class:`!Mailbox` instance, the provided message representation's contents are copied. In neither case is a reference to the message representation kept by - the :class:`Mailbox` instance. + the :class:`!Mailbox` instance. - The default :class:`Mailbox` iterator iterates over message representations, - not keys as the default dictionary iterator does. Moreover, modification of a + The default :class:`!Mailbox` :term:`iterator` iterates over message + representations, not keys as the default :class:`dictionary ` + iterator does. Moreover, modification of a mailbox during iteration is safe and well-defined. Messages added to the mailbox after an iterator is created will not be seen by the iterator. Messages removed from the mailbox before the iterator yields them @@ -69,14 +70,15 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Be very cautious when modifying mailboxes that might be simultaneously changed by some other process. The safest mailbox format to use for such - tasks is Maildir; try to avoid using single-file formats such as mbox for + tasks is :class:`Maildir`; try to avoid using single-file formats such as + :class:`mbox` for concurrent writing. If you're modifying a mailbox, you *must* lock it by calling the :meth:`lock` and :meth:`unlock` methods *before* reading any messages in the file or making any changes by adding or deleting a message. Failing to lock the mailbox runs the risk of losing messages or corrupting the entire mailbox. - :class:`Mailbox` instances have the following methods: + :class:`!Mailbox` instances have the following methods: .. method:: add(message) @@ -127,21 +129,23 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: iterkeys() - keys() - Return an iterator over all keys if called as :meth:`iterkeys` or return a - list of keys if called as :meth:`keys`. + Return an :term:`iterator` over all keys + + + .. method:: keys() + + The same as :meth:`iterkeys`, except that a :class:`list` is returned + rather than an :term:`iterator` .. method:: itervalues() __iter__() - values() - Return an iterator over representations of all messages if called as - :meth:`itervalues` or :meth:`__iter__` or return a list of such - representations if called as :meth:`values`. The messages are represented + Return an :term:`iterator` over representations of all messages. + The messages are represented as instances of the appropriate format-specific :class:`Message` subclass - unless a custom message factory was specified when the :class:`Mailbox` + unless a custom message factory was specified when the :class:`!Mailbox` instance was initialized. .. note:: @@ -150,15 +154,25 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. iterate over keys. + .. method:: values() + + The same as :meth:`itervalues`, except that a :class:`list` is returned + rather than an :term:`iterator` + + .. method:: iteritems() - items() - Return an iterator over (*key*, *message*) pairs, where *key* is a key and - *message* is a message representation, if called as :meth:`iteritems` or - return a list of such pairs if called as :meth:`items`. The messages are + Return an :term:`iterator` over (*key*, *message*) pairs, where *key* is + a key and *message* is a message representation. The messages are represented as instances of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. + + + .. method:: items() + + The same as :meth:`iteritems`, except that a :class:`list` of pairs is + returned rather than an :term:`iterator` of pairs. .. method:: get(key, default=None) @@ -167,9 +181,9 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Return a representation of the message corresponding to *key*. If no such message exists, *default* is returned if the method was called as :meth:`get` and a :exc:`KeyError` exception is raised if the method was - called as :meth:`~object.__getitem__`. The message is represented as an instance + called as :meth:`!__getitem__`. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a - custom message factory was specified when the :class:`Mailbox` instance + custom message factory was specified when the :class:`!Mailbox` instance was initialized. @@ -198,21 +212,23 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_file(key) - Return a file-like representation of the message corresponding to *key*, + Return a :term:`file-like ` representation of the + message corresponding to *key*, or raise a :exc:`KeyError` exception if no such message exists. The file-like object behaves as if open in binary mode. This file should be closed once it is no longer needed. .. versionchanged:: 3.2 - The file object really is a binary file; previously it was incorrectly - returned in text mode. Also, the file-like object now supports the - context management protocol: you can use a :keyword:`with` statement to - automatically close it. + The file object really is a :term:`binary file`; previously it was + incorrectly returned in text mode. Also, the :term:`file-like object` + now supports the :term:`context manager` protocol: you can use a + :keyword:`with` statement to automatically close it. .. note:: - Unlike other representations of messages, file-like representations are - not necessarily independent of the :class:`Mailbox` instance that + Unlike other representations of messages, + :term:`file-like ` representations are not + necessarily independent of the :class:`!Mailbox` instance that created them or of the underlying mailbox. More specific documentation is provided by each subclass. @@ -238,7 +254,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. the message. If no such message exists, return *default*. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. .. method:: popitem() @@ -248,7 +264,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. message. If the mailbox is empty, raise a :exc:`KeyError` exception. The message is represented as an instance of the appropriate format-specific :class:`Message` subclass unless a custom message factory was specified - when the :class:`Mailbox` instance was initialized. + when the :class:`!Mailbox` instance was initialized. .. method:: update(arg) @@ -259,7 +275,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. *message* as if by using :meth:`__setitem__`. As with :meth:`__setitem__`, each *key* must already correspond to a message in the mailbox or else a :exc:`KeyError` exception will be raised, so in general it is incorrect - for *arg* to be a :class:`Mailbox` instance. + for *arg* to be a :class:`!Mailbox` instance. .. note:: @@ -269,7 +285,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: flush() Write any pending changes to the filesystem. For some :class:`Mailbox` - subclasses, changes are always written immediately and :meth:`flush` does + subclasses, changes are always written immediately and :meth:`!flush` does nothing, but you should still make a habit of calling this method. @@ -290,13 +306,13 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() Flush the mailbox, unlock it if necessary, and close any open files. For - some :class:`Mailbox` subclasses, this method does nothing. + some :class:`!Mailbox` subclasses, this method does nothing. .. _mailbox-maildir: -:class:`Maildir` -^^^^^^^^^^^^^^^^ +:class:`!Maildir` objects +^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Maildir(dirname, factory=None, create=True) @@ -330,11 +346,11 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Folders of the style introduced by the Courier mail transfer agent are also supported. Any subdirectory of the main mailbox is considered a folder if ``'.'`` is the first character in its name. Folder names are represented by - :class:`Maildir` without the leading ``'.'``. Each folder is itself a Maildir + :class:`!Maildir` without the leading ``'.'``. Each folder is itself a Maildir mailbox but should not contain other folders. Instead, a logical nesting is indicated using ``'.'`` to delimit levels, e.g., "Archived.2005.07". - .. note:: + .. attribute:: Maildir.colon The Maildir specification requires the use of a colon (``':'``) in certain message file names. However, some operating systems do not permit this @@ -346,9 +362,9 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. import mailbox mailbox.Maildir.colon = '!' - The :attr:`colon` attribute may also be set on a per-instance basis. + The :attr:`!colon` attribute may also be set on a per-instance basis. - :class:`Maildir` instances have all of the methods of :class:`Mailbox` in + :class:`!Maildir` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -359,14 +375,14 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_folder(folder) - Return a :class:`Maildir` instance representing the folder whose name is + Return a :class:`!Maildir` instance representing the folder whose name is *folder*. A :exc:`NoSuchMailboxError` exception is raised if the folder does not exist. .. method:: add_folder(folder) - Create a folder whose name is *folder* and return a :class:`Maildir` + Create a folder whose name is *folder* and return a :class:`!Maildir` instance representing it. @@ -485,7 +501,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. versionadded:: 3.13 - Some :class:`Mailbox` methods implemented by :class:`Maildir` deserve special + Some :class:`Mailbox` methods implemented by :class:`!Maildir` deserve special remarks: @@ -516,7 +532,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() - :class:`Maildir` instances do not keep any open files and the underlying + :class:`!Maildir` instances do not keep any open files and the underlying mailboxes do not support locking, so this method does nothing. @@ -539,8 +555,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mbox: -:class:`mbox` -^^^^^^^^^^^^^ +:class:`!mbox` objects +^^^^^^^^^^^^^^^^^^^^^^ .. class:: mbox(path, factory=None, create=True) @@ -557,22 +573,22 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. each message indicated by a line whose first five characters are "From ". Several variations of the mbox format exist to address perceived shortcomings in - the original. In the interest of compatibility, :class:`mbox` implements the + the original. In the interest of compatibility, :class:`!mbox` implements the original format, which is sometimes referred to as :dfn:`mboxo`. This means that the :mailheader:`Content-Length` header, if present, is ignored and that any occurrences of "From " at the beginning of a line in a message body are transformed to ">From " when storing the message, although occurrences of ">From " are not transformed to "From " when reading the message. - Some :class:`Mailbox` methods implemented by :class:`mbox` deserve special + Some :class:`Mailbox` methods implemented by :class:`!mbox` deserve special remarks: .. method:: get_file(key) - Using the file after calling :meth:`flush` or :meth:`close` on the - :class:`mbox` instance may yield unpredictable results or raise an - exception. + Using the file after calling :meth:`~Mailbox.flush` or + :meth:`~Mailbox.close` on the :class:`!mbox` instance may yield + unpredictable results or raise an exception. .. method:: lock() @@ -596,8 +612,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mh: -:class:`MH` -^^^^^^^^^^^ +:class:`!MH` objects +^^^^^^^^^^^^^^^^^^^^ .. class:: MH(path, factory=None, create=True) @@ -617,12 +633,12 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. messages without moving them to sub-folders. Sequences are defined in a file called :file:`.mh_sequences` in each folder. - The :class:`MH` class manipulates MH mailboxes, but it does not attempt to + The :class:`!MH` class manipulates MH mailboxes, but it does not attempt to emulate all of :program:`mh`'s behaviors. In particular, it does not modify and is not affected by the :file:`context` or :file:`.mh_profile` files that are used by :program:`mh` to store its state and configuration. - :class:`MH` instances have all of the methods of :class:`Mailbox` in addition + :class:`!MH` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -633,14 +649,14 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: get_folder(folder) - Return an :class:`MH` instance representing the folder whose name is + Return an :class:`!MH` instance representing the folder whose name is *folder*. A :exc:`NoSuchMailboxError` exception is raised if the folder does not exist. .. method:: add_folder(folder) - Create a folder whose name is *folder* and return an :class:`MH` instance + Create a folder whose name is *folder* and return an :class:`!MH` instance representing it. @@ -674,7 +690,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Already-issued keys are invalidated by this operation and should not be subsequently used. - Some :class:`Mailbox` methods implemented by :class:`MH` deserve special + Some :class:`Mailbox` methods implemented by :class:`!MH` deserve special remarks: @@ -710,7 +726,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. method:: close() - :class:`MH` instances do not keep any open files, so this method is + :class:`!MH` instances do not keep any open files, so this method is equivalent to :meth:`unlock`. @@ -726,8 +742,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-babyl: -:class:`Babyl` -^^^^^^^^^^^^^^ +:class:`!Babyl` objects +^^^^^^^^^^^^^^^^^^^^^^^ .. class:: Babyl(path, factory=None, create=True) @@ -754,7 +770,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. message, and a list of all user-defined labels found in the mailbox is kept in the Babyl options section. - :class:`Babyl` instances have all of the methods of :class:`Mailbox` in + :class:`!Babyl` instances have all of the methods of :class:`Mailbox` in addition to the following: @@ -769,7 +785,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. options section, but the Babyl section is updated whenever the mailbox is modified. - Some :class:`Mailbox` methods implemented by :class:`Babyl` deserve special + Some :class:`Mailbox` methods implemented by :class:`!Babyl` deserve special remarks: @@ -802,8 +818,8 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-mmdf: -:class:`MMDF` -^^^^^^^^^^^^^ +:class:`!MMDF` objects +^^^^^^^^^^^^^^^^^^^^^^ .. class:: MMDF(path, factory=None, create=True) @@ -824,15 +840,15 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. ">From " when storing messages because the extra message separator lines prevent mistaking such occurrences for the starts of subsequent messages. - Some :class:`Mailbox` methods implemented by :class:`MMDF` deserve special + Some :class:`Mailbox` methods implemented by :class:`!MMDF` deserve special remarks: .. method:: get_file(key) - Using the file after calling :meth:`flush` or :meth:`close` on the - :class:`MMDF` instance may yield unpredictable results or raise an - exception. + Using the file after calling :meth:`~Mailbox.flush` or + :meth:`~Mailbox.close` on the :class:`!MMDF` instance may yield + unpredictable results or raise an exception. .. method:: lock() @@ -854,20 +870,20 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. .. _mailbox-message-objects: -:class:`Message` objects ------------------------- +:class:`!Message` objects +------------------------- .. class:: Message(message=None) A subclass of the :mod:`email.message` module's - :class:`~email.message.Message`. Subclasses of :class:`mailbox.Message` add + :class:`~email.message.Message`. Subclasses of :class:`!mailbox.Message` add mailbox-format-specific state and behavior. If *message* is omitted, the new instance is created in a default, empty state. If *message* is an :class:`email.message.Message` instance, its contents are copied; furthermore, any format-specific information is converted insofar as - possible if *message* is a :class:`Message` instance. If *message* is a string, + possible if *message* is a :class:`!Message` instance. If *message* is a string, a byte string, or a file, it should contain an :rfc:`2822`\ -compliant message, which is read and parsed. Files should be open in binary mode, but text mode files @@ -882,18 +898,18 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. such as whether a message has been read by the user or marked as important is retained, because it applies to the message itself. - There is no requirement that :class:`Message` instances be used to represent + There is no requirement that :class:`!Message` instances be used to represent messages retrieved using :class:`Mailbox` instances. In some situations, the - time and memory required to generate :class:`Message` representations might - not be acceptable. For such situations, :class:`Mailbox` instances also + time and memory required to generate :class:`!Message` representations might + not be acceptable. For such situations, :class:`!Mailbox` instances also offer string and file-like representations, and a custom message factory may - be specified when a :class:`Mailbox` instance is initialized. + be specified when a :class:`!Mailbox` instance is initialized. .. _mailbox-maildirmessage: -:class:`MaildirMessage` -^^^^^^^^^^^^^^^^^^^^^^^ +:class:`!MaildirMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MaildirMessage(message=None) @@ -928,7 +944,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. | T | Trashed | Marked for subsequent deletion | +------+---------+--------------------------------+ - :class:`MaildirMessage` instances offer the following methods: + :class:`!MaildirMessage` instances offer the following methods: .. method:: get_subdir() @@ -1005,7 +1021,7 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. Set "info" to *info*, which should be a string. -When a :class:`MaildirMessage` instance is created based upon an +When a :class:`!MaildirMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1025,7 +1041,7 @@ take place: | T flag | D flag | +--------------------+----------------------------------------------+ -When a :class:`MaildirMessage` instance is created based upon an +When a :class:`!MaildirMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------------------+--------------------------+ @@ -1040,7 +1056,7 @@ When a :class:`MaildirMessage` instance is created based upon an | R flag | "replied" sequence | +-------------------------------+--------------------------+ -When a :class:`MaildirMessage` instance is created based upon a +When a :class:`!MaildirMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------------------+-------------------------------+ @@ -1060,8 +1076,8 @@ When a :class:`MaildirMessage` instance is created based upon a .. _mailbox-mboxmessage: -:class:`mboxMessage` -^^^^^^^^^^^^^^^^^^^^ +:class:`!mboxMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: mboxMessage(message=None) @@ -1097,7 +1113,7 @@ When a :class:`MaildirMessage` instance is created based upon a "D", "F", and "A" flags are stored in the :mailheader:`X-Status` header. The flags and headers typically appear in the order mentioned. - :class:`mboxMessage` instances offer the following methods: + :class:`!mboxMessage` instances offer the following methods: .. method:: get_from() @@ -1145,7 +1161,7 @@ When a :class:`MaildirMessage` instance is created based upon a remove more than one flag at a time, *flag* maybe a string of more than one character. -When an :class:`mboxMessage` instance is created based upon a +When an :class:`!mboxMessage` instance is created based upon a :class:`MaildirMessage` instance, a "From " line is generated based upon the :class:`MaildirMessage` instance's delivery date, and the following conversions take place: @@ -1164,7 +1180,7 @@ take place: | A flag | R flag | +-----------------+-------------------------------+ -When an :class:`mboxMessage` instance is created based upon an +When an :class:`!mboxMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------+--------------------------+ @@ -1179,7 +1195,7 @@ When an :class:`mboxMessage` instance is created based upon an | A flag | "replied" sequence | +-------------------+--------------------------+ -When an :class:`mboxMessage` instance is created based upon a +When an :class:`!mboxMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------+-----------------------------+ @@ -1194,7 +1210,8 @@ When an :class:`mboxMessage` instance is created based upon a | A flag | "answered" label | +-------------------+-----------------------------+ -When a :class:`Message` instance is created based upon an :class:`MMDFMessage` +When a :class:`!mboxMessage` instance is created based upon an +:class:`MMDFMessage` instance, the "From " line is copied and all flags directly correspond: +-----------------+----------------------------+ @@ -1214,8 +1231,8 @@ instance, the "From " line is copied and all flags directly correspond: .. _mailbox-mhmessage: -:class:`MHMessage` -^^^^^^^^^^^^^^^^^^ +:class:`!MHMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MHMessage(message=None) @@ -1239,7 +1256,7 @@ instance, the "From " line is copied and all flags directly correspond: | flagged | Marked as important | +----------+------------------------------------------+ - :class:`MHMessage` instances offer the following methods: + :class:`!MHMessage` instances offer the following methods: .. method:: get_sequences() @@ -1261,7 +1278,7 @@ instance, the "From " line is copied and all flags directly correspond: Remove *sequence* from the list of sequences that include this message. -When an :class:`MHMessage` instance is created based upon a +When an :class:`!MHMessage` instance is created based upon a :class:`MaildirMessage` instance, the following conversions take place: +--------------------+-------------------------------+ @@ -1274,7 +1291,7 @@ When an :class:`MHMessage` instance is created based upon a | "flagged" sequence | F flag | +--------------------+-------------------------------+ -When an :class:`MHMessage` instance is created based upon an +When an :class:`!MHMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1290,7 +1307,7 @@ take place: | "flagged" sequence | F flag | +--------------------+----------------------------------------------+ -When an :class:`MHMessage` instance is created based upon a +When an :class:`!MHMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +--------------------+-----------------------------+ @@ -1304,8 +1321,8 @@ When an :class:`MHMessage` instance is created based upon a .. _mailbox-babylmessage: -:class:`BabylMessage` -^^^^^^^^^^^^^^^^^^^^^ +:class:`!BabylMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: BabylMessage(message=None) @@ -1334,11 +1351,11 @@ When an :class:`MHMessage` instance is created based upon a | resent | Resent | +-----------+------------------------------------------+ - By default, Rmail displays only visible headers. The :class:`BabylMessage` + By default, Rmail displays only visible headers. The :class:`!BabylMessage` class, though, uses the original headers because they are more complete. Visible headers may be accessed explicitly if desired. - :class:`BabylMessage` instances offer the following methods: + :class:`!BabylMessage` instances offer the following methods: .. method:: get_labels() @@ -1377,7 +1394,7 @@ When an :class:`MHMessage` instance is created based upon a .. method:: update_visible() - When a :class:`BabylMessage` instance's original headers are modified, the + When a :class:`!BabylMessage` instance's original headers are modified, the visible headers are not automatically modified to correspond. This method updates the visible headers as follows: each visible header with a corresponding original header is set to the value of the original header, @@ -1387,7 +1404,7 @@ When an :class:`MHMessage` instance is created based upon a present in the original headers but not the visible headers are added to the visible headers. -When a :class:`BabylMessage` instance is created based upon a +When a :class:`!BabylMessage` instance is created based upon a :class:`MaildirMessage` instance, the following conversions take place: +-------------------+-------------------------------+ @@ -1402,7 +1419,7 @@ When a :class:`BabylMessage` instance is created based upon a | "forwarded" label | P flag | +-------------------+-------------------------------+ -When a :class:`BabylMessage` instance is created based upon an +When a :class:`!BabylMessage` instance is created based upon an :class:`mboxMessage` or :class:`MMDFMessage` instance, the :mailheader:`Status` and :mailheader:`X-Status` headers are omitted and the following conversions take place: @@ -1418,7 +1435,7 @@ take place: | "answered" label | A flag | +------------------+----------------------------------------------+ -When a :class:`BabylMessage` instance is created based upon an +When a :class:`!BabylMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +------------------+--------------------------+ @@ -1432,8 +1449,8 @@ When a :class:`BabylMessage` instance is created based upon an .. _mailbox-mmdfmessage: -:class:`MMDFMessage` -^^^^^^^^^^^^^^^^^^^^ +:class:`!MMDFMessage` objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. class:: MMDFMessage(message=None) @@ -1467,7 +1484,7 @@ When a :class:`BabylMessage` instance is created based upon an "D", "F", and "A" flags are stored in the :mailheader:`X-Status` header. The flags and headers typically appear in the order mentioned. - :class:`MMDFMessage` instances offer the following methods, which are + :class:`!MMDFMessage` instances offer the following methods, which are identical to those offered by :class:`mboxMessage`: @@ -1516,7 +1533,7 @@ When a :class:`BabylMessage` instance is created based upon an remove more than one flag at a time, *flag* maybe a string of more than one character. -When an :class:`MMDFMessage` instance is created based upon a +When an :class:`!MMDFMessage` instance is created based upon a :class:`MaildirMessage` instance, a "From " line is generated based upon the :class:`MaildirMessage` instance's delivery date, and the following conversions take place: @@ -1535,7 +1552,7 @@ take place: | A flag | R flag | +-----------------+-------------------------------+ -When an :class:`MMDFMessage` instance is created based upon an +When an :class:`!MMDFMessage` instance is created based upon an :class:`MHMessage` instance, the following conversions take place: +-------------------+--------------------------+ @@ -1550,7 +1567,7 @@ When an :class:`MMDFMessage` instance is created based upon an | A flag | "replied" sequence | +-------------------+--------------------------+ -When an :class:`MMDFMessage` instance is created based upon a +When an :class:`!MMDFMessage` instance is created based upon a :class:`BabylMessage` instance, the following conversions take place: +-------------------+-----------------------------+ @@ -1565,7 +1582,7 @@ When an :class:`MMDFMessage` instance is created based upon a | A flag | "answered" label | +-------------------+-----------------------------+ -When an :class:`MMDFMessage` instance is created based upon an +When an :class:`!MMDFMessage` instance is created based upon an :class:`mboxMessage` instance, the "From " line is copied and all flags directly correspond: @@ -1587,7 +1604,7 @@ correspond: Exceptions ---------- -The following exception classes are defined in the :mod:`mailbox` module: +The following exception classes are defined in the :mod:`!mailbox` module: .. exception:: Error() diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 20580f78e07b5d..d9147aaeee12bd 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -62,7 +62,6 @@ Doc/library/locale.rst Doc/library/logging.config.rst Doc/library/logging.handlers.rst Doc/library/lzma.rst -Doc/library/mailbox.rst Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/multiprocessing.shared_memory.rst From 5f7d7353b47ccf634b9b65f933d3fdeeb395301f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 14 Dec 2023 17:27:39 -0600 Subject: [PATCH 261/442] Optimize unique_justseen() recipe for a common case. (gh-113147) --- Doc/library/itertools.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 36cea9a835f302..03127afe1b4460 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1017,6 +1017,8 @@ which incur interpreter overhead. "List unique elements, preserving order. Remember only the element just seen." # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B # unique_justseen('ABBcCAD', str.lower) --> A B c A D + if key is None: + return map(operator.itemgetter(0), groupby(iterable)) return map(next, map(operator.itemgetter(1), groupby(iterable, key))) From f34e22c6470d1c5d0e2a3ffb2272d375c22654b9 Mon Sep 17 00:00:00 2001 From: Furkan Onder Date: Fri, 15 Dec 2023 02:42:33 +0300 Subject: [PATCH 262/442] gh-112535: Update _Py_ThreadId() to support RISC-V (gh-113084) Update _Py_ThreadId() to support RISC-V --- Include/object.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Include/object.h b/Include/object.h index bd576b0bd43211..d22e5c2b8be2a9 100644 --- a/Include/object.h +++ b/Include/object.h @@ -283,6 +283,13 @@ _Py_ThreadId(void) // Both GCC and Clang have supported __builtin_thread_pointer // for s390 from long time ago. tid = (uintptr_t)__builtin_thread_pointer(); +#elif defined(__riscv) + #if defined(__clang__) && _Py__has_builtin(__builtin_thread_pointer) + tid = (uintptr_t)__builtin_thread_pointer(); + #else + // tp is Thread Pointer provided by the RISC-V ABI. + __asm__ ("mv %0, tp" : "=r" (tid)); + #endif #else # error "define _Py_ThreadId for this platform" #endif From 7bb00f053e0a86bb8827cc464961a4fad9278e6d Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 08:57:23 +0000 Subject: [PATCH 263/442] gh-101100: Fix Sphinx nitpicks in `library/rlcompleter.rst` (#113125) --- Doc/library/readline.rst | 2 ++ Doc/library/rlcompleter.rst | 40 ++++++++++++++++++++----------------- Doc/tools/.nitignore | 1 - 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst index 2e0f45ced30b9c..1adafcaa02eab9 100644 --- a/Doc/library/readline.rst +++ b/Doc/library/readline.rst @@ -218,6 +218,8 @@ Startup hooks if Python was compiled for a version of the library that supports it. +.. _readline-completion: + Completion ---------- diff --git a/Doc/library/rlcompleter.rst b/Doc/library/rlcompleter.rst index 40b09ce897880e..8287699c5f013e 100644 --- a/Doc/library/rlcompleter.rst +++ b/Doc/library/rlcompleter.rst @@ -10,12 +10,14 @@ -------------- -The :mod:`rlcompleter` module defines a completion function suitable for the -:mod:`readline` module by completing valid Python identifiers and keywords. +The :mod:`!rlcompleter` module defines a completion function suitable to be +passed to :func:`~readline.set_completer` in the :mod:`readline` module. When this module is imported on a Unix platform with the :mod:`readline` module available, an instance of the :class:`Completer` class is automatically created -and its :meth:`complete` method is set as the :mod:`readline` completer. +and its :meth:`~Completer.complete` method is set as the +:ref:`readline completer `. The method provides +completion of valid Python :ref:`identifiers and keywords `. Example:: @@ -28,7 +30,7 @@ Example:: readline.__name__ readline.parse_and_bind( >>> readline. -The :mod:`rlcompleter` module is designed for use with Python's +The :mod:`!rlcompleter` module is designed for use with Python's :ref:`interactive mode `. Unless Python is run with the :option:`-S` option, the module is automatically imported and configured (see :ref:`rlcompleter-config`). @@ -39,23 +41,25 @@ this module can still be used for custom purposes. .. _completer-objects: -Completer Objects ------------------ +.. class:: Completer -Completer objects have the following method: + Completer objects have the following method: + .. method:: Completer.complete(text, state) -.. method:: Completer.complete(text, state) + Return the next possible completion for *text*. - Return the *state*\ th completion for *text*. + When called by the :mod:`readline` module, this method is called + successively with ``state == 0, 1, 2, ...`` until the method returns + ``None``. - If called for *text* that doesn't include a period character (``'.'``), it will - complete from names currently defined in :mod:`__main__`, :mod:`builtins` and - keywords (as defined by the :mod:`keyword` module). - - If called for a dotted name, it will try to evaluate anything without obvious - side-effects (functions will not be evaluated, but it can generate calls to - :meth:`__getattr__`) up to the last part, and find matches for the rest via the - :func:`dir` function. Any exception raised during the evaluation of the - expression is caught, silenced and :const:`None` is returned. + If called for *text* that doesn't include a period character (``'.'``), it will + complete from names currently defined in :mod:`__main__`, :mod:`builtins` and + keywords (as defined by the :mod:`keyword` module). + If called for a dotted name, it will try to evaluate anything without obvious + side-effects (functions will not be evaluated, but it can generate calls to + :meth:`~object.__getattr__`) up to the last part, and find matches for the + rest via the :func:`dir` function. Any exception raised during the + evaluation of the expression is caught, silenced and :const:`None` is + returned. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index d9147aaeee12bd..c91e698ff0753a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -79,7 +79,6 @@ Doc/library/pyexpat.rst Doc/library/random.rst Doc/library/readline.rst Doc/library/resource.rst -Doc/library/rlcompleter.rst Doc/library/select.rst Doc/library/signal.rst Doc/library/smtplib.rst From 55ef998a8dead3874e8390284081290c1ccb46e2 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:28:22 +0000 Subject: [PATCH 264/442] gh-112720: Move dis's cache output code to the Formatter, labels lookup to the arg_resolver. Reduce the number of parameters passed around. (#113108) --- Lib/dis.py | 168 +++++++++++++++++++++++-------------------- Lib/test/test_dis.py | 17 +++-- 2 files changed, 104 insertions(+), 81 deletions(-) diff --git a/Lib/dis.py b/Lib/dis.py index 183091cb0d6098..1a2f1032d500af 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -113,7 +113,14 @@ def dis(x=None, *, file=None, depth=None, show_caches=False, adaptive=False, elif hasattr(x, 'co_code'): # Code object _disassemble_recursive(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) elif isinstance(x, (bytes, bytearray)): # Raw bytecode - _disassemble_bytes(x, file=file, show_caches=show_caches, show_offsets=show_offsets) + labels_map = _make_labels_map(x) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + offset_width=len(str(max(len(x) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(labels_map=labels_map) + _disassemble_bytes(x, arg_resolver=arg_resolver, formatter=formatter) elif isinstance(x, str): # Source code _disassemble_str(x, file=file, depth=depth, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) else: @@ -394,23 +401,41 @@ def __str__(self): class Formatter: def __init__(self, file=None, lineno_width=0, offset_width=0, label_width=0, - line_offset=0): + line_offset=0, show_caches=False): """Create a Formatter *file* where to write the output *lineno_width* sets the width of the line number field (0 omits it) *offset_width* sets the width of the instruction offset field *label_width* sets the width of the label field + *show_caches* is a boolean indicating whether to display cache lines - *line_offset* the line number (within the code unit) """ self.file = file self.lineno_width = lineno_width self.offset_width = offset_width self.label_width = label_width - + self.show_caches = show_caches def print_instruction(self, instr, mark_as_current=False): + self.print_instruction_line(instr, mark_as_current) + if self.show_caches and instr.cache_info: + offset = instr.offset + for name, size, data in instr.cache_info: + for i in range(size): + offset += 2 + # Only show the fancy argrepr for a CACHE instruction when it's + # the first entry for a particular cache value: + if i == 0: + argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" + else: + argrepr = "" + self.print_instruction_line( + Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, + False, None, None, instr.positions), + False) + + def print_instruction_line(self, instr, mark_as_current): """Format instruction details for inclusion in disassembly output.""" lineno_width = self.lineno_width offset_width = self.offset_width @@ -474,11 +499,14 @@ def print_exception_table(self, exception_entries): class ArgResolver: - def __init__(self, co_consts, names, varname_from_oparg, labels_map): + def __init__(self, co_consts=None, names=None, varname_from_oparg=None, labels_map=None): self.co_consts = co_consts self.names = names self.varname_from_oparg = varname_from_oparg - self.labels_map = labels_map + self.labels_map = labels_map or {} + + def get_label_for_offset(self, offset): + return self.labels_map.get(offset, None) def get_argval_argrepr(self, op, arg, offset): get_name = None if self.names is None else self.names.__getitem__ @@ -547,8 +575,7 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = _intrinsic_2_descs[arg] return argval, argrepr - -def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): +def get_instructions(x, *, first_line=None, show_caches=None, adaptive=False): """Iterator for the opcodes in methods, functions or code Generates a series of Instruction named tuples giving the details of @@ -567,9 +594,10 @@ def get_instructions(x, *, first_line=None, show_caches=False, adaptive=False): line_offset = 0 original_code = co.co_code - labels_map = _make_labels_map(original_code) - arg_resolver = ArgResolver(co.co_consts, co.co_names, co._varname_from_oparg, - labels_map) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=_make_labels_map(original_code)) return _get_instructions_bytes(_get_code_array(co, adaptive), linestarts=linestarts, line_offset=line_offset, @@ -648,7 +676,7 @@ def _is_backward_jump(op): 'ENTER_EXECUTOR') def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=None, - original_code=None, labels_map=None, arg_resolver=None): + original_code=None, arg_resolver=None): """Iterate over the instructions in a bytecode string. Generates a sequence of Instruction namedtuples giving the details of each @@ -661,8 +689,6 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N original_code = original_code or code co_positions = co_positions or iter(()) - labels_map = labels_map or _make_labels_map(original_code) - starts_line = False local_line_number = None line_number = None @@ -684,10 +710,6 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N else: argval, argrepr = arg, repr(arg) - instr = Instruction(_all_opname[op], op, arg, argval, argrepr, - offset, start_offset, starts_line, line_number, - labels_map.get(offset, None), positions) - caches = _get_cache_size(_all_opname[deop]) # Advance the co_positions iterator: for _ in range(caches): @@ -701,10 +723,10 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N else: cache_info = None + label = arg_resolver.get_label_for_offset(offset) if arg_resolver else None yield Instruction(_all_opname[op], op, arg, argval, argrepr, offset, start_offset, starts_line, line_number, - labels_map.get(offset, None), positions, cache_info) - + label, positions, cache_info) def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, @@ -712,12 +734,20 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False, """Disassemble a code object.""" linestarts = dict(findlinestarts(co)) exception_entries = _parse_exception_table(co) - _disassemble_bytes(_get_code_array(co, adaptive), - lasti, co._varname_from_oparg, - co.co_names, co.co_consts, linestarts, file=file, - exception_entries=exception_entries, - co_positions=co.co_positions(), show_caches=show_caches, - original_code=co.co_code, show_offsets=show_offsets) + labels_map = _make_labels_map(co.co_code, exception_entries=exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=file, + lineno_width=_get_lineno_width(linestarts), + offset_width=len(str(max(len(co.co_code) - 2, 9999))) if show_offsets else 0, + label_width=label_width, + show_caches=show_caches) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(_get_code_array(co, adaptive), lasti, linestarts, + exception_entries=exception_entries, co_positions=co.co_positions(), + original_code=co.co_code, arg_resolver=arg_resolver, formatter=formatter) def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False): disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets) @@ -764,60 +794,29 @@ def _get_lineno_width(linestarts): return lineno_width -def _disassemble_bytes(code, lasti=-1, varname_from_oparg=None, - names=None, co_consts=None, linestarts=None, - *, file=None, line_offset=0, exception_entries=(), - co_positions=None, show_caches=False, original_code=None, - show_offsets=False): - - offset_width = len(str(max(len(code) - 2, 9999))) if show_offsets else 0 - - labels_map = _make_labels_map(original_code or code, exception_entries) - label_width = 4 + len(str(len(labels_map))) +def _disassemble_bytes(code, lasti=-1, linestarts=None, + *, line_offset=0, exception_entries=(), + co_positions=None, original_code=None, + arg_resolver=None, formatter=None): - formatter = Formatter(file=file, - lineno_width=_get_lineno_width(linestarts), - offset_width=offset_width, - label_width=label_width, - line_offset=line_offset) + assert formatter is not None + assert arg_resolver is not None - arg_resolver = ArgResolver(co_consts, names, varname_from_oparg, labels_map) instrs = _get_instructions_bytes(code, linestarts=linestarts, line_offset=line_offset, co_positions=co_positions, original_code=original_code, - labels_map=labels_map, arg_resolver=arg_resolver) - print_instructions(instrs, exception_entries, formatter, - show_caches=show_caches, lasti=lasti) + print_instructions(instrs, exception_entries, formatter, lasti=lasti) -def print_instructions(instrs, exception_entries, formatter, show_caches=False, lasti=-1): +def print_instructions(instrs, exception_entries, formatter, lasti=-1): for instr in instrs: - if show_caches: - is_current_instr = instr.offset == lasti - else: - # Each CACHE takes 2 bytes - is_current_instr = instr.offset <= lasti \ - <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) + # Each CACHE takes 2 bytes + is_current_instr = instr.offset <= lasti \ + <= instr.offset + 2 * _get_cache_size(_all_opname[_deoptop(instr.opcode)]) formatter.print_instruction(instr, is_current_instr) - deop = _deoptop(instr.opcode) - if show_caches and instr.cache_info: - offset = instr.offset - for name, size, data in instr.cache_info: - for i in range(size): - offset += 2 - # Only show the fancy argrepr for a CACHE instruction when it's - # the first entry for a particular cache value: - if i == 0: - argrepr = f"{name}: {int.from_bytes(data, sys.byteorder)}" - else: - argrepr = "" - formatter.print_instruction( - Instruction("CACHE", CACHE, 0, None, argrepr, offset, offset, - False, None, None, instr.positions), - is_current_instr) formatter.print_exception_table(exception_entries) @@ -960,14 +959,15 @@ def __iter__(self): co = self.codeobj original_code = co.co_code labels_map = _make_labels_map(original_code, self.exception_entries) - arg_resolver = ArgResolver(co.co_consts, co.co_names, co._varname_from_oparg, - labels_map) + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) return _get_instructions_bytes(_get_code_array(co, self.adaptive), linestarts=self._linestarts, line_offset=self._line_offset, co_positions=co.co_positions(), original_code=original_code, - labels_map=labels_map, arg_resolver=arg_resolver) def __repr__(self): @@ -995,18 +995,32 @@ def dis(self): else: offset = -1 with io.StringIO() as output: - _disassemble_bytes(_get_code_array(co, self.adaptive), - varname_from_oparg=co._varname_from_oparg, - names=co.co_names, co_consts=co.co_consts, + code = _get_code_array(co, self.adaptive) + offset_width = len(str(max(len(code) - 2, 9999))) if self.show_offsets else 0 + + + labels_map = _make_labels_map(co.co_code, self.exception_entries) + label_width = 4 + len(str(len(labels_map))) + formatter = Formatter(file=output, + lineno_width=_get_lineno_width(self._linestarts), + offset_width=offset_width, + label_width=label_width, + line_offset=self._line_offset, + show_caches=self.show_caches) + + arg_resolver = ArgResolver(co_consts=co.co_consts, + names=co.co_names, + varname_from_oparg=co._varname_from_oparg, + labels_map=labels_map) + _disassemble_bytes(code, linestarts=self._linestarts, line_offset=self._line_offset, - file=output, lasti=offset, exception_entries=self.exception_entries, co_positions=co.co_positions(), - show_caches=self.show_caches, original_code=co.co_code, - show_offsets=self.show_offsets) + arg_resolver=arg_resolver, + formatter=formatter) return output.getvalue() diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 12e2c57e50b0ba..0c7fd60f640854 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -2,6 +2,7 @@ import contextlib import dis +import functools import io import re import sys @@ -1982,19 +1983,27 @@ def f(opcode, oparg, offset, *init_args): self.assertEqual(f(opcode.opmap["BINARY_OP"], 3, *args), (3, '<<')) self.assertEqual(f(opcode.opmap["CALL_INTRINSIC_1"], 2, *args), (2, 'INTRINSIC_IMPORT_STAR')) + def get_instructions(self, code): + return dis._get_instructions_bytes(code) + def test_start_offset(self): # When no extended args are present, # start_offset should be equal to offset + instructions = list(dis.Bytecode(_f)) for instruction in instructions: self.assertEqual(instruction.offset, instruction.start_offset) + def last_item(iterable): + return functools.reduce(lambda a, b : b, iterable) + code = bytes([ opcode.opmap["LOAD_FAST"], 0x00, opcode.opmap["EXTENDED_ARG"], 0x01, opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, ]) - jump = list(dis._get_instructions_bytes(code))[-1] + labels_map = dis._make_labels_map(code) + jump = last_item(self.get_instructions(code)) self.assertEqual(4, jump.offset) self.assertEqual(2, jump.start_offset) @@ -2006,7 +2015,7 @@ def test_start_offset(self): opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, opcode.opmap["CACHE"], 0x00, ]) - jump = list(dis._get_instructions_bytes(code))[-1] + jump = last_item(self.get_instructions(code)) self.assertEqual(8, jump.offset) self.assertEqual(2, jump.start_offset) @@ -2021,7 +2030,7 @@ def test_start_offset(self): opcode.opmap["POP_JUMP_IF_TRUE"], 0xFF, opcode.opmap["CACHE"], 0x00, ]) - instructions = list(dis._get_instructions_bytes(code)) + instructions = list(self.get_instructions(code)) # 1st jump self.assertEqual(4, instructions[2].offset) self.assertEqual(2, instructions[2].start_offset) @@ -2042,7 +2051,7 @@ def test_cache_offset_and_end_offset(self): opcode.opmap["CACHE"], 0x00, opcode.opmap["CACHE"], 0x00 ]) - instructions = list(dis._get_instructions_bytes(code)) + instructions = list(self.get_instructions(code)) self.assertEqual(2, instructions[0].cache_offset) self.assertEqual(10, instructions[0].end_offset) self.assertEqual(12, instructions[1].cache_offset) From 737d23ffcd16909274f21c932215ec104140fb1c Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 15 Dec 2023 05:03:17 -0800 Subject: [PATCH 265/442] GH-111485: Mark some instructions as `TIER_ONE_ONLY` (GH-113155) --- Python/bytecodes.c | 7 ++ Python/executor_cases.c.h | 139 ------------------------------------- Python/generated_cases.c.h | 7 ++ 3 files changed, 14 insertions(+), 139 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 68bb15c2b536eb..19e2268046fcdc 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -506,6 +506,7 @@ dummy_func( // specializations, but there is no output. // At the end we just skip over the STORE_FAST. op(_BINARY_OP_INPLACE_ADD_UNICODE, (unused/1, left, right --)) { + TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left); @@ -786,6 +787,7 @@ dummy_func( } inst(INTERPRETER_EXIT, (retval --)) { + TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -1072,6 +1074,7 @@ dummy_func( } inst(YIELD_VALUE, (retval -- unused)) { + TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. @@ -2297,6 +2300,7 @@ dummy_func( } inst(JUMP_FORWARD, (--)) { + TIER_ONE_ONLY JUMPBY(oparg); } @@ -2402,6 +2406,7 @@ dummy_func( macro(POP_JUMP_IF_NOT_NONE) = _IS_NONE + _POP_JUMP_IF_FALSE; inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { + TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3454,6 +3459,7 @@ dummy_func( // This is secretly a super-instruction inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, args[oparg] -- unused)) { + TIER_ONE_ONLY assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append); @@ -3792,6 +3798,7 @@ dummy_func( } inst(RETURN_GENERATOR, (--)) { + TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2519a4ee546a5e..7cb60cbc1dd3ff 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -375,38 +375,6 @@ break; } - case _BINARY_OP_INPLACE_ADD_UNICODE: { - PyObject *right; - PyObject *left; - right = stack_pointer[-1]; - left = stack_pointer[-2]; - assert(next_instr->op.code == STORE_FAST); - PyObject **target_local = &GETLOCAL(next_instr->op.arg); - if (*target_local != left) goto deoptimize; - STAT_INC(BINARY_OP, hit); - /* Handle `left = left + right` or `left += right` for str. - * - * When possible, extend `left` in place rather than - * allocating a new PyUnicodeObject. This attempts to avoid - * quadratic behavior when one neglects to use str.join(). - * - * If `left` has only two references remaining (one from - * the stack, one in the locals), DECREFing `left` leaves - * only the locals reference, so PyUnicode_Append knows - * that the string is safe to mutate. - */ - assert(Py_REFCNT(left) >= 2); - _Py_DECREF_NO_DEALLOC(left); - PyUnicode_Append(target_local, right); - _Py_DECREF_SPECIALIZED(right, _PyUnicode_ExactDealloc); - if (*target_local == NULL) goto pop_2_error_tier_two; - // The STORE_FAST is already done. - assert(next_instr->op.code == STORE_FAST); - SKIP_OVER(1); - stack_pointer += -2; - break; - } - case _BINARY_SUBSCR: { PyObject *sub; PyObject *container; @@ -690,18 +658,6 @@ break; } - case _INTERPRETER_EXIT: { - PyObject *retval; - retval = stack_pointer[-1]; - assert(frame == &entry_frame); - assert(_PyFrame_IsIncomplete(frame)); - /* Restore previous frame and return. */ - tstate->current_frame = frame->previous; - assert(!_PyErr_Occurred(tstate)); - tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS; - return retval; - } - case _POP_FRAME: { PyObject *retval; retval = stack_pointer[-1]; @@ -846,33 +802,6 @@ /* _INSTRUMENTED_YIELD_VALUE is not a viable micro-op for tier 2 */ - case _YIELD_VALUE: { - PyObject *retval; - oparg = CURRENT_OPARG(); - retval = stack_pointer[-1]; - // NOTE: It's important that YIELD_VALUE never raises an exception! - // The compiler treats any exception raised here as a failed close() - // or throw() call. - assert(frame != &entry_frame); - frame->instr_ptr = next_instr; - PyGenObject *gen = _PyFrame_GetGenerator(frame); - assert(FRAME_SUSPENDED_YIELD_FROM == FRAME_SUSPENDED + 1); - assert(oparg == 0 || oparg == 1); - gen->gi_frame_state = FRAME_SUSPENDED + oparg; - _PyFrame_SetStackPointer(frame, stack_pointer - 1); - tstate->exc_info = gen->gi_exc_state.previous_item; - gen->gi_exc_state.previous_item = NULL; - _Py_LeaveRecursiveCallPy(tstate); - _PyInterpreterFrame *gen_frame = frame; - frame = tstate->current_frame = frame->previous; - gen_frame->previous = NULL; - _PyFrame_StackPush(frame, retval); - /* We don't know which of these is relevant here, so keep them equal */ - assert(INLINE_CACHE_ENTRIES_SEND == INLINE_CACHE_ENTRIES_FOR_ITER); - LOAD_IP(1 + INLINE_CACHE_ENTRIES_SEND); - goto resume_frame; - } - case _POP_EXCEPT: { PyObject *exc_value; exc_value = stack_pointer[-1]; @@ -2084,12 +2013,6 @@ break; } - case _JUMP_FORWARD: { - oparg = CURRENT_OPARG(); - JUMPBY(oparg); - break; - } - /* _JUMP_BACKWARD is not a viable micro-op for tier 2 */ /* _POP_JUMP_IF_FALSE is not a viable micro-op for tier 2 */ @@ -2111,17 +2034,6 @@ break; } - case _JUMP_BACKWARD_NO_INTERRUPT: { - oparg = CURRENT_OPARG(); - /* This bytecode is used in the `yield from` or `await` loop. - * If there is an interrupt, we want it handled in the innermost - * generator or coroutine, so we deliberately do not check it here. - * (see bpo-30039). - */ - JUMPBY(-oparg); - break; - } - case _GET_LEN: { PyObject *obj; PyObject *len_o; @@ -3060,32 +2972,6 @@ break; } - case _CALL_LIST_APPEND: { - PyObject **args; - PyObject *self; - PyObject *callable; - oparg = CURRENT_OPARG(); - args = &stack_pointer[-oparg]; - self = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; - assert(oparg == 1); - PyInterpreterState *interp = tstate->interp; - if (callable != interp->callable_cache.list_append) goto deoptimize; - assert(self != NULL); - if (!PyList_Check(self)) goto deoptimize; - STAT_INC(CALL, hit); - if (_PyList_AppendTakeRef((PyListObject *)self, args[0]) < 0) { - goto pop_1_error; // Since arg is DECREF'ed already - } - Py_DECREF(self); - Py_DECREF(callable); - STACK_SHRINK(3); - // Skip POP_TOP - assert(next_instr->op.code == POP_TOP); - SKIP_OVER(1); - DISPATCH(); - } - case _CALL_METHOD_DESCRIPTOR_O: { PyObject **args; PyObject *self_or_null; @@ -3307,31 +3193,6 @@ break; } - case _RETURN_GENERATOR: { - assert(PyFunction_Check(frame->f_funcobj)); - PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; - PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); - if (gen == NULL) { - GOTO_ERROR(error); - } - assert(EMPTY()); - _PyFrame_SetStackPointer(frame, stack_pointer); - _PyInterpreterFrame *gen_frame = (_PyInterpreterFrame *)gen->gi_iframe; - frame->instr_ptr = next_instr; - _PyFrame_Copy(frame, gen_frame); - assert(frame->frame_obj == NULL); - gen->gi_frame_state = FRAME_CREATED; - gen_frame->owner = FRAME_OWNED_BY_GENERATOR; - _Py_LeaveRecursiveCallPy(tstate); - assert(frame != &entry_frame); - _PyInterpreterFrame *prev = frame->previous; - _PyThreadState_PopFrame(tstate, frame); - frame = tstate->current_frame = prev; - _PyFrame_StackPush(frame, (PyObject *)gen); - LOAD_IP(frame->return_offset); - goto resume_frame; - } - case _BUILD_SLICE: { PyObject *step = NULL; PyObject *stop; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 65e6f11f68b38c..24f26722d7a745 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -238,6 +238,7 @@ } // _BINARY_OP_INPLACE_ADD_UNICODE { + TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left, BINARY_OP); @@ -1446,6 +1447,7 @@ args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; + TIER_ONE_ONLY assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append, CALL); @@ -3182,6 +3184,7 @@ INSTRUCTION_STATS(INTERPRETER_EXIT); PyObject *retval; retval = stack_pointer[-1]; + TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -3253,6 +3256,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_BACKWARD_NO_INTERRUPT); + TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3266,6 +3270,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_FORWARD); + TIER_ONE_ONLY JUMPBY(oparg); DISPATCH(); } @@ -4793,6 +4798,7 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RETURN_GENERATOR); + TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -5764,6 +5770,7 @@ INSTRUCTION_STATS(YIELD_VALUE); PyObject *retval; retval = stack_pointer[-1]; + TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. From 8f8f0f97e126db9ca470fd7e7b2944c150db6305 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 15 Dec 2023 15:24:30 +0200 Subject: [PATCH 266/442] gh-61648: Detect line numbers of properties in doctests (GH-113161) --- Lib/doctest.py | 2 ++ Lib/test/doctest_lineno.py | 16 ++++++++++++++++ Lib/test/test_doctest.py | 2 ++ ...2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst | 1 + 4 files changed, 21 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst diff --git a/Lib/doctest.py b/Lib/doctest.py index d109b6c9e37343..114aac62a34e95 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1136,6 +1136,8 @@ def _find_lineno(self, obj, source_lines): # Find the line number for functions & methods. if inspect.ismethod(obj): obj = obj.__func__ + if isinstance(obj, property): + obj = obj.fget if inspect.isfunction(obj) and getattr(obj, '__doc__', None): # We don't use `docstring` var here, because `obj` can be changed. obj = obj.__code__ diff --git a/Lib/test/doctest_lineno.py b/Lib/test/doctest_lineno.py index 729a68aceaa990..677c569cf710eb 100644 --- a/Lib/test/doctest_lineno.py +++ b/Lib/test/doctest_lineno.py @@ -49,5 +49,21 @@ def method_with_doctest(self): 'method_with_doctest' """ + @classmethod + def classmethod_with_doctest(cls): + """ + This has a doctest! + >>> MethodWrapper.classmethod_with_doctest.__name__ + 'classmethod_with_doctest' + """ + + @property + def property_with_doctest(self): + """ + This has a doctest! + >>> MethodWrapper.property_with_doctest.__name__ + 'property_with_doctest' + """ + # https://github.com/python/cpython/issues/99433 str_wrapper = object().__str__ diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 36328f8086c7ad..46a51007f9644d 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -670,9 +670,11 @@ def basics(): r""" 30 test.doctest_lineno.ClassWithDoctest None test.doctest_lineno.ClassWithoutDocstring None test.doctest_lineno.MethodWrapper + 53 test.doctest_lineno.MethodWrapper.classmethod_with_doctest 39 test.doctest_lineno.MethodWrapper.method_with_docstring 45 test.doctest_lineno.MethodWrapper.method_with_doctest None test.doctest_lineno.MethodWrapper.method_without_docstring + 61 test.doctest_lineno.MethodWrapper.property_with_doctest 4 test.doctest_lineno.func_with_docstring 12 test.doctest_lineno.func_with_doctest None test.doctest_lineno.func_without_docstring diff --git a/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst b/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst new file mode 100644 index 00000000000000..c841e5c7f7683a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-12-35-28.gh-issue-61648.G-4pz0.rst @@ -0,0 +1 @@ +Detect line numbers of properties in doctests. From d1a2adfb0820ee730fa3e4bbc4bd88a67aa50666 Mon Sep 17 00:00:00 2001 From: AN Long Date: Fri, 15 Dec 2023 21:42:37 +0800 Subject: [PATCH 267/442] gh-112278: Add retry in WMI tests in case of slow initialization (GH-113154) --- Lib/test/test_wmi.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_wmi.py b/Lib/test/test_wmi.py index 3445702846d8a0..bf8c52e646dc18 100644 --- a/Lib/test/test_wmi.py +++ b/Lib/test/test_wmi.py @@ -1,17 +1,29 @@ # Test the internal _wmi module on Windows # This is used by the platform module, and potentially others +import time import unittest -from test.support import import_helper, requires_resource +from test.support import import_helper, requires_resource, LOOPBACK_TIMEOUT # Do this first so test will be skipped if module doesn't exist _wmi = import_helper.import_module('_wmi', required_on=['win']) +def wmi_exec_query(query): + # gh-112278: WMI maybe slow response when first call. + try: + return _wmi.exec_query(query) + except WindowsError as e: + if e.winerror != 258: + raise + time.sleep(LOOPBACK_TIMEOUT) + return _wmi.exec_query(query) + + class WmiTests(unittest.TestCase): def test_wmi_query_os_version(self): - r = _wmi.exec_query("SELECT Version FROM Win32_OperatingSystem").split("\0") + r = wmi_exec_query("SELECT Version FROM Win32_OperatingSystem").split("\0") self.assertEqual(1, len(r)) k, eq, v = r[0].partition("=") self.assertEqual("=", eq, r[0]) @@ -28,7 +40,7 @@ def test_wmi_query_repeated(self): def test_wmi_query_error(self): # Invalid queries fail with OSError try: - _wmi.exec_query("SELECT InvalidColumnName FROM InvalidTableName") + wmi_exec_query("SELECT InvalidColumnName FROM InvalidTableName") except OSError as ex: if ex.winerror & 0xFFFFFFFF == 0x80041010: # This is the expected error code. All others should fail the test @@ -42,7 +54,7 @@ def test_wmi_query_repeated_error(self): def test_wmi_query_not_select(self): # Queries other than SELECT are blocked to avoid potential exploits with self.assertRaises(ValueError): - _wmi.exec_query("not select, just in case someone tries something") + wmi_exec_query("not select, just in case someone tries something") @requires_resource('cpu') def test_wmi_query_overflow(self): @@ -50,11 +62,11 @@ def test_wmi_query_overflow(self): # Test multiple times to ensure consistency for _ in range(2): with self.assertRaises(OSError): - _wmi.exec_query("SELECT * FROM CIM_DataFile") + wmi_exec_query("SELECT * FROM CIM_DataFile") def test_wmi_query_multiple_rows(self): # Multiple instances should have an extra null separator - r = _wmi.exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000") + r = wmi_exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000") self.assertFalse(r.startswith("\0"), r) self.assertFalse(r.endswith("\0"), r) it = iter(r.split("\0")) @@ -69,6 +81,6 @@ def test_wmi_query_threads(self): from concurrent.futures import ThreadPoolExecutor query = "SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000" with ThreadPoolExecutor(4) as pool: - task = [pool.submit(_wmi.exec_query, query) for _ in range(32)] + task = [pool.submit(wmi_exec_query, query) for _ in range(32)] for t in task: self.assertRegex(t.result(), "ProcessId=") From 4026ad5b2c595b855a3605420cfa0e3d49e63db7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 15:57:49 +0100 Subject: [PATCH 268/442] gh-113009: Fix multiprocessing Process.terminate() on Windows (#113128) On Windows, Process.terminate() no longer sets the returncode attribute to always call WaitForSingleObject() in Process.wait(). Previously, sometimes the process was still running after TerminateProcess() even if GetExitCodeProcess() is not STILL_ACTIVE. --- Lib/multiprocessing/popen_spawn_win32.py | 54 ++++++++++--------- ...-12-14-19-00-29.gh-issue-113009.6LNdjz.rst | 5 ++ 2 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst diff --git a/Lib/multiprocessing/popen_spawn_win32.py b/Lib/multiprocessing/popen_spawn_win32.py index af044305709e56..49d4c7eea22411 100644 --- a/Lib/multiprocessing/popen_spawn_win32.py +++ b/Lib/multiprocessing/popen_spawn_win32.py @@ -101,18 +101,20 @@ def duplicate_for_child(self, handle): return reduction.duplicate(handle, self.sentinel) def wait(self, timeout=None): - if self.returncode is None: - if timeout is None: - msecs = _winapi.INFINITE - else: - msecs = max(0, int(timeout * 1000 + 0.5)) - - res = _winapi.WaitForSingleObject(int(self._handle), msecs) - if res == _winapi.WAIT_OBJECT_0: - code = _winapi.GetExitCodeProcess(self._handle) - if code == TERMINATE: - code = -signal.SIGTERM - self.returncode = code + if self.returncode is not None: + return self.returncode + + if timeout is None: + msecs = _winapi.INFINITE + else: + msecs = max(0, int(timeout * 1000 + 0.5)) + + res = _winapi.WaitForSingleObject(int(self._handle), msecs) + if res == _winapi.WAIT_OBJECT_0: + code = _winapi.GetExitCodeProcess(self._handle) + if code == TERMINATE: + code = -signal.SIGTERM + self.returncode = code return self.returncode @@ -120,18 +122,22 @@ def poll(self): return self.wait(timeout=0) def terminate(self): - if self.returncode is None: - try: - _winapi.TerminateProcess(int(self._handle), TERMINATE) - except PermissionError: - # ERROR_ACCESS_DENIED (winerror 5) is received when the - # process already died. - code = _winapi.GetExitCodeProcess(int(self._handle)) - if code == _winapi.STILL_ACTIVE: - raise - self.returncode = code - else: - self.returncode = -signal.SIGTERM + if self.returncode is not None: + return + + try: + _winapi.TerminateProcess(int(self._handle), TERMINATE) + except PermissionError: + # ERROR_ACCESS_DENIED (winerror 5) is received when the + # process already died. + code = _winapi.GetExitCodeProcess(int(self._handle)) + if code == _winapi.STILL_ACTIVE: + raise + + # gh-113009: Don't set self.returncode. Even if GetExitCodeProcess() + # returns an exit code different than STILL_ACTIVE, the process can + # still be running. Only set self.returncode once WaitForSingleObject() + # returns WAIT_OBJECT_0 in wait(). kill = terminate diff --git a/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst new file mode 100644 index 00000000000000..6fd7f7f9afdfa2 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-14-19-00-29.gh-issue-113009.6LNdjz.rst @@ -0,0 +1,5 @@ +:mod:`multiprocessing`: On Windows, fix a race condition in +``Process.terminate()``: no longer set the ``returncode`` attribute to +always call ``WaitForSingleObject()`` in ``Process.wait()``. Previously, +sometimes the process was still running after ``TerminateProcess()`` even if +``GetExitCodeProcess()`` is not ``STILL_ACTIVE``. Patch by Victor Stinner. From 4a153a1d3b18803a684cd1bcc2cdf3ede3dbae19 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 15 Dec 2023 16:10:40 +0100 Subject: [PATCH 269/442] [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116) Detect email address parsing errors and return empty tuple to indicate the parsing error (old API). Add an optional 'strict' parameter to getaddresses() and parseaddr() functions. Patch by Thomas Dwyer. Co-Authored-By: Thomas Dwyer --- Doc/library/email.utils.rst | 19 +- Doc/whatsnew/3.13.rst | 13 ++ Lib/email/utils.py | 151 +++++++++++++- Lib/test/test_email/test_email.py | 187 +++++++++++++++++- ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + 5 files changed, 357 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst index 345b64001c1ace..d693a9bc3933b5 100644 --- a/Doc/library/email.utils.rst +++ b/Doc/library/email.utils.rst @@ -58,13 +58,18 @@ of the new API. begins with angle brackets, they are stripped off. -.. function:: parseaddr(address) +.. function:: parseaddr(address, *, strict=True) Parse address -- which should be the value of some address-containing field such as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and *email address* parts. Returns a tuple of that information, unless the parse fails, in which case a 2-tuple of ``('', '')`` is returned. + If *strict* is true, use a strict parser which rejects malformed inputs. + + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + .. function:: formataddr(pair, charset='utf-8') @@ -82,12 +87,15 @@ of the new API. Added the *charset* option. -.. function:: getaddresses(fieldvalues) +.. function:: getaddresses(fieldvalues, *, strict=True) This method returns a list of 2-tuples of the form returned by ``parseaddr()``. *fieldvalues* is a sequence of header field values as might be returned by - :meth:`Message.get_all `. Here's a simple - example that gets all the recipients of a message:: + :meth:`Message.get_all `. + + If *strict* is true, use a strict parser which rejects malformed inputs. + + Here's a simple example that gets all the recipients of a message:: from email.utils import getaddresses @@ -97,6 +105,9 @@ of the new API. resent_ccs = msg.get_all('resent-cc', []) all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + .. function:: parsedate(date) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e22257853d8333..4f9643967d20cf 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -199,6 +199,19 @@ doctest :attr:`doctest.TestResults.skipped` attributes. (Contributed by Victor Stinner in :gh:`108794`.) +email +----- + +* :func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now return + ``('', '')`` 2-tuples in more situations where invalid email addresses are + encountered instead of potentially inaccurate values. Add optional *strict* + 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. + (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve + the CVE-2023-27043 fix.) + glob ---- diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 9175f2fdb6e69e..103cef61a83538 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -43,6 +43,7 @@ specialsre = re.compile(r'[][\\()<>@,:;".]') escapesre = re.compile(r'[\\"]') + def _has_surrogates(s): """Return True if s may contain surrogate-escaped binary data.""" # This check is based on the fact that unless there are surrogates, utf8 @@ -103,12 +104,127 @@ def formataddr(pair, charset='utf-8'): return address +def _iter_escaped_chars(addr): + pos = 0 + escape = False + for pos, ch in enumerate(addr): + if escape: + yield (pos, '\\' + ch) + escape = False + elif ch == '\\': + escape = True + else: + yield (pos, ch) + if escape: + yield (pos, '\\') + + +def _strip_quoted_realnames(addr): + """Strip real names between quotes.""" + if '"' not in addr: + # Fast path + return addr + + start = 0 + open_pos = None + result = [] + for pos, ch in _iter_escaped_chars(addr): + if ch == '"': + if open_pos is None: + open_pos = pos + else: + if start != open_pos: + result.append(addr[start:open_pos]) + start = pos + 1 + open_pos = None + + if start < len(addr): + result.append(addr[start:]) + + return ''.join(result) -def getaddresses(fieldvalues): - """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" - all = COMMASPACE.join(str(v) for v in fieldvalues) - a = _AddressList(all) - return a.addresslist + +supports_strict_parsing = True + +def getaddresses(fieldvalues, *, strict=True): + """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. + + When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in + its place. + + If strict is true, use a strict parser which rejects malformed inputs. + """ + + # If strict is true, if the resulting list of parsed addresses is greater + # than the number of fieldvalues in the input list, a parsing error has + # occurred and consequently a list containing a single empty 2-tuple [('', + # '')] is returned in its place. This is done to avoid invalid output. + # + # Malformed input: getaddresses(['alice@example.com ']) + # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] + # Safe output: [('', '')] + + if not strict: + all = COMMASPACE.join(str(v) for v in fieldvalues) + a = _AddressList(all) + return a.addresslist + + fieldvalues = [str(v) for v in fieldvalues] + fieldvalues = _pre_parse_validation(fieldvalues) + addr = COMMASPACE.join(fieldvalues) + a = _AddressList(addr) + result = _post_parse_validation(a.addresslist) + + # Treat output as invalid if the number of addresses is not equal to the + # expected number of addresses. + n = 0 + for v in fieldvalues: + # When a comma is used in the Real Name part it is not a deliminator. + # So strip those out before counting the commas. + v = _strip_quoted_realnames(v) + # Expected number of addresses: 1 + number of commas + n += 1 + v.count(',') + if len(result) != n: + return [('', '')] + + return result + + +def _check_parenthesis(addr): + # Ignore parenthesis in quoted real names. + addr = _strip_quoted_realnames(addr) + + opens = 0 + for pos, ch in _iter_escaped_chars(addr): + if ch == '(': + opens += 1 + elif ch == ')': + opens -= 1 + if opens < 0: + return False + return (opens == 0) + + +def _pre_parse_validation(email_header_fields): + accepted_values = [] + for v in email_header_fields: + if not _check_parenthesis(v): + v = "('', '')" + accepted_values.append(v) + + return accepted_values + + +def _post_parse_validation(parsed_email_header_tuples): + accepted_values = [] + # The parser would have parsed a correctly formatted domain-literal + # The existence of an [ after parsing indicates a parsing failure + for v in parsed_email_header_tuples: + if '[' in v[1]: + v = ('', '') + accepted_values.append(v) + + return accepted_values def _format_timetuple_and_zone(timetuple, zone): @@ -207,16 +323,33 @@ def parsedate_to_datetime(data): tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) -def parseaddr(addr): +def parseaddr(addr, *, strict=True): """ Parse addr into its constituent realname and email address parts. Return a tuple of realname and email address, unless the parse fails, in which case return a 2-tuple of ('', ''). + + If strict is True, use a strict parser which rejects malformed inputs. """ - addrs = _AddressList(addr).addresslist - if not addrs: - return '', '' + if not strict: + addrs = _AddressList(addr).addresslist + if not addrs: + return ('', '') + return addrs[0] + + if isinstance(addr, list): + addr = addr[0] + + if not isinstance(addr, str): + return ('', '') + + addr = _pre_parse_validation([addr])[0] + addrs = _post_parse_validation(_AddressList(addr).addresslist) + + if not addrs or len(addrs) > 1: + return ('', '') + return addrs[0] diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 512464f87162cd..39d4ace8d4a1d8 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -16,6 +16,7 @@ import email import email.policy +import email.utils from email.charset import Charset from email.generator import Generator, DecodedGenerator, BytesGenerator @@ -3337,15 +3338,137 @@ def test_getaddresses_comma_in_name(self): ], ) + def test_parsing_errors(self): + """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" + alice = 'alice@example.org' + bob = 'bob@example.com' + empty = ('', '') + + # Test utils.getaddresses() and utils.parseaddr() on malformed email + # addresses: default behavior (strict=True) rejects malformed address, + # and strict=False which tolerates malformed address. + for invalid_separator, expected_non_strict in ( + ('(', [(f'<{bob}>', alice)]), + (')', [('', alice), empty, ('', bob)]), + ('<', [('', alice), empty, ('', bob), empty]), + ('>', [('', alice), empty, ('', bob)]), + ('[', [('', f'{alice}[<{bob}>]')]), + (']', [('', alice), empty, ('', bob)]), + ('@', [empty, empty, ('', bob)]), + (';', [('', alice), empty, ('', bob)]), + (':', [('', alice), ('', bob)]), + ('.', [('', alice + '.'), ('', bob)]), + ('"', [('', alice), ('', f'<{bob}>')]), + ): + address = f'{alice}{invalid_separator}<{bob}>' + with self.subTest(address=address): + self.assertEqual(utils.getaddresses([address]), + [empty]) + self.assertEqual(utils.getaddresses([address], strict=False), + expected_non_strict) + + self.assertEqual(utils.parseaddr([address]), + empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Comma (',') is treated differently depending on strict parameter. + # Comma without quotes. + address = f'{alice},<{bob}>' + self.assertEqual(utils.getaddresses([address]), + [('', alice), ('', bob)]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('', alice), ('', bob)]) + self.assertEqual(utils.parseaddr([address]), + empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Real name between quotes containing comma. + address = '"Alice, alice@example.org" ' + expected_strict = ('Alice, alice@example.org', 'bob@example.com') + self.assertEqual(utils.getaddresses([address]), [expected_strict]) + self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) + self.assertEqual(utils.parseaddr([address]), expected_strict) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Valid parenthesis in comments. + address = 'alice@example.org (Alice)' + expected_strict = ('Alice', 'alice@example.org') + self.assertEqual(utils.getaddresses([address]), [expected_strict]) + self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) + self.assertEqual(utils.parseaddr([address]), expected_strict) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Invalid parenthesis in comments. + address = 'alice@example.org )Alice(' + self.assertEqual(utils.getaddresses([address]), [empty]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) + self.assertEqual(utils.parseaddr([address]), empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Two addresses with quotes separated by comma. + address = '"Jane Doe" , "John Doe" ' + self.assertEqual(utils.getaddresses([address]), + [('Jane Doe', 'jane@example.net'), + ('John Doe', 'john@example.net')]) + self.assertEqual(utils.getaddresses([address], strict=False), + [('Jane Doe', 'jane@example.net'), + ('John Doe', 'john@example.net')]) + self.assertEqual(utils.parseaddr([address]), empty) + self.assertEqual(utils.parseaddr([address], strict=False), + ('', address)) + + # Test email.utils.supports_strict_parsing attribute + self.assertEqual(email.utils.supports_strict_parsing, True) + def test_getaddresses_nasty(self): - eq = self.assertEqual - eq(utils.getaddresses(['foo: ;']), [('', '')]) - eq(utils.getaddresses( - ['[]*-- =~$']), - [('', ''), ('', ''), ('', '*--')]) - eq(utils.getaddresses( - ['foo: ;', '"Jason R. Mastaler" ']), - [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) + for addresses, expected in ( + (['"Sürname, Firstname" '], + [('Sürname, Firstname', 'to@example.com')]), + + (['foo: ;'], + [('', '')]), + + (['foo: ;', '"Jason R. Mastaler" '], + [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), + + ([r'Pete(A nice \) chap) '], + [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), + + (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], + [('', '')]), + + (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], + [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), + + (['John Doe '], + [('John Doe (comment)', 'jdoe@machine.example')]), + + (['"Mary Smith: Personal Account" '], + [('Mary Smith: Personal Account', 'smith@home.example')]), + + (['Undisclosed recipients:;'], + [('', '')]), + + ([r', "Giant; \"Big\" Box" '], + [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), + ): + with self.subTest(addresses=addresses): + self.assertEqual(utils.getaddresses(addresses), + expected) + self.assertEqual(utils.getaddresses(addresses, strict=False), + expected) + + addresses = ['[]*-- =~$'] + self.assertEqual(utils.getaddresses(addresses), + [('', '')]) + self.assertEqual(utils.getaddresses(addresses, strict=False), + [('', ''), ('', ''), ('', '*--')]) def test_getaddresses_embedded_comment(self): """Test proper handling of a nested comment""" @@ -3536,6 +3659,54 @@ def test_mime_classes_policy_argument(self): m = cls(*constructor, policy=email.policy.default) self.assertIs(m.policy, email.policy.default) + def test_iter_escaped_chars(self): + self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), + [(0, 'a'), + (2, '\\\\'), + (3, 'b'), + (5, '\\"'), + (6, 'c'), + (8, '\\\\'), + (9, '"'), + (10, 'd')]) + self.assertEqual(list(utils._iter_escaped_chars('a\\')), + [(0, 'a'), (1, '\\')]) + + def test_strip_quoted_realnames(self): + def check(addr, expected): + self.assertEqual(utils._strip_quoted_realnames(addr), expected) + + check('"Jane Doe" , "John Doe" ', + ' , ') + check(r'"Jane \"Doe\"." ', + ' ') + + # special cases + check(r'before"name"after', 'beforeafter') + check(r'before"name"', 'before') + check(r'b"name"', 'b') # single char + check(r'"name"after', 'after') + check(r'"name"a', 'a') # single char + check(r'"name"', '') + + # no change + for addr in ( + 'Jane Doe , John Doe ', + 'lone " quote', + ): + self.assertEqual(utils._strip_quoted_realnames(addr), addr) + + + def test_check_parenthesis(self): + addr = 'alice@example.net' + self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) + self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) + self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) + self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) + + # Ignore real name between quotes + self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) + # Test the iterator/generators class TestIterators(TestEmailBase): diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst new file mode 100644 index 00000000000000..3d0e9e4078c934 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst @@ -0,0 +1,8 @@ +:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now +return ``('', '')`` 2-tuples in more situations where invalid email +addresses are encountered instead of potentially inaccurate values. Add +optional *strict* 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. Patch by Thomas Dwyer and Victor +Stinner to improve the CVE-2023-27043 fix. From e365c943f24ab577cf7a4bf12bc0d2619ab9ae47 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 15 Dec 2023 17:36:25 +0200 Subject: [PATCH 270/442] gh-113172: Fix compiler warnings in Modules/_xxinterpqueuesmodule.c (GH-113173) Fix compiler waarnings in Modules/_xxinterpqueuesmodule.c --- Modules/_xxinterpqueuesmodule.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 2cc3a2ac5dc85f..537ba9188055dd 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -234,11 +234,13 @@ static int add_exctype(PyObject *mod, PyObject **p_state_field, const char *qualname, const char *doc, PyObject *base) { +#ifndef NDEBUG const char *dot = strrchr(qualname, '.'); assert(dot != NULL); const char *name = dot+1; assert(*p_state_field == NULL); assert(!PyObject_HasAttrStringWithError(mod, name)); +#endif PyObject *exctype = PyErr_NewExceptionWithDoc(qualname, doc, base, NULL); if (exctype == NULL) { return -1; @@ -1505,7 +1507,7 @@ queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) } int64_t qid = qidarg.id; - int is_full; + int is_full = 0; int err = queue_is_full(&_globals.queues, qid, &is_full); if (handle_queue_error(err, self, qid)) { return NULL; From c2c4879b0a535b2a43005ba4983408082ef94bd2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 17:05:12 +0000 Subject: [PATCH 271/442] gh-101100: Fix Sphinx nitpicks in `library/numbers.rst` (#113162) --- Doc/library/numbers.rst | 19 ++++++++++--------- Doc/tools/.nitignore | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst index 2a05b56db051f9..17d1a275f04c9b 100644 --- a/Doc/library/numbers.rst +++ b/Doc/library/numbers.rst @@ -8,7 +8,7 @@ -------------- -The :mod:`numbers` module (:pep:`3141`) defines a hierarchy of numeric +The :mod:`!numbers` module (:pep:`3141`) defines a hierarchy of numeric :term:`abstract base classes ` which progressively define more operations. None of the types defined in this module are intended to be instantiated. @@ -45,7 +45,7 @@ The numeric tower .. class:: Real - To :class:`Complex`, :class:`Real` adds the operations that work on real + To :class:`Complex`, :class:`!Real` adds the operations that work on real numbers. In short, those are: a conversion to :class:`float`, :func:`math.trunc`, @@ -126,7 +126,8 @@ We want to implement the arithmetic operations so that mixed-mode operations either call an implementation whose author knew about the types of both arguments, or convert both to the nearest built in type and do the operation there. For subtypes of :class:`Integral`, this -means that :meth:`__add__` and :meth:`__radd__` should be defined as:: +means that :meth:`~object.__add__` and :meth:`~object.__radd__` should be +defined as:: class MyIntegral(Integral): @@ -160,15 +161,15 @@ refer to ``MyIntegral`` and ``OtherTypeIKnowAbout`` as of :class:`Complex` (``a : A <: Complex``), and ``b : B <: Complex``. I'll consider ``a + b``: -1. If ``A`` defines an :meth:`__add__` which accepts ``b``, all is +1. If ``A`` defines an :meth:`~object.__add__` which accepts ``b``, all is well. 2. If ``A`` falls back to the boilerplate code, and it were to - return a value from :meth:`__add__`, we'd miss the possibility - that ``B`` defines a more intelligent :meth:`__radd__`, so the + return a value from :meth:`~object.__add__`, we'd miss the possibility + that ``B`` defines a more intelligent :meth:`~object.__radd__`, so the boilerplate should return :const:`NotImplemented` from - :meth:`__add__`. (Or ``A`` may not implement :meth:`__add__` at + :meth:`!__add__`. (Or ``A`` may not implement :meth:`!__add__` at all.) -3. Then ``B``'s :meth:`__radd__` gets a chance. If it accepts +3. Then ``B``'s :meth:`~object.__radd__` gets a chance. If it accepts ``a``, all is well. 4. If it falls back to the boilerplate, there are no more possible methods to try, so this is where the default implementation @@ -180,7 +181,7 @@ Complex``. I'll consider ``a + b``: If ``A <: Complex`` and ``B <: Real`` without sharing any other knowledge, then the appropriate shared operation is the one involving the built -in :class:`complex`, and both :meth:`__radd__` s land there, so ``a+b +in :class:`complex`, and both :meth:`~object.__radd__` s land there, so ``a+b == b+a``. Because most of the operations on any given type will be very similar, diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index c91e698ff0753a..23d4d3e522b455 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -65,7 +65,6 @@ Doc/library/lzma.rst Doc/library/mmap.rst Doc/library/multiprocessing.rst Doc/library/multiprocessing.shared_memory.rst -Doc/library/numbers.rst Doc/library/optparse.rst Doc/library/os.rst Doc/library/pickle.rst From 1addde0c698f7f8eb716bcecf63d119e19e1ecda Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 15 Dec 2023 17:15:34 +0000 Subject: [PATCH 272/442] gh-101100: Fix various Sphinx warnings for dunder references in the `library/` directory (#113163) --- Doc/conf.py | 1 + Doc/library/datetime.rst | 3 ++- Doc/library/exceptions.rst | 4 ++-- Doc/library/test.rst | 3 ++- Doc/library/unittest.mock.rst | 23 +++++++++++++---------- Doc/library/unittest.rst | 4 ++-- Doc/library/wsgiref.rst | 11 +++++++---- Doc/library/xmlrpc.client.rst | 9 +++++---- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 4077eaf5a139b0..dc09b0b51ca84c 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -248,6 +248,7 @@ # Attributes/methods/etc. that definitely should be documented better, # but are deferred for now: ('py:attr', '__annotations__'), + ('py:meth', '__missing__'), ('py:attr', '__wrapped__'), ('py:meth', 'index'), # list.index, tuple.index, etc. ] diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 1bab1fb9bd9464..686b37754368c1 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1985,7 +1985,8 @@ Examples of working with a :class:`.time` object:: American EST and EDT. Special requirement for pickling: A :class:`tzinfo` subclass must have an - :meth:`__init__` method that can be called with no arguments, otherwise it can be + :meth:`~object.__init__` method that can be called with no arguments, + otherwise it can be pickled but possibly not unpickled again. This is a technical requirement that may be relaxed in the future. diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index f7891f2732bdb1..f821776c286133 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -1009,9 +1009,9 @@ their subgroups based on the types of the contained exceptions. True - Note that :exc:`BaseExceptionGroup` defines :meth:`__new__`, so + Note that :exc:`BaseExceptionGroup` defines :meth:`~object.__new__`, so subclasses that need a different constructor signature need to - override that rather than :meth:`__init__`. For example, the following + override that rather than :meth:`~object.__init__`. For example, the following defines an exception group subclass which accepts an exit_code and and constructs the group's message from it. :: diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 6cbdc39b3c024d..9173db07fd0071 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1408,7 +1408,8 @@ The :mod:`test.support.os_helper` module provides support for os tests. .. class:: FakePath(path) - Simple :term:`path-like object`. It implements the :meth:`__fspath__` + Simple :term:`path-like object`. It implements the + :meth:`~os.PathLike.__fspath__` method which just returns the *path* argument. If *path* is an exception, it will be raised in :meth:`!__fspath__`. diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index d6ac4d09300d9f..f1cc482c5cfe2a 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -824,8 +824,9 @@ apply to method calls on the mock object. .. class:: PropertyMock(*args, **kwargs) - A mock intended to be used as a property, or other descriptor, on a class. - :class:`PropertyMock` provides :meth:`__get__` and :meth:`__set__` methods + A mock intended to be used as a :class:`property`, or other + :term:`descriptor`, on a class. :class:`PropertyMock` provides + :meth:`~object.__get__` and :meth:`~object.__set__` methods so you can specify a return value when it is fetched. Fetching a :class:`PropertyMock` instance from an object calls the mock, with @@ -1707,8 +1708,9 @@ Keywords can be used in the :func:`patch.dict` call to set values in the diction :func:`patch.dict` can be used with dictionary like objects that aren't actually dictionaries. At the very minimum they must support item getting, setting, deleting and either iteration or membership test. This corresponds to the -magic methods :meth:`~object.__getitem__`, :meth:`__setitem__`, :meth:`__delitem__` and either -:meth:`__iter__` or :meth:`__contains__`. +magic methods :meth:`~object.__getitem__`, :meth:`~object.__setitem__`, +:meth:`~object.__delitem__` and either :meth:`~container.__iter__` or +:meth:`~object.__contains__`. >>> class Container: ... def __init__(self): @@ -2171,7 +2173,7 @@ For example: >>> object() in mock False -The two equality methods, :meth:`__eq__` and :meth:`__ne__`, are special. +The two equality methods, :meth:`!__eq__` and :meth:`!__ne__`, are special. They do the default equality comparison on identity, using the :attr:`~Mock.side_effect` attribute, unless you change their return value to return something else:: @@ -2521,8 +2523,8 @@ mock_open *read_data* is now reset on each call to the *mock*. .. versionchanged:: 3.8 - Added :meth:`__iter__` to implementation so that iteration (such as in for - loops) correctly consumes *read_data*. + Added :meth:`~container.__iter__` to implementation so that iteration + (such as in for loops) correctly consumes *read_data*. Using :func:`open` as a context manager is a great way to ensure your file handles are closed properly and is becoming common:: @@ -2704,7 +2706,7 @@ able to use autospec. On the other hand it is much better to design your objects so that introspection is safe [#]_. A more serious problem is that it is common for instance attributes to be -created in the :meth:`__init__` method and not to exist on the class at all. +created in the :meth:`~object.__init__` method and not to exist on the class at all. *autospec* can't know about any dynamically created attributes and restricts the api to visible attributes. :: @@ -2745,8 +2747,9 @@ this particular scenario: AttributeError: Mock object has no attribute 'a' Probably the best way of solving the problem is to add class attributes as -default values for instance members initialised in :meth:`__init__`. Note that if -you are only setting default attributes in :meth:`__init__` then providing them via +default values for instance members initialised in :meth:`~object.__init__`. +Note that if +you are only setting default attributes in :meth:`!__init__` then providing them via class attributes (shared between instances of course) is faster too. e.g. .. code-block:: python diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index c04e6c378cc3b1..9aad96bdf11bf8 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1733,7 +1733,7 @@ Grouping tests .. method:: __iter__() Tests grouped by a :class:`TestSuite` are always accessed by iteration. - Subclasses can lazily provide tests by overriding :meth:`__iter__`. Note + Subclasses can lazily provide tests by overriding :meth:`!__iter__`. Note that this method may be called several times on a single suite (for example when counting tests or comparing for equality) so the tests returned by repeated iterations before :meth:`TestSuite.run` must be the @@ -1744,7 +1744,7 @@ Grouping tests .. versionchanged:: 3.2 In earlier versions the :class:`TestSuite` accessed tests directly rather - than through iteration, so overriding :meth:`__iter__` wasn't sufficient + than through iteration, so overriding :meth:`!__iter__` wasn't sufficient for providing tests. .. versionchanged:: 3.4 diff --git a/Doc/library/wsgiref.rst b/Doc/library/wsgiref.rst index be9e56b04c1fbf..c2b0ba7046967e 100644 --- a/Doc/library/wsgiref.rst +++ b/Doc/library/wsgiref.rst @@ -201,8 +201,9 @@ manipulation of WSGI response headers using a mapping-like interface. an empty list. :class:`Headers` objects support typical mapping operations including - :meth:`~object.__getitem__`, :meth:`get`, :meth:`__setitem__`, :meth:`setdefault`, - :meth:`__delitem__` and :meth:`__contains__`. For each of + :meth:`~object.__getitem__`, :meth:`~dict.get`, :meth:`~object.__setitem__`, + :meth:`~dict.setdefault`, + :meth:`~object.__delitem__` and :meth:`~object.__contains__`. For each of these methods, the key is the header name (treated case-insensitively), and the value is the first value associated with that header name. Setting a header deletes any existing values for that header, then adds a new value at the end of @@ -520,8 +521,10 @@ input, output, and error streams. want to subclass this instead of :class:`BaseCGIHandler`. This class is a subclass of :class:`BaseHandler`. It overrides the - :meth:`__init__`, :meth:`get_stdin`, :meth:`get_stderr`, :meth:`add_cgi_vars`, - :meth:`_write`, and :meth:`_flush` methods to support explicitly setting the + :meth:`!__init__`, :meth:`~BaseHandler.get_stdin`, + :meth:`~BaseHandler.get_stderr`, :meth:`~BaseHandler.add_cgi_vars`, + :meth:`~BaseHandler._write`, and :meth:`~BaseHandler._flush` methods to + support explicitly setting the environment and streams via the constructor. The supplied environment and streams are stored in the :attr:`stdin`, :attr:`stdout`, :attr:`stderr`, and :attr:`environ` attributes. diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst index 146c4fd768233b..f7f23007fb0522 100644 --- a/Doc/library/xmlrpc.client.rst +++ b/Doc/library/xmlrpc.client.rst @@ -269,8 +269,9 @@ DateTime Objects Write the XML-RPC encoding of this :class:`DateTime` item to the *out* stream object. - It also supports certain of Python's built-in operators through rich comparison - and :meth:`__repr__` methods. + It also supports certain of Python's built-in operators through + :meth:`rich comparison ` and :meth:`~object.__repr__` + methods. A working example follows. The server code:: @@ -334,8 +335,8 @@ Binary Objects which was the de facto standard base64 specification when the XML-RPC spec was written. - It also supports certain of Python's built-in operators through :meth:`__eq__` - and :meth:`__ne__` methods. + It also supports certain of Python's built-in operators through + :meth:`~object.__eq__` and :meth:`~object.__ne__` methods. Example usage of the binary objects. We're going to transfer an image over XMLRPC:: From d07483292b115a5a0e9b9b09f3ec1000ce879986 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 15 Dec 2023 09:27:57 -0800 Subject: [PATCH 273/442] GH-112383: Fix test_loop_quicken when an executor is installed (GH-113153) --- Lib/test/test_dis.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 0c7fd60f640854..e4c7e9ba4649f7 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1210,8 +1210,13 @@ def test_loop_quicken(self): got = self.get_disassembly(loop_test, adaptive=True) expected = dis_loop_test_quickened_code if _testinternalcapi.get_optimizer(): - # We *may* see ENTER_EXECUTOR in the disassembly - got = got.replace("ENTER_EXECUTOR", "JUMP_BACKWARD ") + # We *may* see ENTER_EXECUTOR in the disassembly. This is a + # temporary hack to keep the test working until dis is able to + # handle the instruction correctly (GH-112383): + got = got.replace( + "ENTER_EXECUTOR 16", + "JUMP_BACKWARD 16 (to L1)", + ) self.do_disassembly_compare(got, expected) @cpython_only From 00d2b6d1fca91e1a83f7f99a370685b095ed4928 Mon Sep 17 00:00:00 2001 From: Akshat Khandelwal <35228810+akshatgokul@users.noreply.github.com> Date: Sat, 16 Dec 2023 02:23:16 +0530 Subject: [PATCH 274/442] gh-110746: Improve markup in ``tkinter.ttk.rst`` (#111236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * gh-110746: Improve markup in tkinter.ttk.rst * gh-110746: Improve markup in tkinter.ttk.rst * 📜🤖 Added by blurb_it. --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Ezio Melotti --- Doc/library/tkinter.ttk.rst | 31 ++++++++++--------- ...-10-23-23-43-43.gh-issue-110746.yg77IE.rst | 1 + 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst diff --git a/Doc/library/tkinter.ttk.rst b/Doc/library/tkinter.ttk.rst index 6e01ec7b291255..1609dc2ce9218e 100644 --- a/Doc/library/tkinter.ttk.rst +++ b/Doc/library/tkinter.ttk.rst @@ -986,19 +986,19 @@ ttk.Treeview The valid options/values are: - id + *id* Returns the column name. This is a read-only option. - anchor: One of the standard Tk anchor values. + *anchor*: One of the standard Tk anchor values. Specifies how the text in this column should be aligned with respect to the cell. - minwidth: width + *minwidth*: width The minimum width of the column in pixels. The treeview widget will not make the column any smaller than specified by this option when the widget is resized or the user drags a column. - stretch: ``True``/``False`` + *stretch*: ``True``/``False`` Specifies whether the column's width should be adjusted when the widget is resized. - width: width + *width*: width The width of the column in pixels. To configure the tree column, call this with column = "#0" @@ -1041,14 +1041,14 @@ ttk.Treeview The valid options/values are: - text: text + *text*: text The text to display in the column heading. - image: imageName + *image*: imageName Specifies an image to display to the right of the column heading. - anchor: anchor + *anchor*: anchor Specifies how the heading text should be aligned. One of the standard Tk anchor values. - command: callback + *command*: callback A callback to be invoked when the heading label is pressed. To configure the tree column heading, call this with column = "#0". @@ -1573,23 +1573,24 @@ Layouts A layout can be just ``None``, if it takes no options, or a dict of options specifying how to arrange the element. The layout mechanism uses a simplified version of the pack geometry manager: given an -initial cavity, each element is allocated a parcel. Valid -options/values are: +initial cavity, each element is allocated a parcel. -side: whichside +The valid options/values are: + +*side*: whichside Specifies which side of the cavity to place the element; one of top, right, bottom or left. If omitted, the element occupies the entire cavity. -sticky: nswe +*sticky*: nswe Specifies where the element is placed inside its allocated parcel. -unit: 0 or 1 +*unit*: 0 or 1 If set to 1, causes the element and all of its descendants to be treated as a single element for the purposes of :meth:`Widget.identify` et al. It's used for things like scrollbar thumbs with grips. -children: [sublayout... ] +*children*: [sublayout... ] Specifies a list of elements to place inside the element. Each element is a tuple (or other sequence type) where the first item is the layout name, and the other is a `Layout`_. diff --git a/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst b/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst new file mode 100644 index 00000000000000..215db7beb75dcf --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2023-10-23-23-43-43.gh-issue-110746.yg77IE.rst @@ -0,0 +1 @@ +Improved markup for valid options/values for methods ttk.treeview.column and ttk.treeview.heading, and for Layouts. From 40574da0196c7e51e1886f6ff9ed26447622ae58 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 15 Dec 2023 18:03:44 -0600 Subject: [PATCH 275/442] Add reshape() recipe to demonstrate a use case for batched() and chained.from_iterable() (gh-113198) --- Doc/library/itertools.rst | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 03127afe1b4460..6bcda307f256f2 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -1036,10 +1036,15 @@ The following recipes have a more mathematical flavor: # sum_of_squares([10, 20, 30]) -> 1400 return math.sumprod(*tee(it)) - def transpose(it): - "Swap the rows and columns of the input." + def reshape(matrix, cols): + "Reshape a 2-D matrix to have a given number of columns." + # reshape([(0, 1), (2, 3), (4, 5)], 3) --> (0, 1, 2), (3, 4, 5) + return batched(chain.from_iterable(matrix), cols) + + def transpose(matrix): + "Swap the rows and columns of a 2-D matrix." # transpose([(1, 2, 3), (11, 22, 33)]) --> (1, 11) (2, 22) (3, 33) - return zip(*it, strict=True) + return zip(*matrix, strict=True) def matmul(m1, m2): "Multiply two matrices." @@ -1254,6 +1259,22 @@ The following recipes have a more mathematical flavor: >>> sum_of_squares([10, 20, 30]) 1400 + >>> list(reshape([(0, 1), (2, 3), (4, 5)], 3)) + [(0, 1, 2), (3, 4, 5)] + >>> M = [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 1)) + [(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,), (10,), (11,)] + >>> list(reshape(M, 2)) + [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11)] + >>> list(reshape(M, 3)) + [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)] + >>> list(reshape(M, 4)) + [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 6)) + [(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)] + >>> list(reshape(M, 12)) + [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] + >>> list(transpose([(1, 2, 3), (11, 22, 33)])) [(1, 11), (2, 22), (3, 33)] From 5ae75e1be24bd6b031a68040cfddb71732461f67 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 15 Dec 2023 20:56:55 -0500 Subject: [PATCH 276/442] gh-111964: Add _PyRWMutex a "readers-writer" lock (gh-112859) This adds `_PyRWMutex`, a "readers-writer" lock, which wil be used to serialize global stop-the-world pauses with per-interpreter pauses. --- Include/internal/pycore_lock.h | 39 ++++++++++ Modules/_testinternalcapi/test_lock.c | 99 ++++++++++++++++++++++++ Python/lock.c | 106 ++++++++++++++++++++++++++ 3 files changed, 244 insertions(+) diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index 03ad1c9fd584b5..18a8896d97a548 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -213,6 +213,45 @@ _PyOnceFlag_CallOnce(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg) return _PyOnceFlag_CallOnceSlow(flag, fn, arg); } +// A readers-writer (RW) lock. The lock supports multiple concurrent readers or +// a single writer. The lock is write-preferring: if a writer is waiting while +// the lock is read-locked then, new readers will be blocked. This avoids +// starvation of writers. +// +// In C++, the equivalent synchronization primitive is std::shared_mutex +// with shared ("read") and exclusive ("write") locking. +// +// The two least significant bits are used to indicate if the lock is +// write-locked and if there are parked threads (either readers or writers) +// waiting to acquire the lock. The remaining bits are used to indicate the +// number of readers holding the lock. +// +// 0b000..00000: unlocked +// 0bnnn..nnn00: nnn..nnn readers holding the lock +// 0bnnn..nnn10: nnn..nnn readers holding the lock and a writer is waiting +// 0b00000..010: unlocked with awoken writer about to acquire lock +// 0b00000..001: write-locked +// 0b00000..011: write-locked and readers or other writers are waiting +// +// Note that reader_count must be zero if the lock is held by a writer, and +// vice versa. The lock can only be held by readers or a writer, but not both. +// +// The design is optimized for simplicity of the implementation. The lock is +// not fair: if fairness is desired, use an additional PyMutex to serialize +// writers. The lock is also not reentrant. +typedef struct { + uintptr_t bits; +} _PyRWMutex; + +// Read lock (i.e., shared lock) +PyAPI_FUNC(void) _PyRWMutex_RLock(_PyRWMutex *rwmutex); +PyAPI_FUNC(void) _PyRWMutex_RUnlock(_PyRWMutex *rwmutex); + +// Write lock (i.e., exclusive lock) +PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex); +PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex); + + #ifdef __cplusplus } #endif diff --git a/Modules/_testinternalcapi/test_lock.c b/Modules/_testinternalcapi/test_lock.c index 418f71c1441995..83081f73a72f64 100644 --- a/Modules/_testinternalcapi/test_lock.c +++ b/Modules/_testinternalcapi/test_lock.c @@ -372,6 +372,104 @@ test_lock_once(PyObject *self, PyObject *obj) Py_RETURN_NONE; } +struct test_rwlock_data { + Py_ssize_t nthreads; + _PyRWMutex rw; + PyEvent step1; + PyEvent step2; + PyEvent step3; + PyEvent done; +}; + +static void +rdlock_thread(void *arg) +{ + struct test_rwlock_data *test_data = arg; + + // Acquire the lock in read mode + _PyRWMutex_RLock(&test_data->rw); + PyEvent_Wait(&test_data->step1); + _PyRWMutex_RUnlock(&test_data->rw); + + _PyRWMutex_RLock(&test_data->rw); + PyEvent_Wait(&test_data->step3); + _PyRWMutex_RUnlock(&test_data->rw); + + if (_Py_atomic_add_ssize(&test_data->nthreads, -1) == 1) { + _PyEvent_Notify(&test_data->done); + } +} +static void +wrlock_thread(void *arg) +{ + struct test_rwlock_data *test_data = arg; + + // First acquire the lock in write mode + _PyRWMutex_Lock(&test_data->rw); + PyEvent_Wait(&test_data->step2); + _PyRWMutex_Unlock(&test_data->rw); + + if (_Py_atomic_add_ssize(&test_data->nthreads, -1) == 1) { + _PyEvent_Notify(&test_data->done); + } +} + +static void +wait_until(uintptr_t *ptr, uintptr_t value) +{ + // wait up to two seconds for *ptr == value + int iters = 0; + uintptr_t bits; + do { + pysleep(10); + bits = _Py_atomic_load_uintptr(ptr); + iters++; + } while (bits != value && iters < 200); +} + +static PyObject * +test_lock_rwlock(PyObject *self, PyObject *obj) +{ + struct test_rwlock_data test_data = {.nthreads = 3}; + + _PyRWMutex_Lock(&test_data.rw); + assert(test_data.rw.bits == 1); + + _PyRWMutex_Unlock(&test_data.rw); + assert(test_data.rw.bits == 0); + + // Start two readers + PyThread_start_new_thread(rdlock_thread, &test_data); + PyThread_start_new_thread(rdlock_thread, &test_data); + + // wait up to two seconds for the threads to attempt to read-lock "rw" + wait_until(&test_data.rw.bits, 8); + assert(test_data.rw.bits == 8); + + // start writer (while readers hold lock) + PyThread_start_new_thread(wrlock_thread, &test_data); + wait_until(&test_data.rw.bits, 10); + assert(test_data.rw.bits == 10); + + // readers release lock, writer should acquire it + _PyEvent_Notify(&test_data.step1); + wait_until(&test_data.rw.bits, 3); + assert(test_data.rw.bits == 3); + + // writer releases lock, readers acquire it + _PyEvent_Notify(&test_data.step2); + wait_until(&test_data.rw.bits, 8); + assert(test_data.rw.bits == 8); + + // readers release lock again + _PyEvent_Notify(&test_data.step3); + wait_until(&test_data.rw.bits, 0); + assert(test_data.rw.bits == 0); + + PyEvent_Wait(&test_data.done); + Py_RETURN_NONE; +} + static PyMethodDef test_methods[] = { {"test_lock_basic", test_lock_basic, METH_NOARGS}, {"test_lock_two_threads", test_lock_two_threads, METH_NOARGS}, @@ -380,6 +478,7 @@ static PyMethodDef test_methods[] = { _TESTINTERNALCAPI_BENCHMARK_LOCKS_METHODDEF {"test_lock_benchmark", test_lock_benchmark, METH_NOARGS}, {"test_lock_once", test_lock_once, METH_NOARGS}, + {"test_lock_rwlock", test_lock_rwlock, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/lock.c b/Python/lock.c index e9279f0b92a5e7..f0ff1176941da8 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -353,3 +353,109 @@ _PyOnceFlag_CallOnceSlow(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg) v = _Py_atomic_load_uint8(&flag->v); } } + +#define _Py_WRITE_LOCKED 1 +#define _PyRWMutex_READER_SHIFT 2 +#define _Py_RWMUTEX_MAX_READERS (UINTPTR_MAX >> _PyRWMutex_READER_SHIFT) + +static uintptr_t +rwmutex_set_parked_and_wait(_PyRWMutex *rwmutex, uintptr_t bits) +{ + // Set _Py_HAS_PARKED and wait until we are woken up. + if ((bits & _Py_HAS_PARKED) == 0) { + uintptr_t newval = bits | _Py_HAS_PARKED; + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, newval)) { + return bits; + } + bits = newval; + } + + _PyParkingLot_Park(&rwmutex->bits, &bits, sizeof(bits), -1, NULL, 1); + return _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); +} + +// The number of readers holding the lock +static uintptr_t +rwmutex_reader_count(uintptr_t bits) +{ + return bits >> _PyRWMutex_READER_SHIFT; +} + +void +_PyRWMutex_RLock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); + for (;;) { + if ((bits & _Py_WRITE_LOCKED)) { + // A writer already holds the lock. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + continue; + } + else if ((bits & _Py_HAS_PARKED)) { + // Reader(s) hold the lock (or just gave up the lock), but there is + // at least one waiting writer. We can't grab the lock because we + // don't want to starve the writer. Instead, we park ourselves and + // wait for the writer to eventually wake us up. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + continue; + } + else { + // The lock is unlocked or read-locked. Try to grab it. + assert(rwmutex_reader_count(bits) < _Py_RWMUTEX_MAX_READERS); + uintptr_t newval = bits + (1 << _PyRWMutex_READER_SHIFT); + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, newval)) { + continue; + } + return; + } + } +} + +void +_PyRWMutex_RUnlock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_add_uintptr(&rwmutex->bits, -(1 << _PyRWMutex_READER_SHIFT)); + assert(rwmutex_reader_count(bits) > 0 && "lock was not read-locked"); + bits -= (1 << _PyRWMutex_READER_SHIFT); + + if (rwmutex_reader_count(bits) == 0 && (bits & _Py_HAS_PARKED)) { + _PyParkingLot_UnparkAll(&rwmutex->bits); + return; + } +} + +void +_PyRWMutex_Lock(_PyRWMutex *rwmutex) +{ + uintptr_t bits = _Py_atomic_load_uintptr_relaxed(&rwmutex->bits); + for (;;) { + // If there are no active readers and it's not already write-locked, + // then we can grab the lock. + if ((bits & ~_Py_HAS_PARKED) == 0) { + if (!_Py_atomic_compare_exchange_uintptr(&rwmutex->bits, + &bits, + bits | _Py_WRITE_LOCKED)) { + continue; + } + return; + } + + // Otherwise, we have to wait. + bits = rwmutex_set_parked_and_wait(rwmutex, bits); + } +} + +void +_PyRWMutex_Unlock(_PyRWMutex *rwmutex) +{ + uintptr_t old_bits = _Py_atomic_exchange_uintptr(&rwmutex->bits, 0); + + assert((old_bits & _Py_WRITE_LOCKED) && "lock was not write-locked"); + assert(rwmutex_reader_count(old_bits) == 0 && "lock was read-locked"); + + if ((old_bits & _Py_HAS_PARKED) != 0) { + _PyParkingLot_UnparkAll(&rwmutex->bits); + } +} From 84df3172efe8767ddf5c28bdb6696b3f216bcaa6 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 16 Dec 2023 03:12:39 -0500 Subject: [PATCH 277/442] gh-113046: Revise csv.reader doc (#113207) Clarify nature of csvfile. --- Doc/library/csv.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/library/csv.rst b/Doc/library/csv.rst index 4d52254e6d6db5..7a5589e68b3052 100644 --- a/Doc/library/csv.rst +++ b/Doc/library/csv.rst @@ -55,10 +55,11 @@ The :mod:`csv` module defines the following functions: .. function:: reader(csvfile, dialect='excel', **fmtparams) - Return a reader object which will iterate over lines in the given *csvfile*. - *csvfile* can be any object which supports the :term:`iterator` protocol and returns a - string each time its :meth:`!__next__` method is called --- :term:`file objects - ` and list objects are both suitable. If *csvfile* is a file object, + Return a :ref:`reader object ` that will process + lines from the given *csvfile*. A csvfile must be an iterable of + strings, each in the reader's defined csv format. + A csvfile is most commonly a file-like object or list. + 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 @@ -449,6 +450,8 @@ Dialects support the following attributes: When ``True``, raise exception :exc:`Error` on bad CSV input. The default is ``False``. +.. _reader-objects: + Reader Objects -------------- From fe479fb8a979894224a4d279d1e46a5cdb108fa4 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sat, 16 Dec 2023 10:58:31 +0000 Subject: [PATCH 278/442] gh-67790: Support basic formatting for Fraction (#111320) PR #100161 added fancy float-style formatting for the Fraction type, but left us in a state where basic formatting for fractions (alignment, fill, minimum width, thousands separators) still wasn't supported. This PR adds that support. --------- Co-authored-by: Serhiy Storchaka --- Doc/library/fractions.rst | 37 ++++++-- Doc/whatsnew/3.13.rst | 8 ++ Lib/fractions.py | 87 +++++++++++++++---- Lib/test/test_fractions.py | 52 +++++++++-- ...3-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst | 2 + 5 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst index 509c63686f5a7f..887c3844d20faa 100644 --- a/Doc/library/fractions.rst +++ b/Doc/library/fractions.rst @@ -106,6 +106,10 @@ another rational number, or from a string. presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"`` and ``"%""``. + .. versionchanged:: 3.13 + Formatting of :class:`Fraction` instances without a presentation type + now supports fill, alignment, sign handling, minimum width and grouping. + .. attribute:: numerator Numerator of the Fraction in lowest term. @@ -201,17 +205,36 @@ another rational number, or from a string. .. method:: __format__(format_spec, /) - Provides support for float-style formatting of :class:`Fraction` - instances via the :meth:`str.format` method, the :func:`format` built-in - function, or :ref:`Formatted string literals `. The - presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"`` - and ``"%"`` are supported. For these presentation types, formatting for a - :class:`Fraction` object ``x`` follows the rules outlined for - the :class:`float` type in the :ref:`formatspec` section. + Provides support for formatting of :class:`Fraction` instances via the + :meth:`str.format` method, the :func:`format` built-in function, or + :ref:`Formatted string literals `. + + If the ``format_spec`` format specification string does not end with one + of the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``, + ``'G'`` or ``'%'`` then formatting follows the general rules for fill, + alignment, sign handling, minimum width, and grouping as described in the + :ref:`format specification mini-language `. The "alternate + form" flag ``'#'`` is supported: if present, it forces the output string + to always include an explicit denominator, even when the value being + formatted is an exact integer. The zero-fill flag ``'0'`` is not + supported. + + If the ``format_spec`` format specification string ends with one of + the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``, + ``'G'`` or ``'%'`` then formatting follows the rules outlined for the + :class:`float` type in the :ref:`formatspec` section. Here are some examples:: >>> from fractions import Fraction + >>> format(Fraction(103993, 33102), '_') + '103_993/33_102' + >>> format(Fraction(1, 7), '.^+10') + '...+1/7...' + >>> format(Fraction(3, 1), '') + '3' + >>> format(Fraction(3, 1), '#') + '3/1' >>> format(Fraction(1, 7), '.40g') '0.1428571428571428571428571428571428571429' >>> format(Fraction('1234567.855'), '_.2f') diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4f9643967d20cf..ce4f66b97a0be4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -212,6 +212,14 @@ email (Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve the CVE-2023-27043 fix.) +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`) + glob ---- diff --git a/Lib/fractions.py b/Lib/fractions.py index c95db0730e5b6d..6532d5d54e3c35 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures): return sign, significand, exponent +# Pattern for matching non-float-style format specifications. +_GENERAL_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" + (?: + (?P.)? + (?P[<>=^]) + )? + (?P[-+ ]?) + # Alt flag forces a slash and denominator in the output, even for + # integer-valued Fraction objects. + (?P\#)? + # We don't implement the zeropad flag since there's no single obvious way + # to interpret it. + (?P0|[1-9][0-9]*)? + (?P[,_])? +""", re.DOTALL | re.VERBOSE).fullmatch + + # Pattern for matching float-style format specifications; # supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types. _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" @@ -414,27 +431,42 @@ def __str__(self): else: return '%s/%s' % (self._numerator, self._denominator) - def __format__(self, format_spec, /): - """Format this fraction according to the given format specification.""" - - # Backwards compatiblility with existing formatting. - if not format_spec: - return str(self) + def _format_general(self, match): + """Helper method for __format__. + Handles fill, alignment, signs, and thousands separators in the + case of no presentation type. + """ # Validate and parse the format specifier. - match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec) - if match is None: - raise ValueError( - f"Invalid format specifier {format_spec!r} " - f"for object of type {type(self).__name__!r}" - ) - elif match["align"] is not None and match["zeropad"] is not None: - # Avoid the temptation to guess. - raise ValueError( - f"Invalid format specifier {format_spec!r} " - f"for object of type {type(self).__name__!r}; " - "can't use explicit alignment when zero-padding" - ) + fill = match["fill"] or " " + align = match["align"] or ">" + pos_sign = "" if match["sign"] == "-" else match["sign"] + alternate_form = bool(match["alt"]) + minimumwidth = int(match["minimumwidth"] or "0") + thousands_sep = match["thousands_sep"] or '' + + # Determine the body and sign representation. + n, d = self._numerator, self._denominator + if d > 1 or alternate_form: + body = f"{abs(n):{thousands_sep}}/{d:{thousands_sep}}" + else: + body = f"{abs(n):{thousands_sep}}" + sign = '-' if n < 0 else pos_sign + + # Pad with fill character if necessary and return. + padding = fill * (minimumwidth - len(sign) - len(body)) + if align == ">": + return padding + sign + body + elif align == "<": + return sign + body + padding + elif align == "^": + half = len(padding) // 2 + return padding[:half] + sign + body + padding[half:] + else: # align == "=" + return sign + padding + body + + def _format_float_style(self, match): + """Helper method for __format__; handles float presentation types.""" fill = match["fill"] or " " align = match["align"] or ">" pos_sign = "" if match["sign"] == "-" else match["sign"] @@ -530,6 +562,23 @@ def __format__(self, format_spec, /): else: # align == "=" return sign + padding + body + def __format__(self, format_spec, /): + """Format this fraction according to the given format specification.""" + + if match := _GENERAL_FORMAT_SPECIFICATION_MATCHER(format_spec): + return self._format_general(match) + + if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec): + # Refuse the temptation to guess if both alignment _and_ + # zero padding are specified. + if match["align"] is None or match["zeropad"] is None: + return self._format_float_style(match) + + raise ValueError( + f"Invalid format specifier {format_spec!r} " + f"for object of type {type(self).__name__!r}" + ) + def _operator_fallbacks(monomorphic_operator, fallback_operator): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 499e3b6e656faa..84779526ce0eb0 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -849,12 +849,50 @@ def denominator(self): self.assertEqual(type(f.denominator), myint) def test_format_no_presentation_type(self): - # Triples (fraction, specification, expected_result) + # Triples (fraction, specification, expected_result). testcases = [ - (F(1, 3), '', '1/3'), - (F(-1, 3), '', '-1/3'), - (F(3), '', '3'), - (F(-3), '', '-3'), + # Explicit sign handling + (F(2, 3), '+', '+2/3'), + (F(-2, 3), '+', '-2/3'), + (F(3), '+', '+3'), + (F(-3), '+', '-3'), + (F(2, 3), ' ', ' 2/3'), + (F(-2, 3), ' ', '-2/3'), + (F(3), ' ', ' 3'), + (F(-3), ' ', '-3'), + (F(2, 3), '-', '2/3'), + (F(-2, 3), '-', '-2/3'), + (F(3), '-', '3'), + (F(-3), '-', '-3'), + # Padding + (F(0), '5', ' 0'), + (F(2, 3), '5', ' 2/3'), + (F(-2, 3), '5', ' -2/3'), + (F(2, 3), '0', '2/3'), + (F(2, 3), '1', '2/3'), + (F(2, 3), '2', '2/3'), + # Alignment + (F(2, 3), '<5', '2/3 '), + (F(2, 3), '>5', ' 2/3'), + (F(2, 3), '^5', ' 2/3 '), + (F(2, 3), '=5', ' 2/3'), + (F(-2, 3), '<5', '-2/3 '), + (F(-2, 3), '>5', ' -2/3'), + (F(-2, 3), '^5', '-2/3 '), + (F(-2, 3), '=5', '- 2/3'), + # Fill + (F(2, 3), 'X>5', 'XX2/3'), + (F(-2, 3), '.<5', '-2/3.'), + (F(-2, 3), '\n^6', '\n-2/3\n'), + # Thousands separators + (F(1234, 5679), ',', '1,234/5,679'), + (F(-1234, 5679), '_', '-1_234/5_679'), + (F(1234567), '_', '1_234_567'), + (F(-1234567), ',', '-1,234,567'), + # Alternate form forces a slash in the output + (F(123), '#', '123/1'), + (F(-123), '#', '-123/1'), + (F(0), '#', '0/1'), ] for fraction, spec, expected in testcases: with self.subTest(fraction=fraction, spec=spec): @@ -1218,6 +1256,10 @@ def test_invalid_formats(self): '.%', # Z instead of z for negative zero suppression 'Z.2f' + # z flag not supported for general formatting + 'z', + # zero padding not supported for general formatting + '05', ] for spec in invalid_specs: with self.subTest(spec=spec): diff --git a/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst b/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst new file mode 100644 index 00000000000000..44c5702a6551b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-25-13-07-53.gh-issue-67790.jMn9Ad.rst @@ -0,0 +1,2 @@ +Implement basic formatting support (minimum width, alignment, fill) for +:class:`fractions.Fraction`. From 1583c40be938d2caf363c126976bc8757df90b13 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 16 Dec 2023 09:13:50 -0600 Subject: [PATCH 279/442] gh-113202: Add a strict option to itertools.batched() (gh-113203) --- Doc/library/itertools.rst | 18 +++++++++-- Lib/test/test_itertools.py | 4 +++ ...-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst | 1 + Modules/clinic/itertoolsmodule.c.h | 32 +++++++++++++------ Modules/itertoolsmodule.c | 29 ++++++++++------- 5 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 6bcda307f256f2..c016fb76bfd0a0 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -164,11 +164,14 @@ loops that truncate the stream. Added the optional *initial* parameter. -.. function:: batched(iterable, n) +.. function:: batched(iterable, n, *, strict=False) Batch data from the *iterable* into tuples of length *n*. The last batch may be shorter than *n*. + If *strict* is true, will raise a :exc:`ValueError` if the final + batch is shorter than *n*. + Loops over the input iterable and accumulates data into tuples up to size *n*. The input is consumed lazily, just enough to fill a batch. The result is yielded as soon as the batch is full or when the input @@ -190,16 +193,21 @@ loops that truncate the stream. Roughly equivalent to:: - def batched(iterable, n): + def batched(iterable, n, *, strict=False): # batched('ABCDEFG', 3) --> ABC DEF G if n < 1: raise ValueError('n must be at least one') it = iter(iterable) while batch := tuple(islice(it, n)): + if strict and len(batch) != n: + raise ValueError('batched(): incomplete batch') yield batch .. versionadded:: 3.12 + .. versionchanged:: 3.13 + Added the *strict* option. + .. function:: chain(*iterables) @@ -1039,7 +1047,7 @@ The following recipes have a more mathematical flavor: def reshape(matrix, cols): "Reshape a 2-D matrix to have a given number of columns." # reshape([(0, 1), (2, 3), (4, 5)], 3) --> (0, 1, 2), (3, 4, 5) - return batched(chain.from_iterable(matrix), cols) + return batched(chain.from_iterable(matrix), cols, strict=True) def transpose(matrix): "Swap the rows and columns of a 2-D matrix." @@ -1270,6 +1278,10 @@ The following recipes have a more mathematical flavor: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)] >>> list(reshape(M, 4)) [(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)] + >>> list(reshape(M, 5)) + Traceback (most recent call last): + ... + ValueError: batched(): incomplete batch >>> list(reshape(M, 6)) [(0, 1, 2, 3, 4, 5), (6, 7, 8, 9, 10, 11)] >>> list(reshape(M, 12)) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 705e880d98685e..9af0730ea98004 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -187,7 +187,11 @@ def test_batched(self): [('A', 'B'), ('C', 'D'), ('E', 'F'), ('G',)]) self.assertEqual(list(batched('ABCDEFG', 1)), [('A',), ('B',), ('C',), ('D',), ('E',), ('F',), ('G',)]) + self.assertEqual(list(batched('ABCDEF', 2, strict=True)), + [('A', 'B'), ('C', 'D'), ('E', 'F')]) + with self.assertRaises(ValueError): # Incomplete batch when strict + list(batched('ABCDEFG', 3, strict=True)) with self.assertRaises(TypeError): # Too few arguments list(batched('ABCDEFG')) with self.assertRaises(TypeError): diff --git a/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst b/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst new file mode 100644 index 00000000000000..44f26aef60a33a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-18-10-26.gh-issue-113202.xv_Ww8.rst @@ -0,0 +1 @@ +Add a ``strict`` option to ``batched()`` in the ``itertools`` module. diff --git a/Modules/clinic/itertoolsmodule.c.h b/Modules/clinic/itertoolsmodule.c.h index fa2c5e0e922387..3ec479943a83d4 100644 --- a/Modules/clinic/itertoolsmodule.c.h +++ b/Modules/clinic/itertoolsmodule.c.h @@ -10,7 +10,7 @@ preserve #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() PyDoc_STRVAR(batched_new__doc__, -"batched(iterable, n)\n" +"batched(iterable, n, *, strict=False)\n" "--\n" "\n" "Batch data into tuples of length n. The last batch may be shorter than n.\n" @@ -25,10 +25,14 @@ PyDoc_STRVAR(batched_new__doc__, " ...\n" " (\'A\', \'B\', \'C\')\n" " (\'D\', \'E\', \'F\')\n" -" (\'G\',)"); +" (\'G\',)\n" +"\n" +"If \"strict\" is True, raises a ValueError if the final batch is shorter\n" +"than n."); static PyObject * -batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n); +batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n, + int strict); static PyObject * batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) @@ -36,14 +40,14 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 2 + #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(iterable), &_Py_ID(n), }, + .ob_item = { &_Py_ID(iterable), &_Py_ID(n), &_Py_ID(strict), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -52,18 +56,20 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"iterable", "n", NULL}; + static const char * const _keywords[] = {"iterable", "n", "strict", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "batched", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[2]; + PyObject *argsbuf[3]; PyObject * const *fastargs; Py_ssize_t nargs = PyTuple_GET_SIZE(args); + Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 2; PyObject *iterable; Py_ssize_t n; + int strict = 0; fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 2, 2, 0, argsbuf); if (!fastargs) { @@ -82,7 +88,15 @@ batched_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } n = ival; } - return_value = batched_new_impl(type, iterable, n); + if (!noptargs) { + goto skip_optional_kwonly; + } + strict = PyObject_IsTrue(fastargs[2]); + if (strict < 0) { + goto exit; + } +skip_optional_kwonly: + return_value = batched_new_impl(type, iterable, n, strict); exit: return return_value; @@ -914,4 +928,4 @@ itertools_count(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=782fe7e30733779b input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c6a515f765da86b5 input=a9049054013a1b77]*/ diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index ab99fa4d873bf5..164741495c7baf 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -105,20 +105,11 @@ class itertools.pairwise "pairwiseobject *" "clinic_state()->pairwise_type" /* batched object ************************************************************/ -/* Note: The built-in zip() function includes a "strict" argument - that was needed because that function would silently truncate data, - and there was no easy way for a user to detect the data loss. - The same reasoning does not apply to batched() which never drops data. - Instead, batched() produces a shorter tuple which can be handled - as the user sees fit. If requested, it would be reasonable to add - "fillvalue" support which had demonstrated value in zip_longest(). - For now, the API is kept simple and clean. - */ - typedef struct { PyObject_HEAD PyObject *it; Py_ssize_t batch_size; + bool strict; } batchedobject; /*[clinic input] @@ -126,6 +117,9 @@ typedef struct { itertools.batched.__new__ as batched_new iterable: object n: Py_ssize_t + * + strict: bool = False + Batch data into tuples of length n. The last batch may be shorter than n. Loops over the input iterable and accumulates data into tuples @@ -140,11 +134,15 @@ or when the input iterable is exhausted. ('D', 'E', 'F') ('G',) +If "strict" is True, raises a ValueError if the final batch is shorter +than n. + [clinic start generated code]*/ static PyObject * -batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n) -/*[clinic end generated code: output=7ebc954d655371b6 input=ffd70726927c5129]*/ +batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n, + int strict) +/*[clinic end generated code: output=c6de11b061529d3e input=7814b47e222f5467]*/ { PyObject *it; batchedobject *bo; @@ -170,6 +168,7 @@ batched_new_impl(PyTypeObject *type, PyObject *iterable, Py_ssize_t n) } bo->batch_size = n; bo->it = it; + bo->strict = (bool) strict; return (PyObject *)bo; } @@ -233,6 +232,12 @@ batched_next(batchedobject *bo) Py_DECREF(result); return NULL; } + if (bo->strict) { + Py_CLEAR(bo->it); + Py_DECREF(result); + PyErr_SetString(PyExc_ValueError, "batched(): incomplete batch"); + return NULL; + } _PyTuple_Resize(&result, i); return result; } From d91e43ed7839b601cbeadd137d89e69e2cc3efda Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 16 Dec 2023 19:04:33 +0000 Subject: [PATCH 280/442] GH-110109: Move tests for pathlib ABCs to new module. (#112904) --- Lib/test/test_pathlib/__init__.py | 5 + Lib/test/{ => test_pathlib}/test_pathlib.py | 1896 +----------------- Lib/test/test_pathlib/test_pathlib_abc.py | 1918 +++++++++++++++++++ Makefile.pre.in | 1 + 4 files changed, 1927 insertions(+), 1893 deletions(-) create mode 100644 Lib/test/test_pathlib/__init__.py rename Lib/test/{ => test_pathlib}/test_pathlib.py (50%) create mode 100644 Lib/test/test_pathlib/test_pathlib_abc.py diff --git a/Lib/test/test_pathlib/__init__.py b/Lib/test/test_pathlib/__init__.py new file mode 100644 index 00000000000000..4b16ecc31156a5 --- /dev/null +++ b/Lib/test/test_pathlib/__init__.py @@ -0,0 +1,5 @@ +import os +from test.support import load_package_tests + +def load_tests(*args): + return load_package_tests(os.path.dirname(__file__), *args) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py similarity index 50% rename from Lib/test/test_pathlib.py rename to Lib/test/test_pathlib/test_pathlib.py index a1acba406ea37c..304786fc6f7fd9 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1,11 +1,9 @@ -import collections.abc import io import os import sys import errno import pathlib import pickle -import posixpath import socket import stat import tempfile @@ -14,10 +12,10 @@ from urllib.request import pathname2url from test.support import import_helper -from test.support import set_recursion_limit from test.support import is_emscripten, is_wasi from test.support import os_helper from test.support.os_helper import TESTFN, FakePath +from test.test_pathlib import test_pathlib_abc try: import grp, pwd @@ -25,12 +23,6 @@ grp = pwd = None -class UnsupportedOperationTest(unittest.TestCase): - def test_is_notimplemented(self): - self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError)) - self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) - - # Make sure any symbolic links in the base test path are resolved. BASE = os.path.realpath(TESTFN) join = lambda *x: os.path.join(BASE, *x) @@ -49,654 +41,7 @@ def test_is_notimplemented(self): # Tests for the pure classes. # - -class PurePathBaseTest(unittest.TestCase): - cls = pathlib._abc.PurePathBase - - def test_magic_methods(self): - P = self.cls - self.assertFalse(hasattr(P, '__fspath__')) - self.assertFalse(hasattr(P, '__bytes__')) - self.assertIs(P.__reduce__, object.__reduce__) - self.assertIs(P.__hash__, object.__hash__) - self.assertIs(P.__eq__, object.__eq__) - self.assertIs(P.__lt__, object.__lt__) - self.assertIs(P.__le__, object.__le__) - self.assertIs(P.__gt__, object.__gt__) - self.assertIs(P.__ge__, object.__ge__) - - -class DummyPurePath(pathlib._abc.PurePathBase): - def __eq__(self, other): - if not isinstance(other, DummyPurePath): - return NotImplemented - return str(self) == str(other) - - def __hash__(self): - return hash(str(self)) - - -class DummyPurePathTest(unittest.TestCase): - cls = DummyPurePath - - # Keys are canonical paths, values are list of tuples of arguments - # supposed to produce equal paths. - equivalences = { - 'a/b': [ - ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), - ('a/b/',), ('a//b',), ('a//b//',), - # Empty components get removed. - ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), - ], - '/b/c/d': [ - ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), - # Empty components get removed. - ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), - ], - } - - def setUp(self): - p = self.cls('a') - self.pathmod = p.pathmod - self.sep = self.pathmod.sep - self.altsep = self.pathmod.altsep - - def test_constructor_common(self): - P = self.cls - p = P('a') - self.assertIsInstance(p, P) - P('a', 'b', 'c') - P('/a', 'b', 'c') - P('a/b/c') - P('/a/b/c') - - def test_concrete_class(self): - if self.cls is pathlib.PurePath: - expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath - else: - expected = self.cls - p = self.cls('a') - self.assertIs(type(p), expected) - - def test_different_pathmods_unequal(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - self.assertNotEqual(p, q) - - def test_different_pathmods_unordered(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - with self.assertRaises(TypeError): - p < q - with self.assertRaises(TypeError): - p <= q - with self.assertRaises(TypeError): - p > q - with self.assertRaises(TypeError): - p >= q - - 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 - # a pure str object. - class StrSubclass(str): - pass - P = self.cls - p = P(*(StrSubclass(x) for x in args)) - self.assertEqual(p, P(*args)) - for part in p.parts: - self.assertIs(type(part), str) - - def test_str_subclass_common(self): - self._check_str_subclass('') - self._check_str_subclass('.') - self._check_str_subclass('a') - self._check_str_subclass('a/b.txt') - self._check_str_subclass('/a/b.txt') - - def test_with_segments_common(self): - class P(self.cls): - def __init__(self, *pathsegments, session_id): - super().__init__(*pathsegments) - self.session_id = session_id - - def with_segments(self, *pathsegments): - return type(self)(*pathsegments, session_id=self.session_id) - p = P('foo', 'bar', session_id=42) - self.assertEqual(42, (p / 'foo').session_id) - self.assertEqual(42, ('foo' / p).session_id) - self.assertEqual(42, p.joinpath('foo').session_id) - self.assertEqual(42, p.with_name('foo').session_id) - self.assertEqual(42, p.with_stem('foo').session_id) - self.assertEqual(42, p.with_suffix('.foo').session_id) - self.assertEqual(42, p.with_segments('foo').session_id) - self.assertEqual(42, p.relative_to('foo').session_id) - self.assertEqual(42, p.parent.session_id) - for parent in p.parents: - self.assertEqual(42, parent.session_id) - - def _check_parse_path(self, raw_path, *expected): - sep = self.pathmod.sep - actual = self.cls._parse_path(raw_path.replace('/', sep)) - self.assertEqual(actual, expected) - if altsep := self.pathmod.altsep: - actual = self.cls._parse_path(raw_path.replace('/', altsep)) - self.assertEqual(actual, expected) - - def test_parse_path_common(self): - check = self._check_parse_path - sep = self.pathmod.sep - check('', '', '', []) - check('a', '', '', ['a']) - check('a/', '', '', ['a']) - check('a/b', '', '', ['a', 'b']) - check('a/b/', '', '', ['a', 'b']) - check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) - check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) - check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) - check('.', '', '', []) - check('././b', '', '', ['b']) - check('a/./b', '', '', ['a', 'b']) - check('a/./.', '', '', ['a']) - check('/a/b', '', sep, ['a', 'b']) - - def test_join_common(self): - P = self.cls - p = P('a/b') - pp = p.joinpath('c') - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p.joinpath('c', 'd') - self.assertEqual(pp, P('a/b/c/d')) - pp = p.joinpath('/c') - self.assertEqual(pp, P('/c')) - - def test_div_common(self): - # Basically the same as joinpath(). - P = self.cls - p = P('a/b') - pp = p / 'c' - self.assertEqual(pp, P('a/b/c')) - self.assertIs(type(pp), type(p)) - pp = p / 'c/d' - self.assertEqual(pp, P('a/b/c/d')) - pp = p / 'c' / 'd' - self.assertEqual(pp, P('a/b/c/d')) - pp = 'c' / p / 'd' - self.assertEqual(pp, P('c/a/b/d')) - pp = p/ '/c' - self.assertEqual(pp, P('/c')) - - def _check_str(self, expected, args): - p = self.cls(*args) - self.assertEqual(str(p), expected.replace('/', self.sep)) - - def test_str_common(self): - # Canonicalized paths roundtrip. - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self._check_str(pathstr, (pathstr,)) - # Special case for the empty path. - self._check_str('.', ('',)) - # Other tests for str() are in test_equivalences(). - - def test_as_posix_common(self): - P = self.cls - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - self.assertEqual(P(pathstr).as_posix(), pathstr) - # Other tests for as_posix() are in test_equivalences(). - - def test_repr_common(self): - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - with self.subTest(pathstr=pathstr): - p = self.cls(pathstr) - clsname = p.__class__.__name__ - r = repr(p) - # The repr() is in the form ClassName("forward-slashes path"). - self.assertTrue(r.startswith(clsname + '('), r) - self.assertTrue(r.endswith(')'), r) - inner = r[len(clsname) + 1 : -1] - self.assertEqual(eval(inner), p.as_posix()) - - def test_eq_common(self): - P = self.cls - self.assertEqual(P('a/b'), P('a/b')) - self.assertEqual(P('a/b'), P('a', 'b')) - self.assertNotEqual(P('a/b'), P('a')) - self.assertNotEqual(P('a/b'), P('/a/b')) - self.assertNotEqual(P('a/b'), P()) - self.assertNotEqual(P('/a/b'), P('/')) - self.assertNotEqual(P(), P('/')) - self.assertNotEqual(P(), "") - self.assertNotEqual(P(), {}) - self.assertNotEqual(P(), int) - - def test_match_common(self): - P = self.cls - self.assertRaises(ValueError, P('a').match, '') - self.assertRaises(ValueError, P('a').match, '.') - # Simple relative pattern. - self.assertTrue(P('b.py').match('b.py')) - self.assertTrue(P('a/b.py').match('b.py')) - self.assertTrue(P('/a/b.py').match('b.py')) - self.assertFalse(P('a.py').match('b.py')) - self.assertFalse(P('b/py').match('b.py')) - self.assertFalse(P('/a.py').match('b.py')) - self.assertFalse(P('b.py/c').match('b.py')) - # Wildcard relative pattern. - self.assertTrue(P('b.py').match('*.py')) - self.assertTrue(P('a/b.py').match('*.py')) - self.assertTrue(P('/a/b.py').match('*.py')) - self.assertFalse(P('b.pyc').match('*.py')) - self.assertFalse(P('b./py').match('*.py')) - self.assertFalse(P('b.py/c').match('*.py')) - # Multi-part relative pattern. - self.assertTrue(P('ab/c.py').match('a*/*.py')) - self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) - self.assertFalse(P('a.py').match('a*/*.py')) - self.assertFalse(P('/dab/c.py').match('a*/*.py')) - self.assertFalse(P('ab/c.py/d').match('a*/*.py')) - # Absolute pattern. - self.assertTrue(P('/b.py').match('/*.py')) - self.assertFalse(P('b.py').match('/*.py')) - self.assertFalse(P('a/b.py').match('/*.py')) - self.assertFalse(P('/a/b.py').match('/*.py')) - # Multi-part absolute pattern. - self.assertTrue(P('/a/b.py').match('/a/*.py')) - 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('**/')) - 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.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)) - self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) - 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('**/*')) - - def test_parts_common(self): - # `parts` returns a tuple. - sep = self.sep - P = self.cls - p = P('a/b') - parts = p.parts - self.assertEqual(parts, ('a', 'b')) - # When the path is absolute, the anchor is a separate part. - p = P('/a/b') - parts = p.parts - self.assertEqual(parts, (sep, 'a', 'b')) - - def test_equivalences(self): - for k, tuples in self.equivalences.items(): - canon = k.replace('/', self.sep) - posix = k.replace(self.sep, '/') - if canon != posix: - tuples = tuples + [ - tuple(part.replace('/', self.sep) for part in t) - for t in tuples - ] - tuples.append((posix, )) - pcanon = self.cls(canon) - for t in tuples: - p = self.cls(*t) - self.assertEqual(p, pcanon, "failed with args {}".format(t)) - self.assertEqual(hash(p), hash(pcanon)) - self.assertEqual(str(p), canon) - self.assertEqual(p.as_posix(), posix) - - def test_parent_common(self): - # Relative - P = self.cls - p = P('a/b/c') - self.assertEqual(p.parent, P('a/b')) - self.assertEqual(p.parent.parent, P('a')) - self.assertEqual(p.parent.parent.parent, P()) - self.assertEqual(p.parent.parent.parent.parent, P()) - # Anchored - p = P('/a/b/c') - self.assertEqual(p.parent, P('/a/b')) - self.assertEqual(p.parent.parent, P('/a')) - self.assertEqual(p.parent.parent.parent, P('/')) - self.assertEqual(p.parent.parent.parent.parent, P('/')) - - def test_parents_common(self): - # Relative - P = self.cls - p = P('a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('a/b')) - self.assertEqual(par[1], P('a')) - self.assertEqual(par[2], P('.')) - self.assertEqual(par[-1], P('.')) - self.assertEqual(par[-2], P('a')) - self.assertEqual(par[-3], P('a/b')) - self.assertEqual(par[0:1], (P('a/b'),)) - self.assertEqual(par[:2], (P('a/b'), P('a'))) - self.assertEqual(par[:-1], (P('a/b'), P('a'))) - self.assertEqual(par[1:], (P('a'), P('.'))) - self.assertEqual(par[::2], (P('a/b'), P('.'))) - self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) - self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - with self.assertRaises(TypeError): - par[0] = p - # Anchored - p = P('/a/b/c') - par = p.parents - self.assertEqual(len(par), 3) - self.assertEqual(par[0], P('/a/b')) - self.assertEqual(par[1], P('/a')) - self.assertEqual(par[2], P('/')) - self.assertEqual(par[-1], P('/')) - self.assertEqual(par[-2], P('/a')) - self.assertEqual(par[-3], P('/a/b')) - self.assertEqual(par[0:1], (P('/a/b'),)) - self.assertEqual(par[:2], (P('/a/b'), P('/a'))) - self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) - self.assertEqual(par[1:], (P('/a'), P('/'))) - self.assertEqual(par[::2], (P('/a/b'), P('/'))) - self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) - self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) - with self.assertRaises(IndexError): - par[-4] - with self.assertRaises(IndexError): - par[3] - - def test_drive_common(self): - P = self.cls - self.assertEqual(P('a/b').drive, '') - self.assertEqual(P('/a/b').drive, '') - self.assertEqual(P('').drive, '') - - def test_root_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').root, '') - self.assertEqual(P('a/b').root, '') - self.assertEqual(P('/').root, sep) - self.assertEqual(P('/a/b').root, sep) - - def test_anchor_common(self): - P = self.cls - sep = self.sep - self.assertEqual(P('').anchor, '') - self.assertEqual(P('a/b').anchor, '') - self.assertEqual(P('/').anchor, sep) - self.assertEqual(P('/a/b').anchor, sep) - - def test_name_common(self): - P = self.cls - self.assertEqual(P('').name, '') - self.assertEqual(P('.').name, '') - self.assertEqual(P('/').name, '') - self.assertEqual(P('a/b').name, 'b') - self.assertEqual(P('/a/b').name, 'b') - self.assertEqual(P('/a/b/.').name, 'b') - self.assertEqual(P('a/b.py').name, 'b.py') - self.assertEqual(P('/a/b.py').name, 'b.py') - - def test_suffix_common(self): - P = self.cls - self.assertEqual(P('').suffix, '') - self.assertEqual(P('.').suffix, '') - self.assertEqual(P('..').suffix, '') - self.assertEqual(P('/').suffix, '') - self.assertEqual(P('a/b').suffix, '') - self.assertEqual(P('/a/b').suffix, '') - self.assertEqual(P('/a/b/.').suffix, '') - self.assertEqual(P('a/b.py').suffix, '.py') - self.assertEqual(P('/a/b.py').suffix, '.py') - self.assertEqual(P('a/.hgrc').suffix, '') - self.assertEqual(P('/a/.hgrc').suffix, '') - self.assertEqual(P('a/.hg.rc').suffix, '.rc') - self.assertEqual(P('/a/.hg.rc').suffix, '.rc') - self.assertEqual(P('a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') - self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') - self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') - - def test_suffixes_common(self): - P = self.cls - self.assertEqual(P('').suffixes, []) - self.assertEqual(P('.').suffixes, []) - self.assertEqual(P('/').suffixes, []) - self.assertEqual(P('a/b').suffixes, []) - self.assertEqual(P('/a/b').suffixes, []) - self.assertEqual(P('/a/b/.').suffixes, []) - self.assertEqual(P('a/b.py').suffixes, ['.py']) - self.assertEqual(P('/a/b.py').suffixes, ['.py']) - self.assertEqual(P('a/.hgrc').suffixes, []) - self.assertEqual(P('/a/.hgrc').suffixes, []) - self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) - self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) - self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) - self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) - - def test_stem_common(self): - P = self.cls - self.assertEqual(P('').stem, '') - self.assertEqual(P('.').stem, '') - self.assertEqual(P('..').stem, '..') - self.assertEqual(P('/').stem, '') - self.assertEqual(P('a/b').stem, 'b') - self.assertEqual(P('a/b.py').stem, 'b') - self.assertEqual(P('a/.hgrc').stem, '.hgrc') - self.assertEqual(P('a/.hg.rc').stem, '.hg') - self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') - self.assertEqual(P('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')) - self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) - self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) - self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) - 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')) - self.assertRaises(ValueError, P('').with_name, 'd.xml') - self.assertRaises(ValueError, P('.').with_name, 'd.xml') - self.assertRaises(ValueError, P('/').with_name, 'd.xml') - self.assertRaises(ValueError, P('a/b').with_name, '') - self.assertRaises(ValueError, P('a/b').with_name, '.') - self.assertRaises(ValueError, P('a/b').with_name, '/c') - self.assertRaises(ValueError, P('a/b').with_name, 'c/') - self.assertRaises(ValueError, P('a/b').with_name, 'c/d') - - def test_with_stem_common(self): - P = self.cls - self.assertEqual(P('a/b').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) - self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) - self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) - self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) - self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) - self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) - self.assertRaises(ValueError, P('').with_stem, 'd') - self.assertRaises(ValueError, P('.').with_stem, 'd') - self.assertRaises(ValueError, P('/').with_stem, 'd') - self.assertRaises(ValueError, P('a/b').with_stem, '') - self.assertRaises(ValueError, P('a/b').with_stem, '.') - self.assertRaises(ValueError, P('a/b').with_stem, '/c') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/') - self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') - - def test_with_suffix_common(self): - P = self.cls - self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) - self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) - self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) - # Stripping suffix. - self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) - self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) - # 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') - # Invalid suffix. - self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') - self.assertRaises(ValueError, P('a/b').with_suffix, '/') - self.assertRaises(ValueError, P('a/b').with_suffix, '.') - self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') - self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') - self.assertRaises(ValueError, P('a/b').with_suffix, './.d') - self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') - - def test_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.relative_to) - self.assertRaises(TypeError, p.relative_to, b'a') - self.assertEqual(p.relative_to(P()), P('a/b')) - self.assertEqual(p.relative_to(''), P('a/b')) - self.assertEqual(p.relative_to(P('a')), P('b')) - self.assertEqual(p.relative_to('a'), P('b')) - self.assertEqual(p.relative_to('a/'), P('b')) - self.assertEqual(p.relative_to(P('a/b')), P()) - self.assertEqual(p.relative_to('a/b'), P()) - self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.relative_to('a', 'b') - p.relative_to('a', 'b', walk_up=True) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('c')) - self.assertRaises(ValueError, p.relative_to, P('a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('a/c')) - self.assertRaises(ValueError, p.relative_to, P('/a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - p = P('/a/b') - self.assertEqual(p.relative_to(P('/')), P('a/b')) - self.assertEqual(p.relative_to('/'), P('a/b')) - self.assertEqual(p.relative_to(P('/a')), P('b')) - self.assertEqual(p.relative_to('/a'), P('b')) - self.assertEqual(p.relative_to('/a/'), P('b')) - self.assertEqual(p.relative_to(P('/a/b')), P()) - self.assertEqual(p.relative_to('/a/b'), P()) - self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) - self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) - self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) - self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) - self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P()) - self.assertEqual(p.relative_to('/a/b', walk_up=True), P()) - self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) - self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) - self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) - self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) - self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) - self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) - # Unrelated paths. - self.assertRaises(ValueError, p.relative_to, P('/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) - self.assertRaises(ValueError, p.relative_to, P('/a/c')) - self.assertRaises(ValueError, p.relative_to, P()) - self.assertRaises(ValueError, p.relative_to, '') - self.assertRaises(ValueError, p.relative_to, P('a')) - self.assertRaises(ValueError, p.relative_to, P("../a")) - self.assertRaises(ValueError, p.relative_to, P("a/..")) - self.assertRaises(ValueError, p.relative_to, P("/a/..")) - self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) - self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) - - def test_is_relative_to_common(self): - P = self.cls - p = P('a/b') - self.assertRaises(TypeError, p.is_relative_to) - self.assertRaises(TypeError, p.is_relative_to, b'a') - self.assertTrue(p.is_relative_to(P())) - self.assertTrue(p.is_relative_to('')) - self.assertTrue(p.is_relative_to(P('a'))) - self.assertTrue(p.is_relative_to('a/')) - self.assertTrue(p.is_relative_to(P('a/b'))) - self.assertTrue(p.is_relative_to('a/b')) - # With several args. - with self.assertWarns(DeprecationWarning): - p.is_relative_to('a', 'b') - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('c'))) - self.assertFalse(p.is_relative_to(P('a/b/c'))) - self.assertFalse(p.is_relative_to(P('a/c'))) - self.assertFalse(p.is_relative_to(P('/a'))) - p = P('/a/b') - self.assertTrue(p.is_relative_to(P('/'))) - self.assertTrue(p.is_relative_to('/')) - self.assertTrue(p.is_relative_to(P('/a'))) - self.assertTrue(p.is_relative_to('/a')) - self.assertTrue(p.is_relative_to('/a/')) - self.assertTrue(p.is_relative_to(P('/a/b'))) - self.assertTrue(p.is_relative_to('/a/b')) - # Unrelated paths. - self.assertFalse(p.is_relative_to(P('/c'))) - self.assertFalse(p.is_relative_to(P('/a/b/c'))) - self.assertFalse(p.is_relative_to(P('/a/c'))) - self.assertFalse(p.is_relative_to(P())) - self.assertFalse(p.is_relative_to('')) - self.assertFalse(p.is_relative_to(P('a'))) - - -class PurePathTest(DummyPurePathTest): +class PurePathTest(test_pathlib_abc.DummyPurePathTest): cls = pathlib.PurePath def test_constructor_nested(self): @@ -1581,1246 +926,11 @@ class cls(pathlib.PurePath): test_repr_roundtrips = None -# -# Tests for the virtual classes. -# - -class PathBaseTest(PurePathBaseTest): - cls = pathlib._abc.PathBase - - def test_unsupported_operation(self): - P = self.cls - p = self.cls() - e = pathlib.UnsupportedOperation - self.assertRaises(e, p.stat) - self.assertRaises(e, p.lstat) - self.assertRaises(e, p.exists) - self.assertRaises(e, p.samefile, 'foo') - self.assertRaises(e, p.is_dir) - self.assertRaises(e, p.is_file) - self.assertRaises(e, p.is_mount) - self.assertRaises(e, p.is_symlink) - self.assertRaises(e, p.is_block_device) - self.assertRaises(e, p.is_char_device) - self.assertRaises(e, p.is_fifo) - self.assertRaises(e, p.is_socket) - self.assertRaises(e, p.open) - self.assertRaises(e, p.read_bytes) - self.assertRaises(e, p.read_text) - self.assertRaises(e, p.write_bytes, b'foo') - self.assertRaises(e, p.write_text, 'foo') - self.assertRaises(e, p.iterdir) - self.assertRaises(e, p.glob, '*') - self.assertRaises(e, p.rglob, '*') - self.assertRaises(e, lambda: list(p.walk())) - self.assertRaises(e, p.absolute) - self.assertRaises(e, P.cwd) - self.assertRaises(e, p.expanduser) - self.assertRaises(e, p.home) - self.assertRaises(e, p.readlink) - self.assertRaises(e, p.symlink_to, 'foo') - self.assertRaises(e, p.hardlink_to, 'foo') - self.assertRaises(e, p.mkdir) - self.assertRaises(e, p.touch) - self.assertRaises(e, p.rename, 'foo') - self.assertRaises(e, p.replace, 'foo') - self.assertRaises(e, p.chmod, 0o755) - self.assertRaises(e, p.lchmod, 0o755) - self.assertRaises(e, p.unlink) - self.assertRaises(e, p.rmdir) - self.assertRaises(e, p.owner) - self.assertRaises(e, p.group) - self.assertRaises(e, p.as_uri) - - def test_as_uri_common(self): - e = pathlib.UnsupportedOperation - self.assertRaises(e, self.cls().as_uri) - - def test_fspath_common(self): - self.assertRaises(TypeError, os.fspath, self.cls()) - - def test_as_bytes_common(self): - self.assertRaises(TypeError, bytes, self.cls()) - - def test_matches_path_api(self): - our_names = {name for name in dir(self.cls) if name[0] != '_'} - path_names = {name for name in dir(pathlib.Path) if name[0] != '_'} - self.assertEqual(our_names, path_names) - for attr_name in our_names: - our_attr = getattr(self.cls, attr_name) - path_attr = getattr(pathlib.Path, attr_name) - self.assertEqual(our_attr.__doc__, path_attr.__doc__) - - -class DummyPathIO(io.BytesIO): - """ - Used by DummyPath to implement `open('w')` - """ - - def __init__(self, files, path): - super().__init__() - self.files = files - self.path = path - - def close(self): - self.files[self.path] = self.getvalue() - super().close() - - -class DummyPath(pathlib._abc.PathBase): - """ - Simple implementation of PathBase that keeps files and directories in - memory. - """ - _files = {} - _directories = {} - _symlinks = {} - - def __eq__(self, other): - if not isinstance(other, DummyPath): - return NotImplemented - return str(self) == str(other) - - def __hash__(self): - return hash(str(self)) - - def stat(self, *, follow_symlinks=True): - if follow_symlinks: - path = str(self.resolve()) - else: - path = str(self.parent.resolve() / self.name) - if path in self._files: - st_mode = stat.S_IFREG - elif path in self._directories: - st_mode = stat.S_IFDIR - elif path in self._symlinks: - st_mode = stat.S_IFLNK - else: - raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) - return os.stat_result((st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)) - - def open(self, mode='r', buffering=-1, encoding=None, - errors=None, newline=None): - if buffering != -1: - raise NotImplementedError - path_obj = self.resolve() - path = str(path_obj) - name = path_obj.name - parent = str(path_obj.parent) - if path in self._directories: - raise IsADirectoryError(errno.EISDIR, "Is a directory", path) - - text = 'b' not in mode - mode = ''.join(c for c in mode if c not in 'btU') - if mode == 'r': - if path not in self._files: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - stream = io.BytesIO(self._files[path]) - elif mode == 'w': - if parent not in self._directories: - raise FileNotFoundError(errno.ENOENT, "File not found", parent) - stream = DummyPathIO(self._files, path) - self._files[path] = b'' - self._directories[parent].add(name) - else: - raise NotImplementedError - if text: - stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline) - return stream - - def iterdir(self): - path = str(self.resolve()) - if path in self._files: - raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path) - elif path in self._directories: - return (self / name for name in self._directories[path]) - else: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - - def mkdir(self, mode=0o777, parents=False, exist_ok=False): - path = str(self.resolve()) - if path in self._directories: - if exist_ok: - return - else: - raise FileExistsError(errno.EEXIST, "File exists", path) - try: - if self.name: - self._directories[str(self.parent)].add(self.name) - self._directories[path] = set() - except KeyError: - if not parents: - raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None - self.parent.mkdir(parents=True, exist_ok=True) - self.mkdir(mode, parents=False, exist_ok=exist_ok) - - -class DummyPathTest(DummyPurePathTest): - """Tests for PathBase methods that use stat(), open() and iterdir().""" - - cls = DummyPath - can_symlink = False - - # (BASE) - # | - # |-- brokenLink -> non-existing - # |-- dirA - # | `-- linkC -> ../dirB - # |-- dirB - # | |-- fileB - # | `-- linkD -> ../dirB - # |-- dirC - # | |-- dirD - # | | `-- fileD - # | `-- fileC - # | `-- novel.txt - # |-- dirE # No permissions - # |-- fileA - # |-- linkA -> fileA - # |-- linkB -> dirB - # `-- brokenLinkLoop -> brokenLinkLoop - # - - def setUp(self): - super().setUp() - pathmod = self.cls.pathmod - p = self.cls(BASE) - p.mkdir(parents=True) - p.joinpath('dirA').mkdir() - p.joinpath('dirB').mkdir() - p.joinpath('dirC').mkdir() - p.joinpath('dirC', 'dirD').mkdir() - p.joinpath('dirE').mkdir() - with p.joinpath('fileA').open('wb') as f: - f.write(b"this is file A\n") - with p.joinpath('dirB', 'fileB').open('wb') as f: - f.write(b"this is file B\n") - with p.joinpath('dirC', 'fileC').open('wb') as f: - f.write(b"this is file C\n") - with p.joinpath('dirC', 'novel.txt').open('wb') as f: - f.write(b"this is a novel\n") - with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f: - f.write(b"this is file D\n") - if self.can_symlink: - p.joinpath('linkA').symlink_to('fileA') - p.joinpath('brokenLink').symlink_to('non-existing') - p.joinpath('linkB').symlink_to('dirB') - p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB')) - p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB')) - p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop') - - def tearDown(self): - cls = self.cls - cls._files.clear() - cls._directories.clear() - cls._symlinks.clear() - - def tempdir(self): - path = self.cls(BASE).with_name('tmp-dirD') - path.mkdir() - return path - - def assertFileNotFound(self, func, *args, **kwargs): - with self.assertRaises(FileNotFoundError) as cm: - func(*args, **kwargs) - self.assertEqual(cm.exception.errno, errno.ENOENT) - - def assertEqualNormCase(self, path_a, path_b): - self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) - - def test_samefile(self): - fileA_path = os.path.join(BASE, 'fileA') - fileB_path = os.path.join(BASE, 'dirB', 'fileB') - p = self.cls(fileA_path) - pp = self.cls(fileA_path) - q = self.cls(fileB_path) - self.assertTrue(p.samefile(fileA_path)) - self.assertTrue(p.samefile(pp)) - self.assertFalse(p.samefile(fileB_path)) - self.assertFalse(p.samefile(q)) - # Test the non-existent file case - non_existent = os.path.join(BASE, 'foo') - r = self.cls(non_existent) - self.assertRaises(FileNotFoundError, p.samefile, r) - self.assertRaises(FileNotFoundError, p.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, p) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - self.assertRaises(FileNotFoundError, r.samefile, r) - self.assertRaises(FileNotFoundError, r.samefile, non_existent) - - def test_empty_path(self): - # The empty path points to '.' - p = self.cls('') - self.assertEqual(str(p), '.') - - def test_exists(self): - P = self.cls - p = P(BASE) - self.assertIs(True, p.exists()) - self.assertIs(True, (p / 'dirA').exists()) - self.assertIs(True, (p / 'fileA').exists()) - self.assertIs(False, (p / 'fileA' / 'bah').exists()) - if self.can_symlink: - self.assertIs(True, (p / 'linkA').exists()) - self.assertIs(True, (p / 'linkB').exists()) - self.assertIs(True, (p / 'linkB' / 'fileB').exists()) - self.assertIs(False, (p / 'linkA' / 'bah').exists()) - self.assertIs(False, (p / 'brokenLink').exists()) - self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) - self.assertIs(False, (p / 'foo').exists()) - self.assertIs(False, P('/xyzzy').exists()) - self.assertIs(False, P(BASE + '\udfff').exists()) - self.assertIs(False, P(BASE + '\x00').exists()) - - def test_open_common(self): - p = self.cls(BASE) - with (p / 'fileA').open('r') as f: - self.assertIsInstance(f, io.TextIOBase) - self.assertEqual(f.read(), "this is file A\n") - with (p / 'fileA').open('rb') as f: - self.assertIsInstance(f, io.BufferedIOBase) - self.assertEqual(f.read().strip(), b"this is file A") - - def test_read_write_bytes(self): - p = self.cls(BASE) - (p / 'fileA').write_bytes(b'abcdefg') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - # Check that trying to write str does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - - def test_read_write_text(self): - p = self.cls(BASE) - (p / 'fileA').write_text('äbcdefg', encoding='latin-1') - self.assertEqual((p / 'fileA').read_text( - encoding='utf-8', errors='ignore'), 'bcdefg') - # Check that trying to write bytes does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') - self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') - - def test_read_text_with_newlines(self): - p = self.cls(BASE) - # Check that `\n` character change nothing - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\n'), - 'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\r'), - 'abcde\r\nfghlk\n\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_text(newline='\r\n'), - 'abcde\r\nfghlk\n\rmnopq') - - def test_write_text_with_newlines(self): - p = self.cls(BASE) - # Check that `\n` character change nothing - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\rfghlk\r\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\r\nfghlk\r\n\rmnopq') - # Check that no argument passed will change `\n` to `os.linesep` - os_linesep_byte = bytes(os.linesep, encoding='ascii') - (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') - - def test_iterdir(self): - P = self.cls - p = P(BASE) - it = p.iterdir() - paths = set(it) - expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] - if self.can_symlink: - expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] - self.assertEqual(paths, { P(BASE, q) for q in expected }) - - 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(BASE, 'linkB') - paths = set(p.iterdir()) - expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } - self.assertEqual(paths, expected) - - def test_iterdir_nodir(self): - # __iter__ on something that is not a directory. - p = self.cls(BASE, 'fileA') - with self.assertRaises(OSError) as cm: - p.iterdir() - # ENOENT or EINVAL under Windows, ENOTDIR otherwise - # (see issue #12802). - self.assertIn(cm.exception.errno, (errno.ENOTDIR, - errno.ENOENT, errno.EINVAL)) - - def test_glob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - it = p.glob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.glob("fileB"), []) - _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) - if not self.can_symlink: - _check(p.glob("*A"), ['dirA', 'fileA']) - else: - _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) - if not self.can_symlink: - _check(p.glob("*B/*"), ['dirB/fileB']) - else: - _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', - 'linkB/fileB', 'linkB/linkD']) - if not self.can_symlink: - _check(p.glob("*/fileB"), ['dirB/fileB']) - else: - _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) - if self.can_symlink: - _check(p.glob("brokenLink"), ['brokenLink']) - - if not self.can_symlink: - _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) - else: - _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) - - def test_glob_empty_pattern(self): - p = self.cls() - with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): - list(p.glob('')) - - def test_glob_case_sensitive(self): - P = self.cls - def _check(path, pattern, case_sensitive, expected): - actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} - expected = {str(P(BASE, q)) for q in expected} - self.assertEqual(actual, expected) - path = P(BASE) - _check(path, "DIRB/FILE*", True, []) - _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) - _check(path, "dirb/file*", True, []) - _check(path, "dirb/file*", False, ["dirB/fileB"]) - - 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 "linkD" not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", []) - _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) - _check(p, "*A", ["dirA", "fileA", "linkA"]) - _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) - _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) - _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."]) - _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", - "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) - _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."]) - _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**/", ["dirC/dirD/"]) - - 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(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", []) - _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) - _check(p, "*A", ["dirA", "fileA", "linkA"]) - _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"]) - _check(p, "*/fileB", ["dirB/fileB"]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) - _check(p, "dir*/*/..", ["dirC/dirD/.."]) - _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) - _check(p, "dir*/*/**/", ["dirC/dirD/"]) - _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) - _check(p, "dir*/**/fileC", ["dirC/fileC"]) - _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) - _check(p, "*/dirD/**/", ["dirC/dirD/"]) - - def test_rglob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), {P(BASE, q) for q in expected}) - P = self.cls - p = P(BASE) - it = p.rglob("fileA") - self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.rglob("fileB"), ["dirB/fileB"]) - _check(p.rglob("**/fileB"), ["dirB/fileB"]) - _check(p.rglob("*/fileA"), []) - if not self.can_symlink: - _check(p.rglob("*/fileB"), ["dirB/fileB"]) - else: - _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", - "linkB/fileB", "dirA/linkC/fileB"]) - _check(p.rglob("file*"), ["fileA", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD"]) - if not self.can_symlink: - _check(p.rglob("*/"), [ - "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", - ]) - else: - _check(p.rglob("*/"), [ - "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", - "dirC/dirD/", "dirE/", "linkB/", - ]) - _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - - p = P(BASE, "dirC") - _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", - "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/"]) - _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) - _check(p.rglob("*/"), ["dirC/dirD/"]) - _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) - _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p.rglob("*.txt"), ["dirC/novel.txt"]) - _check(p.rglob("*.*"), ["dirC/novel.txt"]) - - 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 'linkD' not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) - _check(p, "*/fileA", []) - _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) - _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) - _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) - _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", - "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) - - p = P(BASE, "dirC") - _check(p, "*", ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD/"]) - _check(p, "", ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p, "*.txt", ["dirC/novel.txt"]) - _check(p, "*.*", ["dirC/novel.txt"]) - - 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(BASE, q) for q in expected }) - P = self.cls - p = P(BASE) - _check(p, "fileB", ["dirB/fileB"]) - _check(p, "*/fileA", []) - _check(p, "*/fileB", ["dirB/fileB"]) - _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ]) - _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) - _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - - p = P(BASE, "dirC") - _check(p, "*", ["dirC/fileC", "dirC/novel.txt", - "dirC/dirD", "dirC/dirD/fileD"]) - _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p, "*/*", ["dirC/dirD/fileD"]) - _check(p, "*/", ["dirC/dirD/"]) - _check(p, "", ["dirC/", "dirC/dirD/"]) - # gh-91616, a re module regression - _check(p, "*.txt", ["dirC/novel.txt"]) - _check(p, "*.*", ["dirC/novel.txt"]) - - 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(BASE) - given = set(p.rglob('*')) - expect = {'brokenLink', - 'dirA', 'dirA/linkC', - 'dirB', 'dirB/fileB', 'dirB/linkD', - 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', - 'dirC/fileC', 'dirC/novel.txt', - 'dirE', - 'fileA', - 'linkA', - 'linkB', - 'brokenLinkLoop', - } - self.assertEqual(given, {p / x for x in expect}) - - def test_glob_many_open_files(self): - depth = 30 - P = self.cls - p = base = P(BASE) / 'deep' - p.mkdir() - for _ in range(depth): - p /= 'd' - p.mkdir() - pattern = '/'.join(['*'] * depth) - iters = [base.glob(pattern) for j in range(100)] - for it in iters: - self.assertEqual(next(it), p) - iters = [base.rglob('d') for j in range(100)] - p = base - for i in range(depth): - p = p / 'd' - for it in iters: - self.assertEqual(next(it), p) - - def test_glob_dotdot(self): - # ".." is not special in globs. - P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) - self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) - self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) - self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) - self.assertEqual(set(p.glob("dirA/../file*/..")), set()) - self.assertEqual(set(p.glob("../xyzzy")), set()) - self.assertEqual(set(p.glob("xyzzy/..")), set()) - self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) - - def test_glob_permissions(self): - # See bpo-38894 - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls - base = P(BASE) / 'permissions' - base.mkdir() - - for i in range(100): - link = base / f"link{i}" - if i % 2: - link.symlink_to(P(BASE, "dirE", "nonexistent")) - else: - link.symlink_to(P(BASE, "dirC")) - - self.assertEqual(len(set(base.glob("*"))), 100) - self.assertEqual(len(set(base.glob("*/"))), 50) - self.assertEqual(len(set(base.glob("*/fileC"))), 50) - self.assertEqual(len(set(base.glob("*/file*"))), 50) - - def test_glob_long_symlink(self): - # See gh-87695 - if not self.can_symlink: - self.skipTest("symlinks required") - base = self.cls(BASE) / 'long_symlink' - base.mkdir() - bad_link = base / 'bad_link' - bad_link.symlink_to("bad" * 200) - self.assertEqual(sorted(base.glob('**/*')), [bad_link]) - - def test_glob_above_recursion_limit(self): - recursion_limit = 50 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.glob('**/')) - - def test_glob_recursive_no_trailing_slash(self): - P = self.cls - p = P(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_readlink(self): - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls(BASE) - self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) - self.assertEqual((P / 'brokenLink').readlink(), - self.cls('non-existing')) - self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) - self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB')) - with self.assertRaises(OSError): - (P / 'fileA').readlink() - - @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") - def test_readlink_unsupported(self): - P = self.cls(BASE) - p = P / 'fileA' - with self.assertRaises(pathlib.UnsupportedOperation): - q.readlink(p) - - def _check_resolve(self, p, expected, strict=True): - q = p.resolve(strict) - self.assertEqual(q, expected) - - # This can be used to check both relative and absolute resolutions. - _check_resolve_relative = _check_resolve_absolute = _check_resolve - - def test_resolve_common(self): - if not self.can_symlink: - self.skipTest("symlinks required") - P = self.cls - p = P(BASE, 'foo') - with self.assertRaises(OSError) as cm: - p.resolve(strict=True) - self.assertEqual(cm.exception.errno, errno.ENOENT) - # Non-strict - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo')) - p = P(BASE, 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo', 'in', 'spam')) - p = P(BASE, '..', 'foo', 'in', 'spam') - self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.abspath(os.path.join('foo', 'in', 'spam'))) - # These are all relative symlinks. - p = P(BASE, 'dirB', 'fileB') - self._check_resolve_relative(p, p) - p = P(BASE, 'linkA') - self._check_resolve_relative(p, P(BASE, 'fileA')) - p = P(BASE, 'dirA', 'linkC', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - p = P(BASE, 'dirB', 'linkD', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', - 'spam'), False) - p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', - 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - # Now create absolute symlinks. - d = self.tempdir() - P(BASE, 'dirA', 'linkX').symlink_to(d) - P(BASE, str(d), 'linkY').symlink_to(join('dirB')) - p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') - self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) - # Non-strict - p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), - False) - p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): - # In Windows, if linkY points to dirB, 'dirA\linkY\..' - # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) - else: - # In Posix, if linkY points to dirB, 'dirA/linkY/..' - # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) - - 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") - p = self.cls(BASE) - p.joinpath('0').symlink_to('.', target_is_directory=True) - p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) - p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) - q = p / '2' - self.assertEqual(q.resolve(strict=True), p) - r = q / '3' / '4' - self.assertRaises(FileNotFoundError, r.resolve, strict=True) - # Non-strict - self.assertEqual(r.resolve(strict=False), p / '3' / '4') - - def _check_symlink_loop(self, *args): - path = self.cls(*args) - with self.assertRaises(OSError) as cm: - path.resolve(strict=True) - self.assertEqual(cm.exception.errno, errno.ELOOP) - - def test_resolve_loop(self): - if not self.can_symlink: - self.skipTest("symlinks required") - if os.name == 'nt' and issubclass(self.cls, pathlib.Path): - self.skipTest("symlink loops work differently with concrete Windows paths") - # Loops with relative symlinks. - self.cls(BASE, 'linkX').symlink_to('linkX/inside') - self._check_symlink_loop(BASE, 'linkX') - self.cls(BASE, 'linkY').symlink_to('linkY') - self._check_symlink_loop(BASE, 'linkY') - self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') - self._check_symlink_loop(BASE, 'linkZ') - # Non-strict - p = self.cls(BASE, 'linkZ', 'foo') - self.assertEqual(p.resolve(strict=False), p) - # Loops with absolute symlinks. - self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) - self._check_symlink_loop(BASE, 'linkU') - self.cls(BASE, 'linkV').symlink_to(join('linkV')) - self._check_symlink_loop(BASE, 'linkV') - self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) - self._check_symlink_loop(BASE, 'linkW') - # Non-strict - q = self.cls(BASE, 'linkW', 'foo') - self.assertEqual(q.resolve(strict=False), q) - - def test_stat(self): - statA = self.cls(BASE).joinpath('fileA').stat() - statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() - statC = self.cls(BASE).joinpath('dirC').stat() - # st_mode: files are the same, directory differs. - self.assertIsInstance(statA.st_mode, int) - self.assertEqual(statA.st_mode, statB.st_mode) - self.assertNotEqual(statA.st_mode, statC.st_mode) - self.assertNotEqual(statB.st_mode, statC.st_mode) - # st_ino: all different, - self.assertIsInstance(statA.st_ino, int) - self.assertNotEqual(statA.st_ino, statB.st_ino) - self.assertNotEqual(statA.st_ino, statC.st_ino) - self.assertNotEqual(statB.st_ino, statC.st_ino) - # st_dev: all the same. - self.assertIsInstance(statA.st_dev, int) - self.assertEqual(statA.st_dev, statB.st_dev) - self.assertEqual(statA.st_dev, statC.st_dev) - # other attributes not used by pathlib. - - def test_stat_no_follow_symlinks(self): - if not self.can_symlink: - self.skipTest("symlinks required") - p = self.cls(BASE) / 'linkA' - st = p.stat() - self.assertNotEqual(st, p.stat(follow_symlinks=False)) - - def test_stat_no_follow_symlinks_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.stat(follow_symlinks=False)) - - def test_lstat(self): - if not self.can_symlink: - self.skipTest("symlinks required") - p = self.cls(BASE)/ 'linkA' - st = p.stat() - self.assertNotEqual(st, p.lstat()) - - def test_lstat_nosymlink(self): - p = self.cls(BASE) / 'fileA' - st = p.stat() - self.assertEqual(st, p.lstat()) - - def test_is_dir(self): - P = self.cls(BASE) - self.assertTrue((P / 'dirA').is_dir()) - self.assertFalse((P / 'fileA').is_dir()) - self.assertFalse((P / 'non-existing').is_dir()) - self.assertFalse((P / 'fileA' / 'bah').is_dir()) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_dir()) - self.assertTrue((P / 'linkB').is_dir()) - self.assertFalse((P/ 'brokenLink').is_dir()) - self.assertFalse((P / 'dirA\udfff').is_dir()) - self.assertFalse((P / 'dirA\x00').is_dir()) - - def test_is_dir_no_follow_symlinks(self): - P = self.cls(BASE) - self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) - self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) - self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) - - def test_is_file(self): - P = self.cls(BASE) - self.assertTrue((P / 'fileA').is_file()) - self.assertFalse((P / 'dirA').is_file()) - self.assertFalse((P / 'non-existing').is_file()) - self.assertFalse((P / 'fileA' / 'bah').is_file()) - if self.can_symlink: - self.assertTrue((P / 'linkA').is_file()) - self.assertFalse((P / 'linkB').is_file()) - self.assertFalse((P/ 'brokenLink').is_file()) - self.assertFalse((P / 'fileA\udfff').is_file()) - self.assertFalse((P / 'fileA\x00').is_file()) - - def test_is_file_no_follow_symlinks(self): - P = self.cls(BASE) - self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) - self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) - self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False)) - self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) - - def test_is_mount(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_mount()) - self.assertFalse((P / 'dirA').is_mount()) - self.assertFalse((P / 'non-existing').is_mount()) - self.assertFalse((P / 'fileA' / 'bah').is_mount()) - if self.can_symlink: - self.assertFalse((P / 'linkA').is_mount()) - - def test_is_symlink(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_symlink()) - self.assertFalse((P / 'dirA').is_symlink()) - self.assertFalse((P / 'non-existing').is_symlink()) - self.assertFalse((P / 'fileA' / 'bah').is_symlink()) - if self.can_symlink: - self.assertTrue((P / 'linkA').is_symlink()) - self.assertTrue((P / 'linkB').is_symlink()) - self.assertTrue((P/ 'brokenLink').is_symlink()) - self.assertIs((P / 'fileA\udfff').is_file(), False) - self.assertIs((P / 'fileA\x00').is_file(), False) - if self.can_symlink: - self.assertIs((P / 'linkA\udfff').is_file(), False) - self.assertIs((P / 'linkA\x00').is_file(), False) - - def test_is_junction_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_junction()) - self.assertFalse((P / 'dirA').is_junction()) - self.assertFalse((P / 'non-existing').is_junction()) - self.assertFalse((P / 'fileA' / 'bah').is_junction()) - self.assertFalse((P / 'fileA\udfff').is_junction()) - self.assertFalse((P / 'fileA\x00').is_junction()) - - def test_is_fifo_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_fifo()) - self.assertFalse((P / 'dirA').is_fifo()) - self.assertFalse((P / 'non-existing').is_fifo()) - self.assertFalse((P / 'fileA' / 'bah').is_fifo()) - self.assertIs((P / 'fileA\udfff').is_fifo(), False) - self.assertIs((P / 'fileA\x00').is_fifo(), False) - - def test_is_socket_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_socket()) - self.assertFalse((P / 'dirA').is_socket()) - self.assertFalse((P / 'non-existing').is_socket()) - self.assertFalse((P / 'fileA' / 'bah').is_socket()) - self.assertIs((P / 'fileA\udfff').is_socket(), False) - self.assertIs((P / 'fileA\x00').is_socket(), False) - - def test_is_block_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_block_device()) - self.assertFalse((P / 'dirA').is_block_device()) - self.assertFalse((P / 'non-existing').is_block_device()) - self.assertFalse((P / 'fileA' / 'bah').is_block_device()) - self.assertIs((P / 'fileA\udfff').is_block_device(), False) - self.assertIs((P / 'fileA\x00').is_block_device(), False) - - def test_is_char_device_false(self): - P = self.cls(BASE) - self.assertFalse((P / 'fileA').is_char_device()) - self.assertFalse((P / 'dirA').is_char_device()) - self.assertFalse((P / 'non-existing').is_char_device()) - self.assertFalse((P / 'fileA' / 'bah').is_char_device()) - self.assertIs((P / 'fileA\udfff').is_char_device(), False) - self.assertIs((P / 'fileA\x00').is_char_device(), False) - - def test_pickling_common(self): - p = self.cls(BASE, 'fileA') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertEqual(pp.stat(), p.stat()) - - def test_parts_interning(self): - P = self.cls - p = P('/usr/bin/foo') - q = P('/usr/local/bin') - # 'usr' - self.assertIs(p.parts[1], q.parts[1]) - # 'bin' - self.assertIs(p.parts[2], q.parts[3]) - - 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). - P = self.cls(BASE) - P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) - P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) - P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) - P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) - - # Resolve absolute paths. - p = (P / 'link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = (P / 'link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - - # Resolve relative paths. - try: - self.cls().absolute() - except pathlib.UnsupportedOperation: - return - old_path = os.getcwd() - os.chdir(BASE) - try: - p = self.cls('link0').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link1').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link2').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - p = self.cls('link3').resolve() - self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) - finally: - os.chdir(old_path) - - def test_complex_symlinks_absolute(self): - self._check_complex_symlinks(BASE) - - def test_complex_symlinks_relative(self): - self._check_complex_symlinks('.') - - def test_complex_symlinks_relative_dot_dot(self): - self._check_complex_symlinks(os.path.join('dirA', '..')) - - def setUpWalk(self): - # Build: - # TESTFN/ - # TEST1/ a file kid and two directory kids - # tmp1 - # SUB1/ a file kid and a directory kid - # tmp2 - # SUB11/ no kids - # SUB2/ a file kid and a dirsymlink kid - # tmp3 - # link/ a symlink to TEST2 - # broken_link - # broken_link2 - # TEST2/ - # tmp4 a lone file - self.walk_path = self.cls(BASE, "TEST1") - self.sub1_path = self.walk_path / "SUB1" - self.sub11_path = self.sub1_path / "SUB11" - self.sub2_path = self.walk_path / "SUB2" - tmp1_path = self.walk_path / "tmp1" - tmp2_path = self.sub1_path / "tmp2" - tmp3_path = self.sub2_path / "tmp3" - self.link_path = self.sub2_path / "link" - t2_path = self.cls(BASE, "TEST2") - tmp4_path = self.cls(BASE, "TEST2", "tmp4") - broken_link_path = self.sub2_path / "broken_link" - broken_link2_path = self.sub2_path / "broken_link2" - - self.sub11_path.mkdir(parents=True) - self.sub2_path.mkdir(parents=True) - t2_path.mkdir(parents=True) - - for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: - with path.open("w", encoding='utf-8') as f: - f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") - - if self.can_symlink: - self.link_path.symlink_to(t2_path) - broken_link_path.symlink_to('broken') - broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) - self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) - else: - self.sub2_tree = (self.sub2_path, [], ["tmp3"]) - - def test_walk_topdown(self): - self.setUpWalk() - walker = self.walk_path.walk() - entry = next(walker) - entry[1].sort() # Ensure we visit SUB1 before SUB2 - self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) - entry = next(walker) - self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) - entry = next(walker) - self.assertEqual(entry, (self.sub11_path, [], [])) - entry = next(walker) - entry[1].sort() - entry[2].sort() - self.assertEqual(entry, self.sub2_tree) - with self.assertRaises(StopIteration): - next(walker) - - def test_walk_prune(self): - self.setUpWalk() - # Prune the search. - all = [] - for root, dirs, files in self.walk_path.walk(): - all.append((root, dirs, files)) - if 'SUB1' in dirs: - # Note that this also mutates the dirs we appended to all! - dirs.remove('SUB1') - - self.assertEqual(len(all), 2) - self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) - - all[1][-1].sort() - all[1][1].sort() - self.assertEqual(all[1], self.sub2_tree) - - def test_walk_bottom_up(self): - self.setUpWalk() - seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False - for path, dirnames, filenames in self.walk_path.walk(top_down=False): - if path == self.walk_path: - self.assertFalse(seen_testfn) - self.assertTrue(seen_sub1) - self.assertTrue(seen_sub2) - self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) - self.assertEqual(filenames, ["tmp1"]) - seen_testfn = True - elif path == self.sub1_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub1) - self.assertTrue(seen_sub11) - self.assertEqual(dirnames, ["SUB11"]) - self.assertEqual(filenames, ["tmp2"]) - seen_sub1 = True - elif path == self.sub11_path: - self.assertFalse(seen_sub1) - self.assertFalse(seen_sub11) - self.assertEqual(dirnames, []) - self.assertEqual(filenames, []) - seen_sub11 = True - elif path == self.sub2_path: - self.assertFalse(seen_testfn) - self.assertFalse(seen_sub2) - self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) - self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) - seen_sub2 = True - else: - raise AssertionError(f"Unexpected path: {path}") - self.assertTrue(seen_testfn) - - 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: - if root == self.link_path: - self.assertEqual(dirs, []) - self.assertEqual(files, ["tmp4"]) - break - else: - self.fail("Didn't follow symlink with follow_symlinks=True") - - 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. - walk_it = self.walk_path.walk(follow_symlinks=False) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", files) - break - else: - self.fail("symlink not found") - - walk_it = self.walk_path.walk(follow_symlinks=True) - for root, dirs, files in walk_it: - if root == self.sub2_path: - self.assertIn("link", dirs) - break - else: - self.fail("symlink not found") - - def test_walk_above_recursion_limit(self): - recursion_limit = 40 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.walk()) - list(base.walk(top_down=False)) - -class DummyPathWithSymlinks(DummyPath): - def readlink(self): - path = str(self.parent.resolve() / self.name) - if path in self._symlinks: - return self.with_segments(self._symlinks[path]) - elif path in self._files or path in self._directories: - raise OSError(errno.EINVAL, "Not a symlink", path) - else: - raise FileNotFoundError(errno.ENOENT, "File not found", path) - - def symlink_to(self, target, target_is_directory=False): - self._directories[str(self.parent)].add(self.name) - self._symlinks[str(self)] = str(target) - - -class DummyPathWithSymlinksTest(DummyPathTest): - cls = DummyPathWithSymlinks - can_symlink = True - - # # Tests for the concrete classes. # -class PathTest(DummyPathTest, PurePathTest): +class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest): """Tests for the FS-accessing functionalities of the Path classes.""" cls = pathlib.Path can_symlink = os_helper.can_symlink() diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py new file mode 100644 index 00000000000000..61ed3cb6a4a7f8 --- /dev/null +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -0,0 +1,1918 @@ +import collections.abc +import io +import os +import errno +import pathlib +import pickle +import posixpath +import stat +import unittest + +from test.support import set_recursion_limit +from test.support.os_helper import TESTFN + + +class UnsupportedOperationTest(unittest.TestCase): + def test_is_notimplemented(self): + self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError)) + self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) + + +# Make sure any symbolic links in the base test path are resolved. +BASE = os.path.realpath(TESTFN) +join = lambda *x: os.path.join(BASE, *x) + +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') + + +# +# Tests for the pure classes. +# + + +class PurePathBaseTest(unittest.TestCase): + cls = pathlib._abc.PurePathBase + + def test_magic_methods(self): + P = self.cls + self.assertFalse(hasattr(P, '__fspath__')) + self.assertFalse(hasattr(P, '__bytes__')) + self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__hash__, object.__hash__) + self.assertIs(P.__eq__, object.__eq__) + self.assertIs(P.__lt__, object.__lt__) + self.assertIs(P.__le__, object.__le__) + self.assertIs(P.__gt__, object.__gt__) + self.assertIs(P.__ge__, object.__ge__) + + +class DummyPurePath(pathlib._abc.PurePathBase): + def __eq__(self, other): + if not isinstance(other, DummyPurePath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + +class DummyPurePathTest(unittest.TestCase): + cls = DummyPurePath + + # Keys are canonical paths, values are list of tuples of arguments + # supposed to produce equal paths. + equivalences = { + 'a/b': [ + ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'), + ('a/b/',), ('a//b',), ('a//b//',), + # Empty components get removed. + ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''), + ], + '/b/c/d': [ + ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'), + # Empty components get removed. + ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'), + ], + } + + def setUp(self): + p = self.cls('a') + self.pathmod = p.pathmod + self.sep = self.pathmod.sep + self.altsep = self.pathmod.altsep + + def test_constructor_common(self): + P = self.cls + p = P('a') + self.assertIsInstance(p, P) + P('a', 'b', 'c') + P('/a', 'b', 'c') + P('a/b/c') + P('/a/b/c') + + def test_concrete_class(self): + if self.cls is pathlib.PurePath: + expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath + else: + expected = self.cls + p = self.cls('a') + self.assertIs(type(p), expected) + + def test_different_pathmods_unequal(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + self.assertNotEqual(p, q) + + def test_different_pathmods_unordered(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + with self.assertRaises(TypeError): + p < q + with self.assertRaises(TypeError): + p <= q + with self.assertRaises(TypeError): + p > q + with self.assertRaises(TypeError): + p >= q + + 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 + # a pure str object. + class StrSubclass(str): + pass + P = self.cls + p = P(*(StrSubclass(x) for x in args)) + self.assertEqual(p, P(*args)) + for part in p.parts: + self.assertIs(type(part), str) + + def test_str_subclass_common(self): + self._check_str_subclass('') + self._check_str_subclass('.') + self._check_str_subclass('a') + self._check_str_subclass('a/b.txt') + self._check_str_subclass('/a/b.txt') + + def test_with_segments_common(self): + class P(self.cls): + def __init__(self, *pathsegments, session_id): + super().__init__(*pathsegments) + self.session_id = session_id + + def with_segments(self, *pathsegments): + return type(self)(*pathsegments, session_id=self.session_id) + p = P('foo', 'bar', session_id=42) + self.assertEqual(42, (p / 'foo').session_id) + self.assertEqual(42, ('foo' / p).session_id) + self.assertEqual(42, p.joinpath('foo').session_id) + self.assertEqual(42, p.with_name('foo').session_id) + self.assertEqual(42, p.with_stem('foo').session_id) + self.assertEqual(42, p.with_suffix('.foo').session_id) + self.assertEqual(42, p.with_segments('foo').session_id) + self.assertEqual(42, p.relative_to('foo').session_id) + self.assertEqual(42, p.parent.session_id) + for parent in p.parents: + self.assertEqual(42, parent.session_id) + + def _check_parse_path(self, raw_path, *expected): + sep = self.pathmod.sep + actual = self.cls._parse_path(raw_path.replace('/', sep)) + self.assertEqual(actual, expected) + if altsep := self.pathmod.altsep: + actual = self.cls._parse_path(raw_path.replace('/', altsep)) + self.assertEqual(actual, expected) + + def test_parse_path_common(self): + check = self._check_parse_path + sep = self.pathmod.sep + check('', '', '', []) + check('a', '', '', ['a']) + check('a/', '', '', ['a']) + check('a/b', '', '', ['a', 'b']) + check('a/b/', '', '', ['a', 'b']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b//c/d', '', '', ['a', 'b', 'c', 'd']) + check('a/b/c/d', '', '', ['a', 'b', 'c', 'd']) + check('.', '', '', []) + check('././b', '', '', ['b']) + check('a/./b', '', '', ['a', 'b']) + check('a/./.', '', '', ['a']) + check('/a/b', '', sep, ['a', 'b']) + + def test_join_common(self): + P = self.cls + p = P('a/b') + pp = p.joinpath('c') + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p.joinpath('c', 'd') + self.assertEqual(pp, P('a/b/c/d')) + pp = p.joinpath('/c') + self.assertEqual(pp, P('/c')) + + def test_div_common(self): + # Basically the same as joinpath(). + P = self.cls + p = P('a/b') + pp = p / 'c' + self.assertEqual(pp, P('a/b/c')) + self.assertIs(type(pp), type(p)) + pp = p / 'c/d' + self.assertEqual(pp, P('a/b/c/d')) + pp = p / 'c' / 'd' + self.assertEqual(pp, P('a/b/c/d')) + pp = 'c' / p / 'd' + self.assertEqual(pp, P('c/a/b/d')) + pp = p/ '/c' + self.assertEqual(pp, P('/c')) + + def _check_str(self, expected, args): + p = self.cls(*args) + self.assertEqual(str(p), expected.replace('/', self.sep)) + + def test_str_common(self): + # Canonicalized paths roundtrip. + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self._check_str(pathstr, (pathstr,)) + # Special case for the empty path. + self._check_str('.', ('',)) + # Other tests for str() are in test_equivalences(). + + def test_as_posix_common(self): + P = self.cls + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + self.assertEqual(P(pathstr).as_posix(), pathstr) + # Other tests for as_posix() are in test_equivalences(). + + def test_repr_common(self): + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + with self.subTest(pathstr=pathstr): + p = self.cls(pathstr) + clsname = p.__class__.__name__ + r = repr(p) + # The repr() is in the form ClassName("forward-slashes path"). + self.assertTrue(r.startswith(clsname + '('), r) + self.assertTrue(r.endswith(')'), r) + inner = r[len(clsname) + 1 : -1] + self.assertEqual(eval(inner), p.as_posix()) + + def test_eq_common(self): + P = self.cls + self.assertEqual(P('a/b'), P('a/b')) + self.assertEqual(P('a/b'), P('a', 'b')) + self.assertNotEqual(P('a/b'), P('a')) + self.assertNotEqual(P('a/b'), P('/a/b')) + self.assertNotEqual(P('a/b'), P()) + self.assertNotEqual(P('/a/b'), P('/')) + self.assertNotEqual(P(), P('/')) + self.assertNotEqual(P(), "") + self.assertNotEqual(P(), {}) + self.assertNotEqual(P(), int) + + def test_match_common(self): + P = self.cls + self.assertRaises(ValueError, P('a').match, '') + self.assertRaises(ValueError, P('a').match, '.') + # Simple relative pattern. + self.assertTrue(P('b.py').match('b.py')) + self.assertTrue(P('a/b.py').match('b.py')) + self.assertTrue(P('/a/b.py').match('b.py')) + self.assertFalse(P('a.py').match('b.py')) + self.assertFalse(P('b/py').match('b.py')) + self.assertFalse(P('/a.py').match('b.py')) + self.assertFalse(P('b.py/c').match('b.py')) + # Wildcard relative pattern. + self.assertTrue(P('b.py').match('*.py')) + self.assertTrue(P('a/b.py').match('*.py')) + self.assertTrue(P('/a/b.py').match('*.py')) + self.assertFalse(P('b.pyc').match('*.py')) + self.assertFalse(P('b./py').match('*.py')) + self.assertFalse(P('b.py/c').match('*.py')) + # Multi-part relative pattern. + self.assertTrue(P('ab/c.py').match('a*/*.py')) + self.assertTrue(P('/d/ab/c.py').match('a*/*.py')) + self.assertFalse(P('a.py').match('a*/*.py')) + self.assertFalse(P('/dab/c.py').match('a*/*.py')) + self.assertFalse(P('ab/c.py/d').match('a*/*.py')) + # Absolute pattern. + self.assertTrue(P('/b.py').match('/*.py')) + self.assertFalse(P('b.py').match('/*.py')) + self.assertFalse(P('a/b.py').match('/*.py')) + self.assertFalse(P('/a/b.py').match('/*.py')) + # Multi-part absolute pattern. + self.assertTrue(P('/a/b.py').match('/a/*.py')) + 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('**/')) + 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.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)) + self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) + 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('**/*')) + + def test_parts_common(self): + # `parts` returns a tuple. + sep = self.sep + P = self.cls + p = P('a/b') + parts = p.parts + self.assertEqual(parts, ('a', 'b')) + # When the path is absolute, the anchor is a separate part. + p = P('/a/b') + parts = p.parts + self.assertEqual(parts, (sep, 'a', 'b')) + + def test_equivalences(self): + for k, tuples in self.equivalences.items(): + canon = k.replace('/', self.sep) + posix = k.replace(self.sep, '/') + if canon != posix: + tuples = tuples + [ + tuple(part.replace('/', self.sep) for part in t) + for t in tuples + ] + tuples.append((posix, )) + pcanon = self.cls(canon) + for t in tuples: + p = self.cls(*t) + self.assertEqual(p, pcanon, "failed with args {}".format(t)) + self.assertEqual(hash(p), hash(pcanon)) + self.assertEqual(str(p), canon) + self.assertEqual(p.as_posix(), posix) + + def test_parent_common(self): + # Relative + P = self.cls + p = P('a/b/c') + self.assertEqual(p.parent, P('a/b')) + self.assertEqual(p.parent.parent, P('a')) + self.assertEqual(p.parent.parent.parent, P()) + self.assertEqual(p.parent.parent.parent.parent, P()) + # Anchored + p = P('/a/b/c') + self.assertEqual(p.parent, P('/a/b')) + self.assertEqual(p.parent.parent, P('/a')) + self.assertEqual(p.parent.parent.parent, P('/')) + self.assertEqual(p.parent.parent.parent.parent, P('/')) + + def test_parents_common(self): + # Relative + P = self.cls + p = P('a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('a/b')) + self.assertEqual(par[1], P('a')) + self.assertEqual(par[2], P('.')) + self.assertEqual(par[-1], P('.')) + self.assertEqual(par[-2], P('a')) + self.assertEqual(par[-3], P('a/b')) + self.assertEqual(par[0:1], (P('a/b'),)) + self.assertEqual(par[:2], (P('a/b'), P('a'))) + self.assertEqual(par[:-1], (P('a/b'), P('a'))) + self.assertEqual(par[1:], (P('a'), P('.'))) + self.assertEqual(par[::2], (P('a/b'), P('.'))) + self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) + self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + with self.assertRaises(TypeError): + par[0] = p + # Anchored + p = P('/a/b/c') + par = p.parents + self.assertEqual(len(par), 3) + self.assertEqual(par[0], P('/a/b')) + self.assertEqual(par[1], P('/a')) + self.assertEqual(par[2], P('/')) + self.assertEqual(par[-1], P('/')) + self.assertEqual(par[-2], P('/a')) + self.assertEqual(par[-3], P('/a/b')) + self.assertEqual(par[0:1], (P('/a/b'),)) + self.assertEqual(par[:2], (P('/a/b'), P('/a'))) + self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) + self.assertEqual(par[1:], (P('/a'), P('/'))) + self.assertEqual(par[::2], (P('/a/b'), P('/'))) + self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) + self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) + with self.assertRaises(IndexError): + par[-4] + with self.assertRaises(IndexError): + par[3] + + def test_drive_common(self): + P = self.cls + self.assertEqual(P('a/b').drive, '') + self.assertEqual(P('/a/b').drive, '') + self.assertEqual(P('').drive, '') + + def test_root_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').root, '') + self.assertEqual(P('a/b').root, '') + self.assertEqual(P('/').root, sep) + self.assertEqual(P('/a/b').root, sep) + + def test_anchor_common(self): + P = self.cls + sep = self.sep + self.assertEqual(P('').anchor, '') + self.assertEqual(P('a/b').anchor, '') + self.assertEqual(P('/').anchor, sep) + self.assertEqual(P('/a/b').anchor, sep) + + def test_name_common(self): + P = self.cls + self.assertEqual(P('').name, '') + self.assertEqual(P('.').name, '') + self.assertEqual(P('/').name, '') + self.assertEqual(P('a/b').name, 'b') + self.assertEqual(P('/a/b').name, 'b') + self.assertEqual(P('/a/b/.').name, 'b') + self.assertEqual(P('a/b.py').name, 'b.py') + self.assertEqual(P('/a/b.py').name, 'b.py') + + def test_suffix_common(self): + P = self.cls + self.assertEqual(P('').suffix, '') + self.assertEqual(P('.').suffix, '') + self.assertEqual(P('..').suffix, '') + self.assertEqual(P('/').suffix, '') + self.assertEqual(P('a/b').suffix, '') + self.assertEqual(P('/a/b').suffix, '') + self.assertEqual(P('/a/b/.').suffix, '') + self.assertEqual(P('a/b.py').suffix, '.py') + self.assertEqual(P('/a/b.py').suffix, '.py') + self.assertEqual(P('a/.hgrc').suffix, '') + self.assertEqual(P('/a/.hgrc').suffix, '') + self.assertEqual(P('a/.hg.rc').suffix, '.rc') + self.assertEqual(P('/a/.hg.rc').suffix, '.rc') + self.assertEqual(P('a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('/a/b.tar.gz').suffix, '.gz') + self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '') + self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '') + + def test_suffixes_common(self): + P = self.cls + self.assertEqual(P('').suffixes, []) + self.assertEqual(P('.').suffixes, []) + self.assertEqual(P('/').suffixes, []) + self.assertEqual(P('a/b').suffixes, []) + self.assertEqual(P('/a/b').suffixes, []) + self.assertEqual(P('/a/b/.').suffixes, []) + self.assertEqual(P('a/b.py').suffixes, ['.py']) + self.assertEqual(P('/a/b.py').suffixes, ['.py']) + self.assertEqual(P('a/.hgrc').suffixes, []) + self.assertEqual(P('/a/.hgrc').suffixes, []) + self.assertEqual(P('a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc']) + self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz']) + self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, []) + self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, []) + + def test_stem_common(self): + P = self.cls + self.assertEqual(P('').stem, '') + self.assertEqual(P('.').stem, '') + self.assertEqual(P('..').stem, '..') + self.assertEqual(P('/').stem, '') + self.assertEqual(P('a/b').stem, 'b') + self.assertEqual(P('a/b.py').stem, 'b') + self.assertEqual(P('a/.hgrc').stem, '.hgrc') + self.assertEqual(P('a/.hg.rc').stem, '.hg') + self.assertEqual(P('a/b.tar.gz').stem, 'b.tar') + self.assertEqual(P('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')) + self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml')) + self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml')) + self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml')) + 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')) + self.assertRaises(ValueError, P('').with_name, 'd.xml') + self.assertRaises(ValueError, P('.').with_name, 'd.xml') + self.assertRaises(ValueError, P('/').with_name, 'd.xml') + self.assertRaises(ValueError, P('a/b').with_name, '') + self.assertRaises(ValueError, P('a/b').with_name, '.') + self.assertRaises(ValueError, P('a/b').with_name, '/c') + self.assertRaises(ValueError, P('a/b').with_name, 'c/') + self.assertRaises(ValueError, P('a/b').with_name, 'c/d') + + def test_with_stem_common(self): + P = self.cls + self.assertEqual(P('a/b').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/b').with_stem('d'), P('/a/d')) + self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py')) + self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py')) + self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz')) + self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d')) + self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d')) + self.assertRaises(ValueError, P('').with_stem, 'd') + self.assertRaises(ValueError, P('.').with_stem, 'd') + self.assertRaises(ValueError, P('/').with_stem, 'd') + self.assertRaises(ValueError, P('a/b').with_stem, '') + self.assertRaises(ValueError, P('a/b').with_stem, '.') + self.assertRaises(ValueError, P('a/b').with_stem, '/c') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/') + self.assertRaises(ValueError, P('a/b').with_stem, 'c/d') + + def test_with_suffix_common(self): + P = self.cls + self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz')) + self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz')) + self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz')) + # Stripping suffix. + self.assertEqual(P('a/b.py').with_suffix(''), P('a/b')) + self.assertEqual(P('/a/b').with_suffix(''), P('/a/b')) + # 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') + # Invalid suffix. + self.assertRaises(ValueError, P('a/b').with_suffix, 'gz') + self.assertRaises(ValueError, P('a/b').with_suffix, '/') + self.assertRaises(ValueError, P('a/b').with_suffix, '.') + self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz') + self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d') + self.assertRaises(ValueError, P('a/b').with_suffix, './.d') + self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.') + + def test_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.relative_to) + self.assertRaises(TypeError, p.relative_to, b'a') + self.assertEqual(p.relative_to(P()), P('a/b')) + self.assertEqual(p.relative_to(''), P('a/b')) + self.assertEqual(p.relative_to(P('a')), P('b')) + self.assertEqual(p.relative_to('a'), P('b')) + self.assertEqual(p.relative_to('a/'), P('b')) + self.assertEqual(p.relative_to(P('a/b')), P()) + self.assertEqual(p.relative_to('a/b'), P()) + self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P()) + self.assertEqual(p.relative_to('a/b', walk_up=True), P()) + self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b')) + # With several args. + with self.assertWarns(DeprecationWarning): + p.relative_to('a', 'b') + p.relative_to('a', 'b', walk_up=True) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('c')) + self.assertRaises(ValueError, p.relative_to, P('a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('a/c')) + self.assertRaises(ValueError, p.relative_to, P('/a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + p = P('/a/b') + self.assertEqual(p.relative_to(P('/')), P('a/b')) + self.assertEqual(p.relative_to('/'), P('a/b')) + self.assertEqual(p.relative_to(P('/a')), P('b')) + self.assertEqual(p.relative_to('/a'), P('b')) + self.assertEqual(p.relative_to('/a/'), P('b')) + self.assertEqual(p.relative_to(P('/a/b')), P()) + self.assertEqual(p.relative_to('/a/b'), P()) + self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b')) + self.assertEqual(p.relative_to('/', walk_up=True), P('a/b')) + self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a', walk_up=True), P('b')) + self.assertEqual(p.relative_to('/a/', walk_up=True), P('b')) + self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P()) + self.assertEqual(p.relative_to('/a/b', walk_up=True), P()) + self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b')) + self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b')) + self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..')) + self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..')) + self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b')) + self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b')) + # Unrelated paths. + self.assertRaises(ValueError, p.relative_to, P('/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/b/c')) + self.assertRaises(ValueError, p.relative_to, P('/a/c')) + self.assertRaises(ValueError, p.relative_to, P()) + self.assertRaises(ValueError, p.relative_to, '') + self.assertRaises(ValueError, p.relative_to, P('a')) + self.assertRaises(ValueError, p.relative_to, P("../a")) + self.assertRaises(ValueError, p.relative_to, P("a/..")) + self.assertRaises(ValueError, p.relative_to, P("/a/..")) + self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True) + self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True) + + def test_is_relative_to_common(self): + P = self.cls + p = P('a/b') + self.assertRaises(TypeError, p.is_relative_to) + self.assertRaises(TypeError, p.is_relative_to, b'a') + self.assertTrue(p.is_relative_to(P())) + self.assertTrue(p.is_relative_to('')) + self.assertTrue(p.is_relative_to(P('a'))) + self.assertTrue(p.is_relative_to('a/')) + self.assertTrue(p.is_relative_to(P('a/b'))) + self.assertTrue(p.is_relative_to('a/b')) + # With several args. + with self.assertWarns(DeprecationWarning): + p.is_relative_to('a', 'b') + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('c'))) + self.assertFalse(p.is_relative_to(P('a/b/c'))) + self.assertFalse(p.is_relative_to(P('a/c'))) + self.assertFalse(p.is_relative_to(P('/a'))) + p = P('/a/b') + self.assertTrue(p.is_relative_to(P('/'))) + self.assertTrue(p.is_relative_to('/')) + self.assertTrue(p.is_relative_to(P('/a'))) + self.assertTrue(p.is_relative_to('/a')) + self.assertTrue(p.is_relative_to('/a/')) + self.assertTrue(p.is_relative_to(P('/a/b'))) + self.assertTrue(p.is_relative_to('/a/b')) + # Unrelated paths. + self.assertFalse(p.is_relative_to(P('/c'))) + self.assertFalse(p.is_relative_to(P('/a/b/c'))) + self.assertFalse(p.is_relative_to(P('/a/c'))) + self.assertFalse(p.is_relative_to(P())) + self.assertFalse(p.is_relative_to('')) + self.assertFalse(p.is_relative_to(P('a'))) + + +# +# Tests for the virtual classes. +# + +class PathBaseTest(PurePathBaseTest): + cls = pathlib._abc.PathBase + + def test_unsupported_operation(self): + P = self.cls + p = self.cls() + e = pathlib.UnsupportedOperation + self.assertRaises(e, p.stat) + self.assertRaises(e, p.lstat) + self.assertRaises(e, p.exists) + self.assertRaises(e, p.samefile, 'foo') + self.assertRaises(e, p.is_dir) + self.assertRaises(e, p.is_file) + self.assertRaises(e, p.is_mount) + self.assertRaises(e, p.is_symlink) + self.assertRaises(e, p.is_block_device) + self.assertRaises(e, p.is_char_device) + self.assertRaises(e, p.is_fifo) + self.assertRaises(e, p.is_socket) + self.assertRaises(e, p.open) + self.assertRaises(e, p.read_bytes) + self.assertRaises(e, p.read_text) + self.assertRaises(e, p.write_bytes, b'foo') + self.assertRaises(e, p.write_text, 'foo') + self.assertRaises(e, p.iterdir) + self.assertRaises(e, p.glob, '*') + self.assertRaises(e, p.rglob, '*') + self.assertRaises(e, lambda: list(p.walk())) + self.assertRaises(e, p.absolute) + self.assertRaises(e, P.cwd) + self.assertRaises(e, p.expanduser) + self.assertRaises(e, p.home) + self.assertRaises(e, p.readlink) + self.assertRaises(e, p.symlink_to, 'foo') + self.assertRaises(e, p.hardlink_to, 'foo') + self.assertRaises(e, p.mkdir) + self.assertRaises(e, p.touch) + self.assertRaises(e, p.rename, 'foo') + self.assertRaises(e, p.replace, 'foo') + self.assertRaises(e, p.chmod, 0o755) + self.assertRaises(e, p.lchmod, 0o755) + self.assertRaises(e, p.unlink) + self.assertRaises(e, p.rmdir) + self.assertRaises(e, p.owner) + self.assertRaises(e, p.group) + self.assertRaises(e, p.as_uri) + + def test_as_uri_common(self): + e = pathlib.UnsupportedOperation + self.assertRaises(e, self.cls().as_uri) + + def test_fspath_common(self): + self.assertRaises(TypeError, os.fspath, self.cls()) + + def test_as_bytes_common(self): + self.assertRaises(TypeError, bytes, self.cls()) + + def test_matches_path_api(self): + our_names = {name for name in dir(self.cls) if name[0] != '_'} + path_names = {name for name in dir(pathlib.Path) if name[0] != '_'} + self.assertEqual(our_names, path_names) + for attr_name in our_names: + our_attr = getattr(self.cls, attr_name) + path_attr = getattr(pathlib.Path, attr_name) + self.assertEqual(our_attr.__doc__, path_attr.__doc__) + + +class DummyPathIO(io.BytesIO): + """ + Used by DummyPath to implement `open('w')` + """ + + def __init__(self, files, path): + super().__init__() + self.files = files + self.path = path + + def close(self): + self.files[self.path] = self.getvalue() + super().close() + + +class DummyPath(pathlib._abc.PathBase): + """ + Simple implementation of PathBase that keeps files and directories in + memory. + """ + _files = {} + _directories = {} + _symlinks = {} + + def __eq__(self, other): + if not isinstance(other, DummyPath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + def stat(self, *, follow_symlinks=True): + if follow_symlinks: + path = str(self.resolve()) + else: + path = str(self.parent.resolve() / self.name) + if path in self._files: + st_mode = stat.S_IFREG + elif path in self._directories: + st_mode = stat.S_IFDIR + elif path in self._symlinks: + st_mode = stat.S_IFLNK + else: + raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) + return os.stat_result((st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)) + + def open(self, mode='r', buffering=-1, encoding=None, + errors=None, newline=None): + if buffering != -1: + raise NotImplementedError + path_obj = self.resolve() + path = str(path_obj) + name = path_obj.name + parent = str(path_obj.parent) + if path in self._directories: + raise IsADirectoryError(errno.EISDIR, "Is a directory", path) + + text = 'b' not in mode + mode = ''.join(c for c in mode if c not in 'btU') + if mode == 'r': + if path not in self._files: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + stream = io.BytesIO(self._files[path]) + elif mode == 'w': + if parent not in self._directories: + raise FileNotFoundError(errno.ENOENT, "File not found", parent) + stream = DummyPathIO(self._files, path) + self._files[path] = b'' + self._directories[parent].add(name) + else: + raise NotImplementedError + if text: + stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline) + return stream + + def iterdir(self): + path = str(self.resolve()) + if path in self._files: + raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path) + elif path in self._directories: + return (self / name for name in self._directories[path]) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def mkdir(self, mode=0o777, parents=False, exist_ok=False): + path = str(self.resolve()) + if path in self._directories: + if exist_ok: + return + else: + raise FileExistsError(errno.EEXIST, "File exists", path) + try: + if self.name: + self._directories[str(self.parent)].add(self.name) + self._directories[path] = set() + except KeyError: + if not parents: + raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(mode, parents=False, exist_ok=exist_ok) + + +class DummyPathTest(DummyPurePathTest): + """Tests for PathBase methods that use stat(), open() and iterdir().""" + + cls = DummyPath + can_symlink = False + + # (BASE) + # | + # |-- brokenLink -> non-existing + # |-- dirA + # | `-- linkC -> ../dirB + # |-- dirB + # | |-- fileB + # | `-- linkD -> ../dirB + # |-- dirC + # | |-- dirD + # | | `-- fileD + # | `-- fileC + # | `-- novel.txt + # |-- dirE # No permissions + # |-- fileA + # |-- linkA -> fileA + # |-- linkB -> dirB + # `-- brokenLinkLoop -> brokenLinkLoop + # + + def setUp(self): + super().setUp() + pathmod = self.cls.pathmod + p = self.cls(BASE) + p.mkdir(parents=True) + p.joinpath('dirA').mkdir() + p.joinpath('dirB').mkdir() + p.joinpath('dirC').mkdir() + p.joinpath('dirC', 'dirD').mkdir() + p.joinpath('dirE').mkdir() + with p.joinpath('fileA').open('wb') as f: + f.write(b"this is file A\n") + with p.joinpath('dirB', 'fileB').open('wb') as f: + f.write(b"this is file B\n") + with p.joinpath('dirC', 'fileC').open('wb') as f: + f.write(b"this is file C\n") + with p.joinpath('dirC', 'novel.txt').open('wb') as f: + f.write(b"this is a novel\n") + with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f: + f.write(b"this is file D\n") + if self.can_symlink: + p.joinpath('linkA').symlink_to('fileA') + p.joinpath('brokenLink').symlink_to('non-existing') + p.joinpath('linkB').symlink_to('dirB') + p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB')) + p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB')) + p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop') + + def tearDown(self): + cls = self.cls + cls._files.clear() + cls._directories.clear() + cls._symlinks.clear() + + def tempdir(self): + path = self.cls(BASE).with_name('tmp-dirD') + path.mkdir() + return path + + def assertFileNotFound(self, func, *args, **kwargs): + with self.assertRaises(FileNotFoundError) as cm: + func(*args, **kwargs) + self.assertEqual(cm.exception.errno, errno.ENOENT) + + def assertEqualNormCase(self, path_a, path_b): + self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) + + def test_samefile(self): + fileA_path = os.path.join(BASE, 'fileA') + fileB_path = os.path.join(BASE, 'dirB', 'fileB') + p = self.cls(fileA_path) + pp = self.cls(fileA_path) + q = self.cls(fileB_path) + self.assertTrue(p.samefile(fileA_path)) + self.assertTrue(p.samefile(pp)) + self.assertFalse(p.samefile(fileB_path)) + self.assertFalse(p.samefile(q)) + # Test the non-existent file case + non_existent = os.path.join(BASE, 'foo') + r = self.cls(non_existent) + self.assertRaises(FileNotFoundError, p.samefile, r) + self.assertRaises(FileNotFoundError, p.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, p) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + self.assertRaises(FileNotFoundError, r.samefile, r) + self.assertRaises(FileNotFoundError, r.samefile, non_existent) + + def test_empty_path(self): + # The empty path points to '.' + p = self.cls('') + self.assertEqual(str(p), '.') + + def test_exists(self): + P = self.cls + p = P(BASE) + self.assertIs(True, p.exists()) + self.assertIs(True, (p / 'dirA').exists()) + self.assertIs(True, (p / 'fileA').exists()) + self.assertIs(False, (p / 'fileA' / 'bah').exists()) + if self.can_symlink: + self.assertIs(True, (p / 'linkA').exists()) + self.assertIs(True, (p / 'linkB').exists()) + self.assertIs(True, (p / 'linkB' / 'fileB').exists()) + self.assertIs(False, (p / 'linkA' / 'bah').exists()) + self.assertIs(False, (p / 'brokenLink').exists()) + self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) + self.assertIs(False, (p / 'foo').exists()) + self.assertIs(False, P('/xyzzy').exists()) + self.assertIs(False, P(BASE + '\udfff').exists()) + self.assertIs(False, P(BASE + '\x00').exists()) + + def test_open_common(self): + p = self.cls(BASE) + with (p / 'fileA').open('r') as f: + self.assertIsInstance(f, io.TextIOBase) + self.assertEqual(f.read(), "this is file A\n") + with (p / 'fileA').open('rb') as f: + self.assertIsInstance(f, io.BufferedIOBase) + self.assertEqual(f.read().strip(), b"this is file A") + + def test_read_write_bytes(self): + p = self.cls(BASE) + (p / 'fileA').write_bytes(b'abcdefg') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + # Check that trying to write str does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') + self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') + + def test_read_write_text(self): + p = self.cls(BASE) + (p / 'fileA').write_text('äbcdefg', encoding='latin-1') + self.assertEqual((p / 'fileA').read_text( + encoding='utf-8', errors='ignore'), 'bcdefg') + # Check that trying to write bytes does not truncate the file. + self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') + self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') + + def test_read_text_with_newlines(self): + p = self.cls(BASE) + # Check that `\n` character change nothing + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\n'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r'), + 'abcde\r\nfghlk\n\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_text(newline='\r\n'), + 'abcde\r\nfghlk\n\rmnopq') + + def test_write_text_with_newlines(self): + p = self.cls(BASE) + # Check that `\n` character change nothing + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\nfghlk\n\rmnopq') + # Check that `\r` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\rfghlk\r\rmnopq') + # Check that `\r\n` character replaces `\n` + (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde\r\r\nfghlk\r\n\rmnopq') + # Check that no argument passed will change `\n` to `os.linesep` + os_linesep_byte = bytes(os.linesep, encoding='ascii') + (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') + self.assertEqual((p / 'fileA').read_bytes(), + b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') + + def test_iterdir(self): + P = self.cls + p = P(BASE) + it = p.iterdir() + paths = set(it) + expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] + if self.can_symlink: + expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] + self.assertEqual(paths, { P(BASE, q) for q in expected }) + + 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(BASE, 'linkB') + paths = set(p.iterdir()) + expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } + self.assertEqual(paths, expected) + + def test_iterdir_nodir(self): + # __iter__ on something that is not a directory. + p = self.cls(BASE, 'fileA') + with self.assertRaises(OSError) as cm: + p.iterdir() + # ENOENT or EINVAL under Windows, ENOTDIR otherwise + # (see issue #12802). + self.assertIn(cm.exception.errno, (errno.ENOTDIR, + errno.ENOENT, errno.EINVAL)) + + def test_glob_common(self): + def _check(glob, expected): + self.assertEqual(set(glob), { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + it = p.glob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(it, ["fileA"]) + _check(p.glob("fileB"), []) + _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"]) + if not self.can_symlink: + _check(p.glob("*A"), ['dirA', 'fileA']) + else: + _check(p.glob("*A"), ['dirA', 'fileA', 'linkA']) + if not self.can_symlink: + _check(p.glob("*B/*"), ['dirB/fileB']) + else: + _check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD', + 'linkB/fileB', 'linkB/linkD']) + if not self.can_symlink: + _check(p.glob("*/fileB"), ['dirB/fileB']) + else: + _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB']) + if self.can_symlink: + _check(p.glob("brokenLink"), ['brokenLink']) + + if not self.can_symlink: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"]) + else: + _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + + def test_glob_empty_pattern(self): + p = self.cls() + with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'): + list(p.glob('')) + + def test_glob_case_sensitive(self): + P = self.cls + def _check(path, pattern, case_sensitive, expected): + actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} + expected = {str(P(BASE, q)) for q in expected} + self.assertEqual(actual, expected) + path = P(BASE) + _check(path, "DIRB/FILE*", True, []) + _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) + _check(path, "dirb/file*", True, []) + _check(path, "dirb/file*", False, ["dirB/fileB"]) + + 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 "linkD" not in path.parent.parts} # exclude symlink loop. + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", []) + _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) + _check(p, "*A", ["dirA", "fileA", "linkA"]) + _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"]) + _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"]) + _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."]) + _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..", + "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"]) + _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."]) + _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) + + 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(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", []) + _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) + _check(p, "*A", ["dirA", "fileA", "linkA"]) + _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"]) + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"]) + _check(p, "dir*/*/..", ["dirC/dirD/.."]) + _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."]) + _check(p, "dir*/*/**/", ["dirC/dirD/"]) + _check(p, "dir*/*/**/..", ["dirC/dirD/.."]) + _check(p, "dir*/**/fileC", ["dirC/fileC"]) + _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"]) + _check(p, "*/dirD/**/", ["dirC/dirD/"]) + + def test_rglob_common(self): + def _check(glob, expected): + self.assertEqual(set(glob), {P(BASE, q) for q in expected}) + P = self.cls + p = P(BASE) + it = p.rglob("fileA") + self.assertIsInstance(it, collections.abc.Iterator) + _check(it, ["fileA"]) + _check(p.rglob("fileB"), ["dirB/fileB"]) + _check(p.rglob("**/fileB"), ["dirB/fileB"]) + _check(p.rglob("*/fileA"), []) + if not self.can_symlink: + _check(p.rglob("*/fileB"), ["dirB/fileB"]) + else: + _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", + "linkB/fileB", "dirA/linkC/fileB"]) + _check(p.rglob("file*"), ["fileA", "dirB/fileB", + "dirC/fileC", "dirC/dirD/fileD"]) + if not self.can_symlink: + _check(p.rglob("*/"), [ + "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", + ]) + else: + _check(p.rglob("*/"), [ + "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", + "dirC/dirD/", "dirE/", "linkB/", + ]) + _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + + p = P(BASE, "dirC") + _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", + "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/"]) + _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) + _check(p.rglob("*/"), ["dirC/dirD/"]) + _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) + _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p.rglob("*.txt"), ["dirC/novel.txt"]) + _check(p.rglob("*.*"), ["dirC/novel.txt"]) + + 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 'linkD' not in path.parent.parts} # exclude symlink loop. + self.assertEqual(actual, { P(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) + _check(p, "*/fileA", []) + _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) + _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB", + "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"]) + _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"]) + _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", + "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) + + p = P(BASE, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + 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(BASE, q) for q in expected }) + P = self.cls + p = P(BASE) + _check(p, "fileB", ["dirB/fileB"]) + _check(p, "*/fileA", []) + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + + p = P(BASE, "dirC") + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", + "dirC/dirD", "dirC/dirD/fileD"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + # gh-91616, a re module regression + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) + + 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(BASE) + given = set(p.rglob('*')) + expect = {'brokenLink', + 'dirA', 'dirA/linkC', + 'dirB', 'dirB/fileB', 'dirB/linkD', + 'dirC', 'dirC/dirD', 'dirC/dirD/fileD', + 'dirC/fileC', 'dirC/novel.txt', + 'dirE', + 'fileA', + 'linkA', + 'linkB', + 'brokenLinkLoop', + } + self.assertEqual(given, {p / x for x in expect}) + + def test_glob_many_open_files(self): + depth = 30 + P = self.cls + p = base = P(BASE) / 'deep' + p.mkdir() + for _ in range(depth): + p /= 'd' + p.mkdir() + pattern = '/'.join(['*'] * depth) + iters = [base.glob(pattern) for j in range(100)] + for it in iters: + self.assertEqual(next(it), p) + iters = [base.rglob('d') for j in range(100)] + p = base + for i in range(depth): + p = p / 'd' + for it in iters: + self.assertEqual(next(it), p) + + def test_glob_dotdot(self): + # ".." is not special in globs. + P = self.cls + p = P(BASE) + self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) + self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) + self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) + self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) + self.assertEqual(set(p.glob("dirA/../file*/..")), set()) + self.assertEqual(set(p.glob("../xyzzy")), set()) + self.assertEqual(set(p.glob("xyzzy/..")), set()) + self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) + + def test_glob_permissions(self): + # See bpo-38894 + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls + base = P(BASE) / 'permissions' + base.mkdir() + + for i in range(100): + link = base / f"link{i}" + if i % 2: + link.symlink_to(P(BASE, "dirE", "nonexistent")) + else: + link.symlink_to(P(BASE, "dirC")) + + self.assertEqual(len(set(base.glob("*"))), 100) + self.assertEqual(len(set(base.glob("*/"))), 50) + self.assertEqual(len(set(base.glob("*/fileC"))), 50) + self.assertEqual(len(set(base.glob("*/file*"))), 50) + + def test_glob_long_symlink(self): + # See gh-87695 + if not self.can_symlink: + self.skipTest("symlinks required") + base = self.cls(BASE) / 'long_symlink' + base.mkdir() + bad_link = base / 'bad_link' + bad_link.symlink_to("bad" * 200) + self.assertEqual(sorted(base.glob('**/*')), [bad_link]) + + def test_glob_above_recursion_limit(self): + recursion_limit = 50 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(BASE, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.glob('**/')) + + def test_glob_recursive_no_trailing_slash(self): + P = self.cls + p = P(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_readlink(self): + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls(BASE) + self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) + self.assertEqual((P / 'brokenLink').readlink(), + self.cls('non-existing')) + self.assertEqual((P / 'linkB').readlink(), self.cls('dirB')) + self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB')) + with self.assertRaises(OSError): + (P / 'fileA').readlink() + + @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") + def test_readlink_unsupported(self): + P = self.cls(BASE) + p = P / 'fileA' + with self.assertRaises(pathlib.UnsupportedOperation): + q.readlink(p) + + def _check_resolve(self, p, expected, strict=True): + q = p.resolve(strict) + self.assertEqual(q, expected) + + # This can be used to check both relative and absolute resolutions. + _check_resolve_relative = _check_resolve_absolute = _check_resolve + + def test_resolve_common(self): + if not self.can_symlink: + self.skipTest("symlinks required") + P = self.cls + p = P(BASE, 'foo') + with self.assertRaises(OSError) as cm: + p.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ENOENT) + # Non-strict + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.join(BASE, 'foo')) + p = P(BASE, 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.join(BASE, 'foo', 'in', 'spam')) + p = P(BASE, '..', 'foo', 'in', 'spam') + self.assertEqualNormCase(str(p.resolve(strict=False)), + os.path.abspath(os.path.join('foo', 'in', 'spam'))) + # These are all relative symlinks. + p = P(BASE, 'dirB', 'fileB') + self._check_resolve_relative(p, p) + p = P(BASE, 'linkA') + self._check_resolve_relative(p, P(BASE, 'fileA')) + p = P(BASE, 'dirA', 'linkC', 'fileB') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + p = P(BASE, 'dirB', 'linkD', 'fileB') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + # Non-strict + p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', + 'spam'), False) + p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') + if os.name == 'nt' and isinstance(p, pathlib.Path): + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', + 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + # Now create absolute symlinks. + d = self.tempdir() + P(BASE, 'dirA', 'linkX').symlink_to(d) + P(BASE, str(d), 'linkY').symlink_to(join('dirB')) + p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') + self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) + # Non-strict + p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), + False) + p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') + if os.name == 'nt' and isinstance(p, pathlib.Path): + # In Windows, if linkY points to dirB, 'dirA\linkY\..' + # resolves to 'dirA' without resolving linkY first. + self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) + else: + # In Posix, if linkY points to dirB, 'dirA/linkY/..' + # resolves to 'dirB/..' first before resolving to parent of dirB. + self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + + 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") + p = self.cls(BASE) + p.joinpath('0').symlink_to('.', target_is_directory=True) + p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) + p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) + q = p / '2' + self.assertEqual(q.resolve(strict=True), p) + r = q / '3' / '4' + self.assertRaises(FileNotFoundError, r.resolve, strict=True) + # Non-strict + self.assertEqual(r.resolve(strict=False), p / '3' / '4') + + def _check_symlink_loop(self, *args): + path = self.cls(*args) + with self.assertRaises(OSError) as cm: + path.resolve(strict=True) + self.assertEqual(cm.exception.errno, errno.ELOOP) + + def test_resolve_loop(self): + if not self.can_symlink: + self.skipTest("symlinks required") + if os.name == 'nt' and issubclass(self.cls, pathlib.Path): + self.skipTest("symlink loops work differently with concrete Windows paths") + # Loops with relative symlinks. + self.cls(BASE, 'linkX').symlink_to('linkX/inside') + self._check_symlink_loop(BASE, 'linkX') + self.cls(BASE, 'linkY').symlink_to('linkY') + self._check_symlink_loop(BASE, 'linkY') + self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') + self._check_symlink_loop(BASE, 'linkZ') + # Non-strict + p = self.cls(BASE, 'linkZ', 'foo') + self.assertEqual(p.resolve(strict=False), p) + # Loops with absolute symlinks. + self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) + self._check_symlink_loop(BASE, 'linkU') + self.cls(BASE, 'linkV').symlink_to(join('linkV')) + self._check_symlink_loop(BASE, 'linkV') + self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) + self._check_symlink_loop(BASE, 'linkW') + # Non-strict + q = self.cls(BASE, 'linkW', 'foo') + self.assertEqual(q.resolve(strict=False), q) + + def test_stat(self): + statA = self.cls(BASE).joinpath('fileA').stat() + statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() + statC = self.cls(BASE).joinpath('dirC').stat() + # st_mode: files are the same, directory differs. + self.assertIsInstance(statA.st_mode, int) + self.assertEqual(statA.st_mode, statB.st_mode) + self.assertNotEqual(statA.st_mode, statC.st_mode) + self.assertNotEqual(statB.st_mode, statC.st_mode) + # st_ino: all different, + self.assertIsInstance(statA.st_ino, int) + self.assertNotEqual(statA.st_ino, statB.st_ino) + self.assertNotEqual(statA.st_ino, statC.st_ino) + self.assertNotEqual(statB.st_ino, statC.st_ino) + # st_dev: all the same. + self.assertIsInstance(statA.st_dev, int) + self.assertEqual(statA.st_dev, statB.st_dev) + self.assertEqual(statA.st_dev, statC.st_dev) + # other attributes not used by pathlib. + + def test_stat_no_follow_symlinks(self): + if not self.can_symlink: + self.skipTest("symlinks required") + p = self.cls(BASE) / 'linkA' + st = p.stat() + self.assertNotEqual(st, p.stat(follow_symlinks=False)) + + def test_stat_no_follow_symlinks_nosymlink(self): + p = self.cls(BASE) / 'fileA' + st = p.stat() + self.assertEqual(st, p.stat(follow_symlinks=False)) + + def test_lstat(self): + if not self.can_symlink: + self.skipTest("symlinks required") + p = self.cls(BASE)/ 'linkA' + st = p.stat() + self.assertNotEqual(st, p.lstat()) + + def test_lstat_nosymlink(self): + p = self.cls(BASE) / 'fileA' + st = p.stat() + self.assertEqual(st, p.lstat()) + + def test_is_dir(self): + P = self.cls(BASE) + self.assertTrue((P / 'dirA').is_dir()) + self.assertFalse((P / 'fileA').is_dir()) + self.assertFalse((P / 'non-existing').is_dir()) + self.assertFalse((P / 'fileA' / 'bah').is_dir()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir()) + self.assertTrue((P / 'linkB').is_dir()) + self.assertFalse((P/ 'brokenLink').is_dir()) + self.assertFalse((P / 'dirA\udfff').is_dir()) + self.assertFalse((P / 'dirA\x00').is_dir()) + + def test_is_dir_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False)) + self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) + + def test_is_file(self): + P = self.cls(BASE) + self.assertTrue((P / 'fileA').is_file()) + self.assertFalse((P / 'dirA').is_file()) + self.assertFalse((P / 'non-existing').is_file()) + self.assertFalse((P / 'fileA' / 'bah').is_file()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_file()) + self.assertFalse((P / 'linkB').is_file()) + self.assertFalse((P/ 'brokenLink').is_file()) + self.assertFalse((P / 'fileA\udfff').is_file()) + self.assertFalse((P / 'fileA\x00').is_file()) + + def test_is_file_no_follow_symlinks(self): + P = self.cls(BASE) + self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False)) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_file(follow_symlinks=False)) + self.assertFalse((P / 'linkB').is_file(follow_symlinks=False)) + self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False)) + self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) + + def test_is_mount(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_mount()) + self.assertFalse((P / 'dirA').is_mount()) + self.assertFalse((P / 'non-existing').is_mount()) + self.assertFalse((P / 'fileA' / 'bah').is_mount()) + if self.can_symlink: + self.assertFalse((P / 'linkA').is_mount()) + + def test_is_symlink(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_symlink()) + self.assertFalse((P / 'dirA').is_symlink()) + self.assertFalse((P / 'non-existing').is_symlink()) + self.assertFalse((P / 'fileA' / 'bah').is_symlink()) + if self.can_symlink: + self.assertTrue((P / 'linkA').is_symlink()) + self.assertTrue((P / 'linkB').is_symlink()) + self.assertTrue((P/ 'brokenLink').is_symlink()) + self.assertIs((P / 'fileA\udfff').is_file(), False) + self.assertIs((P / 'fileA\x00').is_file(), False) + if self.can_symlink: + self.assertIs((P / 'linkA\udfff').is_file(), False) + self.assertIs((P / 'linkA\x00').is_file(), False) + + def test_is_junction_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_junction()) + self.assertFalse((P / 'dirA').is_junction()) + self.assertFalse((P / 'non-existing').is_junction()) + self.assertFalse((P / 'fileA' / 'bah').is_junction()) + self.assertFalse((P / 'fileA\udfff').is_junction()) + self.assertFalse((P / 'fileA\x00').is_junction()) + + def test_is_fifo_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_fifo()) + self.assertFalse((P / 'dirA').is_fifo()) + self.assertFalse((P / 'non-existing').is_fifo()) + self.assertFalse((P / 'fileA' / 'bah').is_fifo()) + self.assertIs((P / 'fileA\udfff').is_fifo(), False) + self.assertIs((P / 'fileA\x00').is_fifo(), False) + + def test_is_socket_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_socket()) + self.assertFalse((P / 'dirA').is_socket()) + self.assertFalse((P / 'non-existing').is_socket()) + self.assertFalse((P / 'fileA' / 'bah').is_socket()) + self.assertIs((P / 'fileA\udfff').is_socket(), False) + self.assertIs((P / 'fileA\x00').is_socket(), False) + + def test_is_block_device_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_block_device()) + self.assertFalse((P / 'dirA').is_block_device()) + self.assertFalse((P / 'non-existing').is_block_device()) + self.assertFalse((P / 'fileA' / 'bah').is_block_device()) + self.assertIs((P / 'fileA\udfff').is_block_device(), False) + self.assertIs((P / 'fileA\x00').is_block_device(), False) + + def test_is_char_device_false(self): + P = self.cls(BASE) + self.assertFalse((P / 'fileA').is_char_device()) + self.assertFalse((P / 'dirA').is_char_device()) + self.assertFalse((P / 'non-existing').is_char_device()) + self.assertFalse((P / 'fileA' / 'bah').is_char_device()) + self.assertIs((P / 'fileA\udfff').is_char_device(), False) + self.assertIs((P / 'fileA\x00').is_char_device(), False) + + def test_pickling_common(self): + p = self.cls(BASE, 'fileA') + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + dumped = pickle.dumps(p, proto) + pp = pickle.loads(dumped) + self.assertEqual(pp.stat(), p.stat()) + + def test_parts_interning(self): + P = self.cls + p = P('/usr/bin/foo') + q = P('/usr/local/bin') + # 'usr' + self.assertIs(p.parts[1], q.parts[1]) + # 'bin' + self.assertIs(p.parts[2], q.parts[3]) + + 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). + P = self.cls(BASE) + P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) + P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) + P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) + P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) + + # Resolve absolute paths. + p = (P / 'link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = (P / 'link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + + # Resolve relative paths. + try: + self.cls().absolute() + except pathlib.UnsupportedOperation: + return + old_path = os.getcwd() + os.chdir(BASE) + try: + p = self.cls('link0').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link1').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link2').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + p = self.cls('link3').resolve() + self.assertEqual(p, P) + self.assertEqualNormCase(str(p), BASE) + finally: + os.chdir(old_path) + + def test_complex_symlinks_absolute(self): + self._check_complex_symlinks(BASE) + + def test_complex_symlinks_relative(self): + self._check_complex_symlinks('.') + + def test_complex_symlinks_relative_dot_dot(self): + self._check_complex_symlinks(os.path.join('dirA', '..')) + + def setUpWalk(self): + # Build: + # TESTFN/ + # TEST1/ a file kid and two directory kids + # tmp1 + # SUB1/ a file kid and a directory kid + # tmp2 + # SUB11/ no kids + # SUB2/ a file kid and a dirsymlink kid + # tmp3 + # link/ a symlink to TEST2 + # broken_link + # broken_link2 + # TEST2/ + # tmp4 a lone file + self.walk_path = self.cls(BASE, "TEST1") + self.sub1_path = self.walk_path / "SUB1" + self.sub11_path = self.sub1_path / "SUB11" + self.sub2_path = self.walk_path / "SUB2" + tmp1_path = self.walk_path / "tmp1" + tmp2_path = self.sub1_path / "tmp2" + tmp3_path = self.sub2_path / "tmp3" + self.link_path = self.sub2_path / "link" + t2_path = self.cls(BASE, "TEST2") + tmp4_path = self.cls(BASE, "TEST2", "tmp4") + broken_link_path = self.sub2_path / "broken_link" + broken_link2_path = self.sub2_path / "broken_link2" + + self.sub11_path.mkdir(parents=True) + self.sub2_path.mkdir(parents=True) + t2_path.mkdir(parents=True) + + for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: + with path.open("w", encoding='utf-8') as f: + f.write(f"I'm {path} and proud of it. Blame test_pathlib.\n") + + if self.can_symlink: + self.link_path.symlink_to(t2_path) + broken_link_path.symlink_to('broken') + broken_link2_path.symlink_to(self.cls('tmp3', 'broken')) + self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"]) + else: + self.sub2_tree = (self.sub2_path, [], ["tmp3"]) + + def test_walk_topdown(self): + self.setUpWalk() + walker = self.walk_path.walk() + entry = next(walker) + entry[1].sort() # Ensure we visit SUB1 before SUB2 + self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"])) + entry = next(walker) + self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"])) + entry = next(walker) + self.assertEqual(entry, (self.sub11_path, [], [])) + entry = next(walker) + entry[1].sort() + entry[2].sort() + self.assertEqual(entry, self.sub2_tree) + with self.assertRaises(StopIteration): + next(walker) + + def test_walk_prune(self): + self.setUpWalk() + # Prune the search. + all = [] + for root, dirs, files in self.walk_path.walk(): + all.append((root, dirs, files)) + if 'SUB1' in dirs: + # Note that this also mutates the dirs we appended to all! + dirs.remove('SUB1') + + self.assertEqual(len(all), 2) + self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"])) + + all[1][-1].sort() + all[1][1].sort() + self.assertEqual(all[1], self.sub2_tree) + + def test_walk_bottom_up(self): + self.setUpWalk() + seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False + for path, dirnames, filenames in self.walk_path.walk(top_down=False): + if path == self.walk_path: + self.assertFalse(seen_testfn) + self.assertTrue(seen_sub1) + self.assertTrue(seen_sub2) + self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"]) + self.assertEqual(filenames, ["tmp1"]) + seen_testfn = True + elif path == self.sub1_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub1) + self.assertTrue(seen_sub11) + self.assertEqual(dirnames, ["SUB11"]) + self.assertEqual(filenames, ["tmp2"]) + seen_sub1 = True + elif path == self.sub11_path: + self.assertFalse(seen_sub1) + self.assertFalse(seen_sub11) + self.assertEqual(dirnames, []) + self.assertEqual(filenames, []) + seen_sub11 = True + elif path == self.sub2_path: + self.assertFalse(seen_testfn) + self.assertFalse(seen_sub2) + self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1])) + self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2])) + seen_sub2 = True + else: + raise AssertionError(f"Unexpected path: {path}") + self.assertTrue(seen_testfn) + + 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: + if root == self.link_path: + self.assertEqual(dirs, []) + self.assertEqual(files, ["tmp4"]) + break + else: + self.fail("Didn't follow symlink with follow_symlinks=True") + + 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. + walk_it = self.walk_path.walk(follow_symlinks=False) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", files) + break + else: + self.fail("symlink not found") + + walk_it = self.walk_path.walk(follow_symlinks=True) + for root, dirs, files in walk_it: + if root == self.sub2_path: + self.assertIn("link", dirs) + break + else: + self.fail("symlink not found") + + def test_walk_above_recursion_limit(self): + recursion_limit = 40 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(BASE, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.walk()) + list(base.walk(top_down=False)) + +class DummyPathWithSymlinks(DummyPath): + def readlink(self): + path = str(self.parent.resolve() / self.name) + if path in self._symlinks: + return self.with_segments(self._symlinks[path]) + elif path in self._files or path in self._directories: + raise OSError(errno.EINVAL, "Not a symlink", path) + else: + raise FileNotFoundError(errno.ENOENT, "File not found", path) + + def symlink_to(self, target, target_is_directory=False): + self._directories[str(self.parent)].add(self.name) + self._symlinks[str(self)] = str(target) + + +class DummyPathWithSymlinksTest(DummyPathTest): + cls = DummyPathWithSymlinks + can_symlink = True + + +if __name__ == "__main__": + unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index 5fb6ffc4e8f0cf..195fc0ddddecd3 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2272,6 +2272,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_importlib/source \ test/test_json \ test/test_module \ + test/test_pathlib \ test/test_peg_generator \ test/test_sqlite3 \ test/test_tkinter \ From 2f0ec7fa9450caeac820a6dc819d17d14fd16a4b Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 17 Dec 2023 00:07:32 +0000 Subject: [PATCH 281/442] GH-110109: pathlib tests: store base directory as test class attribute (#113221) Store the test base directory as a class attribute named `base` rather than module constants named `BASE`. The base directory is a local file path, and therefore not ideally suited to the pathlib ABC tests. In a future commit we'll change its value in `test_pathlib_abc.py` such that it points to a totally fictitious path, which will help to ensure we're not touching the local filesystem. --- Lib/test/test_pathlib/test_pathlib.py | 167 +++++++------- Lib/test/test_pathlib/test_pathlib_abc.py | 265 +++++++++++----------- 2 files changed, 210 insertions(+), 222 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 304786fc6f7fd9..00cfdd37e568a6 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -23,11 +23,6 @@ grp = pwd = None -# Make sure any symbolic links in the base test path are resolved. -BASE = os.path.realpath(TESTFN) -join = lambda *x: os.path.join(BASE, *x) -rel_join = lambda *x: os.path.join(TESTFN, *x) - only_nt = unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system') only_posix = unittest.skipIf(os.name == 'nt', @@ -937,11 +932,11 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest): def setUp(self): super().setUp() - os.chmod(join('dirE'), 0) + os.chmod(self.pathmod.join(self.base, 'dirE'), 0) def tearDown(self): - os.chmod(join('dirE'), 0o777) - os_helper.rmtree(BASE) + os.chmod(self.pathmod.join(self.base, 'dirE'), 0o777) + os_helper.rmtree(self.base) def tempdir(self): d = os_helper._longpath(tempfile.mkdtemp(suffix='-dirD', @@ -978,23 +973,23 @@ def test_absolute_common(self): P = self.cls with mock.patch("os.getcwd") as getcwd: - getcwd.return_value = BASE + getcwd.return_value = self.base # Simple relative paths. - self.assertEqual(str(P().absolute()), BASE) - self.assertEqual(str(P('.').absolute()), BASE) - self.assertEqual(str(P('a').absolute()), os.path.join(BASE, 'a')) - self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(BASE, 'a', 'b', 'c')) + self.assertEqual(str(P().absolute()), self.base) + self.assertEqual(str(P('.').absolute()), self.base) + self.assertEqual(str(P('a').absolute()), os.path.join(self.base, 'a')) + self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(self.base, 'a', 'b', 'c')) # Symlinks should not be resolved. - self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(BASE, 'linkB', 'fileB')) - self.assertEqual(str(P('brokenLink').absolute()), os.path.join(BASE, 'brokenLink')) - self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(BASE, 'brokenLinkLoop')) + self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(self.base, 'linkB', 'fileB')) + self.assertEqual(str(P('brokenLink').absolute()), os.path.join(self.base, 'brokenLink')) + self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(self.base, 'brokenLinkLoop')) # '..' entries should be preserved and not normalised. - self.assertEqual(str(P('..').absolute()), os.path.join(BASE, '..')) - self.assertEqual(str(P('a', '..').absolute()), os.path.join(BASE, 'a', '..')) - self.assertEqual(str(P('..', 'b').absolute()), os.path.join(BASE, '..', 'b')) + self.assertEqual(str(P('..').absolute()), os.path.join(self.base, '..')) + self.assertEqual(str(P('a', '..').absolute()), os.path.join(self.base, 'a', '..')) + self.assertEqual(str(P('..', 'b').absolute()), os.path.join(self.base, '..', 'b')) def _test_home(self, p): q = self.cls(os.path.expanduser('~')) @@ -1011,11 +1006,11 @@ def test_home(self): self._test_home(self.cls.home()) env.clear() - env['USERPROFILE'] = os.path.join(BASE, 'userprofile') + env['USERPROFILE'] = os.path.join(self.base, 'userprofile') self._test_home(self.cls.home()) # bpo-38883: ignore `HOME` when set on windows - env['HOME'] = os.path.join(BASE, 'home') + env['HOME'] = os.path.join(self.base, 'home') self._test_home(self.cls.home()) @unittest.skipIf(is_wasi, "WASI has no user accounts.") @@ -1042,7 +1037,7 @@ def __init__(self, *pathsegments, session_id): def with_segments(self, *pathsegments): return type(self)(*pathsegments, session_id=self.session_id) - p = P(BASE, session_id=42) + p = P(self.base, session_id=42) self.assertEqual(42, p.absolute().session_id) self.assertEqual(42, p.resolve().session_id) if not is_wasi: # WASI has no user accounts. @@ -1061,7 +1056,7 @@ def with_segments(self, *pathsegments): self.assertEqual(42, dirpath.session_id) def test_open_unbuffered(self): - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'fileA').open('rb', buffering=0) as f: self.assertIsInstance(f, io.RawIOBase) self.assertEqual(f.read().strip(), b"this is file A") @@ -1070,15 +1065,15 @@ def test_resolve_nonexist_relative_issue38671(self): p = self.cls('non', 'exist') old_cwd = os.getcwd() - os.chdir(BASE) + os.chdir(self.base) try: - self.assertEqual(p.resolve(), self.cls(BASE, p)) + self.assertEqual(p.resolve(), self.cls(self.base, p)) finally: os.chdir(old_cwd) @os_helper.skip_unless_working_chmod def test_chmod(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' mode = p.stat().st_mode # Clear writable bit. new_mode = mode & ~0o222 @@ -1093,7 +1088,7 @@ def test_chmod(self): @only_posix @os_helper.skip_unless_working_chmod def test_chmod_follow_symlinks_true(self): - p = self.cls(BASE) / 'linkA' + p = self.cls(self.base) / 'linkA' q = p.resolve() mode = q.stat().st_mode # Clear writable bit. @@ -1116,7 +1111,7 @@ def _get_pw_name_or_skip_test(self, uid): @unittest.skipUnless(pwd, "the pwd module is needed for this test") def test_owner(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' expected_uid = p.stat().st_uid expected_name = self._get_pw_name_or_skip_test(expected_uid) @@ -1129,8 +1124,8 @@ def test_owner_no_follow_symlinks(self): if len(all_users) < 2: self.skipTest("test needs more than one user") - target = self.cls(BASE) / 'fileA' - link = self.cls(BASE) / 'linkA' + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' uid_1, uid_2 = all_users[:2] os.chown(target, uid_1, -1) @@ -1151,7 +1146,7 @@ def _get_gr_name_or_skip_test(self, gid): @unittest.skipUnless(grp, "the grp module is needed for this test") def test_group(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' expected_gid = p.stat().st_gid expected_name = self._get_gr_name_or_skip_test(expected_gid) @@ -1164,8 +1159,8 @@ def test_group_no_follow_symlinks(self): if len(all_groups) < 2: self.skipTest("test needs more than one group") - target = self.cls(BASE) / 'fileA' - link = self.cls(BASE) / 'linkA' + target = self.cls(self.base) / 'fileA' + link = self.cls(self.base) / 'linkA' gid_1, gid_2 = all_groups[:2] os.chown(target, -1, gid_1) @@ -1178,18 +1173,18 @@ def test_group_no_follow_symlinks(self): self.assertEqual(expected_name, link.group(follow_symlinks=False)) def test_unlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' p.unlink() self.assertFileNotFound(p.stat) self.assertFileNotFound(p.unlink) def test_unlink_missing_ok(self): - p = self.cls(BASE) / 'fileAAA' + p = self.cls(self.base) / 'fileAAA' self.assertFileNotFound(p.unlink) p.unlink(missing_ok=True) def test_rmdir(self): - p = self.cls(BASE) / 'dirA' + p = self.cls(self.base) / 'dirA' for q in p.iterdir(): q.unlink() p.rmdir() @@ -1198,7 +1193,7 @@ def test_rmdir(self): @unittest.skipUnless(hasattr(os, "link"), "os.link() is not present") def test_hardlink_to(self): - P = self.cls(BASE) + P = self.cls(self.base) target = P / 'fileA' size = target.stat().st_size # linking to another path. @@ -1209,14 +1204,14 @@ def test_hardlink_to(self): self.assertTrue(target.exists()) # Linking to a str of a relative path. link2 = P / 'dirA' / 'fileAAA' - target2 = rel_join('fileA') + target2 = self.pathmod.join(TESTFN, 'fileA') link2.hardlink_to(target2) self.assertEqual(os.stat(target2).st_size, size) self.assertTrue(link2.exists()) @unittest.skipIf(hasattr(os, "link"), "os.link() is present") def test_hardlink_to_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' # linking to another path. q = P / 'dirA' / 'fileAA' @@ -1224,7 +1219,7 @@ def test_hardlink_to_unsupported(self): q.hardlink_to(p) def test_rename(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' size = p.stat().st_size # Renaming to another path. @@ -1234,14 +1229,14 @@ def test_rename(self): self.assertEqual(q.stat().st_size, size) self.assertFileNotFound(p.stat) # Renaming to a str of a relative path. - r = rel_join('fileAAA') + r = self.pathmod.join(TESTFN, 'fileAAA') renamed_q = q.rename(r) self.assertEqual(renamed_q, self.cls(r)) self.assertEqual(os.stat(r).st_size, size) self.assertFileNotFound(q.stat) def test_replace(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' size = p.stat().st_size # Replacing a non-existing path. @@ -1251,14 +1246,14 @@ def test_replace(self): self.assertEqual(q.stat().st_size, size) self.assertFileNotFound(p.stat) # Replacing another (existing) path. - r = rel_join('dirB', 'fileB') + r = self.pathmod.join(TESTFN, 'dirB', 'fileB') replaced_q = q.replace(r) self.assertEqual(replaced_q, self.cls(r)) self.assertEqual(os.stat(r).st_size, size) self.assertFileNotFound(q.stat) def test_touch_common(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'newfileA' self.assertFalse(p.exists()) p.touch() @@ -1282,14 +1277,14 @@ def test_touch_common(self): self.assertRaises(OSError, p.touch, exist_ok=False) def test_touch_nochange(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' p.touch() with p.open('rb') as f: self.assertEqual(f.read().strip(), b"this is file A") def test_mkdir(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'newdirA' self.assertFalse(p.exists()) p.mkdir() @@ -1301,7 +1296,7 @@ def test_mkdir(self): def test_mkdir_parents(self): # Creating a chain of directories. - p = self.cls(BASE, 'newdirB', 'newdirC') + p = self.cls(self.base, 'newdirB', 'newdirC') self.assertFalse(p.exists()) with self.assertRaises(OSError) as cm: p.mkdir() @@ -1314,7 +1309,7 @@ def test_mkdir_parents(self): self.assertEqual(cm.exception.errno, errno.EEXIST) # Test `mode` arg. mode = stat.S_IMODE(p.stat().st_mode) # Default mode. - p = self.cls(BASE, 'newdirD', 'newdirE') + p = self.cls(self.base, 'newdirD', 'newdirE') p.mkdir(0o555, parents=True) self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) @@ -1325,7 +1320,7 @@ def test_mkdir_parents(self): self.assertEqual(stat.S_IMODE(p.parent.stat().st_mode), mode) def test_mkdir_exist_ok(self): - p = self.cls(BASE, 'dirB') + p = self.cls(self.base, 'dirB') st_ctime_first = p.stat().st_ctime self.assertTrue(p.exists()) self.assertTrue(p.is_dir()) @@ -1337,7 +1332,7 @@ def test_mkdir_exist_ok(self): self.assertEqual(p.stat().st_ctime, st_ctime_first) def test_mkdir_exist_ok_with_parent(self): - p = self.cls(BASE, 'dirC') + p = self.cls(self.base, 'dirC') self.assertTrue(p.exists()) with self.assertRaises(FileExistsError) as cm: p.mkdir() @@ -1371,7 +1366,7 @@ def test_mkdir_with_unknown_drive(self): (p / 'child' / 'path').mkdir(parents=True) def test_mkdir_with_child_file(self): - p = self.cls(BASE, 'dirB', 'fileB') + p = self.cls(self.base, 'dirB', 'fileB') self.assertTrue(p.exists()) # An exception is raised when the last path component is an existing # regular file, regardless of whether exist_ok is true or not. @@ -1383,7 +1378,7 @@ def test_mkdir_with_child_file(self): self.assertEqual(cm.exception.errno, errno.EEXIST) def test_mkdir_no_parents_file(self): - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') self.assertTrue(p.exists()) # An exception is raised when the last path component is an existing # regular file, regardless of whether exist_ok is true or not. @@ -1396,7 +1391,7 @@ def test_mkdir_no_parents_file(self): def test_mkdir_concurrent_parent_creation(self): for pattern_num in range(32): - p = self.cls(BASE, 'dirCPC%d' % pattern_num) + p = self.cls(self.base, 'dirCPC%d' % pattern_num) self.assertFalse(p.exists()) real_mkdir = os.mkdir @@ -1427,7 +1422,7 @@ def my_mkdir(path, mode=0o777): def test_symlink_to(self): if not self.can_symlink: self.skipTest("symlinks required") - P = self.cls(BASE) + P = self.cls(self.base) target = P / 'fileA' # Symlinking a path target. link = P / 'dirA' / 'linkAA' @@ -1451,7 +1446,7 @@ def test_symlink_to(self): @unittest.skipIf(hasattr(os, "symlink"), "os.symlink() is present") def test_symlink_to_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' # linking to another path. q = P / 'dirA' / 'fileAA' @@ -1459,7 +1454,7 @@ def test_symlink_to_unsupported(self): q.symlink_to(p) def test_is_junction(self): - P = self.cls(BASE) + P = self.cls(self.base) with mock.patch.object(P.pathmod, 'isjunction'): self.assertEqual(P.is_junction(), P.pathmod.isjunction.return_value) @@ -1469,7 +1464,7 @@ def test_is_junction(self): @unittest.skipIf(sys.platform == "vxworks", "fifo requires special path on VxWorks") def test_is_fifo_true(self): - P = self.cls(BASE, 'myfifo') + P = self.cls(self.base, 'myfifo') try: os.mkfifo(str(P)) except PermissionError as e: @@ -1477,8 +1472,8 @@ def test_is_fifo_true(self): self.assertTrue(P.is_fifo()) self.assertFalse(P.is_socket()) self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'myfifo\udfff').is_fifo(), False) - self.assertIs(self.cls(BASE, 'myfifo\x00').is_fifo(), False) + self.assertIs(self.cls(self.base, 'myfifo\udfff').is_fifo(), False) + self.assertIs(self.cls(self.base, 'myfifo\x00').is_fifo(), False) @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "Unix sockets required") @unittest.skipIf( @@ -1488,7 +1483,7 @@ def test_is_fifo_true(self): is_wasi, "Cannot create socket on WASI." ) def test_is_socket_true(self): - P = self.cls(BASE, 'mysock') + P = self.cls(self.base, 'mysock') sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.addCleanup(sock.close) try: @@ -1500,8 +1495,8 @@ def test_is_socket_true(self): self.assertTrue(P.is_socket()) self.assertFalse(P.is_fifo()) self.assertFalse(P.is_file()) - self.assertIs(self.cls(BASE, 'mysock\udfff').is_socket(), False) - self.assertIs(self.cls(BASE, 'mysock\x00').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\udfff').is_socket(), False) + self.assertIs(self.cls(self.base, 'mysock\x00').is_socket(), False) def test_is_char_device_true(self): # Under Unix, /dev/null should generally be a char device. @@ -1573,7 +1568,7 @@ def test_walk_bad_dir(self): def test_walk_many_open_files(self): depth = 30 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = self.cls(base, *(['d']*depth)) path.mkdir(parents=True) @@ -1615,15 +1610,15 @@ def test_absolute(self): def test_open_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'new_file').open('wb'): pass - st = os.stat(join('new_file')) + st = os.stat(self.pathmod.join(self.base, 'new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) os.umask(0o022) with (p / 'other_new_file').open('wb'): pass - st = os.stat(join('other_new_file')) + st = os.stat(self.pathmod.join(self.base, 'other_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) def test_resolve_root(self): @@ -1642,31 +1637,31 @@ def test_resolve_root(self): def test_touch_mode(self): old_mask = os.umask(0) self.addCleanup(os.umask, old_mask) - p = self.cls(BASE) + p = self.cls(self.base) (p / 'new_file').touch() - st = os.stat(join('new_file')) + st = os.stat(self.pathmod.join(self.base, 'new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o666) os.umask(0o022) (p / 'other_new_file').touch() - st = os.stat(join('other_new_file')) + st = os.stat(self.pathmod.join(self.base, 'other_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o644) (p / 'masked_new_file').touch(mode=0o750) - st = os.stat(join('masked_new_file')) + st = os.stat(self.pathmod.join(self.base, 'masked_new_file')) self.assertEqual(stat.S_IMODE(st.st_mode), 0o750) def test_glob(self): P = self.cls - p = P(BASE) + p = P(self.base) given = set(p.glob("FILEa")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given + 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()) def test_rglob(self): P = self.cls - p = P(BASE, "dirC") + p = P(self.base, "dirC") given = set(p.rglob("FILEd")) - expect = set() if not os_helper.fs_is_case_insensitive(BASE) else given + 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()) @@ -1797,17 +1792,17 @@ def test_absolute(self): self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(share, 'a', 'b', 'c')) - drive = os.path.splitdrive(BASE)[0] - with os_helper.change_cwd(BASE): + drive = os.path.splitdrive(self.base)[0] + with os_helper.change_cwd(self.base): # Relative path with root self.assertEqual(str(P('\\').absolute()), drive + '\\') self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo') # Relative path on current drive - self.assertEqual(str(P(drive).absolute()), BASE) - self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(BASE, 'foo')) + self.assertEqual(str(P(drive).absolute()), self.base) + self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(self.base, 'foo')) - with os_helper.subst_drive(BASE) as other_drive: + with os_helper.subst_drive(self.base) as other_drive: # Set the working directory on the substitute drive saved_cwd = os.getcwd() other_cwd = f'{other_drive}\\dirA' @@ -1820,18 +1815,18 @@ def test_absolute(self): def test_glob(self): P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") }) - self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA/") }) - self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") }) + 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_rglob(self): P = self.cls - p = P(BASE, "dirC") - self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") }) - self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD/") }) + 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): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 61ed3cb6a4a7f8..a272973d9c1d61 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -18,16 +18,6 @@ def test_is_notimplemented(self): self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) -# Make sure any symbolic links in the base test path are resolved. -BASE = os.path.realpath(TESTFN) -join = lambda *x: os.path.join(BASE, *x) - -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') - - # # Tests for the pure classes. # @@ -62,6 +52,9 @@ def __hash__(self): class DummyPurePathTest(unittest.TestCase): cls = DummyPurePath + # Make sure any symbolic links in the base test path are resolved. + base = os.path.realpath(TESTFN) + # Keys are canonical paths, values are list of tuples of arguments # supposed to produce equal paths. equivalences = { @@ -859,7 +852,7 @@ class DummyPathTest(DummyPurePathTest): cls = DummyPath can_symlink = False - # (BASE) + # (self.base) # | # |-- brokenLink -> non-existing # |-- dirA @@ -882,7 +875,7 @@ class DummyPathTest(DummyPurePathTest): def setUp(self): super().setUp() pathmod = self.cls.pathmod - p = self.cls(BASE) + p = self.cls(self.base) p.mkdir(parents=True) p.joinpath('dirA').mkdir() p.joinpath('dirB').mkdir() @@ -914,7 +907,7 @@ def tearDown(self): cls._symlinks.clear() def tempdir(self): - path = self.cls(BASE).with_name('tmp-dirD') + path = self.cls(self.base).with_name('tmp-dirD') path.mkdir() return path @@ -927,8 +920,8 @@ def assertEqualNormCase(self, path_a, path_b): self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) def test_samefile(self): - fileA_path = os.path.join(BASE, 'fileA') - fileB_path = os.path.join(BASE, 'dirB', 'fileB') + fileA_path = os.path.join(self.base, 'fileA') + fileB_path = os.path.join(self.base, 'dirB', 'fileB') p = self.cls(fileA_path) pp = self.cls(fileA_path) q = self.cls(fileB_path) @@ -937,7 +930,7 @@ def test_samefile(self): self.assertFalse(p.samefile(fileB_path)) self.assertFalse(p.samefile(q)) # Test the non-existent file case - non_existent = os.path.join(BASE, 'foo') + non_existent = os.path.join(self.base, 'foo') r = self.cls(non_existent) self.assertRaises(FileNotFoundError, p.samefile, r) self.assertRaises(FileNotFoundError, p.samefile, non_existent) @@ -953,7 +946,7 @@ def test_empty_path(self): def test_exists(self): P = self.cls - p = P(BASE) + p = P(self.base) self.assertIs(True, p.exists()) self.assertIs(True, (p / 'dirA').exists()) self.assertIs(True, (p / 'fileA').exists()) @@ -967,11 +960,11 @@ def test_exists(self): self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False)) self.assertIs(False, (p / 'foo').exists()) self.assertIs(False, P('/xyzzy').exists()) - self.assertIs(False, P(BASE + '\udfff').exists()) - self.assertIs(False, P(BASE + '\x00').exists()) + self.assertIs(False, P(self.base + '\udfff').exists()) + self.assertIs(False, P(self.base + '\x00').exists()) def test_open_common(self): - p = self.cls(BASE) + p = self.cls(self.base) with (p / 'fileA').open('r') as f: self.assertIsInstance(f, io.TextIOBase) self.assertEqual(f.read(), "this is file A\n") @@ -980,7 +973,7 @@ def test_open_common(self): self.assertEqual(f.read().strip(), b"this is file A") def test_read_write_bytes(self): - p = self.cls(BASE) + p = self.cls(self.base) (p / 'fileA').write_bytes(b'abcdefg') self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') # Check that trying to write str does not truncate the file. @@ -988,7 +981,7 @@ def test_read_write_bytes(self): self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') def test_read_write_text(self): - p = self.cls(BASE) + p = self.cls(self.base) (p / 'fileA').write_text('äbcdefg', encoding='latin-1') self.assertEqual((p / 'fileA').read_text( encoding='utf-8', errors='ignore'), 'bcdefg') @@ -997,7 +990,7 @@ def test_read_write_text(self): self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') def test_read_text_with_newlines(self): - p = self.cls(BASE) + p = self.cls(self.base) # Check that `\n` character change nothing (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq') self.assertEqual((p / 'fileA').read_text(newline='\n'), @@ -1012,7 +1005,7 @@ def test_read_text_with_newlines(self): 'abcde\r\nfghlk\n\rmnopq') def test_write_text_with_newlines(self): - p = self.cls(BASE) + p = self.cls(self.base) # Check that `\n` character change nothing (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') self.assertEqual((p / 'fileA').read_bytes(), @@ -1033,27 +1026,27 @@ def test_write_text_with_newlines(self): def test_iterdir(self): P = self.cls - p = P(BASE) + p = P(self.base) it = p.iterdir() paths = set(it) expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA'] if self.can_symlink: expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop'] - self.assertEqual(paths, { P(BASE, q) for q in expected }) + self.assertEqual(paths, { P(self.base, q) for q in expected }) 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(BASE, 'linkB') + p = P(self.base, 'linkB') paths = set(p.iterdir()) - expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] } + expected = { P(self.base, 'linkB', q) for q in ['fileB', 'linkD'] } self.assertEqual(paths, expected) def test_iterdir_nodir(self): # __iter__ on something that is not a directory. - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') with self.assertRaises(OSError) as cm: p.iterdir() # ENOENT or EINVAL under Windows, ENOTDIR otherwise @@ -1063,9 +1056,9 @@ def test_iterdir_nodir(self): def test_glob_common(self): def _check(glob, expected): - self.assertEqual(set(glob), { P(BASE, q) for q in expected }) + self.assertEqual(set(glob), { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) it = p.glob("fileA") self.assertIsInstance(it, collections.abc.Iterator) _check(it, ["fileA"]) @@ -1101,9 +1094,9 @@ def test_glob_case_sensitive(self): P = self.cls def _check(path, pattern, case_sensitive, expected): actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)} - expected = {str(P(BASE, q)) for q in expected} + expected = {str(P(self.base, q)) for q in expected} self.assertEqual(actual, expected) - path = P(BASE) + path = P(self.base) _check(path, "DIRB/FILE*", True, []) _check(path, "DIRB/FILE*", False, ["dirB/fileB"]) _check(path, "dirb/file*", True, []) @@ -1115,9 +1108,9 @@ def test_glob_follow_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=True) if "linkD" not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", []) _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) _check(p, "*A", ["dirA", "fileA", "linkA"]) @@ -1140,9 +1133,9 @@ def test_glob_no_follow_symlinks_common(self): self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.glob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", []) _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"]) _check(p, "*A", ["dirA", "fileA", "linkA"]) @@ -1160,9 +1153,9 @@ def _check(path, glob, expected): def test_rglob_common(self): def _check(glob, expected): - self.assertEqual(set(glob), {P(BASE, q) for q in expected}) + self.assertEqual(set(glob), {P(self.base, q) for q in expected}) P = self.cls - p = P(BASE) + p = P(self.base) it = p.rglob("fileA") self.assertIsInstance(it, collections.abc.Iterator) _check(it, ["fileA"]) @@ -1187,7 +1180,7 @@ def _check(glob, expected): ]) _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1207,9 +1200,9 @@ def test_rglob_follow_symlinks_common(self): def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=True) if 'linkD' not in path.parent.parts} # exclude symlink loop. - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) _check(p, "*/fileA", []) _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"]) @@ -1220,7 +1213,7 @@ def _check(path, glob, expected): _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/", "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1236,9 +1229,9 @@ def test_rglob_no_follow_symlinks_common(self): self.skipTest("symlinks required") def _check(path, glob, expected): actual = {path for path in path.rglob(glob, follow_symlinks=False)} - self.assertEqual(actual, { P(BASE, q) for q in expected }) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls - p = P(BASE) + p = P(self.base) _check(p, "fileB", ["dirB/fileB"]) _check(p, "*/fileA", []) _check(p, "*/fileB", ["dirB/fileB"]) @@ -1246,7 +1239,7 @@ def _check(path, glob, expected): _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) - p = P(BASE, "dirC") + p = P(self.base, "dirC") _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) @@ -1262,7 +1255,7 @@ def test_rglob_symlink_loop(self): if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - p = P(BASE) + p = P(self.base) given = set(p.rglob('*')) expect = {'brokenLink', 'dirA', 'dirA/linkC', @@ -1280,7 +1273,7 @@ def test_rglob_symlink_loop(self): def test_glob_many_open_files(self): depth = 30 P = self.cls - p = base = P(BASE) / 'deep' + p = base = P(self.base) / 'deep' p.mkdir() for _ in range(depth): p /= 'd' @@ -1299,30 +1292,30 @@ def test_glob_many_open_files(self): def test_glob_dotdot(self): # ".." is not special in globs. P = self.cls - p = P(BASE) - self.assertEqual(set(p.glob("..")), { P(BASE, "..") }) - self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") }) - self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") }) - self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") }) + p = P(self.base) + self.assertEqual(set(p.glob("..")), { P(self.base, "..") }) + self.assertEqual(set(p.glob("../..")), { P(self.base, "..", "..") }) + self.assertEqual(set(p.glob("dirA/..")), { P(self.base, "dirA", "..") }) + self.assertEqual(set(p.glob("dirA/../file*")), { P(self.base, "dirA/../fileA") }) self.assertEqual(set(p.glob("dirA/../file*/..")), set()) self.assertEqual(set(p.glob("../xyzzy")), set()) self.assertEqual(set(p.glob("xyzzy/..")), set()) - self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)}) + self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)}) def test_glob_permissions(self): # See bpo-38894 if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - base = P(BASE) / 'permissions' + base = P(self.base) / 'permissions' base.mkdir() for i in range(100): link = base / f"link{i}" if i % 2: - link.symlink_to(P(BASE, "dirE", "nonexistent")) + link.symlink_to(P(self.base, "dirE", "nonexistent")) else: - link.symlink_to(P(BASE, "dirC")) + link.symlink_to(P(self.base, "dirC")) self.assertEqual(len(set(base.glob("*"))), 100) self.assertEqual(len(set(base.glob("*/"))), 50) @@ -1333,7 +1326,7 @@ def test_glob_long_symlink(self): # See gh-87695 if not self.can_symlink: self.skipTest("symlinks required") - base = self.cls(BASE) / 'long_symlink' + base = self.cls(self.base) / 'long_symlink' base.mkdir() bad_link = base / 'bad_link' bad_link.symlink_to("bad" * 200) @@ -1343,7 +1336,7 @@ def test_glob_above_recursion_limit(self): recursion_limit = 50 # directory_depth > recursion_limit directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) @@ -1352,7 +1345,7 @@ def test_glob_above_recursion_limit(self): def test_glob_recursive_no_trailing_slash(self): P = self.cls - p = P(BASE) + p = P(self.base) with self.assertWarns(FutureWarning): p.glob('**') with self.assertWarns(FutureWarning): @@ -1366,7 +1359,7 @@ def test_glob_recursive_no_trailing_slash(self): def test_readlink(self): if not self.can_symlink: self.skipTest("symlinks required") - P = self.cls(BASE) + P = self.cls(self.base) self.assertEqual((P / 'linkA').readlink(), self.cls('fileA')) self.assertEqual((P / 'brokenLink').readlink(), self.cls('non-existing')) @@ -1377,7 +1370,7 @@ def test_readlink(self): @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present") def test_readlink_unsupported(self): - P = self.cls(BASE) + P = self.cls(self.base) p = P / 'fileA' with self.assertRaises(pathlib.UnsupportedOperation): q.readlink(p) @@ -1393,53 +1386,53 @@ def test_resolve_common(self): if not self.can_symlink: self.skipTest("symlinks required") P = self.cls - p = P(BASE, 'foo') + p = P(self.base, 'foo') with self.assertRaises(OSError) as cm: p.resolve(strict=True) self.assertEqual(cm.exception.errno, errno.ENOENT) # Non-strict self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo')) - p = P(BASE, 'foo', 'in', 'spam') + os.path.join(self.base, 'foo')) + p = P(self.base, 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(BASE, 'foo', 'in', 'spam')) - p = P(BASE, '..', 'foo', 'in', 'spam') + os.path.join(self.base, 'foo', 'in', 'spam')) + p = P(self.base, '..', 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), os.path.abspath(os.path.join('foo', 'in', 'spam'))) # These are all relative symlinks. - p = P(BASE, 'dirB', 'fileB') + p = P(self.base, 'dirB', 'fileB') self._check_resolve_relative(p, p) - p = P(BASE, 'linkA') - self._check_resolve_relative(p, P(BASE, 'fileA')) - p = P(BASE, 'dirA', 'linkC', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) - p = P(BASE, 'dirB', 'linkD', 'fileB') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB')) + p = P(self.base, 'linkA') + self._check_resolve_relative(p, P(self.base, 'fileA')) + p = P(self.base, 'dirA', 'linkC', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) + p = P(self.base, 'dirB', 'linkD', 'fileB') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB')) # Non-strict - p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in', + p = P(self.base, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in', 'spam'), False) - p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') + p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') if os.name == 'nt' and isinstance(p, pathlib.Path): # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. - self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in', + self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in', 'spam'), False) else: # In Posix, if linkY points to dirB, 'dirA/linkY/..' # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) # Now create absolute symlinks. d = self.tempdir() - P(BASE, 'dirA', 'linkX').symlink_to(d) - P(BASE, str(d), 'linkY').symlink_to(join('dirB')) - p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB') - self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB')) + P(self.base, 'dirA', 'linkX').symlink_to(d) + P(self.base, str(d), 'linkY').symlink_to(self.pathmod.join(self.base, 'dirB')) + p = P(self.base, 'dirA', 'linkX', 'linkY', 'fileB') + self._check_resolve_absolute(p, P(self.base, 'dirB', 'fileB')) # Non-strict - p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') - self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'), + p = P(self.base, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam') + self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'), False) - p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') + p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') if os.name == 'nt' and isinstance(p, pathlib.Path): # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. @@ -1447,13 +1440,13 @@ def test_resolve_common(self): else: # In Posix, if linkY points to dirB, 'dirA/linkY/..' # resolves to 'dirB/..' first before resolving to parent of dirB. - self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) + self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False) 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") - p = self.cls(BASE) + p = self.cls(self.base) p.joinpath('0').symlink_to('.', target_is_directory=True) p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) @@ -1476,30 +1469,30 @@ def test_resolve_loop(self): if os.name == 'nt' and issubclass(self.cls, pathlib.Path): self.skipTest("symlink loops work differently with concrete Windows paths") # Loops with relative symlinks. - self.cls(BASE, 'linkX').symlink_to('linkX/inside') - self._check_symlink_loop(BASE, 'linkX') - self.cls(BASE, 'linkY').symlink_to('linkY') - self._check_symlink_loop(BASE, 'linkY') - self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ') - self._check_symlink_loop(BASE, 'linkZ') + self.cls(self.base, 'linkX').symlink_to('linkX/inside') + self._check_symlink_loop(self.base, 'linkX') + self.cls(self.base, 'linkY').symlink_to('linkY') + self._check_symlink_loop(self.base, 'linkY') + self.cls(self.base, 'linkZ').symlink_to('linkZ/../linkZ') + self._check_symlink_loop(self.base, 'linkZ') # Non-strict - p = self.cls(BASE, 'linkZ', 'foo') + p = self.cls(self.base, 'linkZ', 'foo') self.assertEqual(p.resolve(strict=False), p) # Loops with absolute symlinks. - self.cls(BASE, 'linkU').symlink_to(join('linkU/inside')) - self._check_symlink_loop(BASE, 'linkU') - self.cls(BASE, 'linkV').symlink_to(join('linkV')) - self._check_symlink_loop(BASE, 'linkV') - self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW')) - self._check_symlink_loop(BASE, 'linkW') + self.cls(self.base, 'linkU').symlink_to(self.pathmod.join(self.base, 'linkU/inside')) + self._check_symlink_loop(self.base, 'linkU') + self.cls(self.base, 'linkV').symlink_to(self.pathmod.join(self.base, 'linkV')) + self._check_symlink_loop(self.base, 'linkV') + self.cls(self.base, 'linkW').symlink_to(self.pathmod.join(self.base, 'linkW/../linkW')) + self._check_symlink_loop(self.base, 'linkW') # Non-strict - q = self.cls(BASE, 'linkW', 'foo') + q = self.cls(self.base, 'linkW', 'foo') self.assertEqual(q.resolve(strict=False), q) def test_stat(self): - statA = self.cls(BASE).joinpath('fileA').stat() - statB = self.cls(BASE).joinpath('dirB', 'fileB').stat() - statC = self.cls(BASE).joinpath('dirC').stat() + statA = self.cls(self.base).joinpath('fileA').stat() + statB = self.cls(self.base).joinpath('dirB', 'fileB').stat() + statC = self.cls(self.base).joinpath('dirC').stat() # st_mode: files are the same, directory differs. self.assertIsInstance(statA.st_mode, int) self.assertEqual(statA.st_mode, statB.st_mode) @@ -1519,29 +1512,29 @@ def test_stat(self): def test_stat_no_follow_symlinks(self): if not self.can_symlink: self.skipTest("symlinks required") - p = self.cls(BASE) / 'linkA' + p = self.cls(self.base) / 'linkA' st = p.stat() self.assertNotEqual(st, p.stat(follow_symlinks=False)) def test_stat_no_follow_symlinks_nosymlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' st = p.stat() self.assertEqual(st, p.stat(follow_symlinks=False)) def test_lstat(self): if not self.can_symlink: self.skipTest("symlinks required") - p = self.cls(BASE)/ 'linkA' + p = self.cls(self.base)/ 'linkA' st = p.stat() self.assertNotEqual(st, p.lstat()) def test_lstat_nosymlink(self): - p = self.cls(BASE) / 'fileA' + p = self.cls(self.base) / 'fileA' st = p.stat() self.assertEqual(st, p.lstat()) def test_is_dir(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'dirA').is_dir()) self.assertFalse((P / 'fileA').is_dir()) self.assertFalse((P / 'non-existing').is_dir()) @@ -1554,7 +1547,7 @@ def test_is_dir(self): self.assertFalse((P / 'dirA\x00').is_dir()) def test_is_dir_no_follow_symlinks(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False)) self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False)) self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False)) @@ -1567,7 +1560,7 @@ def test_is_dir_no_follow_symlinks(self): self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False)) def test_is_file(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'fileA').is_file()) self.assertFalse((P / 'dirA').is_file()) self.assertFalse((P / 'non-existing').is_file()) @@ -1580,7 +1573,7 @@ def test_is_file(self): self.assertFalse((P / 'fileA\x00').is_file()) def test_is_file_no_follow_symlinks(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertTrue((P / 'fileA').is_file(follow_symlinks=False)) self.assertFalse((P / 'dirA').is_file(follow_symlinks=False)) self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False)) @@ -1593,7 +1586,7 @@ def test_is_file_no_follow_symlinks(self): self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False)) def test_is_mount(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_mount()) self.assertFalse((P / 'dirA').is_mount()) self.assertFalse((P / 'non-existing').is_mount()) @@ -1602,7 +1595,7 @@ def test_is_mount(self): self.assertFalse((P / 'linkA').is_mount()) def test_is_symlink(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_symlink()) self.assertFalse((P / 'dirA').is_symlink()) self.assertFalse((P / 'non-existing').is_symlink()) @@ -1618,7 +1611,7 @@ def test_is_symlink(self): self.assertIs((P / 'linkA\x00').is_file(), False) def test_is_junction_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_junction()) self.assertFalse((P / 'dirA').is_junction()) self.assertFalse((P / 'non-existing').is_junction()) @@ -1627,7 +1620,7 @@ def test_is_junction_false(self): self.assertFalse((P / 'fileA\x00').is_junction()) def test_is_fifo_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_fifo()) self.assertFalse((P / 'dirA').is_fifo()) self.assertFalse((P / 'non-existing').is_fifo()) @@ -1636,7 +1629,7 @@ def test_is_fifo_false(self): self.assertIs((P / 'fileA\x00').is_fifo(), False) def test_is_socket_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_socket()) self.assertFalse((P / 'dirA').is_socket()) self.assertFalse((P / 'non-existing').is_socket()) @@ -1645,7 +1638,7 @@ def test_is_socket_false(self): self.assertIs((P / 'fileA\x00').is_socket(), False) def test_is_block_device_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_block_device()) self.assertFalse((P / 'dirA').is_block_device()) self.assertFalse((P / 'non-existing').is_block_device()) @@ -1654,7 +1647,7 @@ def test_is_block_device_false(self): self.assertIs((P / 'fileA\x00').is_block_device(), False) def test_is_char_device_false(self): - P = self.cls(BASE) + P = self.cls(self.base) self.assertFalse((P / 'fileA').is_char_device()) self.assertFalse((P / 'dirA').is_char_device()) self.assertFalse((P / 'non-existing').is_char_device()) @@ -1663,7 +1656,7 @@ def test_is_char_device_false(self): self.assertIs((P / 'fileA\x00').is_char_device(), False) def test_pickling_common(self): - p = self.cls(BASE, 'fileA') + p = self.cls(self.base, 'fileA') for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): dumped = pickle.dumps(p, proto) pp = pickle.loads(dumped) @@ -1683,7 +1676,7 @@ def _check_complex_symlinks(self, link0_target): self.skipTest("symlinks required") # Test solving a non-looping chain of symlinks (issue #19887). - P = self.cls(BASE) + P = self.cls(self.base) P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) @@ -1692,16 +1685,16 @@ def _check_complex_symlinks(self, link0_target): # Resolve absolute paths. p = (P / 'link0').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link1').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link2').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = (P / 'link3').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) # Resolve relative paths. try: @@ -1709,25 +1702,25 @@ def _check_complex_symlinks(self, link0_target): except pathlib.UnsupportedOperation: return old_path = os.getcwd() - os.chdir(BASE) + os.chdir(self.base) try: p = self.cls('link0').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link1').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link2').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) p = self.cls('link3').resolve() self.assertEqual(p, P) - self.assertEqualNormCase(str(p), BASE) + self.assertEqualNormCase(str(p), self.base) finally: os.chdir(old_path) def test_complex_symlinks_absolute(self): - self._check_complex_symlinks(BASE) + self._check_complex_symlinks(self.base) def test_complex_symlinks_relative(self): self._check_complex_symlinks('.') @@ -1750,7 +1743,7 @@ def setUpWalk(self): # broken_link2 # TEST2/ # tmp4 a lone file - self.walk_path = self.cls(BASE, "TEST1") + self.walk_path = self.cls(self.base, "TEST1") self.sub1_path = self.walk_path / "SUB1" self.sub11_path = self.sub1_path / "SUB11" self.sub2_path = self.walk_path / "SUB2" @@ -1758,8 +1751,8 @@ def setUpWalk(self): tmp2_path = self.sub1_path / "tmp2" tmp3_path = self.sub2_path / "tmp3" self.link_path = self.sub2_path / "link" - t2_path = self.cls(BASE, "TEST2") - tmp4_path = self.cls(BASE, "TEST2", "tmp4") + t2_path = self.cls(self.base, "TEST2") + tmp4_path = self.cls(self.base, "TEST2", "tmp4") broken_link_path = self.sub2_path / "broken_link" broken_link2_path = self.sub2_path / "broken_link2" @@ -1886,7 +1879,7 @@ def test_walk_above_recursion_limit(self): recursion_limit = 40 # directory_depth > recursion_limit directory_depth = recursion_limit + 10 - base = self.cls(BASE, 'deep') + base = self.cls(self.base, 'deep') path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) From cde1335485b7bffb12c378d230ae48ad77d76ab1 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 16 Dec 2023 19:51:25 -0500 Subject: [PATCH 282/442] IDLE: Add util and stub example comments (#113222) --- Lib/idlelib/idle_test/example_stub.pyi | 2 ++ Lib/idlelib/util.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/idle_test/example_stub.pyi b/Lib/idlelib/idle_test/example_stub.pyi index a9811a78d10a6e..17b58010a9d8de 100644 --- a/Lib/idlelib/idle_test/example_stub.pyi +++ b/Lib/idlelib/idle_test/example_stub.pyi @@ -1,2 +1,4 @@ +" Example to test recognition of .pyi file as Python source code. + class Example: def method(self, argument1: str, argument2: list[int]) -> None: ... diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index 5ac69a7b94cb56..a7ae74b0579004 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -13,9 +13,9 @@ * warning stuff (pyshell, run). """ -# .pyw is for Windows; .pyi is for stub files. -py_extensions = ('.py', '.pyw', '.pyi') # Order needed for open/save dialogs. - +# .pyw is for Windows; .pyi is for typing stub files. +# The extension order is needed for iomenu open/save dialogs. +py_extensions = ('.py', '.pyw', '.pyi') if __name__ == '__main__': from unittest import main From 48c907a15ceae7202fcfeb435943addff896c42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Sun, 17 Dec 2023 06:19:05 +0100 Subject: [PATCH 283/442] gh-113119 fix environment handling in subprocess.Popen when posix_spawn is used (#113120) * Allow posix_spawn to inherit environment form parent environ variable. With this change, posix_spawn call can behave similarly to execv with regards to environments when used in subprocess functions. --- Doc/library/os.rst | 6 +++++- Doc/whatsnew/3.13.rst | 4 ++++ Lib/subprocess.py | 3 --- ...023-12-15-18-13-59.gh-issue-113119.al-569.rst | 2 ++ Modules/posixmodule.c | 16 ++++++++++------ 5 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2ff0b73560ac4c..a079f1fa604bf4 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4570,7 +4570,8 @@ written in Python, such as a mail server's external command delivery program. Most users should use :func:`subprocess.run` instead of :func:`posix_spawn`. The positional-only arguments *path*, *args*, and *env* are similar to - :func:`execve`. + :func:`execve`. *env* is allowed to be ``None``, in which case current + process' environment is used. The *path* parameter is the path to the executable file. The *path* should contain a directory. Use :func:`posix_spawnp` to pass an executable file @@ -4645,6 +4646,9 @@ written in Python, such as a mail server's external command delivery program. .. versionadded:: 3.8 + .. versionchanged:: 3.13 + *env* parameter accepts ``None``. + .. availability:: Unix, not Emscripten, not WASI. .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ce4f66b97a0be4..4af023566ff0bc 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -289,6 +289,10 @@ os ``False`` on Windows. (Contributed by Serhiy Storchaka in :gh:`59616`) +* :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned + process use the current process environment. + (Contributed by Jakub Kulik in :gh:`113119`.) + pathlib ------- diff --git a/Lib/subprocess.py b/Lib/subprocess.py index d6edd1a9807d1b..1919ea4bddeeda 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1756,9 +1756,6 @@ def _posix_spawn(self, args, executable, env, restore_signals, c2pread, c2pwrite, errread, errwrite): """Execute program using os.posix_spawn().""" - if env is None: - env = os.environ - kwargs = {} if restore_signals: # See _Py_RestoreSignals() in Python/pylifecycle.c diff --git a/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst b/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst new file mode 100644 index 00000000000000..94087b00515e97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-18-13-59.gh-issue-113119.al-569.rst @@ -0,0 +1,2 @@ +:func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned +process use the current process environment. Patch by Jakub Kulik. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b464a28e63b8ac..2dc5d7d81db973 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7129,9 +7129,9 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a return NULL; } - if (!PyMapping_Check(env)) { + if (!PyMapping_Check(env) && env != Py_None) { PyErr_Format(PyExc_TypeError, - "%s: environment must be a mapping object", func_name); + "%s: environment must be a mapping object or None", func_name); goto exit; } @@ -7145,9 +7145,13 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } - envlist = parse_envlist(env, &envc); - if (envlist == NULL) { - goto exit; + if (env == Py_None) { + envlist = environ; + } else { + envlist = parse_envlist(env, &envc); + if (envlist == NULL) { + goto exit; + } } if (file_actions != NULL && file_actions != Py_None) { @@ -7210,7 +7214,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a if (attrp) { (void)posix_spawnattr_destroy(attrp); } - if (envlist) { + if (envlist && envlist != environ) { free_string_array(envlist, envc); } if (argvlist) { From 21d52995ea490328edf9be3ba072821cd445dd30 Mon Sep 17 00:00:00 2001 From: Taylor Packard <3.t.packard@gmail.com> Date: Sun, 17 Dec 2023 06:14:21 -0500 Subject: [PATCH 284/442] gh-112890: `unittest` Test Discovery page updated "`unittest` dropped the namspace packages support" (GH-113195) --- Doc/library/unittest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 9aad96bdf11bf8..70b4c84c05f818 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -346,8 +346,8 @@ the `load_tests protocol`_. ``python -m unittest discover -s root/namespace -t root``). .. versionchanged:: 3.11 - Python 3.11 dropped the :term:`namespace packages ` - support. It has been broken since Python 3.7. Start directory and + :mod:`unittest` dropped the :term:`namespace packages ` + support in Python 3.11. It has been broken since Python 3.7. Start directory and subdirectories containing tests must be regular package that have ``__init__.py`` file. From cfa25fe3e39e09612a6ba8409c46cf35a091de3c Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Sun, 17 Dec 2023 12:52:26 -0600 Subject: [PATCH 285/442] gh-113149: Improve error message when JSON has trailing comma (GH-113227) --- Lib/json/decoder.py | 7 +++++++ Lib/test/test_json/test_fail.py | 8 +++++--- .../2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst | 2 ++ Modules/_json.c | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py index c5d9ae2d0d5d04..d69a45d6793069 100644 --- a/Lib/json/decoder.py +++ b/Lib/json/decoder.py @@ -200,10 +200,13 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook, break elif nextchar != ',': raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) + comma_idx = end - 1 end = _w(s, end).end() nextchar = s[end:end + 1] end += 1 if nextchar != '"': + if nextchar == '}': + raise JSONDecodeError("Illegal trailing comma before end of object", s, comma_idx) raise JSONDecodeError( "Expecting property name enclosed in double quotes", s, end - 1) if object_pairs_hook is not None: @@ -240,13 +243,17 @@ def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): break elif nextchar != ',': raise JSONDecodeError("Expecting ',' delimiter", s, end - 1) + comma_idx = end - 1 try: if s[end] in _ws: end += 1 if s[end] in _ws: end = _w(s, end + 1).end() + nextchar = s[end:end + 1] except IndexError: pass + if nextchar == ']': + raise JSONDecodeError("Illegal trailing comma before end of array", s, comma_idx) return values, end diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py index efc982e8b0eb04..d6bce605e21463 100644 --- a/Lib/test/test_json/test_fail.py +++ b/Lib/test/test_json/test_fail.py @@ -143,11 +143,11 @@ def test_unexpected_data(self): ('{"spam":[}', 'Expecting value', 9), ('[42:', "Expecting ',' delimiter", 3), ('[42 "spam"', "Expecting ',' delimiter", 4), - ('[42,]', 'Expecting value', 4), + ('[42,]', "Illegal trailing comma before end of array", 3), ('{"spam":[42}', "Expecting ',' delimiter", 11), ('["]', 'Unterminated string starting at', 1), ('["spam":', "Expecting ',' delimiter", 7), - ('["spam",]', 'Expecting value', 8), + ('["spam",]', "Illegal trailing comma before end of array", 7), ('{:', 'Expecting property name enclosed in double quotes', 1), ('{,', 'Expecting property name enclosed in double quotes', 1), ('{42', 'Expecting property name enclosed in double quotes', 1), @@ -159,7 +159,9 @@ def test_unexpected_data(self): ('[{"spam":]', 'Expecting value', 9), ('{"spam":42 "ham"', "Expecting ',' delimiter", 11), ('[{"spam":42]', "Expecting ',' delimiter", 11), - ('{"spam":42,}', 'Expecting property name enclosed in double quotes', 11), + ('{"spam":42,}', "Illegal trailing comma before end of object", 10), + ('{"spam":42 , }', "Illegal trailing comma before end of object", 11), + ('[123 , ]', "Illegal trailing comma before end of array", 6), ] for data, msg, idx in test_cases: with self.assertRaises(self.JSONDecodeError) as cm: diff --git a/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst b/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst new file mode 100644 index 00000000000000..0faa67fefabeca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-23-56-42.gh-issue-113149.7LWgTS.rst @@ -0,0 +1,2 @@ +Improve error message when a JSON array or object contains a trailing comma. +Patch by Carson Radtke. diff --git a/Modules/_json.c b/Modules/_json.c index 0b1bfe34ad9304..24b292ce70e5eb 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -662,6 +662,7 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss PyObject *key = NULL; int has_pairs_hook = (s->object_pairs_hook != Py_None); Py_ssize_t next_idx; + Py_ssize_t comma_idx; str = PyUnicode_DATA(pystr); kind = PyUnicode_KIND(pystr); @@ -741,10 +742,16 @@ _parse_object_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ss raise_errmsg("Expecting ',' delimiter", pystr, idx); goto bail; } + comma_idx = idx; idx++; /* skip whitespace after , delimiter */ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++; + + if (idx <= end_idx && PyUnicode_READ(kind, str, idx) == '}') { + raise_errmsg("Illegal trailing comma before end of object", pystr, comma_idx); + goto bail; + } } } @@ -785,6 +792,7 @@ _parse_array_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssi PyObject *val = NULL; PyObject *rval; Py_ssize_t next_idx; + Py_ssize_t comma_idx; rval = PyList_New(0); if (rval == NULL) @@ -822,10 +830,16 @@ _parse_array_unicode(PyScannerObject *s, PyObject *memo, PyObject *pystr, Py_ssi raise_errmsg("Expecting ',' delimiter", pystr, idx); goto bail; } + comma_idx = idx; idx++; /* skip whitespace after , */ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++; + + if (idx <= end_idx && PyUnicode_READ(kind, str, idx) == ']') { + raise_errmsg("Illegal trailing comma before end of array", pystr, comma_idx); + goto bail; + } } } From 32d87a88994c131a9f3857d01ae7c07918577a55 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 17 Dec 2023 21:23:14 +0200 Subject: [PATCH 286/442] Docs: Add label to grammar spec for linking from PEPs (#113235) --- Doc/reference/grammar.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/reference/grammar.rst b/Doc/reference/grammar.rst index bc1db7b039cd5a..b9cca4444c9141 100644 --- a/Doc/reference/grammar.rst +++ b/Doc/reference/grammar.rst @@ -1,3 +1,5 @@ +.. _full-grammar-specification: + Full Grammar specification ========================== From 2b93f5224216d10f8119373e72b5c2b3984e0af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kul=C3=ADk?= Date: Sun, 17 Dec 2023 22:34:57 +0100 Subject: [PATCH 287/442] gh-113117: Support posix_spawn in subprocess.Popen with close_fds=True (#113118) Add support for `os.POSIX_SPAWN_CLOSEFROM` and `posix_spawn_file_actions_addclosefrom_np` and have the `subprocess` module use them when available. This means `posix_spawn` can now be used in the default `close_fds=True` situation on many platforms. Co-authored-by: Gregory P. Smith [Google LLC] --- Doc/library/os.rst | 15 +++++++++-- Doc/whatsnew/3.13.rst | 26 +++++++++++++++++++ Lib/subprocess.py | 11 +++++--- Lib/test/test_subprocess.py | 5 ++++ ...-12-16-10-58-34.gh-issue-113117.0zF7bH.rst | 4 +++ Modules/posixmodule.c | 24 +++++++++++++++++ configure | 6 +++++ configure.ac | 1 + pyconfig.h.in | 4 +++ 9 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index a079f1fa604bf4..1138cc1f249ee7 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4601,10 +4601,17 @@ written in Python, such as a mail server's external command delivery program. Performs ``os.dup2(fd, new_fd)``. + .. data:: POSIX_SPAWN_CLOSEFROM + + (``os.POSIX_SPAWN_CLOSEFROM``, *fd*) + + Performs ``os.closerange(fd, INF)``. + These tuples correspond to the C library :c:func:`!posix_spawn_file_actions_addopen`, - :c:func:`!posix_spawn_file_actions_addclose`, and - :c:func:`!posix_spawn_file_actions_adddup2` API calls used to prepare + :c:func:`!posix_spawn_file_actions_addclose`, + :c:func:`!posix_spawn_file_actions_adddup2`, and + :c:func:`!posix_spawn_file_actions_addclosefrom_np` API calls used to prepare for the :c:func:`!posix_spawn` call itself. The *setpgroup* argument will set the process group of the child to the value @@ -4649,6 +4656,10 @@ written in Python, such as a mail server's external command delivery program. .. versionchanged:: 3.13 *env* parameter accepts ``None``. + .. versionchanged:: 3.13 + ``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where + :c:func:`!posix_spawn_file_actions_addclosefrom_np` exists. + .. availability:: Unix, not Emscripten, not WASI. .. function:: posix_spawnp(path, argv, env, *, file_actions=None, \ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4af023566ff0bc..2c869cbe11396b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -293,6 +293,11 @@ os process use the current process environment. (Contributed by Jakub Kulik in :gh:`113119`.) +* :func:`os.posix_spawn` gains an :attr:`os.POSIX_SPAWN_CLOSEFROM` attribute for + use in ``file_actions=`` on platforms that support + :c:func:`!posix_spawn_file_actions_addclosefrom_np`. + (Contributed by Jakub Kulik in :gh:`113117`.) + pathlib ------- @@ -342,6 +347,21 @@ sqlite3 object is not :meth:`closed ` explicitly. (Contributed by Erlend E. Aasland in :gh:`105539`.) +subprocess +---------- + +* The :mod:`subprocess` module now uses the :func:`os.posix_spawn` function in + more situations. Notably in the default case of ``close_fds=True`` on more + recent versions of platforms including Linux, FreeBSD, and Solaris where the + C library provides :c:func:`!posix_spawn_file_actions_addclosefrom_np`. + On Linux this should perform similar to our existing Linux :c:func:`!vfork` + based code. A private control knob :attr:`!subprocess._USE_POSIX_SPAWN` can + be set to ``False`` if you need to force :mod:`subprocess` not to ever use + :func:`os.posix_spawn`. Please report your reason and platform details in + the CPython issue tracker if you set this so that we can improve our API + selection logic for everyone. + (Contributed by Jakub Kulik in :gh:`113117`.) + sys --- @@ -415,6 +435,12 @@ Optimizations * :func:`textwrap.indent` is now ~30% faster than before for large input. (Contributed by Inada Naoki in :gh:`107369`.) +* The :mod:`subprocess` module uses :func:`os.posix_spawn` in more situations + including the default where ``close_fds=True`` on many modern platforms. This + should provide a noteworthy performance increase launching processes on + FreeBSD and Solaris. See the ``subprocess`` section above for details. + (Contributed by Jakub Kulik in :gh:`113117`.) + Deprecated ========== diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1919ea4bddeeda..d5bd9a9e31aa04 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -748,6 +748,7 @@ def _use_posix_spawn(): # guarantee the given libc/syscall API will be used. _USE_POSIX_SPAWN = _use_posix_spawn() _USE_VFORK = True +_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM') class Popen: @@ -1751,7 +1752,7 @@ def _get_handles(self, stdin, stdout, stderr): errread, errwrite) - def _posix_spawn(self, args, executable, env, restore_signals, + def _posix_spawn(self, args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): @@ -1777,6 +1778,10 @@ def _posix_spawn(self, args, executable, env, restore_signals, ): if fd != -1: file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) + + if close_fds: + file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3)) + if file_actions: kwargs['file_actions'] = file_actions @@ -1824,7 +1829,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, if (_USE_POSIX_SPAWN and os.path.dirname(executable) and preexec_fn is None - and not close_fds + and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM) and not pass_fds and cwd is None and (p2cread == -1 or p2cread > 2) @@ -1836,7 +1841,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, and gids is None and uid is None and umask < 0): - self._posix_spawn(args, executable, env, restore_signals, + self._posix_spawn(args, executable, env, restore_signals, close_fds, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 5eeea54fd55f1a..6d3228bf92f8ca 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3348,6 +3348,7 @@ def exit_handler(): @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), "vfork() not enabled by configure.") @mock.patch("subprocess._fork_exec") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) def test__use_vfork(self, mock_fork_exec): self.assertTrue(subprocess._USE_VFORK) # The default value regardless. mock_fork_exec.side_effect = RuntimeError("just testing args") @@ -3366,9 +3367,13 @@ def test__use_vfork(self, mock_fork_exec): @unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"), "vfork() not enabled by configure.") @unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.") + @mock.patch("subprocess._USE_POSIX_SPAWN", new=False) def test_vfork_used_when_expected(self): # This is a performance regression test to ensure we default to using # vfork() when possible. + # Technically this test could pass when posix_spawn is used as well + # because libc tends to implement that internally using vfork. But + # that'd just be testing a libc+kernel implementation detail. strace_binary = "/usr/bin/strace" # The only system calls we are interested in. strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group" diff --git a/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst b/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst new file mode 100644 index 00000000000000..718226a0021efe --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-10-58-34.gh-issue-113117.0zF7bH.rst @@ -0,0 +1,4 @@ +The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function +with ``close_fds=True`` on platforms where +``posix_spawn_file_actions_addclosefrom_np`` is available. +Patch by Jakub Kulik. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 2dc5d7d81db973..8ffe0f5de1e7bd 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -6834,6 +6834,9 @@ enum posix_spawn_file_actions_identifier { POSIX_SPAWN_OPEN, POSIX_SPAWN_CLOSE, POSIX_SPAWN_DUP2 +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + ,POSIX_SPAWN_CLOSEFROM +#endif }; #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) @@ -7074,6 +7077,24 @@ parse_file_actions(PyObject *file_actions, } break; } +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + case POSIX_SPAWN_CLOSEFROM: { + int fd; + if (!PyArg_ParseTuple(file_action, "Oi" + ";A closefrom file_action tuple must have 2 elements", + &tag_obj, &fd)) + { + goto fail; + } + errno = posix_spawn_file_actions_addclosefrom_np(file_actionsp, + fd); + if (errno) { + posix_error(); + goto fail; + } + break; + } +#endif default: { PyErr_SetString(PyExc_TypeError, "Unknown file_actions identifier"); @@ -16774,6 +16795,9 @@ all_ins(PyObject *m) if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1; if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1; if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1; +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + if (PyModule_AddIntMacro(m, POSIX_SPAWN_CLOSEFROM)) return -1; +#endif #endif #if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN) diff --git a/configure b/configure index 668a0efd77db0e..7e50abc29d0c1a 100755 --- a/configure +++ b/configure @@ -17779,6 +17779,12 @@ if test "x$ac_cv_func_posix_spawnp" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" +if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes +then : + printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pread" "ac_cv_func_pread" if test "x$ac_cv_func_pread" = xyes diff --git a/configure.ac b/configure.ac index 020553abd71b4f..e064848af9ed1b 100644 --- a/configure.ac +++ b/configure.ac @@ -4757,6 +4757,7 @@ AC_CHECK_FUNCS([ \ 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 \ + posix_spawn_file_actions_addclosefrom_np \ pread preadv preadv2 pthread_condattr_setclock pthread_init pthread_kill \ pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 9c429c03722383..d8a9f68951afbd 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -905,6 +905,10 @@ /* Define to 1 if you have the `posix_spawnp' function. */ #undef HAVE_POSIX_SPAWNP +/* Define to 1 if you have the `posix_spawn_file_actions_addclosefrom_np' + function. */ +#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP + /* Define to 1 if you have the `pread' function. */ #undef HAVE_PREAD From f428c4dafbfa2425ea056e7f2ed2ea45fa90be87 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Dec 2023 08:57:45 +0200 Subject: [PATCH 288/442] gh-101100: Fix Sphinx warnings in library/tarfile.rst (#113237) Fix Sphinx warnings in library/tarfile.rst --- Doc/library/tarfile.rst | 67 ++++++++++++++++++++++++++++++++++------- Doc/tools/.nitignore | 1 - 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index 3e5723a66780ca..f4e83d64bb1580 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -116,7 +116,7 @@ Some facts and figures: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will be done on the file. If given, *fileobj* may be any object that has a - :meth:`read` or :meth:`write` method (depending on the *mode*). *bufsize* + :meth:`~io.TextIOBase.read` or :meth:`~io.TextIOBase.write` method (depending on the *mode*). *bufsize* specifies the blocksize and defaults to ``20 * 512`` bytes. Use this variant in combination with e.g. ``sys.stdin``, a socket :term:`file object` or a tape device. However, such a :class:`TarFile` object is limited in that it does @@ -255,6 +255,51 @@ The following constants are available at the module level: The default character encoding: ``'utf-8'`` on Windows, the value returned by :func:`sys.getfilesystemencoding` otherwise. +.. data:: REGTYPE + AREGTYPE + + A regular file :attr:`~TarInfo.type`. + +.. data:: LNKTYPE + + A link (inside tarfile) :attr:`~TarInfo.type`. + +.. data:: SYMTYPE + + A symbolic link :attr:`~TarInfo.type`. + +.. data:: CHRTYPE + + A character special device :attr:`~TarInfo.type`. + +.. data:: BLKTYPE + + A block special device :attr:`~TarInfo.type`. + +.. data:: DIRTYPE + + A directory :attr:`~TarInfo.type`. + +.. data:: FIFOTYPE + + A FIFO special device :attr:`~TarInfo.type`. + +.. data:: CONTTYPE + + A contiguous file :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_LONGNAME + + A GNU tar longname :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_LONGLINK + + A GNU tar longlink :attr:`~TarInfo.type`. + +.. data:: GNUTYPE_SPARSE + + A GNU tar sparse file :attr:`~TarInfo.type`. + Each of the following constants defines a tar archive format that the :mod:`tarfile` module is able to create. See section :ref:`tar-formats` for @@ -325,7 +370,7 @@ be finalized; only the internally used file object will be closed. See the *name* is the pathname of the archive. *name* may be a :term:`path-like object`. It can be omitted if *fileobj* is given. - In this case, the file object's :attr:`name` attribute is used if it exists. + In this case, the file object's :attr:`!name` attribute is used if it exists. *mode* is either ``'r'`` to read from an existing archive, ``'a'`` to append data to an existing file, ``'w'`` to create a new file overwriting an existing @@ -359,7 +404,7 @@ be finalized; only the internally used file object will be closed. See the messages). The messages are written to ``sys.stderr``. *errorlevel* controls how extraction errors are handled, - see :attr:`the corresponding attribute <~TarFile.errorlevel>`. + see :attr:`the corresponding attribute `. The *encoding* and *errors* arguments define the character encoding to be used for reading or writing the archive and how conversion errors are going @@ -645,8 +690,8 @@ It does *not* contain the file's data itself. :meth:`~TarFile.getmember`, :meth:`~TarFile.getmembers` and :meth:`~TarFile.gettarinfo`. -Modifying the objects returned by :meth:`~!TarFile.getmember` or -:meth:`~!TarFile.getmembers` will affect all subsequent +Modifying the objects returned by :meth:`~TarFile.getmember` or +:meth:`~TarFile.getmembers` will affect all subsequent operations on the archive. For cases where this is unwanted, you can use :mod:`copy.copy() ` or call the :meth:`~TarInfo.replace` method to create a modified copy in one step. @@ -795,8 +840,8 @@ A ``TarInfo`` object has the following public data attributes: A dictionary containing key-value pairs of an associated pax extended header. -.. method:: TarInfo.replace(name=..., mtime=..., mode=..., linkname=..., - uid=..., gid=..., uname=..., gname=..., +.. method:: TarInfo.replace(name=..., mtime=..., mode=..., linkname=..., \ + uid=..., gid=..., uname=..., gname=..., \ deep=True) .. versionadded:: 3.12 @@ -816,7 +861,7 @@ A :class:`TarInfo` object also provides some convenient query methods: .. method:: TarInfo.isfile() - Return :const:`True` if the :class:`Tarinfo` object is a regular file. + Return :const:`True` if the :class:`TarInfo` object is a regular file. .. method:: TarInfo.isreg() @@ -952,7 +997,7 @@ reused in custom filters: path (after following symlinks) would end up outside the destination. This raises :class:`~tarfile.OutsideDestinationError`. - Clear high mode bits (setuid, setgid, sticky) and group/other write bits - (:const:`~stat.S_IWGRP`|:const:`~stat.S_IWOTH`). + (:const:`~stat.S_IWGRP` | :const:`~stat.S_IWOTH`). Return the modified ``TarInfo`` member. @@ -977,9 +1022,9 @@ reused in custom filters: - For regular files, including hard links: - Set the owner read and write permissions - (:const:`~stat.S_IRUSR`|:const:`~stat.S_IWUSR`). + (:const:`~stat.S_IRUSR` | :const:`~stat.S_IWUSR`). - Remove the group & other executable permission - (:const:`~stat.S_IXGRP`|:const:`~stat.S_IXOTH`) + (:const:`~stat.S_IXGRP` | :const:`~stat.S_IXOTH`) if the owner doesn’t have it (:const:`~stat.S_IXUSR`). - For other files (directories), set ``mode`` to ``None``, so diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 23d4d3e522b455..8b0fc13832d51a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -86,7 +86,6 @@ Doc/library/ssl.rst Doc/library/stdtypes.rst Doc/library/string.rst Doc/library/subprocess.rst -Doc/library/tarfile.rst Doc/library/termios.rst Doc/library/test.rst Doc/library/tkinter.rst From 4a24bf9a13a7cf055113c04bde0874186722c62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 18 Dec 2023 11:21:46 +0100 Subject: [PATCH 289/442] gh-113246: Updated bundled pip to 23.3.2 (gh-113249) Updated bundled pip to 23.3.2 --- Lib/ensurepip/__init__.py | 2 +- ...ne-any.whl => pip-23.3.2-py3-none-any.whl} | Bin 2107242 -> 2109393 bytes ...-12-18-09-47-54.gh-issue-113246.em930H.rst | 1 + Misc/sbom.spdx.json | 10 +++++----- Tools/build/generate_sbom.py | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) rename Lib/ensurepip/_bundled/{pip-23.3.1-py3-none-any.whl => pip-23.3.2-py3-none-any.whl} (94%) create mode 100644 Misc/NEWS.d/next/Library/2023-12-18-09-47-54.gh-issue-113246.em930H.rst diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 21e4ad99a39628..a09bf3201e1fb7 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] _PACKAGE_NAMES = ('pip',) -_PIP_VERSION = "23.3.1" +_PIP_VERSION = "23.3.2" _PROJECTS = [ ("pip", _PIP_VERSION, "py3"), ] diff --git a/Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl b/Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl similarity index 94% rename from Lib/ensurepip/_bundled/pip-23.3.1-py3-none-any.whl rename to Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl index a4faf716b663e61faf22bb58fa57d3b2b0e00299..ae78b8a6ce073797af05cd7a833ff23d14cff185 100644 GIT binary patch delta 62513 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
    @A~_VFkV(@xt`85eltqNKG{^Nq0J4e;H77&X41v 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 Date: Mon, 18 Dec 2023 11:14:40 +0000 Subject: [PATCH 290/442] GH-111485: Test the new cases generator (GH-113252) --- Lib/test/test_generated_cases.py | 114 ++++++++++++--------- Python/executor_cases.c.h | 26 ++--- Python/generated_cases.c.h | 30 +++--- Tools/cases_generator/analyzer.py | 3 +- Tools/cases_generator/cwriter.py | 15 +-- Tools/cases_generator/generators_common.py | 6 +- Tools/cases_generator/stack.py | 2 +- Tools/cases_generator/tier1_generator.py | 8 ++ 8 files changed, 116 insertions(+), 88 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index de96a8764594ba..74cebbe469d794 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -32,6 +32,7 @@ def skip_if_different_mount_drives(): import analysis import formatting from parsing import StackEffect + import tier1_generator def handle_stderr(): @@ -108,13 +109,12 @@ def run_cases_test(self, input: str, expected: str): temp_input.write(analysis.END_MARKER) temp_input.flush() - a = generate_cases.Generator([self.temp_input_filename]) with handle_stderr(): - a.parse() - a.analyze() - if a.errors: - raise RuntimeError(f"Found {a.errors} errors") - a.write_instructions(self.temp_output_filename, False) + tier1_generator.generate_tier1_from_files( + [self.temp_input_filename], + self.temp_output_filename, + False + ) with open(self.temp_output_filename) as temp_output: lines = temp_output.readlines() @@ -163,7 +163,7 @@ def test_inst_one_pop(self): PyObject *value; value = stack_pointer[-1]; spam(); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } """ @@ -182,8 +182,8 @@ def test_inst_one_push(self): INSTRUCTION_STATS(OP); PyObject *res; spam(); - STACK_GROW(1); - stack_pointer[-1] = res; + stack_pointer[0] = res; + stack_pointer += 1; DISPATCH(); } """ @@ -227,8 +227,8 @@ def test_binary_op(self): right = stack_pointer[-1]; left = stack_pointer[-2]; spam(); - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } """ @@ -273,7 +273,6 @@ def test_predictions_and_eval_breaker(self): next_instr += 1; INSTRUCTION_STATS(OP1); PREDICTED(OP1); - static_assert(INLINE_CACHE_ENTRIES_OP1 == 0, "incorrect cache size"); PyObject *arg; PyObject *rest; arg = stack_pointer[-1]; @@ -285,6 +284,7 @@ def test_predictions_and_eval_breaker(self): frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(OP3); + static_assert(INLINE_CACHE_ENTRIES_OP1 == 0, "incorrect cache size"); PyObject *arg; PyObject *res; arg = stack_pointer[-1]; @@ -325,6 +325,7 @@ def test_error_if_plain_with_comment(self): next_instr += 1; INSTRUCTION_STATS(OP); if (cond) goto label; + // Comment is ok DISPATCH(); } """ @@ -347,8 +348,8 @@ def test_error_if_pop(self): right = stack_pointer[-1]; left = stack_pointer[-2]; if (cond) goto pop_2_label; - STACK_SHRINK(1); - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; DISPATCH(); } """ @@ -368,7 +369,7 @@ def test_cache_effect(self): value = stack_pointer[-1]; uint16_t counter = read_u16(&this_instr[1].cache); uint32_t extra = read_u32(&this_instr[2].cache); - STACK_SHRINK(1); + stack_pointer += -1; DISPATCH(); } """ @@ -411,26 +412,26 @@ def test_macro_instruction(self): INSTRUCTION_STATS(OP); PREDICTED(OP); _Py_CODEUNIT *this_instr = next_instr - 6; - static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *arg2; PyObject *res; - // OP1 + // _OP1 right = stack_pointer[-1]; left = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); op1(left, right); } + /* Skip 2 cache entries */ // OP2 arg2 = stack_pointer[-3]; { uint32_t extra = read_u32(&this_instr[4].cache); res = op2(arg2, left, right); } - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; DISPATCH(); } @@ -451,6 +452,7 @@ def test_macro_instruction(self): frame->instr_ptr = next_instr; next_instr += 6; INSTRUCTION_STATS(OP3); + static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *arg2; @@ -459,8 +461,24 @@ def test_macro_instruction(self): left = stack_pointer[-2]; arg2 = stack_pointer[-3]; res = op3(arg2, left, right); - STACK_SHRINK(2); - stack_pointer[-1] = res; + stack_pointer[-3] = res; + stack_pointer += -2; + DISPATCH(); + } + """ + self.run_cases_test(input, output) + def test_unused_caches(self): + input = """ + inst(OP, (unused/1, unused/2 --)) { + body(); + } + """ + output = """ + TARGET(OP) { + frame->instr_ptr = next_instr; + next_instr += 4; + INSTRUCTION_STATS(OP); + body(); DISPATCH(); } """ @@ -519,11 +537,10 @@ def test_array_input(self): PyObject **values; PyObject *below; above = stack_pointer[-1]; - values = stack_pointer - 1 - oparg*2; + values = &stack_pointer[-1 - oparg*2]; below = stack_pointer[-2 - oparg*2]; spam(); - STACK_SHRINK(oparg*2); - STACK_SHRINK(2); + stack_pointer += -2 - oparg*2; DISPATCH(); } """ @@ -543,11 +560,11 @@ def test_array_output(self): PyObject *below; PyObject **values; PyObject *above; - values = stack_pointer - 1; + values = &stack_pointer[-1]; spam(values, oparg); - STACK_GROW(oparg*3); - stack_pointer[-2 - oparg*3] = below; - stack_pointer[-1] = above; + stack_pointer[-2] = below; + stack_pointer[-1 + oparg*3] = above; + stack_pointer += oparg*3; DISPATCH(); } """ @@ -566,10 +583,10 @@ def test_array_input_output(self): INSTRUCTION_STATS(OP); PyObject **values; PyObject *above; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; spam(values, oparg); - STACK_GROW(1); - stack_pointer[-1] = above; + stack_pointer[0] = above; + stack_pointer += 1; DISPATCH(); } """ @@ -588,11 +605,10 @@ def test_array_error_if(self): INSTRUCTION_STATS(OP); PyObject **values; PyObject *extra; - values = stack_pointer - oparg; + values = &stack_pointer[-oparg]; extra = stack_pointer[-1 - oparg]; - if (oparg == 0) { STACK_SHRINK(oparg); goto pop_1_somewhere; } - STACK_SHRINK(oparg); - STACK_SHRINK(1); + if (oparg == 0) { stack_pointer += -1 - oparg; goto somewhere; } + stack_pointer += -1 - oparg; DISPATCH(); } """ @@ -616,14 +632,13 @@ def test_cond_effect(self): PyObject *output = NULL; PyObject *zz; cc = stack_pointer[-1]; - if ((oparg & 1) == 1) { input = stack_pointer[-1 - ((oparg & 1) == 1 ? 1 : 0)]; } - aa = stack_pointer[-2 - ((oparg & 1) == 1 ? 1 : 0)]; + if ((oparg & 1) == 1) { input = stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))]; } + aa = stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))]; output = spam(oparg, input); - STACK_SHRINK((((oparg & 1) == 1) ? 1 : 0)); - STACK_GROW(((oparg & 2) ? 1 : 0)); - stack_pointer[-2 - (oparg & 2 ? 1 : 0)] = xx; - if (oparg & 2) { stack_pointer[-1 - (oparg & 2 ? 1 : 0)] = output; } - stack_pointer[-1] = zz; + stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))] = xx; + if (oparg & 2) stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))] = output; + stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0))] = zz; + stack_pointer += -((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0)); DISPATCH(); } """ @@ -661,11 +676,10 @@ def test_macro_cond_effect(self): { # Body of B } - STACK_SHRINK(1); - STACK_GROW((oparg ? 1 : 0)); - stack_pointer[-2 - (oparg ? 1 : 0)] = deep; - if (oparg) { stack_pointer[-1 - (oparg ? 1 : 0)] = extra; } - stack_pointer[-1] = res; + stack_pointer[-3] = deep; + if (oparg) stack_pointer[-2] = extra; + stack_pointer[-2 + (((oparg) ? 1 : 0))] = res; + stack_pointer += -1 + (((oparg) ? 1 : 0)); DISPATCH(); } """ @@ -696,9 +710,9 @@ def test_macro_push_push(self): { val2 = spam(); } - STACK_GROW(2); - stack_pointer[-2] = val1; - stack_pointer[-1] = val2; + stack_pointer[0] = val1; + stack_pointer[1] = val2; + stack_pointer += 2; DISPATCH(); } """ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7cb60cbc1dd3ff..7cc29c8e644d8d 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1126,7 +1126,7 @@ } null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1162,7 +1162,7 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1180,7 +1180,7 @@ STAT_INC(LOAD_GLOBAL, hit); null = NULL; stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); break; } @@ -1612,7 +1612,7 @@ if (attr == NULL) goto pop_1_error_tier_two; } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; + if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += ((oparg & 1)); break; } @@ -1652,7 +1652,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1686,7 +1686,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1730,7 +1730,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1750,7 +1750,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -1778,7 +1778,7 @@ null = NULL; Py_DECREF(owner); stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); break; } @@ -2467,7 +2467,7 @@ assert(_PyType_HasFeature(Py_TYPE(attr), Py_TPFLAGS_METHOD_DESCRIPTOR)); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -2487,7 +2487,7 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -2550,7 +2550,7 @@ attr = Py_NewRef(descr); self = owner; stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); break; } @@ -3199,7 +3199,7 @@ PyObject *start; PyObject *slice; oparg = CURRENT_OPARG(); - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 24f26722d7a745..b202d141b36f4f 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -678,7 +678,7 @@ PyObject *stop; PyObject *start; PyObject *slice; - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } + if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; slice = PySlice_New(start, stop, step); @@ -1161,7 +1161,7 @@ PyObject *callargs; PyObject *func; PyObject *result; - if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } + if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } callargs = stack_pointer[-1 - ((oparg & 1))]; func = stack_pointer[-3 - ((oparg & 1))]; // DICT_MERGE is called before this opcode if there are kwargs. @@ -3385,7 +3385,7 @@ } } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = self_or_null; + if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3418,7 +3418,7 @@ Py_DECREF(owner); } stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3494,7 +3494,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3536,7 +3536,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3571,7 +3571,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3618,7 +3618,7 @@ self = owner; } stack_pointer[-1] = attr; - if (1) stack_pointer[0] = self; + if (1) stack_pointer[0] = self; stack_pointer += (((1) ? 1 : 0)); DISPATCH(); } @@ -3657,7 +3657,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3799,7 +3799,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -3855,7 +3855,7 @@ } /* Skip 5 cache entries */ stack_pointer[-1] = attr; - if (oparg & 1) stack_pointer[0] = null; + if (oparg & 1) stack_pointer[0] = null; stack_pointer += ((oparg & 1)); DISPATCH(); } @@ -4083,7 +4083,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4124,7 +4124,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4158,7 +4158,7 @@ null = NULL; } stack_pointer[0] = res; - if (oparg & 1) stack_pointer[1] = null; + if (oparg & 1) stack_pointer[1] = null; stack_pointer += 1 + ((oparg & 1)); DISPATCH(); } @@ -4286,7 +4286,7 @@ null = NULL; } stack_pointer[-3] = attr; - if (oparg & 1) stack_pointer[-2] = null; + if (oparg & 1) stack_pointer[-2] = null; stack_pointer += -2 + ((oparg & 1)); DISPATCH(); } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index bcc13538e51d9b..2147f6f25b8a6f 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -444,7 +444,8 @@ def analyze_forest(forest: list[parser.AstNode]) -> Analysis: if target.text in instructions: instructions[target.text].is_target = True # Hack - instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] + if "BINARY_OP_INPLACE_ADD_UNICODE" in instructions: + instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] return Analysis(instructions, uops, families, pseudos) diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py index 34e39855a9b40a..67b1c9a169024c 100644 --- a/Tools/cases_generator/cwriter.py +++ b/Tools/cases_generator/cwriter.py @@ -38,7 +38,8 @@ def maybe_dedent(self, txt: str) -> None: parens = txt.count("(") - txt.count(")") if parens < 0: self.indents.pop() - elif "}" in txt or is_label(txt): + braces = txt.count("{") - txt.count("}") + if braces < 0 or is_label(txt): self.indents.pop() def maybe_indent(self, txt: str) -> None: @@ -50,11 +51,13 @@ def maybe_indent(self, txt: str) -> None: self.indents.append(offset) if is_label(txt): self.indents.append(self.indents[-1] + 4) - elif "{" in txt: - if 'extern "C"' in txt: - self.indents.append(self.indents[-1]) - else: - self.indents.append(self.indents[-1] + 4) + else: + braces = txt.count("{") - txt.count("}") + if braces > 0: + if 'extern "C"' in txt: + self.indents.append(self.indents[-1]) + else: + self.indents.append(self.indents[-1] + 4) def emit_text(self, txt: str) -> None: self.out.write(txt) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index e0674a7343498d..1b565bff2c56f6 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -22,8 +22,10 @@ def root_relative_path(filename: str) -> str: - return Path(filename).absolute().relative_to(ROOT).as_posix() - + try: + return Path(filename).absolute().relative_to(ROOT).as_posix() + except ValueError: + return filename def write_header(generator: str, sources: list[str], outfile: TextIO) -> None: outfile.write( diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index c36a56ebf2d381..0b31ce4090f552 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -148,7 +148,7 @@ def flush(self, out: CWriter) -> None: cast = "(PyObject *)" if var.type else "" if var.name != "unused" and not var.is_array(): if var.condition: - out.emit(f" if ({var.condition}) ") + out.emit(f"if ({var.condition}) ") out.emit( f"stack_pointer[{self.base_offset.to_c()}] = {cast}{var.name};\n" ) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 11885dca6fe1a2..bcfd2d88ecd734 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -181,6 +181,14 @@ def generate_tier1( "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" ) + +def generate_tier1_from_files( + filenames: list[str], outfilename: str, lines: bool +) -> None: + data = analyze_files(filenames) + with open(outfilename, "w") as outfile: + generate_tier1(filenames, data, outfile, lines) + if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: From 70d378cdaa99f995bdce278439ef7c4defe4f805 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 18 Dec 2023 13:16:45 +0000 Subject: [PATCH 291/442] GH-111485: Break up instructions with unused cache entries into component micro-ops (GH-113169) --- Lib/test/test_generated_cases.py | 4 ++ Python/generated_cases.c.h | 71 ++++++++++++++++++++++++ Tools/cases_generator/analyzer.py | 31 ++++++++--- Tools/cases_generator/parser.py | 1 + Tools/cases_generator/tier1_generator.py | 3 +- 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 74cebbe469d794..3541a4e70b9a56 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -457,6 +457,7 @@ def test_macro_instruction(self): PyObject *left; PyObject *arg2; PyObject *res; + /* Skip 5 cache entries */ right = stack_pointer[-1]; left = stack_pointer[-2]; arg2 = stack_pointer[-3]; @@ -467,6 +468,7 @@ def test_macro_instruction(self): } """ self.run_cases_test(input, output) + def test_unused_caches(self): input = """ inst(OP, (unused/1, unused/2 --)) { @@ -478,6 +480,8 @@ def test_unused_caches(self): frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(OP); + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ body(); DISPATCH(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index b202d141b36f4f..a274427a699a43 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -452,6 +452,7 @@ PyObject *sub; PyObject *dict; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; dict = stack_pointer[-2]; DEOPT_IF(!PyDict_CheckExact(dict), BINARY_SUBSCR); @@ -476,6 +477,7 @@ static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub; PyObject *container; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; container = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, BINARY_SUBSCR); @@ -509,6 +511,7 @@ PyObject *sub; PyObject *list; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; list = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -536,6 +539,7 @@ PyObject *sub; PyObject *str; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; str = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -563,6 +567,7 @@ PyObject *sub; PyObject *tuple; PyObject *res; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; tuple = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR); @@ -840,6 +845,8 @@ PyObject **args; PyObject *null; PyObject *callable; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1001,6 +1008,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1035,6 +1044,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1080,6 +1091,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1119,6 +1132,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1279,6 +1294,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1408,6 +1425,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1444,6 +1463,8 @@ PyObject **args; PyObject *self; PyObject *callable; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1475,6 +1496,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1517,6 +1540,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1559,6 +1584,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1603,6 +1630,8 @@ PyObject *self_or_null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1728,6 +1757,7 @@ PyObject **args; PyObject *self_or_null; PyObject *callable; + /* Skip 1 cache entry */ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1774,6 +1804,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1801,6 +1833,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1828,6 +1862,8 @@ PyObject *null; PyObject *callable; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ args = &stack_pointer[-oparg]; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1982,6 +2018,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyFloat_CheckExact(left), COMPARE_OP); @@ -2008,6 +2045,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyLong_CheckExact(left), COMPARE_OP); @@ -2038,6 +2076,7 @@ PyObject *right; PyObject *left; PyObject *res; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; DEOPT_IF(!PyUnicode_CheckExact(left), COMPARE_OP); @@ -2469,6 +2508,7 @@ INSTRUCTION_STATS(FOR_ITER_GEN); static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter; + /* Skip 1 cache entry */ iter = stack_pointer[-1]; DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); PyGenObject *gen = (PyGenObject *)iter; @@ -2843,6 +2883,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 4; INSTRUCTION_STATS(INSTRUMENTED_CALL); + /* Skip 3 cache entries */ int is_meth = PEEK(oparg + 1) != NULL; int total_args = oparg + is_meth; PyObject *function = PEEK(oparg + 2); @@ -2929,6 +2970,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_FOR_ITER); + /* Skip 1 cache entry */ _Py_CODEUNIT *target; PyObject *iter = TOP(); PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter); @@ -2976,6 +3018,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_JUMP_BACKWARD); + /* Skip 1 cache entry */ CHECK_EVAL_BREAKER(); INSTRUMENTED_JUMP(this_instr, next_instr - oparg, PY_MONITORING_EVENT_JUMP); DISPATCH(); @@ -2993,6 +3036,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_LOAD_SUPER_ATTR); + /* Skip 1 cache entry */ // cancel out the decrement that will happen in LOAD_SUPER_ATTR; we // don't want to specialize instrumented instructions INCREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); @@ -3003,6 +3047,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_FALSE); + /* Skip 1 cache entry */ PyObject *cond = POP(); assert(PyBool_Check(cond)); int flag = Py_IsFalse(cond); @@ -3018,6 +3063,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NONE); + /* Skip 1 cache entry */ PyObject *value = POP(); int flag = Py_IsNone(value); int offset; @@ -3039,6 +3085,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NOT_NONE); + /* Skip 1 cache entry */ PyObject *value = POP(); int offset; int nflag = Py_IsNone(value); @@ -3060,6 +3107,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_TRUE); + /* Skip 1 cache entry */ PyObject *cond = POP(); assert(PyBool_Check(cond)); int flag = Py_IsTrue(cond); @@ -3216,6 +3264,7 @@ _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 2; INSTRUCTION_STATS(JUMP_BACKWARD); + /* Skip 1 cache entry */ CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -3429,6 +3478,7 @@ INSTRUCTION_STATS(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN); static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); uint32_t func_version = read_u32(&this_instr[4].cache); @@ -3743,6 +3793,7 @@ INSTRUCTION_STATS(LOAD_ATTR_PROPERTY); static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; uint32_t type_version = read_u32(&this_instr[2].cache); uint32_t func_version = read_u32(&this_instr[4].cache); @@ -4300,6 +4351,7 @@ PyObject *class; PyObject *global_super; PyObject *attr; + /* Skip 1 cache entry */ self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; @@ -4328,6 +4380,7 @@ PyObject *global_super; PyObject *attr; PyObject *self_or_null; + /* Skip 1 cache entry */ self = stack_pointer[-1]; class = stack_pointer[-2]; global_super = stack_pointer[-3]; @@ -4927,6 +4980,7 @@ static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v; PyObject *receiver; + /* Skip 1 cache entry */ v = stack_pointer[-1]; receiver = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, SEND); @@ -5157,6 +5211,7 @@ static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner; PyObject *value; + /* Skip 1 cache entry */ owner = stack_pointer[-1]; value = stack_pointer[-2]; uint32_t type_version = read_u32(&this_instr[2].cache); @@ -5374,6 +5429,7 @@ PyObject *sub; PyObject *dict; PyObject *value; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; dict = stack_pointer[-2]; value = stack_pointer[-3]; @@ -5394,6 +5450,7 @@ PyObject *sub; PyObject *list; PyObject *value; + /* Skip 1 cache entry */ sub = stack_pointer[-1]; list = stack_pointer[-2]; value = stack_pointer[-3]; @@ -5470,6 +5527,7 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ value = stack_pointer[-1]; uint32_t version = read_u32(&this_instr[2].cache); // This one is a bit weird, because we expect *some* failures: @@ -5488,6 +5546,8 @@ INSTRUCTION_STATS(TO_BOOL_BOOL); static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyBool_Check(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5501,6 +5561,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyLong_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5523,6 +5585,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyList_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5539,6 +5603,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; // This one is a bit weird, because we expect *some* failures: DEOPT_IF(!Py_IsNone(value), TO_BOOL); @@ -5555,6 +5621,8 @@ static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value; PyObject *res; + /* Skip 1 cache entry */ + /* Skip 2 cache entries */ value = stack_pointer[-1]; DEOPT_IF(!PyUnicode_CheckExact(value), TO_BOOL); STAT_INC(TO_BOOL, hit); @@ -5669,6 +5737,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyList_CheckExact(seq), UNPACK_SEQUENCE); @@ -5690,6 +5759,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -5711,6 +5781,7 @@ static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; PyObject **values; + /* Skip 1 cache entry */ seq = stack_pointer[-1]; values = &stack_pointer[-1]; DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 2147f6f25b8a6f..e077eb0a8ed203 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -234,9 +234,9 @@ def analyze_stack(op: parser.InstDef) -> StackEffect: return StackEffect(inputs, outputs) -def analyze_caches(op: parser.InstDef) -> list[CacheEntry]: +def analyze_caches(inputs: list[parser.InputEffect]) -> list[CacheEntry]: caches: list[parser.CacheEffect] = [ - i for i in op.inputs if isinstance(i, parser.CacheEffect) + i for i in inputs if isinstance(i, parser.CacheEffect) ] return [CacheEntry(i.name, int(i.size)) for i in caches] @@ -314,13 +314,13 @@ def compute_properties(op: parser.InstDef) -> Properties: ) -def make_uop(name: str, op: parser.InstDef) -> Uop: +def make_uop(name: str, op: parser.InstDef, inputs: list[parser.InputEffect]) -> Uop: return Uop( name=name, context=op.context, annotations=op.annotations, stack=analyze_stack(op), - caches=analyze_caches(op), + caches=analyze_caches(inputs), body=op.block.tokens, properties=compute_properties(op), ) @@ -333,7 +333,7 @@ def add_op(op: parser.InstDef, uops: dict[str, Uop]) -> None: raise override_error( op.name, op.context, uops[op.name].context, op.tokens[0] ) - uops[op.name] = make_uop(op.name, op) + uops[op.name] = make_uop(op.name, op, op.inputs) def add_instruction( @@ -347,10 +347,27 @@ def desugar_inst( ) -> None: assert inst.kind == "inst" name = inst.name - uop = make_uop("_" + inst.name, inst) + op_inputs: list[parser.InputEffect] = [] + parts: list[Part] = [] + uop_index = -1 + # Move unused cache entries to the Instruction, removing them from the Uop. + for input in inst.inputs: + if isinstance(input, parser.CacheEffect) and input.name == "unused": + parts.append(Skip(input.size)) + else: + op_inputs.append(input) + if uop_index < 0: + uop_index = len(parts) + # Place holder for the uop. + parts.append(Skip(0)) + uop = make_uop("_" + inst.name, inst, op_inputs) uop.implicitly_created = True uops[inst.name] = uop - add_instruction(name, [uop], instructions) + if uop_index < 0: + parts.append(uop) + else: + parts[uop_index] = uop + add_instruction(name, parts, instructions) def add_macro( diff --git a/Tools/cases_generator/parser.py b/Tools/cases_generator/parser.py index 12173a61199700..fe4e8e476eadee 100644 --- a/Tools/cases_generator/parser.py +++ b/Tools/cases_generator/parser.py @@ -7,6 +7,7 @@ Context, CacheEffect, StackEffect, + InputEffect, OpName, AstNode, ) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index bcfd2d88ecd734..49cede978d821a 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -151,7 +151,8 @@ def generate_tier1( stack = Stack() for part in inst.parts: # Only emit braces if more than one uop - offset = write_uop(part, out, offset, stack, inst, len(inst.parts) > 1) + insert_braces = len([p for p in inst.parts if isinstance(p, Uop)]) > 1 + offset = write_uop(part, out, offset, stack, inst, insert_braces) out.start_line() if not inst.parts[-1].properties.always_exits: stack.flush(out) From 59f0766ae5aef8bd393a53ab9234371b7d165ec3 Mon Sep 17 00:00:00 2001 From: dreamflow Date: Mon, 18 Dec 2023 17:14:15 +0100 Subject: [PATCH 292/442] gh-108113: [docs] mention PyCF_OPTIMIZED_AST in ast Compiler Flags (#113241) --- Doc/library/ast.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 4ebbe0e5471c88..9997edd0fb5fc0 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2457,6 +2457,13 @@ effects on the compilation of a program: Generates and returns an abstract syntax tree instead of returning a compiled code object. +.. data:: PyCF_OPTIMIZED_AST + + The returned AST is optimized according to the *optimize* argument + in :func:`compile` or :func:`ast.parse`. + + .. versionadded:: 3.13 + .. data:: PyCF_TYPE_COMMENTS Enables support for :pep:`484` and :pep:`526` style type comments From d00dbf541525fcb36c9c6ebb7b41a5637c5aa6c0 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Mon, 18 Dec 2023 16:54:49 +0000 Subject: [PATCH 293/442] gh-112535: Implement fallback implementation of _Py_ThreadId() (gh-113185) --------- Co-authored-by: Sam Gross --- Include/object.h | 6 +++++- Python/pystate.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Include/object.h b/Include/object.h index d22e5c2b8be2a9..48f1ddf7510887 100644 --- a/Include/object.h +++ b/Include/object.h @@ -239,6 +239,8 @@ PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y); #define Py_Is(x, y) ((x) == (y)) #if defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +PyAPI_FUNC(uintptr_t) _Py_GetThreadLocal_Addr(void); + static inline uintptr_t _Py_ThreadId(void) { @@ -291,7 +293,9 @@ _Py_ThreadId(void) __asm__ ("mv %0, tp" : "=r" (tid)); #endif #else - # error "define _Py_ThreadId for this platform" + // Fallback to a portable implementation if we do not have a faster + // platform-specific implementation. + tid = _Py_GetThreadLocal_Addr(); #endif return tid; } diff --git a/Python/pystate.c b/Python/pystate.c index e18eb0186d0010..632a119ea6d4f8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1951,6 +1951,20 @@ _PyThreadState_Bind(PyThreadState *tstate) } } +#if defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +uintptr_t +_Py_GetThreadLocal_Addr(void) +{ +#ifdef HAVE_THREAD_LOCAL + // gh-112535: Use the address of the thread-local PyThreadState variable as + // a unique identifier for the current thread. Each thread has a unique + // _Py_tss_tstate variable with a unique address. + return (uintptr_t)&_Py_tss_tstate; +#else +# error "no supported thread-local variable storage classifier" +#endif +} +#endif /***********************************/ /* routines for advanced debuggers */ From 2feec0fc7fd0b9caae7ab2e26e69311d3ed93e77 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Mon, 18 Dec 2023 09:04:40 -0800 Subject: [PATCH 294/442] gh-113039: Avoid using leading dots in the include path for frozen getpath.py (GH-113022) --- Modules/getpath.c | 2 +- PCbuild/pythoncore.vcxproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/getpath.c b/Modules/getpath.c index afa9273ebc59b7..a3c8fc269d1c3c 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -22,7 +22,7 @@ #endif /* Reference the precompiled getpath.py */ -#include "../Python/frozen_modules/getpath.h" +#include "Python/frozen_modules/getpath.h" #if (!defined(PREFIX) || !defined(EXEC_PREFIX) \ || !defined(VERSION) || !defined(VPATH) \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 90aa8cf28f8c5d..c90ad1a3592f67 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -120,6 +120,7 @@ PLATLIBDIR="DLLs"; %(PreprocessorDefinitions) + $(PySourcePath);%(AdditionalIncludeDirectories) From 41336a72b90634d5ac74a57b6826e4dd6fe78eac Mon Sep 17 00:00:00 2001 From: Illia Volochii Date: Mon, 18 Dec 2023 22:17:16 +0200 Subject: [PATCH 295/442] gh-113199: Make read1() and readline() of HTTPResponse close IO after reading all data (GH-113200) --- Lib/http/client.py | 4 ++++ Lib/test/test_httplib.py | 16 ++++++++++++++-- ...023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 7bb5d824bb6da4..5eebfccafbca59 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -665,6 +665,8 @@ def read1(self, n=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def peek(self, n=-1): @@ -689,6 +691,8 @@ def readline(self, limit=-1): self._close_conn() elif self.length is not None: self.length -= len(result) + if not self.length: + self._close_conn() return result def _read1_chunked(self, n): diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index caa4c76a913a01..089bf5be40a0e2 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1546,11 +1546,14 @@ def test_readline(self): resp = self.resp self._verify_readline(self.resp.readline, self.lines_expected) - def _verify_readline(self, readline, expected): + def test_readline_without_limit(self): + self._verify_readline(self.resp.readline, self.lines_expected, limit=-1) + + def _verify_readline(self, readline, expected, limit=5): all = [] while True: # short readlines - line = readline(5) + line = readline(limit) if line and line != b"foo": if len(line) < 5: self.assertTrue(line.endswith(b"\n")) @@ -1558,6 +1561,7 @@ def _verify_readline(self, readline, expected): if not line: break self.assertEqual(b"".join(all), expected) + self.assertTrue(self.resp.isclosed()) def test_read1(self): resp = self.resp @@ -1577,6 +1581,7 @@ def test_read1_unbounded(self): break all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_bounded(self): resp = self.resp @@ -1588,15 +1593,22 @@ def test_read1_bounded(self): self.assertLessEqual(len(data), 10) all.append(data) self.assertEqual(b"".join(all), self.lines_expected) + self.assertTrue(resp.isclosed()) def test_read1_0(self): self.assertEqual(self.resp.read1(0), b"") + self.assertFalse(self.resp.isclosed()) def test_peek_0(self): p = self.resp.peek(0) self.assertLessEqual(0, len(p)) +class ExtendedReadTestContentLengthKnown(ExtendedReadTest): + _header, _body = ExtendedReadTest.lines.split('\r\n\r\n', 1) + lines = _header + f'\r\nContent-Length: {len(_body)}\r\n\r\n' + _body + + class ExtendedReadTestChunked(ExtendedReadTest): """ Test peek(), read1(), readline() in chunked mode diff --git a/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst b/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst new file mode 100644 index 00000000000000..d8e0b1731d1e3b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-16-01-10-47.gh-issue-113199.oDjnjL.rst @@ -0,0 +1,3 @@ +Make ``http.client.HTTPResponse.read1`` and +``http.client.HTTPResponse.readline`` close IO after reading all data when +content length is known. Patch by Illia Volochii. From 4cfce3a4da7ca9513e7f2c8ec94d50f8bddfa41b Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 19 Dec 2023 00:41:41 +0100 Subject: [PATCH 296/442] gh-108269: Add CFBundleAllowMixedLocalizations to Info.plist on macOS (GH-113213) Adding this key with a value of true enables detecting the users prefered language in libraries accessing system APIs for this. --- Mac/IDLE/IDLE.app/Contents/Info.plist | 2 ++ Mac/Resources/app/Info.plist.in | 2 ++ Mac/Resources/framework/Info.plist.in | 2 ++ .../next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst | 4 ++++ 4 files changed, 10 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst diff --git a/Mac/IDLE/IDLE.app/Contents/Info.plist b/Mac/IDLE/IDLE.app/Contents/Info.plist index fea06d46324999..20b97b67f41d1a 100644 --- a/Mac/IDLE/IDLE.app/Contents/Info.plist +++ b/Mac/IDLE/IDLE.app/Contents/Info.plist @@ -56,5 +56,7 @@ %version% NSHighResolutionCapable + CFBundleAllowMixedLocalizations + diff --git a/Mac/Resources/app/Info.plist.in b/Mac/Resources/app/Info.plist.in index 4ec828ff176e7b..8362b19b361b62 100644 --- a/Mac/Resources/app/Info.plist.in +++ b/Mac/Resources/app/Info.plist.in @@ -58,5 +58,7 @@ (c) 2001-2023 Python Software Foundation. NSHighResolutionCapable + CFBundleAllowMixedLocalizations + diff --git a/Mac/Resources/framework/Info.plist.in b/Mac/Resources/framework/Info.plist.in index e131c205ef0b28..238441ce2c76c7 100644 --- a/Mac/Resources/framework/Info.plist.in +++ b/Mac/Resources/framework/Info.plist.in @@ -24,5 +24,7 @@ ???? CFBundleVersion %VERSION% + CFBundleAllowMixedLocalizations + diff --git a/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst b/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst new file mode 100644 index 00000000000000..85598454abcaad --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-16-11-45-32.gh-issue-108269.wVgCHF.rst @@ -0,0 +1,4 @@ +Set ``CFBundleAllowMixedLocalizations`` to true in the Info.plist for the +framework, embedded Python.app and IDLE.app with framework installs on +macOS. This allows applications to pick up the user's preferred locale when +that's different from english. From 893c9ccf48eacb02fa6ae93632f2d0cb6778dbb6 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Tue, 19 Dec 2023 00:51:58 +0100 Subject: [PATCH 297/442] gh-102362: Fix macOS version number in result of sysconfig.get_platform() (GH-112942) Change _osx_support.get_platform_osx() to make sure that the version number in the result includes at least a major and minor version (e.g. 14.2) even if MACOSX_DEPLOYMENT_TARGET is set to just a major version (e.g. 14). This matches the versions expected by pip when selecting appropriate wheels for installation. --- Lib/_osx_support.py | 5 +++++ .../macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py index aa66c8b9f4189f..0cb064fcd791be 100644 --- a/Lib/_osx_support.py +++ b/Lib/_osx_support.py @@ -507,6 +507,11 @@ def get_platform_osx(_config_vars, osname, release, machine): # MACOSX_DEPLOYMENT_TARGET. macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '') + if macver and '.' not in macver: + # Ensure that the version includes at least a major + # and minor version, even if MACOSX_DEPLOYMENT_TARGET + # is set to a single-label version like "14". + macver += '.0' macrelease = _get_system_version() or macver macver = macver or macrelease diff --git a/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst b/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst new file mode 100644 index 00000000000000..55c5ac01434660 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-10-20-30-06.gh-issue-102362.y8svbF.rst @@ -0,0 +1,3 @@ +Make sure the result of :func:`sysconfig.get_plaform` includes at least a +major and minor versions, even if ``MACOSX_DEPLOYMENT_TARGET`` is set to +only a major version during build to match the format expected by pip. From c895403de0b1c301aeef86921348c13f608257dc Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 18 Dec 2023 21:18:30 -0800 Subject: [PATCH 298/442] gh-113119: Fix the macOS framework installer build (#113268) `--enable-framework` builds were failing. we apparently do not have good CI & buildbot coverage here. --- Modules/posixmodule.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 8ffe0f5de1e7bd..c7ee591f30c51f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1557,6 +1557,7 @@ _Py_Sigset_Converter(PyObject *obj, void *addr) ** man environ(7). */ #include +#define USE_DARWIN_NS_GET_ENVIRON 1 #elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__)) extern char **environ; #endif /* !_MSC_VER */ @@ -1579,7 +1580,7 @@ convertenviron(void) through main() instead of wmain(). */ (void)_wgetenv(L""); e = _wenviron; -#elif defined(WITH_NEXT_FRAMEWORK) || (defined(__APPLE__) && defined(Py_ENABLE_SHARED)) +#elif defined(USE_DARWIN_NS_GET_ENVIRON) /* environ is not accessible as an extern in a shared object on OSX; use _NSGetEnviron to resolve it. The value changes if you add environment variables between calls to Py_Initialize, so don't cache the value. */ @@ -7166,7 +7167,15 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a goto exit; } +#ifdef USE_DARWIN_NS_GET_ENVIRON + // There is no environ global in this situation. + char **environ = NULL; +#endif + if (env == Py_None) { +#ifdef USE_DARWIN_NS_GET_ENVIRON + environ = *_NSGetEnviron(); +#endif envlist = environ; } else { envlist = parse_envlist(env, &envc); From fa9ba02353d79632983b9fe24da851894877e342 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 19 Dec 2023 00:26:11 -0500 Subject: [PATCH 299/442] gh-113269: IDLE - Fix test_editor hang (macOS) (#113271) Hangs on installed 3.13.0a2 on macOS Catalina. Behavior on installed 3.12.1 and 3.11.7 is unknown. --- Lib/idlelib/News3.txt | 2 ++ Lib/idlelib/idle_test/test_editor.py | 2 +- .../next/IDLE/2023-12-19-00-03-12.gh-issue-113269.lrU-IC.rst | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/IDLE/2023-12-19-00-03-12.gh-issue-113269.lrU-IC.rst diff --git a/Lib/idlelib/News3.txt b/Lib/idlelib/News3.txt index 4fba4165fddab5..308865d968814c 100644 --- a/Lib/idlelib/News3.txt +++ b/Lib/idlelib/News3.txt @@ -4,6 +4,8 @@ Released on 2024-10-xx ========================= +gh-113269: Fix test_editor hang on macOS Catalina. + gh-112939: Fix processing unsaved files when quitting IDLE on macOS. Patch by Ronald Oussoren and Christopher Chavez. diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 9296a6d235fbbe..0dfe2f3c58befa 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -95,7 +95,7 @@ def test_tabwidth_8(self): def insert(text, string): text.delete('1.0', 'end') text.insert('end', string) - text.update() # Force update for colorizer to finish. + text.update_idletasks() # Force update for colorizer to finish. class IndentAndNewlineTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/IDLE/2023-12-19-00-03-12.gh-issue-113269.lrU-IC.rst b/Misc/NEWS.d/next/IDLE/2023-12-19-00-03-12.gh-issue-113269.lrU-IC.rst new file mode 100644 index 00000000000000..72e75b7910e359 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2023-12-19-00-03-12.gh-issue-113269.lrU-IC.rst @@ -0,0 +1 @@ +Fix test_editor hang on macOS Catalina. From 4658464e9cf092be930d0d8f938e801a69f7f987 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Tue, 19 Dec 2023 00:34:53 -0600 Subject: [PATCH 300/442] gh-113257: Fix SBOM metadata for pip 23.3.2 (#113262) Fix SBOM metadata for pip 23.3.2 --- Misc/sbom.spdx.json | 8 ++++---- Tools/build/generate_sbom.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 5868ceba4134ed..81f8486ea350c1 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1703,16 +1703,16 @@ "checksumValue": "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be" } ], - "downloadLocation": "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl", + "downloadLocation": "https://files.pythonhosted.org/packages/15/aa/3f4c7bcee2057a76562a5b33ecbd199be08cdb4443a02e26bd2c3cf6fc39/pip-23.3.2-py3-none-any.whl", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:pypa:pip:23.2.1:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:pypa:pip:23.3.2:*:*:*:*:*:*:*", "referenceType": "cpe23Type" }, { "referenceCategory": "PACKAGE_MANAGER", - "referenceLocator": "pkg:pypi/pip@23.2.1", + "referenceLocator": "pkg:pypi/pip@23.3.2", "referenceType": "purl" } ], @@ -1720,7 +1720,7 @@ "name": "pip", "originator": "Organization: Python Packaging Authority", "primaryPackagePurpose": "SOURCE", - "versionInfo": "23.2.1" + "versionInfo": "23.3.2" } ], "relationships": [ diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index e0e46552d06f25..c02eb88b46532f 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -50,7 +50,7 @@ class PackageFiles(typing.NamedTuple): include=["Modules/expat/**"] ), "pip": PackageFiles( - include=["Lib/ensurepip/_bundled/pip-*-py3-none-any.whl"] + include=["Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl"] ), "macholib": PackageFiles( include=["Lib/ctypes/macholib/**"], From d71fcdee0f1e4daa35d47bbef103f30259037ddb Mon Sep 17 00:00:00 2001 From: Unique-Usman <86585626+Unique-Usman@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:09:57 +0530 Subject: [PATCH 301/442] gh-113208: Mention namespace packages don't require __init__.py (#113209) Co-authored-by: C.A.M. Gerlach --- Doc/tutorial/modules.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst index bf9e8e0b7b8066..0316239e776a95 100644 --- a/Doc/tutorial/modules.rst +++ b/Doc/tutorial/modules.rst @@ -437,7 +437,8 @@ When importing the package, Python searches through the directories on ``sys.path`` looking for the package subdirectory. The :file:`__init__.py` files are required to make Python treat directories -containing the file as packages. This prevents directories with a common name, +containing the file as packages (unless using a :term:`namespace package`, a +relatively advanced feature). This prevents directories with a common name, such as ``string``, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, :file:`__init__.py` can just be an empty file, but it can also execute initialization code for the package or From 76d757b38b414964546393bdccff31c1f8be3843 Mon Sep 17 00:00:00 2001 From: ryan-duve Date: Tue, 19 Dec 2023 05:29:55 -0500 Subject: [PATCH 302/442] gh-113234: tomllib docs: reorder conversion table & add remaining types (GH-113236) --- Doc/library/tomllib.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/library/tomllib.rst b/Doc/library/tomllib.rst index 918576eb37eaee..f9e2dfeb13dc87 100644 --- a/Doc/library/tomllib.rst +++ b/Doc/library/tomllib.rst @@ -95,7 +95,7 @@ Conversion Table +------------------+--------------------------------------------------------------------------------------+ | TOML | Python | +==================+======================================================================================+ -| table | dict | +| TOML document | dict | +------------------+--------------------------------------------------------------------------------------+ | string | str | +------------------+--------------------------------------------------------------------------------------+ @@ -115,3 +115,9 @@ Conversion Table +------------------+--------------------------------------------------------------------------------------+ | array | list | +------------------+--------------------------------------------------------------------------------------+ +| table | dict | ++------------------+--------------------------------------------------------------------------------------+ +| inline table | dict | ++------------------+--------------------------------------------------------------------------------------+ +| array of tables | list of dicts | ++------------------+--------------------------------------------------------------------------------------+ From e51b4009454939e3ee5f1bfaed45ce65689a71b8 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:04:44 +0000 Subject: [PATCH 303/442] gh-113054: Compiler no longer replaces a redundant jump with no line number by a NOP (#113139) --- Lib/test/test_compile.py | 4 ++++ ...23-12-14-20-08-35.gh-issue-113054.e20CtM.rst | 2 ++ Python/flowgraph.c | 17 ++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-14-20-08-35.gh-issue-113054.e20CtM.rst diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index f681d125db7d7a..906e16cc9437fb 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -444,6 +444,10 @@ def f(): self.assertIn("_A__mangled_mod", A.f.__code__.co_varnames) self.assertIn("__package__", A.f.__code__.co_varnames) + def test_condition_expression_with_dead_blocks_compiles(self): + # See gh-113054 + compile('if (5 if 5 else T): 0', '', 'exec') + def test_compile_invalid_namedexpr(self): # gh-109351 m = ast.Module( diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-14-20-08-35.gh-issue-113054.e20CtM.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-14-20-08-35.gh-issue-113054.e20CtM.rst new file mode 100644 index 00000000000000..d0729f9c44754c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-14-20-08-35.gh-issue-113054.e20CtM.rst @@ -0,0 +1,2 @@ +Fixed bug where a redundant NOP is not removed, causing an assertion to fail +in the compiler in debug mode. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index fe632082d5a66c..d2e3a7ae441c7f 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1110,7 +1110,10 @@ remove_redundant_jumps(cfg_builder *g) { * of that jump. If it is, then the jump instruction is redundant and * can be deleted. */ + assert(no_empty_basic_blocks(g)); + + bool remove_empty_blocks = false; for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { cfg_instr *last = basicblock_last_instr(b); assert(last != NULL); @@ -1122,10 +1125,22 @@ remove_redundant_jumps(cfg_builder *g) { } if (last->i_target == b->b_next) { assert(b->b_next->b_iused); - INSTR_SET_OP0(last, NOP); + if (last->i_loc.lineno == NO_LOCATION.lineno) { + b->b_iused--; + if (b->b_iused == 0) { + remove_empty_blocks = true; + } + } + else { + INSTR_SET_OP0(last, NOP); + } } } } + if (remove_empty_blocks) { + eliminate_empty_basic_blocks(g); + } + assert(no_empty_basic_blocks(g)); return SUCCESS; } From 6a69b80d1b1f3987fcec3300c5dc879c6e965079 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Dec 2023 12:32:28 +0100 Subject: [PATCH 304/442] gh-112205: Require @getter and @setter to be methods (#113278) Co-authored-by: Alex Waygood --- Lib/test/test_clinic.py | 11 +++++++++++ Tools/clinic/clinic.py | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index d3dbde88dd82a9..6c6bd4e75a0b02 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2249,6 +2249,17 @@ class Foo "" "" expected_error = "Cannot apply both @getter and @setter to the same function!" self.expect_failure(block, expected_error, lineno=3) + def test_getset_no_class(self): + for annotation in "@getter", "@setter": + with self.subTest(annotation=annotation): + block = f""" + module m + {annotation} + m.func + """ + expected_error = "@getter and @setter must be methods" + self.expect_failure(block, expected_error, lineno=2) + def test_duplicate_coexist(self): err = "Called @coexist twice" block = """ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index a9bf110291eadd..87feef1b82ca39 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5614,6 +5614,10 @@ def state_modulename_name(self, line: str) -> None: function_name = fields.pop() module, cls = self.clinic._module_and_class(fields) + if self.kind in {GETTER, SETTER}: + if not cls: + fail("@getter and @setter must be methods") + self.update_function_kind(full_name) if self.kind is METHOD_INIT and not return_converter: return_converter = init_return_converter() From 76bef3832bae64664882e27ecb6f89800a12cf43 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 19 Dec 2023 17:44:57 +0200 Subject: [PATCH 305/442] gh-101100: Fix Sphinx warnings in `library/ast.rst` (#113289) Co-authored-by: Alex Waygood --- Doc/library/ast.rst | 35 +++++++++++++++++++---------------- Misc/NEWS.d/3.9.0a1.rst | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 9997edd0fb5fc0..c943c2f498173e 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -45,7 +45,7 @@ Node classes This is the base of all AST node classes. The actual node classes are derived from the :file:`Parser/Python.asdl` file, which is reproduced - :ref:`above `. They are defined in the :mod:`_ast` C + :ref:`above `. They are defined in the :mod:`!_ast` C module and re-exported in :mod:`ast`. There is one class defined for each left-hand side symbol in the abstract @@ -128,14 +128,14 @@ Node classes .. deprecated:: 3.8 - Old classes :class:`ast.Num`, :class:`ast.Str`, :class:`ast.Bytes`, - :class:`ast.NameConstant` and :class:`ast.Ellipsis` are still available, + Old classes :class:`!ast.Num`, :class:`!ast.Str`, :class:`!ast.Bytes`, + :class:`!ast.NameConstant` and :class:`!ast.Ellipsis` are still available, but they will be removed in future Python releases. In the meantime, instantiating them will return an instance of a different class. .. deprecated:: 3.9 - Old classes :class:`ast.Index` and :class:`ast.ExtSlice` are still + Old classes :class:`!ast.Index` and :class:`!ast.ExtSlice` are still available, but they will be removed in future Python releases. In the meantime, instantiating them will return an instance of a different class. @@ -1935,8 +1935,7 @@ Function and class definitions .. class:: arg(arg, annotation, type_comment) A single argument in a list. ``arg`` is a raw string of the argument - name, ``annotation`` is its annotation, such as a :class:`Str` or - :class:`Name` node. + name; ``annotation`` is its annotation, such as a :class:`Name` node. .. attribute:: type_comment @@ -2210,7 +2209,7 @@ and classes for traversing abstract syntax trees: Added ``type_comments``, ``mode='func_type'`` and ``feature_version``. .. versionchanged:: 3.13 - The minimum supported version for feature_version is now (3,7) + The minimum supported version for ``feature_version`` is now ``(3, 7)``. The ``optimize`` argument was added. @@ -2286,8 +2285,8 @@ and classes for traversing abstract syntax trees: .. function:: get_source_segment(source, node, *, padded=False) Get source code segment of the *source* that generated *node*. - If some location information (:attr:`lineno`, :attr:`end_lineno`, - :attr:`col_offset`, or :attr:`end_col_offset`) is missing, return ``None``. + If some location information (:attr:`~ast.AST.lineno`, :attr:`~ast.AST.end_lineno`, + :attr:`~ast.AST.col_offset`, or :attr:`~ast.AST.end_col_offset`) is missing, return ``None``. If *padded* is ``True``, the first line of a multi-line statement will be padded with spaces to match its original position. @@ -2298,7 +2297,7 @@ and classes for traversing abstract syntax trees: .. function:: fix_missing_locations(node) When you compile a node tree with :func:`compile`, the compiler expects - :attr:`lineno` and :attr:`col_offset` attributes for every node that supports + :attr:`~ast.AST.lineno` and :attr:`~ast.AST.col_offset` attributes for every node that supports them. This is rather tedious to fill in for generated nodes, so this helper adds these attributes recursively where not already set, by setting them to the values of the parent node. It works recursively starting at *node*. @@ -2313,8 +2312,8 @@ and classes for traversing abstract syntax trees: .. function:: copy_location(new_node, old_node) - Copy source location (:attr:`lineno`, :attr:`col_offset`, :attr:`end_lineno`, - and :attr:`end_col_offset`) from *old_node* to *new_node* if possible, + Copy source location (:attr:`~ast.AST.lineno`, :attr:`~ast.AST.col_offset`, :attr:`~ast.AST.end_lineno`, + and :attr:`~ast.AST.end_col_offset`) from *old_node* to *new_node* if possible, and return *new_node*. @@ -2360,14 +2359,18 @@ and classes for traversing abstract syntax trees: visited unless the visitor calls :meth:`generic_visit` or visits them itself. + .. method:: visit_Constant(node) + + Handles all constant nodes. + Don't use the :class:`NodeVisitor` if you want to apply changes to nodes during traversal. For this a special visitor exists (:class:`NodeTransformer`) that allows modifications. .. deprecated:: 3.8 - Methods :meth:`visit_Num`, :meth:`visit_Str`, :meth:`visit_Bytes`, - :meth:`visit_NameConstant` and :meth:`visit_Ellipsis` are deprecated + Methods :meth:`!visit_Num`, :meth:`!visit_Str`, :meth:`!visit_Bytes`, + :meth:`!visit_NameConstant` and :meth:`!visit_Ellipsis` are deprecated now and will not be called in future Python versions. Add the :meth:`visit_Constant` method to handle all constant nodes. @@ -2396,7 +2399,7 @@ and classes for traversing abstract syntax trees: ) Keep in mind that if the node you're operating on has child nodes you must - either transform the child nodes yourself or call the :meth:`generic_visit` + either transform the child nodes yourself or call the :meth:`~ast.NodeVisitor.generic_visit` method for the node first. For nodes that were part of a collection of statements (that applies to all @@ -2405,7 +2408,7 @@ and classes for traversing abstract syntax trees: If :class:`NodeTransformer` introduces new nodes (that weren't part of original tree) without giving them location information (such as - :attr:`lineno`), :func:`fix_missing_locations` should be called with + :attr:`~ast.AST.lineno`), :func:`fix_missing_locations` should be called with the new sub-tree to recalculate the location information:: tree = ast.parse('foo', mode='eval') diff --git a/Misc/NEWS.d/3.9.0a1.rst b/Misc/NEWS.d/3.9.0a1.rst index 0444b53895b166..b365e5fbfb0dbe 100644 --- a/Misc/NEWS.d/3.9.0a1.rst +++ b/Misc/NEWS.d/3.9.0a1.rst @@ -2069,7 +2069,7 @@ Restores instantiation of Windows IOCP event loops from the non-main thread. .. section: Library Add default implementation of the :meth:`ast.NodeVisitor.visit_Constant` -method which emits a deprecation warning and calls corresponding methody +method which emits a deprecation warning and calls corresponding methods ``visit_Num()``, ``visit_Str()``, etc. .. From 6a1d5a4c0f209d51ab33d6529935d643bcdb3ba2 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Dec 2023 19:23:52 +0100 Subject: [PATCH 306/442] gh-111973: Update macOS installer to use SQLite 3.44.2 (GH-113279) --- Mac/BuildScript/build-installer.py | 6 +++--- .../macOS/2023-12-19-10-50-08.gh-issue-111973.HMHJfy.rst | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-19-10-50-08.gh-issue-111973.HMHJfy.rst diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 938c895c784f33..32de56bcf13086 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -359,9 +359,9 @@ def library_recipes(): ), ), dict( - name="SQLite 3.43.1", - url="https://sqlite.org/2023/sqlite-autoconf-3430100.tar.gz", - checksum="77e61befe9c3298da0504f87772a24b0", + name="SQLite 3.44.2", + url="https://sqlite.org/2023/sqlite-autoconf-3440200.tar.gz", + checksum="c02f40fd4f809ced95096250adc5764a", extra_cflags=('-Os ' '-DSQLITE_ENABLE_FTS5 ' '-DSQLITE_ENABLE_FTS4 ' diff --git a/Misc/NEWS.d/next/macOS/2023-12-19-10-50-08.gh-issue-111973.HMHJfy.rst b/Misc/NEWS.d/next/macOS/2023-12-19-10-50-08.gh-issue-111973.HMHJfy.rst new file mode 100644 index 00000000000000..0cf3abf3b71890 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-19-10-50-08.gh-issue-111973.HMHJfy.rst @@ -0,0 +1 @@ +Update macOS installer to use SQLite 3.44.2. From e1117cb8864ca337a26388a6186aa85f7cc29a94 Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Tue, 19 Dec 2023 22:04:43 +0300 Subject: [PATCH 307/442] gh-87264: Convert tarinfo type to stat type (GH-113230) Co-authored-by: val-shkolnikov --- Lib/tarfile.py | 7 ++++++- Lib/test/test_tarfile.py | 20 +++++++++++++++---- ...3-12-17-13-56-30.gh-issue-87264.RgfHCv.rst | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-17-13-56-30.gh-issue-87264.RgfHCv.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index ec32f9ba49b03f..5ada0ad626bda8 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2106,6 +2106,10 @@ def list(self, verbose=True, *, members=None): output is produced. `members' is optional and must be a subset of the list returned by getmembers(). """ + # Convert tarinfo type to stat type. + type2mode = {REGTYPE: stat.S_IFREG, SYMTYPE: stat.S_IFLNK, + FIFOTYPE: stat.S_IFIFO, CHRTYPE: stat.S_IFCHR, + DIRTYPE: stat.S_IFDIR, BLKTYPE: stat.S_IFBLK} self._check() if members is None: @@ -2115,7 +2119,8 @@ def list(self, verbose=True, *, members=None): if tarinfo.mode is None: _safe_print("??????????") else: - _safe_print(stat.filemode(tarinfo.mode)) + modetype = type2mode.get(tarinfo.type, 0) + _safe_print(stat.filemode(modetype | tarinfo.mode)) _safe_print("%s/%s" % (tarinfo.uname or tarinfo.uid, tarinfo.gname or tarinfo.gid)) if tarinfo.ischr() or tarinfo.isblk(): diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index edfeac6d6a5edf..da5009126b3815 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -323,11 +323,23 @@ def test_list_verbose(self): # accessories if verbose flag is being used # ... # ?rw-r--r-- tarfile/tarfile 7011 2003-01-06 07:19:43 ustar/conttype - # ?rw-r--r-- tarfile/tarfile 7011 2003-01-06 07:19:43 ustar/regtype + # -rw-r--r-- tarfile/tarfile 7011 2003-01-06 07:19:43 ustar/regtype + # drwxr-xr-x tarfile/tarfile 0 2003-01-05 15:19:43 ustar/dirtype/ # ... - self.assertRegex(out, (br'\?rw-r--r-- tarfile/tarfile\s+7011 ' - br'\d{4}-\d\d-\d\d\s+\d\d:\d\d:\d\d ' - br'ustar/\w+type ?\r?\n') * 2) + # + # Array of values to modify the regex below: + # ((file_type, file_permissions, file_length), ...) + type_perm_lengths = ( + (br'\?', b'rw-r--r--', b'7011'), (b'-', b'rw-r--r--', b'7011'), + (b'd', b'rwxr-xr-x', b'0'), (b'd', b'rwxr-xr-x', b'255'), + (br'\?', b'rw-r--r--', b'0'), (b'l', b'rwxrwxrwx', b'0'), + (b'b', b'rw-rw----', b'3,0'), (b'c', b'rw-rw-rw-', b'1,3'), + (b'p', b'rw-r--r--', b'0')) + self.assertRegex(out, b''.join( + [(tp + (br'%s tarfile/tarfile\s+%s ' % (perm, ln) + + br'\d{4}-\d\d-\d\d\s+\d\d:\d\d:\d\d ' + br'ustar/\w+type[/>\sa-z-]*\n')) for tp, perm, ln + in type_perm_lengths])) # Make sure it prints the source of link with verbose flag self.assertIn(b'ustar/symtype -> regtype', out) self.assertIn(b'./ustar/linktest2/symtype -> ../linktest1/regtype', out) diff --git a/Misc/NEWS.d/next/Library/2023-12-17-13-56-30.gh-issue-87264.RgfHCv.rst b/Misc/NEWS.d/next/Library/2023-12-17-13-56-30.gh-issue-87264.RgfHCv.rst new file mode 100644 index 00000000000000..fa987d4f0af9ba --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-17-13-56-30.gh-issue-87264.RgfHCv.rst @@ -0,0 +1 @@ +Fixed tarfile list() method to show file type. From 14d4c7742a26a3c2253e0a0be476981104b29f5a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Tue, 19 Dec 2023 23:31:52 +0100 Subject: [PATCH 308/442] gh-111973: Update Windows installer to use SQLite 3.44.2 (#113281) --- .../next/Windows/2023-12-19-10-56-46.gh-issue-111973.A9Wtsb.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/2023-12-19-10-56-46.gh-issue-111973.A9Wtsb.rst diff --git a/Misc/NEWS.d/next/Windows/2023-12-19-10-56-46.gh-issue-111973.A9Wtsb.rst b/Misc/NEWS.d/next/Windows/2023-12-19-10-56-46.gh-issue-111973.A9Wtsb.rst new file mode 100644 index 00000000000000..0cefa4e44093f0 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-19-10-56-46.gh-issue-111973.A9Wtsb.rst @@ -0,0 +1 @@ +Update Windows installer to use SQLite 3.44.2. diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index 6151990096e0be..3919c0592ec00d 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.11 -set libraries=%libraries% sqlite-3.43.1.0 +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 diff --git a/PCbuild/python.props b/PCbuild/python.props index 496bc3dd4cf794..3b7a8876a707df 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -68,7 +68,7 @@ - $(ExternalsDir)sqlite-3.43.1.0\ + $(ExternalsDir)sqlite-3.44.2.0\ $(ExternalsDir)bzip2-1.0.8\ $(ExternalsDir)xz-5.2.5\ $(ExternalsDir)libffi-3.4.4\ diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index 175ce918ac20f6..b9d76515c383f7 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -189,7 +189,7 @@ _ssl again when building. _sqlite3 - Wraps SQLite 3.43.1, which is itself built by sqlite3.vcxproj + Wraps SQLite 3.44.2, which is itself built by sqlite3.vcxproj Homepage: https://www.sqlite.org/ _tkinter From 4afa7be32da32fac2a2bcde4b881db174e81240c Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Tue, 19 Dec 2023 17:55:24 -0800 Subject: [PATCH 309/442] add Ujan to ACKS for work on enum docs (GH-113301) --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 12335c911ae42a..6b98be32905391 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1573,6 +1573,7 @@ Liam Routt Todd Rovito Craig Rowland Clinton Roy +Ujan RoyBandyopadhyay Paul Rubin Sam Ruby Demur Rumed From 22b8945d7678be86f801ca54f004a5dba2006835 Mon Sep 17 00:00:00 2001 From: David Greaves Date: Wed, 20 Dec 2023 09:51:16 +0000 Subject: [PATCH 310/442] Fix typo in collections.abc docs example (#113310) Calling the instance reference arg for the __next__ method, "next", seems misleading as it would normally just be "self" --- Doc/library/collections.abc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index e0c72ff9249ee7..582bb18f752bd5 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -87,7 +87,7 @@ the required methods (unless those methods have been set to class E: def __iter__(self): ... - def __next__(next): ... + def __next__(self): ... .. doctest:: From 5a7cc667f816f0377f763322c2367301ea3379ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:58:38 +0000 Subject: [PATCH 311/442] Fix typo in datamodel docs (#113314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> --- Doc/reference/datamodel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 5e3757e1f5c6f6..c774d75433420e 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2811,7 +2811,7 @@ through the object's keys; for sequences, it should iterate through the values. the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a :term:`sequence` type) is up to the :meth:`__getitem__` method. If *key* is - of an inappropriate type, :exc:`TypeError` may be raised; if of a value + of an inappropriate type, :exc:`TypeError` may be raised; if *key* is a value outside the set of indexes for the sequence (after any special interpretation of negative values), :exc:`IndexError` should be raised. For :term:`mapping` types, if *key* is missing (not in the container), From 57b7e52790ae56309832497a1ce17e3e731b920e Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 20 Dec 2023 12:52:12 +0000 Subject: [PATCH 312/442] gh-112205: Support docstring for `@getter` (#113160) --------- Co-authored-by: Erlend E. Aasland --- Lib/test/clinic.test.c | 20 +++-- Lib/test/test_clinic.py | 15 ++++ Modules/_io/clinic/bufferedio.c.h | 29 +++++-- Modules/_io/clinic/stringio.c.h | 29 +++++-- Modules/_io/clinic/textio.c.h | 137 +++++++++++++++++++++++++++--- Modules/_io/textio.c | 58 ++++++++----- Tools/clinic/clinic.py | 38 +++++++-- 7 files changed, 265 insertions(+), 61 deletions(-) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index a6a21664bb82a1..b15aeb898d35a1 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -4956,11 +4956,16 @@ Test_meth_coexist_impl(TestObj *self) Test.property [clinic start generated code]*/ +#if defined(Test_property_HAS_DOCSTR) +# define Test_property_DOCSTR Test_property__doc__ +#else +# define Test_property_DOCSTR NULL +#endif #if defined(TEST_PROPERTY_GETSETDEF) # undef TEST_PROPERTY_GETSETDEF -# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, Test_property_DOCSTR}, #else -# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, NULL, NULL}, +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, NULL, Test_property_DOCSTR}, #endif static PyObject * @@ -4974,16 +4979,21 @@ Test_property_get(TestObj *self, void *Py_UNUSED(context)) static PyObject * Test_property_get_impl(TestObj *self) -/*[clinic end generated code: output=af8140b692e0e2f1 input=2d92b3449fbc7d2b]*/ +/*[clinic end generated code: output=27b519719d992e03 input=2d92b3449fbc7d2b]*/ /*[clinic input] @setter Test.property [clinic start generated code]*/ +#if defined(TEST_PROPERTY_HAS_DOCSTR) +# define Test_property_DOCSTR Test_property__doc__ +#else +# define Test_property_DOCSTR NULL +#endif #if defined(TEST_PROPERTY_GETSETDEF) # undef TEST_PROPERTY_GETSETDEF -# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, NULL}, +# define TEST_PROPERTY_GETSETDEF {"property", (getter)Test_property_get, (setter)Test_property_set, Test_property_DOCSTR}, #else # define TEST_PROPERTY_GETSETDEF {"property", NULL, (setter)Test_property_set, NULL}, #endif @@ -4999,7 +5009,7 @@ Test_property_set(TestObj *self, PyObject *value, void *Py_UNUSED(context)) static int Test_property_set_impl(TestObj *self, PyObject *value) -/*[clinic end generated code: output=f3eba6487d7550e2 input=3bc3f46a23c83a88]*/ +/*[clinic end generated code: output=9797cd03c5204ddb input=3bc3f46a23c83a88]*/ /*[clinic input] output push diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 6c6bd4e75a0b02..714fa6d7cb328f 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2221,6 +2221,21 @@ class Foo "" "" expected_error = f"{annotation} method cannot define parameters" self.expect_failure(block, expected_error) + def test_setter_docstring(self): + block = """ + module foo + class Foo "" "" + @setter + Foo.property + + foo + + bar + [clinic start generated code]*/ + """ + expected_error = "docstrings are only supported for @getter, not @setter" + self.expect_failure(block, expected_error) + def test_duplicate_getset(self): annotations = ["@getter", "@setter"] for annotation in annotations: diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index ec46d5409a3d82..d5bec5f71f5be8 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -327,11 +327,16 @@ _io__Buffered_simple_flush(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#if defined(_io__Buffered_closed_HAS_DOCSTR) +# define _io__Buffered_closed_DOCSTR _io__Buffered_closed__doc__ +#else +# define _io__Buffered_closed_DOCSTR NULL +#endif #if defined(_IO__BUFFERED_CLOSED_GETSETDEF) # undef _IO__BUFFERED_CLOSED_GETSETDEF -# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, (setter)_io__Buffered_closed_set, NULL}, +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, (setter)_io__Buffered_closed_set, _io__Buffered_closed_DOCSTR}, #else -# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, NULL, NULL}, +# define _IO__BUFFERED_CLOSED_GETSETDEF {"closed", (getter)_io__Buffered_closed_get, NULL, _io__Buffered_closed_DOCSTR}, #endif static PyObject * @@ -464,11 +469,16 @@ _io__Buffered_writable(buffered *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#if defined(_io__Buffered_name_HAS_DOCSTR) +# define _io__Buffered_name_DOCSTR _io__Buffered_name__doc__ +#else +# define _io__Buffered_name_DOCSTR NULL +#endif #if defined(_IO__BUFFERED_NAME_GETSETDEF) # undef _IO__BUFFERED_NAME_GETSETDEF -# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, (setter)_io__Buffered_name_set, NULL}, +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, (setter)_io__Buffered_name_set, _io__Buffered_name_DOCSTR}, #else -# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, NULL, NULL}, +# define _IO__BUFFERED_NAME_GETSETDEF {"name", (getter)_io__Buffered_name_get, NULL, _io__Buffered_name_DOCSTR}, #endif static PyObject * @@ -486,11 +496,16 @@ _io__Buffered_name_get(buffered *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io__Buffered_mode_HAS_DOCSTR) +# define _io__Buffered_mode_DOCSTR _io__Buffered_mode__doc__ +#else +# define _io__Buffered_mode_DOCSTR NULL +#endif #if defined(_IO__BUFFERED_MODE_GETSETDEF) # undef _IO__BUFFERED_MODE_GETSETDEF -# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, (setter)_io__Buffered_mode_set, NULL}, +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, (setter)_io__Buffered_mode_set, _io__Buffered_mode_DOCSTR}, #else -# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, NULL, NULL}, +# define _IO__BUFFERED_MODE_GETSETDEF {"mode", (getter)_io__Buffered_mode_get, NULL, _io__Buffered_mode_DOCSTR}, #endif static PyObject * @@ -1230,4 +1245,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=0999c33f666dc692 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=442b05b9a117df6c input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/stringio.c.h b/Modules/_io/clinic/stringio.c.h index fc2962d1c9c9a7..6bdb2181985f7d 100644 --- a/Modules/_io/clinic/stringio.c.h +++ b/Modules/_io/clinic/stringio.c.h @@ -475,11 +475,16 @@ _io_StringIO___setstate__(stringio *self, PyObject *state) return return_value; } +#if defined(_io_StringIO_closed_HAS_DOCSTR) +# define _io_StringIO_closed_DOCSTR _io_StringIO_closed__doc__ +#else +# define _io_StringIO_closed_DOCSTR NULL +#endif #if defined(_IO_STRINGIO_CLOSED_GETSETDEF) # undef _IO_STRINGIO_CLOSED_GETSETDEF -# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, (setter)_io_StringIO_closed_set, NULL}, +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, (setter)_io_StringIO_closed_set, _io_StringIO_closed_DOCSTR}, #else -# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, NULL, NULL}, +# define _IO_STRINGIO_CLOSED_GETSETDEF {"closed", (getter)_io_StringIO_closed_get, NULL, _io_StringIO_closed_DOCSTR}, #endif static PyObject * @@ -497,11 +502,16 @@ _io_StringIO_closed_get(stringio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_StringIO_line_buffering_HAS_DOCSTR) +# define _io_StringIO_line_buffering_DOCSTR _io_StringIO_line_buffering__doc__ +#else +# define _io_StringIO_line_buffering_DOCSTR NULL +#endif #if defined(_IO_STRINGIO_LINE_BUFFERING_GETSETDEF) # undef _IO_STRINGIO_LINE_BUFFERING_GETSETDEF -# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, (setter)_io_StringIO_line_buffering_set, NULL}, +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, (setter)_io_StringIO_line_buffering_set, _io_StringIO_line_buffering_DOCSTR}, #else -# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, NULL}, +# define _IO_STRINGIO_LINE_BUFFERING_GETSETDEF {"line_buffering", (getter)_io_StringIO_line_buffering_get, NULL, _io_StringIO_line_buffering_DOCSTR}, #endif static PyObject * @@ -519,11 +529,16 @@ _io_StringIO_line_buffering_get(stringio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_StringIO_newlines_HAS_DOCSTR) +# define _io_StringIO_newlines_DOCSTR _io_StringIO_newlines__doc__ +#else +# define _io_StringIO_newlines_DOCSTR NULL +#endif #if defined(_IO_STRINGIO_NEWLINES_GETSETDEF) # undef _IO_STRINGIO_NEWLINES_GETSETDEF -# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, (setter)_io_StringIO_newlines_set, NULL}, +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, (setter)_io_StringIO_newlines_set, _io_StringIO_newlines_DOCSTR}, #else -# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, NULL, NULL}, +# define _IO_STRINGIO_NEWLINES_GETSETDEF {"newlines", (getter)_io_StringIO_newlines_get, NULL, _io_StringIO_newlines_DOCSTR}, #endif static PyObject * @@ -540,4 +555,4 @@ _io_StringIO_newlines_get(stringio *self, void *Py_UNUSED(context)) return return_value; } -/*[clinic end generated code: output=27726751d98ab617 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9ffea20cd32d4cd8 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/textio.c.h b/Modules/_io/clinic/textio.c.h index f24f65f0c1d4f9..23b3cc8d71e098 100644 --- a/Modules/_io/clinic/textio.c.h +++ b/Modules/_io/clinic/textio.c.h @@ -201,6 +201,89 @@ _io__TextIOBase_write(PyObject *self, PyTypeObject *cls, PyObject *const *args, return return_value; } +PyDoc_STRVAR(_io__TextIOBase_encoding__doc__, +"Encoding of the text stream.\n" +"\n" +"Subclasses should override."); +#define _io__TextIOBase_encoding_HAS_DOCSTR + +#if defined(_io__TextIOBase_encoding_HAS_DOCSTR) +# define _io__TextIOBase_encoding_DOCSTR _io__TextIOBase_encoding__doc__ +#else +# define _io__TextIOBase_encoding_DOCSTR NULL +#endif +#if defined(_IO__TEXTIOBASE_ENCODING_GETSETDEF) +# undef _IO__TEXTIOBASE_ENCODING_GETSETDEF +# define _IO__TEXTIOBASE_ENCODING_GETSETDEF {"encoding", (getter)_io__TextIOBase_encoding_get, (setter)_io__TextIOBase_encoding_set, _io__TextIOBase_encoding_DOCSTR}, +#else +# define _IO__TEXTIOBASE_ENCODING_GETSETDEF {"encoding", (getter)_io__TextIOBase_encoding_get, NULL, _io__TextIOBase_encoding_DOCSTR}, +#endif + +static PyObject * +_io__TextIOBase_encoding_get_impl(PyObject *self); + +static PyObject * +_io__TextIOBase_encoding_get(PyObject *self, void *Py_UNUSED(context)) +{ + return _io__TextIOBase_encoding_get_impl(self); +} + +PyDoc_STRVAR(_io__TextIOBase_newlines__doc__, +"Line endings translated so far.\n" +"\n" +"Only line endings translated during reading are considered.\n" +"\n" +"Subclasses should override."); +#define _io__TextIOBase_newlines_HAS_DOCSTR + +#if defined(_io__TextIOBase_newlines_HAS_DOCSTR) +# define _io__TextIOBase_newlines_DOCSTR _io__TextIOBase_newlines__doc__ +#else +# define _io__TextIOBase_newlines_DOCSTR NULL +#endif +#if defined(_IO__TEXTIOBASE_NEWLINES_GETSETDEF) +# undef _IO__TEXTIOBASE_NEWLINES_GETSETDEF +# define _IO__TEXTIOBASE_NEWLINES_GETSETDEF {"newlines", (getter)_io__TextIOBase_newlines_get, (setter)_io__TextIOBase_newlines_set, _io__TextIOBase_newlines_DOCSTR}, +#else +# define _IO__TEXTIOBASE_NEWLINES_GETSETDEF {"newlines", (getter)_io__TextIOBase_newlines_get, NULL, _io__TextIOBase_newlines_DOCSTR}, +#endif + +static PyObject * +_io__TextIOBase_newlines_get_impl(PyObject *self); + +static PyObject * +_io__TextIOBase_newlines_get(PyObject *self, void *Py_UNUSED(context)) +{ + return _io__TextIOBase_newlines_get_impl(self); +} + +PyDoc_STRVAR(_io__TextIOBase_errors__doc__, +"The error setting of the decoder or encoder.\n" +"\n" +"Subclasses should override."); +#define _io__TextIOBase_errors_HAS_DOCSTR + +#if defined(_io__TextIOBase_errors_HAS_DOCSTR) +# define _io__TextIOBase_errors_DOCSTR _io__TextIOBase_errors__doc__ +#else +# define _io__TextIOBase_errors_DOCSTR NULL +#endif +#if defined(_IO__TEXTIOBASE_ERRORS_GETSETDEF) +# undef _IO__TEXTIOBASE_ERRORS_GETSETDEF +# define _IO__TEXTIOBASE_ERRORS_GETSETDEF {"errors", (getter)_io__TextIOBase_errors_get, (setter)_io__TextIOBase_errors_set, _io__TextIOBase_errors_DOCSTR}, +#else +# define _IO__TEXTIOBASE_ERRORS_GETSETDEF {"errors", (getter)_io__TextIOBase_errors_get, NULL, _io__TextIOBase_errors_DOCSTR}, +#endif + +static PyObject * +_io__TextIOBase_errors_get_impl(PyObject *self); + +static PyObject * +_io__TextIOBase_errors_get(PyObject *self, void *Py_UNUSED(context)) +{ + return _io__TextIOBase_errors_get_impl(self); +} + PyDoc_STRVAR(_io_IncrementalNewlineDecoder___init____doc__, "IncrementalNewlineDecoder(decoder, translate, errors=\'strict\')\n" "--\n" @@ -1048,11 +1131,16 @@ _io_TextIOWrapper_close(textio *self, PyObject *Py_UNUSED(ignored)) return return_value; } +#if defined(_io_TextIOWrapper_name_HAS_DOCSTR) +# define _io_TextIOWrapper_name_DOCSTR _io_TextIOWrapper_name__doc__ +#else +# define _io_TextIOWrapper_name_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER_NAME_GETSETDEF) # undef _IO_TEXTIOWRAPPER_NAME_GETSETDEF -# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, (setter)_io_TextIOWrapper_name_set, NULL}, +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, (setter)_io_TextIOWrapper_name_set, _io_TextIOWrapper_name_DOCSTR}, #else -# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, NULL, NULL}, +# define _IO_TEXTIOWRAPPER_NAME_GETSETDEF {"name", (getter)_io_TextIOWrapper_name_get, NULL, _io_TextIOWrapper_name_DOCSTR}, #endif static PyObject * @@ -1070,11 +1158,16 @@ _io_TextIOWrapper_name_get(textio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_TextIOWrapper_closed_HAS_DOCSTR) +# define _io_TextIOWrapper_closed_DOCSTR _io_TextIOWrapper_closed__doc__ +#else +# define _io_TextIOWrapper_closed_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER_CLOSED_GETSETDEF) # undef _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF -# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, (setter)_io_TextIOWrapper_closed_set, NULL}, +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, (setter)_io_TextIOWrapper_closed_set, _io_TextIOWrapper_closed_DOCSTR}, #else -# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, NULL, NULL}, +# define _IO_TEXTIOWRAPPER_CLOSED_GETSETDEF {"closed", (getter)_io_TextIOWrapper_closed_get, NULL, _io_TextIOWrapper_closed_DOCSTR}, #endif static PyObject * @@ -1092,11 +1185,16 @@ _io_TextIOWrapper_closed_get(textio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_TextIOWrapper_newlines_HAS_DOCSTR) +# define _io_TextIOWrapper_newlines_DOCSTR _io_TextIOWrapper_newlines__doc__ +#else +# define _io_TextIOWrapper_newlines_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF) # undef _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF -# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, (setter)_io_TextIOWrapper_newlines_set, NULL}, +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, (setter)_io_TextIOWrapper_newlines_set, _io_TextIOWrapper_newlines_DOCSTR}, #else -# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, NULL, NULL}, +# define _IO_TEXTIOWRAPPER_NEWLINES_GETSETDEF {"newlines", (getter)_io_TextIOWrapper_newlines_get, NULL, _io_TextIOWrapper_newlines_DOCSTR}, #endif static PyObject * @@ -1114,11 +1212,16 @@ _io_TextIOWrapper_newlines_get(textio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_TextIOWrapper_errors_HAS_DOCSTR) +# define _io_TextIOWrapper_errors_DOCSTR _io_TextIOWrapper_errors__doc__ +#else +# define _io_TextIOWrapper_errors_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER_ERRORS_GETSETDEF) # undef _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF -# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, (setter)_io_TextIOWrapper_errors_set, NULL}, +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, (setter)_io_TextIOWrapper_errors_set, _io_TextIOWrapper_errors_DOCSTR}, #else -# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, NULL, NULL}, +# define _IO_TEXTIOWRAPPER_ERRORS_GETSETDEF {"errors", (getter)_io_TextIOWrapper_errors_get, NULL, _io_TextIOWrapper_errors_DOCSTR}, #endif static PyObject * @@ -1136,11 +1239,16 @@ _io_TextIOWrapper_errors_get(textio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_io_TextIOWrapper__CHUNK_SIZE_HAS_DOCSTR) +# define _io_TextIOWrapper__CHUNK_SIZE_DOCSTR _io_TextIOWrapper__CHUNK_SIZE__doc__ +#else +# define _io_TextIOWrapper__CHUNK_SIZE_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) # undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF -# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, _io_TextIOWrapper__CHUNK_SIZE_DOCSTR}, #else -# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, NULL, NULL}, +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, NULL, _io_TextIOWrapper__CHUNK_SIZE_DOCSTR}, #endif static PyObject * @@ -1158,9 +1266,14 @@ _io_TextIOWrapper__CHUNK_SIZE_get(textio *self, void *Py_UNUSED(context)) return return_value; } +#if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_HAS_DOCSTR) +# define _io_TextIOWrapper__CHUNK_SIZE_DOCSTR _io_TextIOWrapper__CHUNK_SIZE__doc__ +#else +# define _io_TextIOWrapper__CHUNK_SIZE_DOCSTR NULL +#endif #if defined(_IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF) # undef _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF -# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, +# define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", (getter)_io_TextIOWrapper__CHUNK_SIZE_get, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, _io_TextIOWrapper__CHUNK_SIZE_DOCSTR}, #else # define _IO_TEXTIOWRAPPER__CHUNK_SIZE_GETSETDEF {"_CHUNK_SIZE", NULL, (setter)_io_TextIOWrapper__CHUNK_SIZE_set, NULL}, #endif @@ -1179,4 +1292,4 @@ _io_TextIOWrapper__CHUNK_SIZE_set(textio *self, PyObject *value, void *Py_UNUSED return return_value; } -/*[clinic end generated code: output=7af87bf848a5d3f3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=d01aa598647c1385 input=a9049054013a1b77]*/ diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 702336ca2aeb06..4507930c14bb50 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -131,40 +131,52 @@ _io__TextIOBase_write_impl(PyObject *self, PyTypeObject *cls, return _unsupported(state, "write"); } -PyDoc_STRVAR(textiobase_encoding_doc, - "Encoding of the text stream.\n" - "\n" - "Subclasses should override.\n" - ); +/*[clinic input] +@getter +_io._TextIOBase.encoding + +Encoding of the text stream. + +Subclasses should override. +[clinic start generated code]*/ static PyObject * -textiobase_encoding_get(PyObject *self, void *context) +_io__TextIOBase_encoding_get_impl(PyObject *self) +/*[clinic end generated code: output=e0f5d8f548b92432 input=4736d7621dd38f43]*/ { Py_RETURN_NONE; } -PyDoc_STRVAR(textiobase_newlines_doc, - "Line endings translated so far.\n" - "\n" - "Only line endings translated during reading are considered.\n" - "\n" - "Subclasses should override.\n" - ); +/*[clinic input] +@getter +_io._TextIOBase.newlines + +Line endings translated so far. + +Only line endings translated during reading are considered. + +Subclasses should override. +[clinic start generated code]*/ static PyObject * -textiobase_newlines_get(PyObject *self, void *context) +_io__TextIOBase_newlines_get_impl(PyObject *self) +/*[clinic end generated code: output=46ec147fb9f00c2a input=a5b196d076af1164]*/ { Py_RETURN_NONE; } -PyDoc_STRVAR(textiobase_errors_doc, - "The error setting of the decoder or encoder.\n" - "\n" - "Subclasses should override.\n" - ); +/*[clinic input] +@getter +_io._TextIOBase.errors + +The error setting of the decoder or encoder. + +Subclasses should override. +[clinic start generated code]*/ static PyObject * -textiobase_errors_get(PyObject *self, void *context) +_io__TextIOBase_errors_get_impl(PyObject *self) +/*[clinic end generated code: output=c6623d6addcd087d input=974aa52d1db93a82]*/ { Py_RETURN_NONE; } @@ -179,9 +191,9 @@ static PyMethodDef textiobase_methods[] = { }; static PyGetSetDef textiobase_getset[] = { - {"encoding", (getter)textiobase_encoding_get, NULL, textiobase_encoding_doc}, - {"newlines", (getter)textiobase_newlines_get, NULL, textiobase_newlines_doc}, - {"errors", (getter)textiobase_errors_get, NULL, textiobase_errors_doc}, + _IO__TEXTIOBASE_ENCODING_GETSETDEF + _IO__TEXTIOBASE_NEWLINES_GETSETDEF + _IO__TEXTIOBASE_ERRORS_GETSETDEF {NULL} }; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 87feef1b82ca39..345459cf2fd623 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -865,6 +865,11 @@ class CLanguage(Language): PyDoc_STRVAR({c_basename}__doc__, {docstring}); """) + GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet(""" + PyDoc_STRVAR({getset_basename}__doc__, + {docstring}); + #define {getset_basename}_HAS_DOCSTR + """) IMPL_DEFINITION_PROTOTYPE: Final[str] = normalize_snippet(""" static {impl_return_type} {c_basename}_impl({impl_parameters}) @@ -874,17 +879,27 @@ class CLanguage(Language): {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, """) GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #if defined({getset_basename}_HAS_DOCSTR) + # define {getset_basename}_DOCSTR {getset_basename}__doc__ + #else + # define {getset_basename}_DOCSTR NULL + #endif #if defined({getset_name}_GETSETDEF) # undef {getset_name}_GETSETDEF - # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}}, #else - # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, NULL}}, + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}}, #endif """) SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + #if defined({getset_name}_HAS_DOCSTR) + # define {getset_basename}_DOCSTR {getset_basename}__doc__ + #else + # define {getset_basename}_DOCSTR NULL + #endif #if defined({getset_name}_GETSETDEF) # undef {getset_name}_GETSETDEF - # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, NULL}}, + # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, (setter){getset_basename}_set, {getset_basename}_DOCSTR}}, #else # define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}}, #endif @@ -1187,11 +1202,17 @@ def output_templates( docstring_prototype = docstring_definition = '' elif f.kind is GETTER: methoddef_define = self.GETTERDEF_PROTOTYPE_DEFINE - docstring_prototype = docstring_definition = '' + if f.docstring: + docstring_prototype = '' + docstring_definition = self.GETSET_DOCSTRING_PROTOTYPE_STRVAR + else: + docstring_prototype = docstring_definition = '' elif f.kind is SETTER: + if f.docstring: + fail("docstrings are only supported for @getter, not @setter") return_value_declaration = "int {return_value};" methoddef_define = self.SETTERDEF_PROTOTYPE_DEFINE - docstring_prototype = docstring_prototype = docstring_definition = '' + docstring_prototype = docstring_definition = '' else: docstring_prototype = self.DOCSTRING_PROTOTYPE_VAR docstring_definition = self.DOCSTRING_PROTOTYPE_STRVAR @@ -6255,6 +6276,9 @@ def format_docstring_signature( add(f.displayname) if self.forced_text_signature: add(self.forced_text_signature) + elif f.kind in {GETTER, SETTER}: + # @getter and @setter do not need signatures like a method or a function. + return '' else: add('(') @@ -6427,8 +6451,8 @@ def format_docstring_parameters(params: list[Parameter]) -> str: def format_docstring(self) -> str: assert self.function is not None f = self.function - if f.kind.new_or_init and not f.docstring: - # don't render a docstring at all, no signature, nothing. + # For the following special cases, it does not make sense to render a docstring. + if f.kind in {METHOD_INIT, METHOD_NEW, GETTER, SETTER} and not f.docstring: return f.docstring # Enforce the summary line! From a545a86ec64fbab325db101bdd8964f524a89790 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Wed, 20 Dec 2023 08:13:44 -0600 Subject: [PATCH 313/442] gh-111178: Make slot functions in typeobject.c have compatible types (GH-112752) --- Include/internal/pycore_typeobject.h | 2 +- Objects/object.c | 2 +- Objects/typeobject.c | 53 ++++++++++++++++++---------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index f983de56049631..c03c3d766bef61 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -133,7 +133,7 @@ _PyType_IsReady(PyTypeObject *type) extern PyObject* _Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int *suppress_missing_attribute); -extern PyObject* _Py_type_getattro(PyTypeObject *type, PyObject *name); +extern PyObject* _Py_type_getattro(PyObject *type, PyObject *name); extern PyObject* _Py_BaseObject_RichCompare(PyObject* self, PyObject* other, int op); diff --git a/Objects/object.c b/Objects/object.c index cdb7a08a7828fb..d970a26756173b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1196,7 +1196,7 @@ PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result) } return 0; } - if (tp->tp_getattro == (getattrofunc)_Py_type_getattro) { + if (tp->tp_getattro == _Py_type_getattro) { int supress_missing_attribute_exception = 0; *result = _Py_type_getattro_impl((PyTypeObject*)v, name, &supress_missing_attribute_exception); if (supress_missing_attribute_exception) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 08f5f47d586729..5261ef92413615 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1597,8 +1597,9 @@ static PyGetSetDef type_getsets[] = { }; static PyObject * -type_repr(PyTypeObject *type) +type_repr(PyObject *self) { + PyTypeObject *type = (PyTypeObject *)self; if (type->tp_name == NULL) { // type_repr() called before the type is fully initialized // by PyType_Ready(). @@ -1630,8 +1631,9 @@ type_repr(PyTypeObject *type) } static PyObject * -type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) +type_call(PyObject *self, PyObject *args, PyObject *kwds) { + PyTypeObject *type = (PyTypeObject *)self; PyObject *obj; PyThreadState *tstate = _PyThreadState_GET(); @@ -4917,14 +4919,15 @@ _Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * suppress_missin /* This is similar to PyObject_GenericGetAttr(), but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ PyObject * -_Py_type_getattro(PyTypeObject *type, PyObject *name) +_Py_type_getattro(PyObject *type, PyObject *name) { - return _Py_type_getattro_impl(type, name, NULL); + return _Py_type_getattro_impl((PyTypeObject *)type, name, NULL); } static int -type_setattro(PyTypeObject *type, PyObject *name, PyObject *value) +type_setattro(PyObject *self, PyObject *name, PyObject *value) { + PyTypeObject *type = (PyTypeObject *)self; int res; if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) { PyErr_Format( @@ -5069,8 +5072,10 @@ _PyStaticType_Dealloc(PyInterpreterState *interp, PyTypeObject *type) static void -type_dealloc(PyTypeObject *type) +type_dealloc(PyObject *self) { + PyTypeObject *type = (PyTypeObject *)self; + // Assert this is a heap-allocated type object _PyObject_ASSERT((PyObject *)type, type->tp_flags & Py_TPFLAGS_HEAPTYPE); @@ -5257,8 +5262,10 @@ PyDoc_STRVAR(type_doc, "type(name, bases, dict, **kwds) -> a new type"); static int -type_traverse(PyTypeObject *type, visitproc visit, void *arg) +type_traverse(PyObject *self, visitproc visit, void *arg) { + PyTypeObject *type = (PyTypeObject *)self; + /* Because of type_is_gc(), the collector only calls this for heaptypes. */ if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { @@ -5286,8 +5293,10 @@ type_traverse(PyTypeObject *type, visitproc visit, void *arg) } static int -type_clear(PyTypeObject *type) +type_clear(PyObject *self) { + PyTypeObject *type = (PyTypeObject *)self; + /* Because of type_is_gc(), the collector only calls this for heaptypes. */ _PyObject_ASSERT((PyObject *)type, type->tp_flags & Py_TPFLAGS_HEAPTYPE); @@ -5334,9 +5343,9 @@ type_clear(PyTypeObject *type) } static int -type_is_gc(PyTypeObject *type) +type_is_gc(PyObject *type) { - return type->tp_flags & Py_TPFLAGS_HEAPTYPE; + return ((PyTypeObject *)type)->tp_flags & Py_TPFLAGS_HEAPTYPE; } @@ -5349,28 +5358,28 @@ PyTypeObject PyType_Type = { "type", /* tp_name */ sizeof(PyHeapTypeObject), /* tp_basicsize */ sizeof(PyMemberDef), /* tp_itemsize */ - (destructor)type_dealloc, /* tp_dealloc */ + type_dealloc, /* tp_dealloc */ offsetof(PyTypeObject, tp_vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)type_repr, /* tp_repr */ + type_repr, /* tp_repr */ &type_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - (ternaryfunc)type_call, /* tp_call */ + type_call, /* tp_call */ 0, /* tp_str */ - (getattrofunc)_Py_type_getattro, /* tp_getattro */ - (setattrofunc)type_setattro, /* tp_setattro */ + _Py_type_getattro, /* tp_getattro */ + type_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_ITEMS_AT_END, /* tp_flags */ type_doc, /* tp_doc */ - (traverseproc)type_traverse, /* tp_traverse */ - (inquiry)type_clear, /* tp_clear */ + type_traverse, /* tp_traverse */ + type_clear, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyTypeObject, tp_weaklist), /* tp_weaklistoffset */ 0, /* tp_iter */ @@ -5387,7 +5396,7 @@ PyTypeObject PyType_Type = { 0, /* tp_alloc */ type_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ - (inquiry)type_is_gc, /* tp_is_gc */ + type_is_gc, /* tp_is_gc */ .tp_vectorcall = type_vectorcall, }; @@ -6561,6 +6570,12 @@ PyDoc_STRVAR(object_doc, "When called, it accepts no arguments and returns a new featureless\n" "instance that has no instance attributes and cannot be given any.\n"); +static Py_hash_t +object_hash(PyObject *obj) +{ + return _Py_HashPointer(obj); +} + PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", /* tp_name */ @@ -6575,7 +6590,7 @@ PyTypeObject PyBaseObject_Type = { 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ - (hashfunc)_Py_HashPointer, /* tp_hash */ + object_hash, /* tp_hash */ 0, /* tp_call */ object_str, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ From e96f26083bff31e86c068aa22542e91f38293ea3 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 20 Dec 2023 14:27:25 +0000 Subject: [PATCH 314/442] GH-111485: Generate instruction and uop metadata (GH-113287) --- Include/internal/pycore_opcode_metadata.h | 1166 ++++------------- Include/internal/pycore_uop_ids.h | 193 ++- Include/internal/pycore_uop_metadata.h | 403 ++++++ Include/opcode_ids.h | 2 +- Lib/_opcode_metadata.py | 18 +- Lib/test/test_capi/test_opt.py | 5 +- Makefile.pre.in | 25 +- Python/assemble.c | 10 +- Python/bytecodes.c | 72 +- Python/ceval.c | 1 + Python/compile.c | 37 +- Python/flowgraph.c | 4 +- Python/generated_cases.c.h | 19 + Python/optimizer.c | 32 +- Python/specialize.c | 1 + Tools/cases_generator/analyzer.py | 162 ++- Tools/cases_generator/cwriter.py | 35 +- Tools/cases_generator/generate_cases.py | 1 - Tools/cases_generator/generators_common.py | 45 +- Tools/cases_generator/opcode_id_generator.py | 112 +- .../opcode_metadata_generator.py | 386 ++++++ .../cases_generator/py_metadata_generator.py | 97 ++ Tools/cases_generator/stack.py | 40 +- Tools/cases_generator/tier1_generator.py | 1 + Tools/cases_generator/tier2_generator.py | 11 +- Tools/cases_generator/uop_id_generator.py | 60 +- .../cases_generator/uop_metadata_generator.py | 73 ++ 27 files changed, 1740 insertions(+), 1271 deletions(-) create mode 100644 Include/internal/pycore_uop_metadata.h create mode 100644 Tools/cases_generator/opcode_metadata_generator.py create mode 100644 Tools/cases_generator/py_metadata_generator.py create mode 100644 Tools/cases_generator/uop_metadata_generator.py diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 36b6cd52d2b272..a9e0cd1238929f 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1,13 +1,20 @@ -// This file is generated by Tools/cases_generator/generate_cases.py +// This file is generated by Tools/cases_generator/opcode_metadata_generator.py // from: // Python/bytecodes.c // Do not edit! +#ifndef Py_CORE_OPCODE_METADATA_H +#define Py_CORE_OPCODE_METADATA_H +#ifdef __cplusplus +extern "C" { +#endif + #ifndef Py_BUILD_CORE # error "this header requires Py_BUILD_CORE define" #endif #include // bool +#include "opcode_ids.h" #define IS_PSEUDO_INSTR(OP) ( \ @@ -26,10 +33,9 @@ 0) #include "pycore_uop_ids.h" - -extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); +extern int _PyOpcode_num_popped(int opcode, int oparg); #ifdef NEED_OPCODE_METADATA -int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { +int _PyOpcode_num_popped(int opcode, int oparg) { switch(opcode) { case BEFORE_ASYNC_WITH: return 1; @@ -68,7 +74,7 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case BINARY_SUBSCR_TUPLE_INT: return 2; case BUILD_CONST_KEY_MAP: - return oparg + 1; + return 1 + oparg; case BUILD_LIST: return oparg; case BUILD_MAP: @@ -76,7 +82,7 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case BUILD_SET: return oparg; case BUILD_SLICE: - return ((oparg == 3) ? 1 : 0) + 2; + return 2 + (((oparg == 3) ? 1 : 0)); case BUILD_STRING: return oparg; case BUILD_TUPLE: @@ -84,51 +90,51 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case CACHE: return 0; case CALL: - return oparg + 2; + return 2 + oparg; case CALL_ALLOC_AND_ENTER_INIT: - return oparg + 2; + return 2 + oparg; case CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; + return 2 + oparg; case CALL_BUILTIN_CLASS: - return oparg + 2; + return 2 + oparg; case CALL_BUILTIN_FAST: - return oparg + 2; + return 2 + oparg; case CALL_BUILTIN_FAST_WITH_KEYWORDS: - return oparg + 2; + return 2 + oparg; case CALL_BUILTIN_O: - return oparg + 2; + return 2 + oparg; case CALL_FUNCTION_EX: - return ((oparg & 1) ? 1 : 0) + 3; + return 3 + ((oparg & 1)); case CALL_INTRINSIC_1: return 1; case CALL_INTRINSIC_2: return 2; case CALL_ISINSTANCE: - return oparg + 2; + return 2 + oparg; case CALL_KW: - return oparg + 3; + return 3 + oparg; case CALL_LEN: - return oparg + 2; + return 2 + oparg; case CALL_LIST_APPEND: - return oparg + 2; + return 2 + oparg; case CALL_METHOD_DESCRIPTOR_FAST: - return oparg + 2; + return 2 + oparg; case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: - return oparg + 2; + return 2 + oparg; case CALL_METHOD_DESCRIPTOR_NOARGS: - return oparg + 2; + return 2 + oparg; case CALL_METHOD_DESCRIPTOR_O: - return oparg + 2; + return 2 + oparg; case CALL_PY_EXACT_ARGS: - return oparg + 2; + return 2 + oparg; case CALL_PY_WITH_DEFAULTS: - return oparg + 2; + return 2 + oparg; case CALL_STR_1: - return oparg + 2; + return 2 + oparg; case CALL_TUPLE_1: - return oparg + 2; + return 2 + oparg; case CALL_TYPE_1: - return oparg + 2; + return 2 + oparg; case CHECK_EG_MATCH: return 2; case CHECK_EXC_MATCH: @@ -148,7 +154,7 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case CONVERT_VALUE: return 1; case COPY: - return (oparg-1) + 1; + return 1 + (oparg-1); case COPY_FREE_VARS: return 0; case DELETE_ATTR: @@ -164,9 +170,9 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case DELETE_SUBSCR: return 2; case DICT_MERGE: - return (oparg - 1) + 5; + return 5 + (oparg - 1); case DICT_UPDATE: - return (oparg - 1) + 2; + return 2 + (oparg - 1); case END_ASYNC_FOR: return 2; case END_FOR: @@ -249,20 +255,16 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 1; case IS_OP: return 2; - case JUMP: - return 0; case JUMP_BACKWARD: return 0; case JUMP_BACKWARD_NO_INTERRUPT: return 0; case JUMP_FORWARD: return 0; - case JUMP_NO_INTERRUPT: - return 0; case LIST_APPEND: - return (oparg-1) + 2; + return 2 + (oparg-1); case LIST_EXTEND: - return (oparg-1) + 2; + return 2 + (oparg-1); case LOAD_ASSERTION_ERROR: return 0; case LOAD_ATTR: @@ -293,8 +295,6 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 1; case LOAD_BUILD_CLASS: return 0; - case LOAD_CLOSURE: - return 0; case LOAD_CONST: return 0; case LOAD_DEREF: @@ -319,8 +319,6 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 0; case LOAD_LOCALS: return 0; - case LOAD_METHOD: - return 1; case LOAD_NAME: return 0; case LOAD_SUPER_ATTR: @@ -329,18 +327,12 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 3; case LOAD_SUPER_ATTR_METHOD: return 3; - case LOAD_SUPER_METHOD: - return 3; - case LOAD_ZERO_SUPER_ATTR: - return 3; - case LOAD_ZERO_SUPER_METHOD: - return 3; case MAKE_CELL: return 0; case MAKE_FUNCTION: return 1; case MAP_ADD: - return (oparg - 1) + 3; + return 3 + (oparg - 1); case MATCH_CLASS: return 3; case MATCH_KEYS: @@ -351,8 +343,6 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 1; case NOP: return 0; - case POP_BLOCK: - return 0; case POP_EXCEPT: return 1; case POP_JUMP_IF_FALSE: @@ -372,7 +362,7 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case RAISE_VARARGS: return oparg; case RERAISE: - return oparg + 1; + return 1 + oparg; case RESERVED: return 0; case RESUME: @@ -391,18 +381,12 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 2; case SETUP_ANNOTATIONS: return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_FINALLY: - return 0; - case SETUP_WITH: - return 0; case SET_ADD: - return (oparg-1) + 2; + return 2 + (oparg-1); case SET_FUNCTION_ATTRIBUTE: return 2; case SET_UPDATE: - return (oparg-1) + 2; + return 2 + (oparg-1); case STORE_ATTR: return 2; case STORE_ATTR_INSTANCE_VALUE: @@ -417,8 +401,6 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 1; case STORE_FAST_LOAD_FAST: return 1; - case STORE_FAST_MAYBE_NULL: - return 1; case STORE_FAST_STORE_FAST: return 2; case STORE_GLOBAL: @@ -434,7 +416,7 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { case STORE_SUBSCR_LIST_INT: return 3; case SWAP: - return (oparg-2) + 2; + return 2 + (oparg-2); case TO_BOOL: return 1; case TO_BOOL_ALWAYS_TRUE: @@ -469,207 +451,16 @@ int _PyOpcode_num_popped(int opcode, int oparg, bool jump) { return 4; case YIELD_VALUE: return 1; - case _BINARY_OP: - return 2; - case _BINARY_OP_ADD_FLOAT: - return 2; - case _BINARY_OP_ADD_INT: - return 2; - case _BINARY_OP_ADD_UNICODE: - return 2; - case _BINARY_OP_INPLACE_ADD_UNICODE: - return 2; - case _BINARY_OP_MULTIPLY_FLOAT: - return 2; - case _BINARY_OP_MULTIPLY_INT: - return 2; - case _BINARY_OP_SUBTRACT_FLOAT: - return 2; - case _BINARY_OP_SUBTRACT_INT: - return 2; - case _BINARY_SUBSCR: - return 2; - case _CALL: - return oparg + 2; - case _CHECK_ATTR_CLASS: - return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: - return 1; - case _CHECK_ATTR_MODULE: - return 1; - case _CHECK_ATTR_WITH_HINT: - return 1; - case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: - return 1; - case _CHECK_PEP_523: - return 0; - case _CHECK_STACK_SPACE: - return oparg + 2; - case _CHECK_VALIDITY: - return 0; - case _COMPARE_OP: - return 2; - case _EXIT_TRACE: - return 0; - case _FOR_ITER: - return 1; - case _FOR_ITER_TIER_TWO: - return 1; - case _GUARD_BOTH_FLOAT: - return 2; - case _GUARD_BOTH_INT: - return 2; - case _GUARD_BOTH_UNICODE: - return 2; - case _GUARD_BUILTINS_VERSION: - return 0; - case _GUARD_DORV_VALUES: - return 1; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: - return 1; - case _GUARD_GLOBALS_VERSION: - return 0; - case _GUARD_IS_FALSE_POP: - return 1; - case _GUARD_IS_NONE_POP: - return 1; - case _GUARD_IS_NOT_NONE_POP: - return 1; - case _GUARD_IS_TRUE_POP: - return 1; - case _GUARD_KEYS_VERSION: - return 1; - case _GUARD_NOT_EXHAUSTED_LIST: - return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: - return 1; - case _GUARD_NOT_EXHAUSTED_TUPLE: - return 1; - case _GUARD_TYPE_VERSION: - return 1; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return oparg + 2; - case _INSERT: - return oparg + 1; - case _IS_NONE: - return 1; - case _ITER_CHECK_LIST: - return 1; - case _ITER_CHECK_RANGE: - return 1; - case _ITER_CHECK_TUPLE: - return 1; - case _ITER_JUMP_LIST: - return 1; - case _ITER_JUMP_RANGE: - return 1; - case _ITER_JUMP_TUPLE: - return 1; - case _ITER_NEXT_LIST: - return 1; - case _ITER_NEXT_RANGE: - return 1; - case _ITER_NEXT_TUPLE: - return 1; - case _JUMP_TO_TOP: - return 0; - case _LOAD_ATTR: - return 1; - case _LOAD_ATTR_CLASS: - return 1; - case _LOAD_ATTR_INSTANCE_VALUE: - return 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: - return 1; - case _LOAD_ATTR_METHOD_NO_DICT: - return 1; - case _LOAD_ATTR_METHOD_WITH_VALUES: - return 1; - case _LOAD_ATTR_MODULE: - return 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: - return 1; - case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: - return 1; - case _LOAD_ATTR_SLOT: - return 1; - case _LOAD_ATTR_WITH_HINT: - return 1; - case _LOAD_GLOBAL: - return 0; - case _LOAD_GLOBAL_BUILTINS: - return 0; - case _LOAD_GLOBAL_MODULE: - return 0; - case _LOAD_SUPER_ATTR: - return 3; - case _POP_FRAME: - return 1; - case _POP_JUMP_IF_FALSE: - return 1; - case _POP_JUMP_IF_TRUE: - return 1; - case _PUSH_FRAME: - return 1; - case _SAVE_RETURN_OFFSET: - return 0; - case _SEND: - return 2; - case _SET_IP: - return 0; - case _SPECIALIZE_BINARY_OP: - return 2; - case _SPECIALIZE_BINARY_SUBSCR: - return 2; - case _SPECIALIZE_CALL: - return oparg + 2; - case _SPECIALIZE_COMPARE_OP: - return 2; - case _SPECIALIZE_FOR_ITER: - return 1; - case _SPECIALIZE_LOAD_ATTR: - return 1; - case _SPECIALIZE_LOAD_GLOBAL: - return 0; - case _SPECIALIZE_LOAD_SUPER_ATTR: - return 3; - case _SPECIALIZE_SEND: - return 2; - case _SPECIALIZE_STORE_ATTR: - return 1; - case _SPECIALIZE_STORE_SUBSCR: - return 2; - case _SPECIALIZE_TO_BOOL: - return 1; - case _SPECIALIZE_UNPACK_SEQUENCE: - return 1; - case _STORE_ATTR: - return 2; - case _STORE_ATTR_INSTANCE_VALUE: - return 2; - case _STORE_ATTR_SLOT: - return 2; - case _STORE_SUBSCR: - return 3; - case _TO_BOOL: - return 1; - case _UNPACK_SEQUENCE: - return 1; default: return -1; } } -#endif // NEED_OPCODE_METADATA -extern int _PyOpcode_num_pushed(int opcode, int oparg, bool jump); +#endif + +extern int _PyOpcode_num_pushed(int opcode, int oparg); #ifdef NEED_OPCODE_METADATA -int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { +int _PyOpcode_num_pushed(int opcode, int oparg) { switch(opcode) { case BEFORE_ASYNC_WITH: return 2; @@ -728,7 +519,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case CALL_ALLOC_AND_ENTER_INIT: return 1; case CALL_BOUND_METHOD_EXACT_ARGS: - return 0; + return (((0) ? 1 : 0)); case CALL_BUILTIN_CLASS: return 1; case CALL_BUILTIN_FAST: @@ -760,7 +551,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case CALL_METHOD_DESCRIPTOR_O: return 1; case CALL_PY_EXACT_ARGS: - return 0; + return (((0) ? 1 : 0)); case CALL_PY_WITH_DEFAULTS: return 1; case CALL_STR_1: @@ -788,7 +579,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case CONVERT_VALUE: return 1; case COPY: - return (oparg-1) + 2; + return 2 + (oparg-1); case COPY_FREE_VARS: return 0; case DELETE_ATTR: @@ -804,9 +595,9 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case DELETE_SUBSCR: return 0; case DICT_MERGE: - return (oparg - 1) + 4; + return 4 + (oparg - 1); case DICT_UPDATE: - return (oparg - 1) + 1; + return 1 + (oparg - 1); case END_ASYNC_FOR: return 0; case END_FOR: @@ -868,7 +659,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case INSTRUMENTED_JUMP_FORWARD: return 0; case INSTRUMENTED_LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; case INSTRUMENTED_POP_JUMP_IF_NONE: @@ -889,52 +680,46 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { return 0; case IS_OP: return 1; - case JUMP: - return 0; case JUMP_BACKWARD: return 0; case JUMP_BACKWARD_NO_INTERRUPT: return 0; case JUMP_FORWARD: return 0; - case JUMP_NO_INTERRUPT: - return 0; case LIST_APPEND: - return (oparg-1) + 1; + return 1 + (oparg-1); case LIST_EXTEND: - return (oparg-1) + 1; + return 1 + (oparg-1); case LOAD_ASSERTION_ERROR: return 1; case LOAD_ATTR: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_ATTR_CLASS: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: - return 1; + return 1 + (((0) ? 1 : 0)); case LOAD_ATTR_INSTANCE_VALUE: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_ATTR_METHOD_LAZY_DICT: - return 2; + return 1 + (((1) ? 1 : 0)); case LOAD_ATTR_METHOD_NO_DICT: - return 2; + return 1 + (((1) ? 1 : 0)); case LOAD_ATTR_METHOD_WITH_VALUES: - return 2; + return 1 + (((1) ? 1 : 0)); case LOAD_ATTR_MODULE: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: - return 1; + return 1 + (((0) ? 1 : 0)); case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: - return 1; + return 1 + (((0) ? 1 : 0)); case LOAD_ATTR_PROPERTY: - return 1; + return 1 + (((0) ? 1 : 0)); case LOAD_ATTR_SLOT: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_ATTR_WITH_HINT: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_BUILD_CLASS: return 1; - case LOAD_CLOSURE: - return 1; case LOAD_CONST: return 1; case LOAD_DEREF: @@ -952,35 +737,27 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case LOAD_FROM_DICT_OR_GLOBALS: return 1; case LOAD_GLOBAL: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_GLOBAL_BUILTIN: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_GLOBAL_MODULE: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_LOCALS: return 1; - case LOAD_METHOD: - return (oparg & 1 ? 1 : 0) + 1; case LOAD_NAME: return 1; case LOAD_SUPER_ATTR: - return (oparg & 1 ? 1 : 0) + 1; + return 1 + ((oparg & 1)); case LOAD_SUPER_ATTR_ATTR: - return 1; + return 1 + (((0) ? 1 : 0)); case LOAD_SUPER_ATTR_METHOD: return 2; - case LOAD_SUPER_METHOD: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_ATTR: - return (oparg & 1 ? 1 : 0) + 1; - case LOAD_ZERO_SUPER_METHOD: - return (oparg & 1 ? 1 : 0) + 1; case MAKE_CELL: return 0; case MAKE_FUNCTION: return 1; case MAP_ADD: - return (oparg - 1) + 1; + return 1 + (oparg - 1); case MATCH_CLASS: return 1; case MATCH_KEYS: @@ -991,8 +768,6 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { return 2; case NOP: return 0; - case POP_BLOCK: - return 0; case POP_EXCEPT: return 0; case POP_JUMP_IF_FALSE: @@ -1031,18 +806,12 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { return 2; case SETUP_ANNOTATIONS: return 0; - case SETUP_CLEANUP: - return 0; - case SETUP_FINALLY: - return 0; - case SETUP_WITH: - return 0; case SET_ADD: - return (oparg-1) + 1; + return 1 + (oparg-1); case SET_FUNCTION_ATTRIBUTE: return 1; case SET_UPDATE: - return (oparg-1) + 1; + return 1 + (oparg-1); case STORE_ATTR: return 0; case STORE_ATTR_INSTANCE_VALUE: @@ -1057,8 +826,6 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { return 0; case STORE_FAST_LOAD_FAST: return 1; - case STORE_FAST_MAYBE_NULL: - return 0; case STORE_FAST_STORE_FAST: return 0; case STORE_GLOBAL: @@ -1074,7 +841,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case STORE_SUBSCR_LIST_INT: return 0; case SWAP: - return (oparg-2) + 2; + return 2 + (oparg-2); case TO_BOOL: return 1; case TO_BOOL_ALWAYS_TRUE: @@ -1096,7 +863,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { case UNARY_NOT: return 1; case UNPACK_EX: - return (oparg & 0xFF) + (oparg >> 8) + 1; + return 1 + (oparg >> 8) + (oparg & 0xFF); case UNPACK_SEQUENCE: return oparg; case UNPACK_SEQUENCE_LIST: @@ -1109,221 +876,27 @@ int _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { return 5; case YIELD_VALUE: return 1; - case _BINARY_OP: - return 1; - case _BINARY_OP_ADD_FLOAT: - return 1; - case _BINARY_OP_ADD_INT: - return 1; - case _BINARY_OP_ADD_UNICODE: - return 1; - case _BINARY_OP_INPLACE_ADD_UNICODE: - return 0; - case _BINARY_OP_MULTIPLY_FLOAT: - return 1; - case _BINARY_OP_MULTIPLY_INT: - return 1; - case _BINARY_OP_SUBTRACT_FLOAT: - return 1; - case _BINARY_OP_SUBTRACT_INT: - return 1; - case _BINARY_SUBSCR: - return 1; - case _CALL: - return 1; - case _CHECK_ATTR_CLASS: - return 1; - case _CHECK_ATTR_METHOD_LAZY_DICT: - return 1; - case _CHECK_ATTR_MODULE: - return 1; - case _CHECK_ATTR_WITH_HINT: - return 1; - case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _CHECK_FUNCTION_EXACT_ARGS: - return oparg + 2; - case _CHECK_MANAGED_OBJECT_HAS_VALUES: - return 1; - case _CHECK_PEP_523: - return 0; - case _CHECK_STACK_SPACE: - return oparg + 2; - case _CHECK_VALIDITY: - return 0; - case _COMPARE_OP: - return 1; - case _EXIT_TRACE: - return 0; - case _FOR_ITER: - return 2; - case _FOR_ITER_TIER_TWO: - return 2; - case _GUARD_BOTH_FLOAT: - return 2; - case _GUARD_BOTH_INT: - return 2; - case _GUARD_BOTH_UNICODE: - return 2; - case _GUARD_BUILTINS_VERSION: - return 0; - case _GUARD_DORV_VALUES: - return 1; - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: - return 1; - case _GUARD_GLOBALS_VERSION: - return 0; - case _GUARD_IS_FALSE_POP: - return 0; - case _GUARD_IS_NONE_POP: - return 0; - case _GUARD_IS_NOT_NONE_POP: - return 0; - case _GUARD_IS_TRUE_POP: - return 0; - case _GUARD_KEYS_VERSION: - return 1; - case _GUARD_NOT_EXHAUSTED_LIST: - return 1; - case _GUARD_NOT_EXHAUSTED_RANGE: - return 1; - case _GUARD_NOT_EXHAUSTED_TUPLE: - return 1; - case _GUARD_TYPE_VERSION: - return 1; - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: - return oparg + 2; - case _INIT_CALL_PY_EXACT_ARGS: - return 1; - case _INSERT: - return oparg + 1; - case _IS_NONE: - return 1; - case _ITER_CHECK_LIST: - return 1; - case _ITER_CHECK_RANGE: - return 1; - case _ITER_CHECK_TUPLE: - return 1; - case _ITER_JUMP_LIST: - return 1; - case _ITER_JUMP_RANGE: - return 1; - case _ITER_JUMP_TUPLE: - return 1; - case _ITER_NEXT_LIST: - return 2; - case _ITER_NEXT_RANGE: - return 2; - case _ITER_NEXT_TUPLE: - return 2; - case _JUMP_TO_TOP: - return 0; - case _LOAD_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_ATTR_CLASS: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_ATTR_INSTANCE_VALUE: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_ATTR_METHOD_LAZY_DICT: - return 2; - case _LOAD_ATTR_METHOD_NO_DICT: - return 2; - case _LOAD_ATTR_METHOD_WITH_VALUES: - return 2; - case _LOAD_ATTR_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: - return 1; - case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: - return 1; - case _LOAD_ATTR_SLOT: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_ATTR_WITH_HINT: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_GLOBAL: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_GLOBAL_BUILTINS: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_GLOBAL_MODULE: - return ((oparg & 1) ? 1 : 0) + 1; - case _LOAD_SUPER_ATTR: - return ((oparg & 1) ? 1 : 0) + 1; - case _POP_FRAME: - return 0; - case _POP_JUMP_IF_FALSE: - return 0; - case _POP_JUMP_IF_TRUE: - return 0; - case _PUSH_FRAME: - return 0; - case _SAVE_RETURN_OFFSET: - return 0; - case _SEND: - return 2; - case _SET_IP: - return 0; - case _SPECIALIZE_BINARY_OP: - return 2; - case _SPECIALIZE_BINARY_SUBSCR: - return 2; - case _SPECIALIZE_CALL: - return oparg + 2; - case _SPECIALIZE_COMPARE_OP: - return 2; - case _SPECIALIZE_FOR_ITER: - return 1; - case _SPECIALIZE_LOAD_ATTR: - return 1; - case _SPECIALIZE_LOAD_GLOBAL: - return 0; - case _SPECIALIZE_LOAD_SUPER_ATTR: - return 3; - case _SPECIALIZE_SEND: - return 2; - case _SPECIALIZE_STORE_ATTR: - return 1; - case _SPECIALIZE_STORE_SUBSCR: - return 2; - case _SPECIALIZE_TO_BOOL: - return 1; - case _SPECIALIZE_UNPACK_SEQUENCE: - return 1; - case _STORE_ATTR: - return 0; - case _STORE_ATTR_INSTANCE_VALUE: - return 0; - case _STORE_ATTR_SLOT: - return 0; - case _STORE_SUBSCR: - return 0; - case _TO_BOOL: - return 1; - case _UNPACK_SEQUENCE: - return oparg; default: return -1; } } -#endif // NEED_OPCODE_METADATA + +#endif enum InstructionFormat { - INSTR_FMT_IB, - INSTR_FMT_IBC, - INSTR_FMT_IBC0, - INSTR_FMT_IBC00, - INSTR_FMT_IBC000, - INSTR_FMT_IBC0000000, - INSTR_FMT_IBC00000000, - INSTR_FMT_IX, - INSTR_FMT_IXC, - INSTR_FMT_IXC0, - INSTR_FMT_IXC00, - INSTR_FMT_IXC000, + INSTR_FMT_IB = 1, + INSTR_FMT_IBC = 2, + INSTR_FMT_IBC00 = 3, + INSTR_FMT_IBC000 = 4, + INSTR_FMT_IBC00000000 = 5, + INSTR_FMT_IX = 6, + INSTR_FMT_IXC = 7, + INSTR_FMT_IXC00 = 8, + INSTR_FMT_IXC000 = 9, }; #define IS_VALID_OPCODE(OP) \ - (((OP) >= 0) && ((OP) < OPCODE_METADATA_SIZE) && \ + (((OP) >= 0) && ((OP) < 268) && \ (_PyOpcode_opcode_metadata[(OP)].valid_entry)) #define HAS_ARG_FLAG (1) @@ -1347,17 +920,6 @@ enum InstructionFormat { #define OPCODE_HAS_ERROR(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ERROR_FLAG)) #define OPCODE_HAS_ESCAPES(OP) (_PyOpcode_opcode_metadata[OP].flags & (HAS_ESCAPES_FLAG)) -struct opcode_metadata { - bool valid_entry; - enum InstructionFormat instr_format; - int flags; -}; - -struct opcode_macro_expansion { - int nuops; - struct { int16_t uop; int8_t size; int8_t offset; } uops[12]; -}; - #define OPARG_FULL 0 #define OPARG_CACHE_1 1 #define OPARG_CACHE_2 2 @@ -1365,18 +927,17 @@ struct opcode_macro_expansion { #define OPARG_TOP 5 #define OPARG_BOTTOM 6 #define OPARG_SAVE_RETURN_OFFSET 7 +#define OPARG_REPLACED 9 -#define OPCODE_METADATA_FLAGS(OP) (_PyOpcode_opcode_metadata[(OP)].flags & (HAS_ARG_FLAG | HAS_JUMP_FLAG)) -#define SAME_OPCODE_METADATA(OP1, OP2) \ - (OPCODE_METADATA_FLAGS(OP1) == OPCODE_METADATA_FLAGS(OP2)) - -#define OPCODE_METADATA_SIZE 512 -#define OPCODE_UOP_NAME_SIZE 512 -#define OPCODE_MACRO_EXPANSION_SIZE 256 +struct opcode_metadata { + uint8_t valid_entry; + int8_t instr_format; + int16_t flags; +}; -extern const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE]; +extern const struct opcode_metadata _PyOpcode_opcode_metadata[268]; #ifdef NEED_OPCODE_METADATA -const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { +const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [BEFORE_ASYNC_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BEFORE_WITH] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1486,11 +1047,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [INSTRUMENTED_YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [INTERPRETER_EXIT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [IS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [JUMP] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_BACKWARD_NO_INTERRUPT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [JUMP_NO_INTERRUPT] = { true, 0, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ASSERTION_ERROR] = { true, INSTR_FMT_IX, 0 }, @@ -1508,7 +1067,6 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_BUILD_CLASS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_CLOSURE] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, @@ -1521,14 +1079,10 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [LOAD_GLOBAL_BUILTIN] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_LOCALS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_SUPER_ATTR_METHOD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_ATTR] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ZERO_SUPER_METHOD] = { true, 0, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MAKE_CELL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1537,7 +1091,6 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, [NOP] = { true, INSTR_FMT_IX, 0 }, - [POP_BLOCK] = { true, 0, 0 }, [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, @@ -1557,9 +1110,6 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [SEND] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [SEND_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [SETUP_ANNOTATIONS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [SETUP_CLEANUP] = { true, 0, HAS_ARG_FLAG }, - [SETUP_FINALLY] = { true, 0, HAS_ARG_FLAG }, - [SETUP_WITH] = { true, 0, HAS_ARG_FLAG }, [SET_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1570,7 +1120,6 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, [STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [STORE_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [STORE_FAST_MAYBE_NULL] = { true, 0, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [STORE_FAST_STORE_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [STORE_GLOBAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_NAME] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1596,110 +1145,33 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE] = { [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, - [_BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC, 0 }, - [_BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG }, - [_BINARY_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_CALL] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_CHECK_ATTR_CLASS] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_CHECK_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_CHECK_ATTR_MODULE] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_CHECK_ATTR_WITH_HINT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_FUNCTION_EXACT_ARGS] = { true, INSTR_FMT_IBC0, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_CHECK_PEP_523] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_CHECK_STACK_SPACE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_CHECK_VALIDITY] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_COMPARE_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_EXIT_TRACE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_FOR_ITER] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_FOR_ITER_TIER_TWO] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_GUARD_BOTH_FLOAT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_BOTH_INT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_BOTH_UNICODE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_BUILTINS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_DORV_VALUES] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_GLOBALS_VERSION] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, - [_GUARD_IS_FALSE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_IS_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_IS_NOT_NONE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_IS_TRUE_POP] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_KEYS_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_GUARD_NOT_EXHAUSTED_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_NOT_EXHAUSTED_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_NOT_EXHAUSTED_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_GUARD_TYPE_VERSION] = { true, INSTR_FMT_IXC0, HAS_DEOPT_FLAG }, - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_INIT_CALL_PY_EXACT_ARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_INSERT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_IS_NONE] = { true, INSTR_FMT_IX, 0 }, - [_ITER_CHECK_LIST] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_CHECK_RANGE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_CHECK_TUPLE] = { true, INSTR_FMT_IX, HAS_DEOPT_FLAG }, - [_ITER_JUMP_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_ITER_JUMP_RANGE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_ITER_JUMP_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_ITER_NEXT_LIST] = { true, INSTR_FMT_IX, 0 }, - [_ITER_NEXT_RANGE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_ITER_NEXT_TUPLE] = { true, INSTR_FMT_IX, 0 }, - [_JUMP_TO_TOP] = { true, INSTR_FMT_IX, HAS_EVAL_BREAK_FLAG }, - [_LOAD_ATTR] = { true, INSTR_FMT_IBC0000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [_LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG }, - [_LOAD_ATTR_SLOT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_GLOBAL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_LOAD_GLOBAL_BUILTINS] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_GLOBAL_MODULE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, - [_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_POP_FRAME] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [_POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [_PUSH_FRAME] = { true, INSTR_FMT_IX, 0 }, - [_SAVE_RETURN_OFFSET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [_SEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_SET_IP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_BINARY_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SPECIALIZE_CALL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_GLOBAL] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_LOAD_SUPER_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_SEND] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_ATTR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ESCAPES_FLAG }, - [_SPECIALIZE_STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SPECIALIZE_TO_BOOL] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_SPECIALIZE_UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC, HAS_ESCAPES_FLAG }, - [_STORE_SUBSCR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_TO_BOOL] = { true, INSTR_FMT_IXC0, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [_UNPACK_SEQUENCE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG }, + [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_METHOD] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_SUPER_METHOD] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_ATTR] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ZERO_SUPER_METHOD] = { true, -1, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [POP_BLOCK] = { true, -1, 0 }, + [SETUP_CLEANUP] = { true, -1, HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, -1, HAS_ARG_FLAG }, + [SETUP_WITH] = { true, -1, HAS_ARG_FLAG }, + [STORE_FAST_MAYBE_NULL] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, }; -#endif // NEED_OPCODE_METADATA +#endif + +#define MAX_UOP_PER_EXPANSION 8 +struct opcode_macro_expansion { + int nuops; + struct { int16_t uop; int8_t size; int8_t offset; } uops[MAX_UOP_PER_EXPANSION]; +}; +extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256]; -extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE]; #ifdef NEED_OPCODE_METADATA -const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE] = { - [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { BEFORE_ASYNC_WITH, 0, 0 } } }, - [BEFORE_WITH] = { .nuops = 1, .uops = { { BEFORE_WITH, 0, 0 } } }, +const struct opcode_macro_expansion +_PyOpcode_macro_expansion[256] = { + [BEFORE_ASYNC_WITH] = { .nuops = 1, .uops = { { _BEFORE_ASYNC_WITH, 0, 0 } } }, + [BEFORE_WITH] = { .nuops = 1, .uops = { { _BEFORE_WITH, 0, 0 } } }, [BINARY_OP] = { .nuops = 1, .uops = { { _BINARY_OP, 0, 0 } } }, [BINARY_OP_ADD_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_ADD_FLOAT, 0, 0 } } }, [BINARY_OP_ADD_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_ADD_INT, 0, 0 } } }, @@ -1708,73 +1180,73 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPAN [BINARY_OP_MULTIPLY_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_MULTIPLY_INT, 0, 0 } } }, [BINARY_OP_SUBTRACT_FLOAT] = { .nuops = 2, .uops = { { _GUARD_BOTH_FLOAT, 0, 0 }, { _BINARY_OP_SUBTRACT_FLOAT, 0, 0 } } }, [BINARY_OP_SUBTRACT_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _BINARY_OP_SUBTRACT_INT, 0, 0 } } }, - [BINARY_SLICE] = { .nuops = 1, .uops = { { BINARY_SLICE, 0, 0 } } }, + [BINARY_SLICE] = { .nuops = 1, .uops = { { _BINARY_SLICE, 0, 0 } } }, [BINARY_SUBSCR] = { .nuops = 1, .uops = { { _BINARY_SUBSCR, 0, 0 } } }, - [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_DICT, 0, 0 } } }, - [BINARY_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_LIST_INT, 0, 0 } } }, - [BINARY_SUBSCR_STR_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_STR_INT, 0, 0 } } }, - [BINARY_SUBSCR_TUPLE_INT] = { .nuops = 1, .uops = { { BINARY_SUBSCR_TUPLE_INT, 0, 0 } } }, - [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { BUILD_CONST_KEY_MAP, 0, 0 } } }, - [BUILD_LIST] = { .nuops = 1, .uops = { { BUILD_LIST, 0, 0 } } }, - [BUILD_MAP] = { .nuops = 1, .uops = { { BUILD_MAP, 0, 0 } } }, - [BUILD_SET] = { .nuops = 1, .uops = { { BUILD_SET, 0, 0 } } }, - [BUILD_SLICE] = { .nuops = 1, .uops = { { BUILD_SLICE, 0, 0 } } }, - [BUILD_STRING] = { .nuops = 1, .uops = { { BUILD_STRING, 0, 0 } } }, - [BUILD_TUPLE] = { .nuops = 1, .uops = { { BUILD_TUPLE, 0, 0 } } }, + [BINARY_SUBSCR_DICT] = { .nuops = 1, .uops = { { _BINARY_SUBSCR_DICT, 0, 0 } } }, + [BINARY_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { _BINARY_SUBSCR_LIST_INT, 0, 0 } } }, + [BINARY_SUBSCR_STR_INT] = { .nuops = 1, .uops = { { _BINARY_SUBSCR_STR_INT, 0, 0 } } }, + [BINARY_SUBSCR_TUPLE_INT] = { .nuops = 1, .uops = { { _BINARY_SUBSCR_TUPLE_INT, 0, 0 } } }, + [BUILD_CONST_KEY_MAP] = { .nuops = 1, .uops = { { _BUILD_CONST_KEY_MAP, 0, 0 } } }, + [BUILD_LIST] = { .nuops = 1, .uops = { { _BUILD_LIST, 0, 0 } } }, + [BUILD_MAP] = { .nuops = 1, .uops = { { _BUILD_MAP, 0, 0 } } }, + [BUILD_SET] = { .nuops = 1, .uops = { { _BUILD_SET, 0, 0 } } }, + [BUILD_SLICE] = { .nuops = 1, .uops = { { _BUILD_SLICE, 0, 0 } } }, + [BUILD_STRING] = { .nuops = 1, .uops = { { _BUILD_STRING, 0, 0 } } }, + [BUILD_TUPLE] = { .nuops = 1, .uops = { { _BUILD_TUPLE, 0, 0 } } }, [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { CALL_BUILTIN_CLASS, 0, 0 } } }, - [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST, 0, 0 } } }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { CALL_BUILTIN_O, 0, 0 } } }, - [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { CALL_INTRINSIC_1, 0, 0 } } }, - [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { CALL_INTRINSIC_2, 0, 0 } } }, - [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { CALL_ISINSTANCE, 0, 0 } } }, - [CALL_LEN] = { .nuops = 1, .uops = { { CALL_LEN, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, + [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { _CALL_BUILTIN_CLASS, 0, 0 } } }, + [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { _CALL_BUILTIN_FAST, 0, 0 } } }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { _CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { _CALL_BUILTIN_O, 0, 0 } } }, + [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_1, 0, 0 } } }, + [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_2, 0, 0 } } }, + [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { _CALL_ISINSTANCE, 0, 0 } } }, + [CALL_LEN] = { .nuops = 1, .uops = { { _CALL_LEN, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_STR_1] = { .nuops = 1, .uops = { { CALL_STR_1, 0, 0 } } }, - [CALL_TUPLE_1] = { .nuops = 1, .uops = { { CALL_TUPLE_1, 0, 0 } } }, - [CALL_TYPE_1] = { .nuops = 1, .uops = { { CALL_TYPE_1, 0, 0 } } }, - [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { CHECK_EG_MATCH, 0, 0 } } }, - [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { CHECK_EXC_MATCH, 0, 0 } } }, + [CALL_STR_1] = { .nuops = 1, .uops = { { _CALL_STR_1, 0, 0 } } }, + [CALL_TUPLE_1] = { .nuops = 1, .uops = { { _CALL_TUPLE_1, 0, 0 } } }, + [CALL_TYPE_1] = { .nuops = 1, .uops = { { _CALL_TYPE_1, 0, 0 } } }, + [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { _CHECK_EG_MATCH, 0, 0 } } }, + [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { _CHECK_EXC_MATCH, 0, 0 } } }, [COMPARE_OP] = { .nuops = 1, .uops = { { _COMPARE_OP, 0, 0 } } }, - [COMPARE_OP_FLOAT] = { .nuops = 1, .uops = { { COMPARE_OP_FLOAT, 0, 0 } } }, - [COMPARE_OP_INT] = { .nuops = 1, .uops = { { COMPARE_OP_INT, 0, 0 } } }, - [COMPARE_OP_STR] = { .nuops = 1, .uops = { { COMPARE_OP_STR, 0, 0 } } }, - [CONTAINS_OP] = { .nuops = 1, .uops = { { CONTAINS_OP, 0, 0 } } }, - [CONVERT_VALUE] = { .nuops = 1, .uops = { { CONVERT_VALUE, 0, 0 } } }, - [COPY] = { .nuops = 1, .uops = { { COPY, 0, 0 } } }, - [COPY_FREE_VARS] = { .nuops = 1, .uops = { { COPY_FREE_VARS, 0, 0 } } }, - [DELETE_ATTR] = { .nuops = 1, .uops = { { DELETE_ATTR, 0, 0 } } }, - [DELETE_DEREF] = { .nuops = 1, .uops = { { DELETE_DEREF, 0, 0 } } }, - [DELETE_FAST] = { .nuops = 1, .uops = { { DELETE_FAST, 0, 0 } } }, - [DELETE_GLOBAL] = { .nuops = 1, .uops = { { DELETE_GLOBAL, 0, 0 } } }, - [DELETE_NAME] = { .nuops = 1, .uops = { { DELETE_NAME, 0, 0 } } }, - [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_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 } } }, - [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { FORMAT_WITH_SPEC, 0, 0 } } }, - [FOR_ITER] = { .nuops = 1, .uops = { { _FOR_ITER, 0, 0 } } }, - [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, 0, 0 }, { _ITER_JUMP_LIST, 0, 0 }, { _ITER_NEXT_LIST, 0, 0 } } }, - [FOR_ITER_RANGE] = { .nuops = 3, .uops = { { _ITER_CHECK_RANGE, 0, 0 }, { _ITER_JUMP_RANGE, 0, 0 }, { _ITER_NEXT_RANGE, 0, 0 } } }, - [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 0, 0 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, - [GET_AITER] = { .nuops = 1, .uops = { { GET_AITER, 0, 0 } } }, - [GET_ANEXT] = { .nuops = 1, .uops = { { GET_ANEXT, 0, 0 } } }, - [GET_AWAITABLE] = { .nuops = 1, .uops = { { GET_AWAITABLE, 0, 0 } } }, - [GET_ITER] = { .nuops = 1, .uops = { { GET_ITER, 0, 0 } } }, - [GET_LEN] = { .nuops = 1, .uops = { { GET_LEN, 0, 0 } } }, - [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { GET_YIELD_FROM_ITER, 0, 0 } } }, - [IS_OP] = { .nuops = 1, .uops = { { IS_OP, 0, 0 } } }, - [LIST_APPEND] = { .nuops = 1, .uops = { { LIST_APPEND, 0, 0 } } }, - [LIST_EXTEND] = { .nuops = 1, .uops = { { LIST_EXTEND, 0, 0 } } }, - [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { LOAD_ASSERTION_ERROR, 0, 0 } } }, + [COMPARE_OP_FLOAT] = { .nuops = 1, .uops = { { _COMPARE_OP_FLOAT, 0, 0 } } }, + [COMPARE_OP_INT] = { .nuops = 1, .uops = { { _COMPARE_OP_INT, 0, 0 } } }, + [COMPARE_OP_STR] = { .nuops = 1, .uops = { { _COMPARE_OP_STR, 0, 0 } } }, + [CONTAINS_OP] = { .nuops = 1, .uops = { { _CONTAINS_OP, 0, 0 } } }, + [CONVERT_VALUE] = { .nuops = 1, .uops = { { _CONVERT_VALUE, 0, 0 } } }, + [COPY] = { .nuops = 1, .uops = { { _COPY, 0, 0 } } }, + [COPY_FREE_VARS] = { .nuops = 1, .uops = { { _COPY_FREE_VARS, 0, 0 } } }, + [DELETE_ATTR] = { .nuops = 1, .uops = { { _DELETE_ATTR, 0, 0 } } }, + [DELETE_DEREF] = { .nuops = 1, .uops = { { _DELETE_DEREF, 0, 0 } } }, + [DELETE_FAST] = { .nuops = 1, .uops = { { _DELETE_FAST, 0, 0 } } }, + [DELETE_GLOBAL] = { .nuops = 1, .uops = { { _DELETE_GLOBAL, 0, 0 } } }, + [DELETE_NAME] = { .nuops = 1, .uops = { { _DELETE_NAME, 0, 0 } } }, + [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_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 } } }, + [FORMAT_WITH_SPEC] = { .nuops = 1, .uops = { { _FORMAT_WITH_SPEC, 0, 0 } } }, + [FOR_ITER] = { .nuops = 1, .uops = { { _FOR_ITER, 9, 0 } } }, + [FOR_ITER_LIST] = { .nuops = 3, .uops = { { _ITER_CHECK_LIST, 0, 0 }, { _ITER_JUMP_LIST, 9, 1 }, { _ITER_NEXT_LIST, 0, 0 } } }, + [FOR_ITER_RANGE] = { .nuops = 3, .uops = { { _ITER_CHECK_RANGE, 0, 0 }, { _ITER_JUMP_RANGE, 9, 1 }, { _ITER_NEXT_RANGE, 0, 0 } } }, + [FOR_ITER_TUPLE] = { .nuops = 3, .uops = { { _ITER_CHECK_TUPLE, 0, 0 }, { _ITER_JUMP_TUPLE, 9, 1 }, { _ITER_NEXT_TUPLE, 0, 0 } } }, + [GET_AITER] = { .nuops = 1, .uops = { { _GET_AITER, 0, 0 } } }, + [GET_ANEXT] = { .nuops = 1, .uops = { { _GET_ANEXT, 0, 0 } } }, + [GET_AWAITABLE] = { .nuops = 1, .uops = { { _GET_AWAITABLE, 0, 0 } } }, + [GET_ITER] = { .nuops = 1, .uops = { { _GET_ITER, 0, 0 } } }, + [GET_LEN] = { .nuops = 1, .uops = { { _GET_LEN, 0, 0 } } }, + [GET_YIELD_FROM_ITER] = { .nuops = 1, .uops = { { _GET_YIELD_FROM_ITER, 0, 0 } } }, + [IS_OP] = { .nuops = 1, .uops = { { _IS_OP, 0, 0 } } }, + [LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, 0, 0 } } }, + [LIST_EXTEND] = { .nuops = 1, .uops = { { _LIST_EXTEND, 0, 0 } } }, + [LOAD_ASSERTION_ERROR] = { .nuops = 1, .uops = { { _LOAD_ASSERTION_ERROR, 0, 0 } } }, [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, @@ -1786,183 +1258,81 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[OPCODE_MACRO_EXPAN [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT, 0, 0 }, { _GUARD_KEYS_VERSION, 2, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, [LOAD_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 } } }, [LOAD_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_WITH_HINT, 0, 0 }, { _LOAD_ATTR_WITH_HINT, 1, 3 } } }, - [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { LOAD_BUILD_CLASS, 0, 0 } } }, - [LOAD_CONST] = { .nuops = 1, .uops = { { LOAD_CONST, 0, 0 } } }, - [LOAD_DEREF] = { .nuops = 1, .uops = { { LOAD_DEREF, 0, 0 } } }, - [LOAD_FAST] = { .nuops = 1, .uops = { { LOAD_FAST, 0, 0 } } }, - [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { LOAD_FAST_AND_CLEAR, 0, 0 } } }, - [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { LOAD_FAST_CHECK, 0, 0 } } }, - [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { LOAD_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, - [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, + [LOAD_BUILD_CLASS] = { .nuops = 1, .uops = { { _LOAD_BUILD_CLASS, 0, 0 } } }, + [LOAD_CONST] = { .nuops = 1, .uops = { { _LOAD_CONST, 0, 0 } } }, + [LOAD_DEREF] = { .nuops = 1, .uops = { { _LOAD_DEREF, 0, 0 } } }, + [LOAD_FAST] = { .nuops = 1, .uops = { { _LOAD_FAST, 0, 0 } } }, + [LOAD_FAST_AND_CLEAR] = { .nuops = 1, .uops = { { _LOAD_FAST_AND_CLEAR, 0, 0 } } }, + [LOAD_FAST_CHECK] = { .nuops = 1, .uops = { { _LOAD_FAST_CHECK, 0, 0 } } }, + [LOAD_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { _LOAD_FAST, 5, 0 }, { _LOAD_FAST, 6, 0 } } }, + [LOAD_FROM_DICT_OR_DEREF] = { .nuops = 1, .uops = { { _LOAD_FROM_DICT_OR_DEREF, 0, 0 } } }, + [LOAD_FROM_DICT_OR_GLOBALS] = { .nuops = 1, .uops = { { _LOAD_FROM_DICT_OR_GLOBALS, 0, 0 } } }, [LOAD_GLOBAL] = { .nuops = 1, .uops = { { _LOAD_GLOBAL, 0, 0 } } }, [LOAD_GLOBAL_BUILTIN] = { .nuops = 3, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _GUARD_BUILTINS_VERSION, 1, 2 }, { _LOAD_GLOBAL_BUILTINS, 1, 3 } } }, [LOAD_GLOBAL_MODULE] = { .nuops = 2, .uops = { { _GUARD_GLOBALS_VERSION, 1, 1 }, { _LOAD_GLOBAL_MODULE, 1, 3 } } }, - [LOAD_LOCALS] = { .nuops = 1, .uops = { { LOAD_LOCALS, 0, 0 } } }, - [LOAD_NAME] = { .nuops = 1, .uops = { { LOAD_NAME, 0, 0 } } }, - [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, - [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, - [MAKE_CELL] = { .nuops = 1, .uops = { { MAKE_CELL, 0, 0 } } }, - [MAKE_FUNCTION] = { .nuops = 1, .uops = { { MAKE_FUNCTION, 0, 0 } } }, - [MAP_ADD] = { .nuops = 1, .uops = { { MAP_ADD, 0, 0 } } }, - [MATCH_CLASS] = { .nuops = 1, .uops = { { MATCH_CLASS, 0, 0 } } }, - [MATCH_KEYS] = { .nuops = 1, .uops = { { MATCH_KEYS, 0, 0 } } }, - [MATCH_MAPPING] = { .nuops = 1, .uops = { { MATCH_MAPPING, 0, 0 } } }, - [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { MATCH_SEQUENCE, 0, 0 } } }, - [NOP] = { .nuops = 1, .uops = { { NOP, 0, 0 } } }, - [POP_EXCEPT] = { .nuops = 1, .uops = { { POP_EXCEPT, 0, 0 } } }, - [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 0, 0 } } }, - [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 0, 0 } } }, - [POP_TOP] = { .nuops = 1, .uops = { { POP_TOP, 0, 0 } } }, - [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { PUSH_EXC_INFO, 0, 0 } } }, - [PUSH_NULL] = { .nuops = 1, .uops = { { PUSH_NULL, 0, 0 } } }, - [RESUME_CHECK] = { .nuops = 1, .uops = { { RESUME_CHECK, 0, 0 } } }, - [RETURN_CONST] = { .nuops = 2, .uops = { { LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, + [LOAD_LOCALS] = { .nuops = 1, .uops = { { _LOAD_LOCALS, 0, 0 } } }, + [LOAD_NAME] = { .nuops = 1, .uops = { { _LOAD_NAME, 0, 0 } } }, + [LOAD_SUPER_ATTR_ATTR] = { .nuops = 1, .uops = { { _LOAD_SUPER_ATTR_ATTR, 0, 0 } } }, + [LOAD_SUPER_ATTR_METHOD] = { .nuops = 1, .uops = { { _LOAD_SUPER_ATTR_METHOD, 0, 0 } } }, + [MAKE_CELL] = { .nuops = 1, .uops = { { _MAKE_CELL, 0, 0 } } }, + [MAKE_FUNCTION] = { .nuops = 1, .uops = { { _MAKE_FUNCTION, 0, 0 } } }, + [MAP_ADD] = { .nuops = 1, .uops = { { _MAP_ADD, 0, 0 } } }, + [MATCH_CLASS] = { .nuops = 1, .uops = { { _MATCH_CLASS, 0, 0 } } }, + [MATCH_KEYS] = { .nuops = 1, .uops = { { _MATCH_KEYS, 0, 0 } } }, + [MATCH_MAPPING] = { .nuops = 1, .uops = { { _MATCH_MAPPING, 0, 0 } } }, + [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { _MATCH_SEQUENCE, 0, 0 } } }, + [NOP] = { .nuops = 1, .uops = { { _NOP, 0, 0 } } }, + [POP_EXCEPT] = { .nuops = 1, .uops = { { _POP_EXCEPT, 0, 0 } } }, + [POP_JUMP_IF_FALSE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_FALSE, 9, 1 } } }, + [POP_JUMP_IF_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_TRUE, 9, 1 } } }, + [POP_JUMP_IF_NOT_NONE] = { .nuops = 2, .uops = { { _IS_NONE, 0, 0 }, { _POP_JUMP_IF_FALSE, 9, 1 } } }, + [POP_JUMP_IF_TRUE] = { .nuops = 1, .uops = { { _POP_JUMP_IF_TRUE, 9, 1 } } }, + [POP_TOP] = { .nuops = 1, .uops = { { _POP_TOP, 0, 0 } } }, + [PUSH_EXC_INFO] = { .nuops = 1, .uops = { { _PUSH_EXC_INFO, 0, 0 } } }, + [PUSH_NULL] = { .nuops = 1, .uops = { { _PUSH_NULL, 0, 0 } } }, + [RESUME_CHECK] = { .nuops = 1, .uops = { { _RESUME_CHECK, 0, 0 } } }, + [RETURN_CONST] = { .nuops = 2, .uops = { { _LOAD_CONST, 0, 0 }, { _POP_FRAME, 0, 0 } } }, [RETURN_VALUE] = { .nuops = 1, .uops = { { _POP_FRAME, 0, 0 } } }, - [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { SETUP_ANNOTATIONS, 0, 0 } } }, - [SET_ADD] = { .nuops = 1, .uops = { { SET_ADD, 0, 0 } } }, - [SET_FUNCTION_ATTRIBUTE] = { .nuops = 1, .uops = { { SET_FUNCTION_ATTRIBUTE, 0, 0 } } }, - [SET_UPDATE] = { .nuops = 1, .uops = { { SET_UPDATE, 0, 0 } } }, + [SETUP_ANNOTATIONS] = { .nuops = 1, .uops = { { _SETUP_ANNOTATIONS, 0, 0 } } }, + [SET_ADD] = { .nuops = 1, .uops = { { _SET_ADD, 0, 0 } } }, + [SET_FUNCTION_ATTRIBUTE] = { .nuops = 1, .uops = { { _SET_FUNCTION_ATTRIBUTE, 0, 0 } } }, + [SET_UPDATE] = { .nuops = 1, .uops = { { _SET_UPDATE, 0, 0 } } }, [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, 0, 0 } } }, [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _GUARD_DORV_VALUES, 0, 0 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 } } }, [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, - [STORE_DEREF] = { .nuops = 1, .uops = { { STORE_DEREF, 0, 0 } } }, - [STORE_FAST] = { .nuops = 1, .uops = { { STORE_FAST, 0, 0 } } }, - [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { LOAD_FAST, 6, 0 } } }, - [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { STORE_FAST, 5, 0 }, { STORE_FAST, 6, 0 } } }, - [STORE_GLOBAL] = { .nuops = 1, .uops = { { STORE_GLOBAL, 0, 0 } } }, - [STORE_NAME] = { .nuops = 1, .uops = { { STORE_NAME, 0, 0 } } }, - [STORE_SLICE] = { .nuops = 1, .uops = { { STORE_SLICE, 0, 0 } } }, + [STORE_DEREF] = { .nuops = 1, .uops = { { _STORE_DEREF, 0, 0 } } }, + [STORE_FAST] = { .nuops = 1, .uops = { { _STORE_FAST, 0, 0 } } }, + [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { _STORE_FAST, 5, 0 }, { _LOAD_FAST, 6, 0 } } }, + [STORE_FAST_STORE_FAST] = { .nuops = 2, .uops = { { _STORE_FAST, 5, 0 }, { _STORE_FAST, 6, 0 } } }, + [STORE_GLOBAL] = { .nuops = 1, .uops = { { _STORE_GLOBAL, 0, 0 } } }, + [STORE_NAME] = { .nuops = 1, .uops = { { _STORE_NAME, 0, 0 } } }, + [STORE_SLICE] = { .nuops = 1, .uops = { { _STORE_SLICE, 0, 0 } } }, [STORE_SUBSCR] = { .nuops = 1, .uops = { { _STORE_SUBSCR, 0, 0 } } }, - [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { STORE_SUBSCR_DICT, 0, 0 } } }, - [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { STORE_SUBSCR_LIST_INT, 0, 0 } } }, - [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } }, + [STORE_SUBSCR_DICT] = { .nuops = 1, .uops = { { _STORE_SUBSCR_DICT, 0, 0 } } }, + [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { _STORE_SUBSCR_LIST_INT, 0, 0 } } }, + [SWAP] = { .nuops = 1, .uops = { { _SWAP, 0, 0 } } }, [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, - [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, - [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { TO_BOOL_BOOL, 0, 0 } } }, - [TO_BOOL_INT] = { .nuops = 1, .uops = { { TO_BOOL_INT, 0, 0 } } }, - [TO_BOOL_LIST] = { .nuops = 1, .uops = { { TO_BOOL_LIST, 0, 0 } } }, - [TO_BOOL_NONE] = { .nuops = 1, .uops = { { TO_BOOL_NONE, 0, 0 } } }, - [TO_BOOL_STR] = { .nuops = 1, .uops = { { TO_BOOL_STR, 0, 0 } } }, - [UNARY_INVERT] = { .nuops = 1, .uops = { { UNARY_INVERT, 0, 0 } } }, - [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { UNARY_NEGATIVE, 0, 0 } } }, - [UNARY_NOT] = { .nuops = 1, .uops = { { UNARY_NOT, 0, 0 } } }, - [UNPACK_EX] = { .nuops = 1, .uops = { { UNPACK_EX, 0, 0 } } }, + [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { _TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, + [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL_BOOL, 0, 0 } } }, + [TO_BOOL_INT] = { .nuops = 1, .uops = { { _TO_BOOL_INT, 0, 0 } } }, + [TO_BOOL_LIST] = { .nuops = 1, .uops = { { _TO_BOOL_LIST, 0, 0 } } }, + [TO_BOOL_NONE] = { .nuops = 1, .uops = { { _TO_BOOL_NONE, 0, 0 } } }, + [TO_BOOL_STR] = { .nuops = 1, .uops = { { _TO_BOOL_STR, 0, 0 } } }, + [UNARY_INVERT] = { .nuops = 1, .uops = { { _UNARY_INVERT, 0, 0 } } }, + [UNARY_NEGATIVE] = { .nuops = 1, .uops = { { _UNARY_NEGATIVE, 0, 0 } } }, + [UNARY_NOT] = { .nuops = 1, .uops = { { _UNARY_NOT, 0, 0 } } }, + [UNPACK_EX] = { .nuops = 1, .uops = { { _UNPACK_EX, 0, 0 } } }, [UNPACK_SEQUENCE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE, 0, 0 } } }, - [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_LIST, 0, 0 } } }, - [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, - [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, - [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { WITH_EXCEPT_START, 0, 0 } } }, + [UNPACK_SEQUENCE_LIST] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE_LIST, 0, 0 } } }, + [UNPACK_SEQUENCE_TUPLE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE_TUPLE, 0, 0 } } }, + [UNPACK_SEQUENCE_TWO_TUPLE] = { .nuops = 1, .uops = { { _UNPACK_SEQUENCE_TWO_TUPLE, 0, 0 } } }, + [WITH_EXCEPT_START] = { .nuops = 1, .uops = { { _WITH_EXCEPT_START, 0, 0 } } }, }; #endif // NEED_OPCODE_METADATA -extern const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]; +extern const char *_PyOpcode_OpName[268]; #ifdef NEED_OPCODE_METADATA -const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE] = { - [_EXIT_TRACE] = "_EXIT_TRACE", - [_SET_IP] = "_SET_IP", - [_BINARY_OP] = "_BINARY_OP", - [_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT", - [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", - [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", - [_BINARY_OP_INPLACE_ADD_UNICODE] = "_BINARY_OP_INPLACE_ADD_UNICODE", - [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", - [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", - [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", - [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", - [_BINARY_SUBSCR] = "_BINARY_SUBSCR", - [_CALL] = "_CALL", - [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", - [_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_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", - [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", - [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", - [_CHECK_PEP_523] = "_CHECK_PEP_523", - [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", - [_CHECK_VALIDITY] = "_CHECK_VALIDITY", - [_COMPARE_OP] = "_COMPARE_OP", - [_FOR_ITER] = "_FOR_ITER", - [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", - [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", - [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", - [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", - [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", - [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", - [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", - [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", - [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", - [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", - [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", - [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", - [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", - [_GUARD_NOT_EXHAUSTED_LIST] = "_GUARD_NOT_EXHAUSTED_LIST", - [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", - [_GUARD_NOT_EXHAUSTED_TUPLE] = "_GUARD_NOT_EXHAUSTED_TUPLE", - [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", - [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", - [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", - [_INSERT] = "_INSERT", - [_IS_NONE] = "_IS_NONE", - [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", - [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", - [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", - [_ITER_JUMP_LIST] = "_ITER_JUMP_LIST", - [_ITER_JUMP_RANGE] = "_ITER_JUMP_RANGE", - [_ITER_JUMP_TUPLE] = "_ITER_JUMP_TUPLE", - [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", - [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", - [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", - [_JUMP_TO_TOP] = "_JUMP_TO_TOP", - [_LOAD_ATTR] = "_LOAD_ATTR", - [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", - [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", - [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", - [_LOAD_ATTR_METHOD_NO_DICT] = "_LOAD_ATTR_METHOD_NO_DICT", - [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", - [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", - [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", - [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", - [_LOAD_GLOBAL] = "_LOAD_GLOBAL", - [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", - [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", - [_LOAD_SUPER_ATTR] = "_LOAD_SUPER_ATTR", - [_POP_FRAME] = "_POP_FRAME", - [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", - [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", - [_PUSH_FRAME] = "_PUSH_FRAME", - [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", - [_SEND] = "_SEND", - [_SPECIALIZE_BINARY_OP] = "_SPECIALIZE_BINARY_OP", - [_SPECIALIZE_BINARY_SUBSCR] = "_SPECIALIZE_BINARY_SUBSCR", - [_SPECIALIZE_CALL] = "_SPECIALIZE_CALL", - [_SPECIALIZE_COMPARE_OP] = "_SPECIALIZE_COMPARE_OP", - [_SPECIALIZE_FOR_ITER] = "_SPECIALIZE_FOR_ITER", - [_SPECIALIZE_LOAD_ATTR] = "_SPECIALIZE_LOAD_ATTR", - [_SPECIALIZE_LOAD_GLOBAL] = "_SPECIALIZE_LOAD_GLOBAL", - [_SPECIALIZE_LOAD_SUPER_ATTR] = "_SPECIALIZE_LOAD_SUPER_ATTR", - [_SPECIALIZE_SEND] = "_SPECIALIZE_SEND", - [_SPECIALIZE_STORE_ATTR] = "_SPECIALIZE_STORE_ATTR", - [_SPECIALIZE_STORE_SUBSCR] = "_SPECIALIZE_STORE_SUBSCR", - [_SPECIALIZE_TO_BOOL] = "_SPECIALIZE_TO_BOOL", - [_SPECIALIZE_UNPACK_SEQUENCE] = "_SPECIALIZE_UNPACK_SEQUENCE", - [_STORE_ATTR] = "_STORE_ATTR", - [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", - [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", - [_STORE_SUBSCR] = "_STORE_SUBSCR", - [_TO_BOOL] = "_TO_BOOL", - [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", -}; -#endif // NEED_OPCODE_METADATA - -extern const char *const _PyOpcode_OpName[268]; -#ifdef NEED_OPCODE_METADATA -const char *const _PyOpcode_OpName[268] = { +const char *_PyOpcode_OpName[268] = { [BEFORE_ASYNC_WITH] = "BEFORE_ASYNC_WITH", [BEFORE_WITH] = "BEFORE_WITH", [BINARY_OP] = "BINARY_OP", @@ -2184,13 +1554,13 @@ const char *const _PyOpcode_OpName[268] = { [WITH_EXCEPT_START] = "WITH_EXCEPT_START", [YIELD_VALUE] = "YIELD_VALUE", }; -#endif // NEED_OPCODE_METADATA +#endif extern const uint8_t _PyOpcode_Caches[256]; #ifdef NEED_OPCODE_METADATA const uint8_t _PyOpcode_Caches[256] = { + [JUMP_BACKWARD] = 1, [TO_BOOL] = 3, - [BINARY_OP_INPLACE_ADD_UNICODE] = 1, [BINARY_SUBSCR] = 1, [STORE_SUBSCR] = 1, [SEND] = 1, @@ -2200,7 +1570,6 @@ const uint8_t _PyOpcode_Caches[256] = { [LOAD_SUPER_ATTR] = 1, [LOAD_ATTR] = 9, [COMPARE_OP] = 1, - [JUMP_BACKWARD] = 1, [POP_JUMP_IF_TRUE] = 1, [POP_JUMP_IF_FALSE] = 1, [POP_JUMP_IF_NONE] = 1, @@ -2209,7 +1578,7 @@ const uint8_t _PyOpcode_Caches[256] = { [CALL] = 3, [BINARY_OP] = 1, }; -#endif // NEED_OPCODE_METADATA +#endif extern const uint8_t _PyOpcode_Deopt[256]; #ifdef NEED_OPCODE_METADATA @@ -2423,6 +1792,7 @@ const uint8_t _PyOpcode_Deopt[256] = { [WITH_EXCEPT_START] = WITH_EXCEPT_START, [YIELD_VALUE] = YIELD_VALUE, }; + #endif // NEED_OPCODE_METADATA #define EXTRA_CASES \ @@ -2475,4 +1845,40 @@ const uint8_t _PyOpcode_Deopt[256] = { case 235: \ case 255: \ ; +struct pseudo_targets { + uint8_t targets[3]; +}; +extern const struct pseudo_targets _PyOpcode_PseudoTargets[12]; +#ifdef NEED_OPCODE_METADATA +const struct pseudo_targets _PyOpcode_PseudoTargets[12] = { + [LOAD_CLOSURE-256] = { { LOAD_FAST, 0, 0 } }, + [STORE_FAST_MAYBE_NULL-256] = { { STORE_FAST, 0, 0 } }, + [LOAD_SUPER_METHOD-256] = { { LOAD_SUPER_ATTR, 0, 0 } }, + [LOAD_ZERO_SUPER_METHOD-256] = { { LOAD_SUPER_ATTR, 0, 0 } }, + [LOAD_ZERO_SUPER_ATTR-256] = { { LOAD_SUPER_ATTR, 0, 0 } }, + [LOAD_METHOD-256] = { { LOAD_ATTR, 0, 0 } }, + [JUMP-256] = { { JUMP_FORWARD, JUMP_BACKWARD, 0 } }, + [JUMP_NO_INTERRUPT-256] = { { JUMP_FORWARD, JUMP_BACKWARD_NO_INTERRUPT, 0 } }, + [SETUP_FINALLY-256] = { { NOP, 0, 0 } }, + [SETUP_CLEANUP-256] = { { NOP, 0, 0 } }, + [SETUP_WITH-256] = { { NOP, 0, 0 } }, + [POP_BLOCK-256] = { { NOP, 0, 0 } }, +}; +#endif // NEED_OPCODE_METADATA +static inline bool +is_pseudo_target(int pseudo, int target) { + if (pseudo < 256 || pseudo >= 268) { + return false; + } + for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) { + if (_PyOpcode_PseudoTargets[pseudo-256].targets[i] == target) return true; + } + return false; +} + + +#ifdef __cplusplus +} +#endif +#endif /* !Py_CORE_OPCODE_METADATA_H */ diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index c96ea51ae1acb6..4a9a00ba352d33 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -2,6 +2,7 @@ // from: // Python/bytecodes.c // Do not edit! + #ifndef Py_CORE_UOP_IDS_H #define Py_CORE_UOP_IDS_H #ifdef __cplusplus @@ -11,7 +12,6 @@ extern "C" { #define _EXIT_TRACE 300 #define _SET_IP 301 #define _NOP NOP -#define _RESUME RESUME #define _RESUME_CHECK RESUME_CHECK #define _INSTRUMENTED_RESUME INSTRUMENTED_RESUME #define _LOAD_FAST_CHECK LOAD_FAST_CHECK @@ -24,13 +24,10 @@ extern "C" { #define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST #define _POP_TOP POP_TOP #define _PUSH_NULL PUSH_NULL -#define _INSTRUMENTED_END_FOR INSTRUMENTED_END_FOR #define _END_SEND END_SEND -#define _INSTRUMENTED_END_SEND INSTRUMENTED_END_SEND #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT -#define _SPECIALIZE_TO_BOOL 302 -#define _TO_BOOL 303 +#define _TO_BOOL 302 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT #define _TO_BOOL_LIST TO_BOOL_LIST @@ -38,19 +35,17 @@ extern "C" { #define _TO_BOOL_STR TO_BOOL_STR #define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE #define _UNARY_INVERT UNARY_INVERT -#define _GUARD_BOTH_INT 304 -#define _BINARY_OP_MULTIPLY_INT 305 -#define _BINARY_OP_ADD_INT 306 -#define _BINARY_OP_SUBTRACT_INT 307 -#define _GUARD_BOTH_FLOAT 308 -#define _BINARY_OP_MULTIPLY_FLOAT 309 -#define _BINARY_OP_ADD_FLOAT 310 -#define _BINARY_OP_SUBTRACT_FLOAT 311 -#define _GUARD_BOTH_UNICODE 312 -#define _BINARY_OP_ADD_UNICODE 313 -#define _BINARY_OP_INPLACE_ADD_UNICODE 314 -#define _SPECIALIZE_BINARY_SUBSCR 315 -#define _BINARY_SUBSCR 316 +#define _GUARD_BOTH_INT 303 +#define _BINARY_OP_MULTIPLY_INT 304 +#define _BINARY_OP_ADD_INT 305 +#define _BINARY_OP_SUBTRACT_INT 306 +#define _GUARD_BOTH_FLOAT 307 +#define _BINARY_OP_MULTIPLY_FLOAT 308 +#define _BINARY_OP_ADD_FLOAT 309 +#define _BINARY_OP_SUBTRACT_FLOAT 310 +#define _GUARD_BOTH_UNICODE 311 +#define _BINARY_OP_ADD_UNICODE 312 +#define _BINARY_SUBSCR 313 #define _BINARY_SLICE BINARY_SLICE #define _STORE_SLICE STORE_SLICE #define _BINARY_SUBSCR_LIST_INT BINARY_SUBSCR_LIST_INT @@ -60,54 +55,43 @@ extern "C" { #define _BINARY_SUBSCR_GETITEM BINARY_SUBSCR_GETITEM #define _LIST_APPEND LIST_APPEND #define _SET_ADD SET_ADD -#define _SPECIALIZE_STORE_SUBSCR 317 -#define _STORE_SUBSCR 318 +#define _STORE_SUBSCR 314 #define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT #define _DELETE_SUBSCR DELETE_SUBSCR #define _CALL_INTRINSIC_1 CALL_INTRINSIC_1 #define _CALL_INTRINSIC_2 CALL_INTRINSIC_2 -#define _RAISE_VARARGS RAISE_VARARGS -#define _INTERPRETER_EXIT INTERPRETER_EXIT -#define _POP_FRAME 319 +#define _POP_FRAME 315 #define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE #define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST #define _GET_AITER GET_AITER #define _GET_ANEXT GET_ANEXT #define _GET_AWAITABLE GET_AWAITABLE -#define _SPECIALIZE_SEND 320 -#define _SEND 321 +#define _SEND 316 #define _SEND_GEN SEND_GEN #define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE -#define _YIELD_VALUE YIELD_VALUE #define _POP_EXCEPT POP_EXCEPT -#define _RERAISE RERAISE -#define _END_ASYNC_FOR END_ASYNC_FOR -#define _CLEANUP_THROW CLEANUP_THROW #define _LOAD_ASSERTION_ERROR LOAD_ASSERTION_ERROR #define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS #define _STORE_NAME STORE_NAME #define _DELETE_NAME DELETE_NAME -#define _SPECIALIZE_UNPACK_SEQUENCE 322 -#define _UNPACK_SEQUENCE 323 +#define _UNPACK_SEQUENCE 317 #define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE #define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE #define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST #define _UNPACK_EX UNPACK_EX -#define _SPECIALIZE_STORE_ATTR 324 -#define _STORE_ATTR 325 +#define _STORE_ATTR 318 #define _DELETE_ATTR DELETE_ATTR #define _STORE_GLOBAL STORE_GLOBAL #define _DELETE_GLOBAL DELETE_GLOBAL #define _LOAD_LOCALS LOAD_LOCALS #define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS #define _LOAD_NAME LOAD_NAME -#define _SPECIALIZE_LOAD_GLOBAL 326 -#define _LOAD_GLOBAL 327 -#define _GUARD_GLOBALS_VERSION 328 -#define _GUARD_BUILTINS_VERSION 329 -#define _LOAD_GLOBAL_MODULE 330 -#define _LOAD_GLOBAL_BUILTINS 331 +#define _LOAD_GLOBAL 319 +#define _GUARD_GLOBALS_VERSION 320 +#define _GUARD_BUILTINS_VERSION 321 +#define _LOAD_GLOBAL_MODULE 322 +#define _LOAD_GLOBAL_BUILTINS 323 #define _DELETE_FAST DELETE_FAST #define _MAKE_CELL MAKE_CELL #define _DELETE_DEREF DELETE_DEREF @@ -128,30 +112,26 @@ extern "C" { #define _DICT_MERGE DICT_MERGE #define _MAP_ADD MAP_ADD #define _INSTRUMENTED_LOAD_SUPER_ATTR INSTRUMENTED_LOAD_SUPER_ATTR -#define _SPECIALIZE_LOAD_SUPER_ATTR 332 -#define _LOAD_SUPER_ATTR 333 #define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR #define _LOAD_SUPER_ATTR_METHOD LOAD_SUPER_ATTR_METHOD -#define _SPECIALIZE_LOAD_ATTR 334 -#define _LOAD_ATTR 335 -#define _GUARD_TYPE_VERSION 336 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES 337 -#define _LOAD_ATTR_INSTANCE_VALUE 338 -#define _CHECK_ATTR_MODULE 339 -#define _LOAD_ATTR_MODULE 340 -#define _CHECK_ATTR_WITH_HINT 341 -#define _LOAD_ATTR_WITH_HINT 342 -#define _LOAD_ATTR_SLOT 343 -#define _CHECK_ATTR_CLASS 344 -#define _LOAD_ATTR_CLASS 345 +#define _LOAD_ATTR 324 +#define _GUARD_TYPE_VERSION 325 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 326 +#define _LOAD_ATTR_INSTANCE_VALUE 327 +#define _CHECK_ATTR_MODULE 328 +#define _LOAD_ATTR_MODULE 329 +#define _CHECK_ATTR_WITH_HINT 330 +#define _LOAD_ATTR_WITH_HINT 331 +#define _LOAD_ATTR_SLOT 332 +#define _CHECK_ATTR_CLASS 333 +#define _LOAD_ATTR_CLASS 334 #define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY #define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN -#define _GUARD_DORV_VALUES 346 -#define _STORE_ATTR_INSTANCE_VALUE 347 +#define _GUARD_DORV_VALUES 335 +#define _STORE_ATTR_INSTANCE_VALUE 336 #define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT -#define _STORE_ATTR_SLOT 348 -#define _SPECIALIZE_COMPARE_OP 349 -#define _COMPARE_OP 350 +#define _STORE_ATTR_SLOT 337 +#define _COMPARE_OP 338 #define _COMPARE_OP_FLOAT COMPARE_OP_FLOAT #define _COMPARE_OP_INT COMPARE_OP_INT #define _COMPARE_OP_STR COMPARE_OP_STR @@ -159,15 +139,10 @@ extern "C" { #define _CONTAINS_OP CONTAINS_OP #define _CHECK_EG_MATCH CHECK_EG_MATCH #define _CHECK_EXC_MATCH CHECK_EXC_MATCH -#define _IMPORT_NAME IMPORT_NAME -#define _IMPORT_FROM IMPORT_FROM -#define _JUMP_FORWARD JUMP_FORWARD #define _JUMP_BACKWARD JUMP_BACKWARD -#define _ENTER_EXECUTOR ENTER_EXECUTOR -#define _POP_JUMP_IF_FALSE 351 -#define _POP_JUMP_IF_TRUE 352 -#define _IS_NONE 353 -#define _JUMP_BACKWARD_NO_INTERRUPT JUMP_BACKWARD_NO_INTERRUPT +#define _POP_JUMP_IF_FALSE 339 +#define _POP_JUMP_IF_TRUE 340 +#define _IS_NONE 341 #define _GET_LEN GET_LEN #define _MATCH_CLASS MATCH_CLASS #define _MATCH_MAPPING MATCH_MAPPING @@ -175,45 +150,43 @@ extern "C" { #define _MATCH_KEYS MATCH_KEYS #define _GET_ITER GET_ITER #define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER -#define _SPECIALIZE_FOR_ITER 354 -#define _FOR_ITER 355 -#define _FOR_ITER_TIER_TWO 356 +#define _FOR_ITER 342 +#define _FOR_ITER_TIER_TWO 343 #define _INSTRUMENTED_FOR_ITER INSTRUMENTED_FOR_ITER -#define _ITER_CHECK_LIST 357 -#define _ITER_JUMP_LIST 358 -#define _GUARD_NOT_EXHAUSTED_LIST 359 -#define _ITER_NEXT_LIST 360 -#define _ITER_CHECK_TUPLE 361 -#define _ITER_JUMP_TUPLE 362 -#define _GUARD_NOT_EXHAUSTED_TUPLE 363 -#define _ITER_NEXT_TUPLE 364 -#define _ITER_CHECK_RANGE 365 -#define _ITER_JUMP_RANGE 366 -#define _GUARD_NOT_EXHAUSTED_RANGE 367 -#define _ITER_NEXT_RANGE 368 +#define _ITER_CHECK_LIST 344 +#define _ITER_JUMP_LIST 345 +#define _GUARD_NOT_EXHAUSTED_LIST 346 +#define _ITER_NEXT_LIST 347 +#define _ITER_CHECK_TUPLE 348 +#define _ITER_JUMP_TUPLE 349 +#define _GUARD_NOT_EXHAUSTED_TUPLE 350 +#define _ITER_NEXT_TUPLE 351 +#define _ITER_CHECK_RANGE 352 +#define _ITER_JUMP_RANGE 353 +#define _GUARD_NOT_EXHAUSTED_RANGE 354 +#define _ITER_NEXT_RANGE 355 #define _FOR_ITER_GEN FOR_ITER_GEN #define _BEFORE_ASYNC_WITH BEFORE_ASYNC_WITH #define _BEFORE_WITH BEFORE_WITH #define _WITH_EXCEPT_START WITH_EXCEPT_START #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 369 -#define _GUARD_KEYS_VERSION 370 -#define _LOAD_ATTR_METHOD_WITH_VALUES 371 -#define _LOAD_ATTR_METHOD_NO_DICT 372 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 373 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 374 -#define _CHECK_ATTR_METHOD_LAZY_DICT 375 -#define _LOAD_ATTR_METHOD_LAZY_DICT 376 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 356 +#define _GUARD_KEYS_VERSION 357 +#define _LOAD_ATTR_METHOD_WITH_VALUES 358 +#define _LOAD_ATTR_METHOD_NO_DICT 359 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 360 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 361 +#define _CHECK_ATTR_METHOD_LAZY_DICT 362 +#define _LOAD_ATTR_METHOD_LAZY_DICT 363 #define _INSTRUMENTED_CALL INSTRUMENTED_CALL -#define _SPECIALIZE_CALL 377 -#define _CALL 378 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 379 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 380 -#define _CHECK_PEP_523 381 -#define _CHECK_FUNCTION_EXACT_ARGS 382 -#define _CHECK_STACK_SPACE 383 -#define _INIT_CALL_PY_EXACT_ARGS 384 -#define _PUSH_FRAME 385 +#define _CALL 364 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 365 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 366 +#define _CHECK_PEP_523 367 +#define _CHECK_FUNCTION_EXACT_ARGS 368 +#define _CHECK_STACK_SPACE 369 +#define _INIT_CALL_PY_EXACT_ARGS 370 +#define _PUSH_FRAME 371 #define _CALL_PY_WITH_DEFAULTS CALL_PY_WITH_DEFAULTS #define _CALL_TYPE_1 CALL_TYPE_1 #define _CALL_STR_1 CALL_STR_1 @@ -226,7 +199,6 @@ extern "C" { #define _CALL_BUILTIN_FAST_WITH_KEYWORDS CALL_BUILTIN_FAST_WITH_KEYWORDS #define _CALL_LEN CALL_LEN #define _CALL_ISINSTANCE CALL_ISINSTANCE -#define _CALL_LIST_APPEND CALL_LIST_APPEND #define _CALL_METHOD_DESCRIPTOR_O CALL_METHOD_DESCRIPTOR_O #define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS #define _CALL_METHOD_DESCRIPTOR_NOARGS CALL_METHOD_DESCRIPTOR_NOARGS @@ -237,14 +209,12 @@ extern "C" { #define _CALL_FUNCTION_EX CALL_FUNCTION_EX #define _MAKE_FUNCTION MAKE_FUNCTION #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE -#define _RETURN_GENERATOR RETURN_GENERATOR #define _BUILD_SLICE BUILD_SLICE #define _CONVERT_VALUE CONVERT_VALUE #define _FORMAT_SIMPLE FORMAT_SIMPLE #define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC #define _COPY COPY -#define _SPECIALIZE_BINARY_OP 386 -#define _BINARY_OP 387 +#define _BINARY_OP 372 #define _SWAP SWAP #define _INSTRUMENTED_INSTRUCTION INSTRUMENTED_INSTRUCTION #define _INSTRUMENTED_JUMP_FORWARD INSTRUMENTED_JUMP_FORWARD @@ -253,16 +223,17 @@ extern "C" { #define _INSTRUMENTED_POP_JUMP_IF_FALSE INSTRUMENTED_POP_JUMP_IF_FALSE #define _INSTRUMENTED_POP_JUMP_IF_NONE INSTRUMENTED_POP_JUMP_IF_NONE #define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE INSTRUMENTED_POP_JUMP_IF_NOT_NONE -#define _GUARD_IS_TRUE_POP 388 -#define _GUARD_IS_FALSE_POP 389 -#define _GUARD_IS_NONE_POP 390 -#define _GUARD_IS_NOT_NONE_POP 391 -#define _JUMP_TO_TOP 392 -#define _SAVE_RETURN_OFFSET 393 -#define _INSERT 394 -#define _CHECK_VALIDITY 395 +#define _GUARD_IS_TRUE_POP 373 +#define _GUARD_IS_FALSE_POP 374 +#define _GUARD_IS_NONE_POP 375 +#define _GUARD_IS_NOT_NONE_POP 376 +#define _JUMP_TO_TOP 377 +#define _SAVE_RETURN_OFFSET 378 +#define _INSERT 379 +#define _CHECK_VALIDITY 380 +#define MAX_UOP_ID 380 #ifdef __cplusplus } #endif -#endif /* !Py_OPCODE_IDS_H */ +#endif /* !Py_CORE_UOP_IDS_H */ diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h new file mode 100644 index 00000000000000..300bd3baa7b377 --- /dev/null +++ b/Include/internal/pycore_uop_metadata.h @@ -0,0 +1,403 @@ +// This file is generated by Tools/cases_generator/uop_metadata_generator.py +// from: +// Python/bytecodes.c +// Do not edit! + +#ifndef Py_CORE_UOP_METADATA_H +#define Py_CORE_UOP_METADATA_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "pycore_uop_ids.h" +extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1]; +extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1]; + +#ifdef NEED_OPCODE_METADATA +const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { + [_NOP] = 0, + [_RESUME_CHECK] = HAS_DEOPT_FLAG, + [_LOAD_FAST_CHECK] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG, + [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_LOAD_FAST_AND_CLEAR] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_LOAD_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_LOAD_CONST] = HAS_ARG_FLAG | HAS_CONST_FLAG, + [_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_STORE_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_STORE_FAST_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_POP_TOP] = 0, + [_PUSH_NULL] = 0, + [_END_SEND] = 0, + [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_UNARY_NOT] = 0, + [_TO_BOOL] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_TO_BOOL_BOOL] = HAS_DEOPT_FLAG, + [_TO_BOOL_INT] = HAS_DEOPT_FLAG, + [_TO_BOOL_LIST] = HAS_DEOPT_FLAG, + [_TO_BOOL_NONE] = HAS_DEOPT_FLAG, + [_TO_BOOL_STR] = HAS_DEOPT_FLAG, + [_TO_BOOL_ALWAYS_TRUE] = HAS_DEOPT_FLAG, + [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GUARD_BOTH_INT] = HAS_DEOPT_FLAG, + [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG, + [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG, + [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG, + [_GUARD_BOTH_FLOAT] = HAS_DEOPT_FLAG, + [_BINARY_OP_MULTIPLY_FLOAT] = 0, + [_BINARY_OP_ADD_FLOAT] = 0, + [_BINARY_OP_SUBTRACT_FLOAT] = 0, + [_GUARD_BOTH_UNICODE] = HAS_DEOPT_FLAG, + [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BINARY_SUBSCR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BINARY_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG, + [_BINARY_SUBSCR_STR_INT] = HAS_DEOPT_FLAG, + [_BINARY_SUBSCR_TUPLE_INT] = HAS_DEOPT_FLAG, + [_BINARY_SUBSCR_DICT] = HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LIST_APPEND] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_SET_ADD] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_SUBSCR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_STORE_SUBSCR_DICT] = HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DELETE_SUBSCR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_INTRINSIC_1] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_INTRINSIC_2] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_POP_FRAME] = HAS_ESCAPES_FLAG, + [_GET_AITER] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GET_ANEXT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GET_AWAITABLE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_POP_EXCEPT] = HAS_ESCAPES_FLAG, + [_LOAD_ASSERTION_ERROR] = 0, + [_LOAD_BUILD_CLASS] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_NAME] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DELETE_NAME] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_UNPACK_SEQUENCE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_UNPACK_SEQUENCE_TWO_TUPLE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_UNPACK_SEQUENCE_TUPLE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_UNPACK_SEQUENCE_LIST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_UNPACK_EX] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DELETE_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_GLOBAL] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DELETE_GLOBAL] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_LOCALS] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_FROM_DICT_OR_GLOBALS] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_NAME] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_GLOBAL] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GUARD_GLOBALS_VERSION] = HAS_DEOPT_FLAG, + [_GUARD_BUILTINS_VERSION] = HAS_DEOPT_FLAG, + [_LOAD_GLOBAL_MODULE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_LOAD_GLOBAL_BUILTINS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_DELETE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG, + [_MAKE_CELL] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DELETE_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_FROM_DICT_OR_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_STORE_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG, + [_COPY_FREE_VARS] = HAS_ARG_FLAG, + [_BUILD_STRING] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_TUPLE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_LIST] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LIST_EXTEND] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_SET_UPDATE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_SET] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_MAP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_SETUP_ANNOTATIONS] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_CONST_KEY_MAP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DICT_UPDATE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_DICT_MERGE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_MAP_ADD] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_SUPER_ATTR_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_SUPER_ATTR_METHOD] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG, + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_CHECK_ATTR_MODULE] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_MODULE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_CHECK_ATTR_WITH_HINT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_SLOT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_CHECK_ATTR_CLASS] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_CLASS] = HAS_ARG_FLAG, + [_GUARD_DORV_VALUES] = HAS_DEOPT_FLAG, + [_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG, + [_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG, + [_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_INT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_STR] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, + [_IS_OP] = HAS_ARG_FLAG, + [_CONTAINS_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CHECK_EG_MATCH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CHECK_EXC_MATCH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_IS_NONE] = 0, + [_GET_LEN] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_MATCH_CLASS] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_MATCH_MAPPING] = 0, + [_MATCH_SEQUENCE] = 0, + [_MATCH_KEYS] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GET_ITER] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_GET_YIELD_FROM_ITER] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_FOR_ITER_TIER_TWO] = HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_ITER_CHECK_LIST] = HAS_DEOPT_FLAG, + [_GUARD_NOT_EXHAUSTED_LIST] = HAS_DEOPT_FLAG, + [_ITER_NEXT_LIST] = 0, + [_ITER_CHECK_TUPLE] = HAS_DEOPT_FLAG, + [_GUARD_NOT_EXHAUSTED_TUPLE] = HAS_DEOPT_FLAG, + [_ITER_NEXT_TUPLE] = 0, + [_ITER_CHECK_RANGE] = HAS_DEOPT_FLAG, + [_GUARD_NOT_EXHAUSTED_RANGE] = HAS_DEOPT_FLAG, + [_ITER_NEXT_RANGE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BEFORE_ASYNC_WITH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BEFORE_WITH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_WITH_EXCEPT_START] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_PUSH_EXC_INFO] = 0, + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = HAS_DEOPT_FLAG, + [_GUARD_KEYS_VERSION] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_METHOD_WITH_VALUES] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_METHOD_NO_DICT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = HAS_ARG_FLAG, + [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = HAS_ARG_FLAG, + [_CHECK_ATTR_METHOD_LAZY_DICT] = HAS_DEOPT_FLAG, + [_LOAD_ATTR_METHOD_LAZY_DICT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = HAS_ARG_FLAG, + [_CHECK_PEP_523] = HAS_DEOPT_FLAG, + [_CHECK_FUNCTION_EXACT_ARGS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_CHECK_STACK_SPACE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_PUSH_FRAME] = 0, + [_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_CALL_STR_1] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_TUPLE_1] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_EXIT_INIT_CHECK] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_CLASS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG, + [_CALL_BUILTIN_O] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_FAST] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_LEN] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_ISINSTANCE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_NOARGS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_FAST] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_MAKE_FUNCTION] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_SET_FUNCTION_ATTRIBUTE] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_FORMAT_SIMPLE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_FORMAT_WITH_SPEC] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_COPY] = HAS_ARG_FLAG, + [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_SWAP] = HAS_ARG_FLAG, + [_GUARD_IS_TRUE_POP] = HAS_DEOPT_FLAG, + [_GUARD_IS_FALSE_POP] = HAS_DEOPT_FLAG, + [_GUARD_IS_NONE_POP] = HAS_DEOPT_FLAG, + [_GUARD_IS_NOT_NONE_POP] = HAS_DEOPT_FLAG, + [_JUMP_TO_TOP] = HAS_EVAL_BREAK_FLAG, + [_SET_IP] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, + [_EXIT_TRACE] = HAS_DEOPT_FLAG, + [_INSERT] = HAS_ARG_FLAG, + [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, +}; + +const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { + [_BEFORE_ASYNC_WITH] = "_BEFORE_ASYNC_WITH", + [_BEFORE_WITH] = "_BEFORE_WITH", + [_BINARY_OP] = "_BINARY_OP", + [_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT", + [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", + [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", + [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", + [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", + [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", + [_BINARY_SLICE] = "_BINARY_SLICE", + [_BINARY_SUBSCR] = "_BINARY_SUBSCR", + [_BINARY_SUBSCR_DICT] = "_BINARY_SUBSCR_DICT", + [_BINARY_SUBSCR_LIST_INT] = "_BINARY_SUBSCR_LIST_INT", + [_BINARY_SUBSCR_STR_INT] = "_BINARY_SUBSCR_STR_INT", + [_BINARY_SUBSCR_TUPLE_INT] = "_BINARY_SUBSCR_TUPLE_INT", + [_BUILD_CONST_KEY_MAP] = "_BUILD_CONST_KEY_MAP", + [_BUILD_LIST] = "_BUILD_LIST", + [_BUILD_MAP] = "_BUILD_MAP", + [_BUILD_SET] = "_BUILD_SET", + [_BUILD_SLICE] = "_BUILD_SLICE", + [_BUILD_STRING] = "_BUILD_STRING", + [_BUILD_TUPLE] = "_BUILD_TUPLE", + [_CALL_BUILTIN_CLASS] = "_CALL_BUILTIN_CLASS", + [_CALL_BUILTIN_FAST] = "_CALL_BUILTIN_FAST", + [_CALL_BUILTIN_FAST_WITH_KEYWORDS] = "_CALL_BUILTIN_FAST_WITH_KEYWORDS", + [_CALL_BUILTIN_O] = "_CALL_BUILTIN_O", + [_CALL_INTRINSIC_1] = "_CALL_INTRINSIC_1", + [_CALL_INTRINSIC_2] = "_CALL_INTRINSIC_2", + [_CALL_ISINSTANCE] = "_CALL_ISINSTANCE", + [_CALL_LEN] = "_CALL_LEN", + [_CALL_METHOD_DESCRIPTOR_FAST] = "_CALL_METHOD_DESCRIPTOR_FAST", + [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = "_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + [_CALL_METHOD_DESCRIPTOR_NOARGS] = "_CALL_METHOD_DESCRIPTOR_NOARGS", + [_CALL_METHOD_DESCRIPTOR_O] = "_CALL_METHOD_DESCRIPTOR_O", + [_CALL_STR_1] = "_CALL_STR_1", + [_CALL_TUPLE_1] = "_CALL_TUPLE_1", + [_CALL_TYPE_1] = "_CALL_TYPE_1", + [_CHECK_ATTR_CLASS] = "_CHECK_ATTR_CLASS", + [_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_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_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", + [_CHECK_PEP_523] = "_CHECK_PEP_523", + [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", + [_CHECK_VALIDITY] = "_CHECK_VALIDITY", + [_COMPARE_OP] = "_COMPARE_OP", + [_COMPARE_OP_FLOAT] = "_COMPARE_OP_FLOAT", + [_COMPARE_OP_INT] = "_COMPARE_OP_INT", + [_COMPARE_OP_STR] = "_COMPARE_OP_STR", + [_CONTAINS_OP] = "_CONTAINS_OP", + [_CONVERT_VALUE] = "_CONVERT_VALUE", + [_COPY] = "_COPY", + [_COPY_FREE_VARS] = "_COPY_FREE_VARS", + [_DELETE_ATTR] = "_DELETE_ATTR", + [_DELETE_DEREF] = "_DELETE_DEREF", + [_DELETE_FAST] = "_DELETE_FAST", + [_DELETE_GLOBAL] = "_DELETE_GLOBAL", + [_DELETE_NAME] = "_DELETE_NAME", + [_DELETE_SUBSCR] = "_DELETE_SUBSCR", + [_DICT_MERGE] = "_DICT_MERGE", + [_DICT_UPDATE] = "_DICT_UPDATE", + [_END_SEND] = "_END_SEND", + [_EXIT_INIT_CHECK] = "_EXIT_INIT_CHECK", + [_EXIT_TRACE] = "_EXIT_TRACE", + [_FORMAT_SIMPLE] = "_FORMAT_SIMPLE", + [_FORMAT_WITH_SPEC] = "_FORMAT_WITH_SPEC", + [_FOR_ITER_TIER_TWO] = "_FOR_ITER_TIER_TWO", + [_GET_AITER] = "_GET_AITER", + [_GET_ANEXT] = "_GET_ANEXT", + [_GET_AWAITABLE] = "_GET_AWAITABLE", + [_GET_ITER] = "_GET_ITER", + [_GET_LEN] = "_GET_LEN", + [_GET_YIELD_FROM_ITER] = "_GET_YIELD_FROM_ITER", + [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", + [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", + [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", + [_GUARD_DORV_VALUES] = "_GUARD_DORV_VALUES", + [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = "_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT", + [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", + [_GUARD_IS_FALSE_POP] = "_GUARD_IS_FALSE_POP", + [_GUARD_IS_NONE_POP] = "_GUARD_IS_NONE_POP", + [_GUARD_IS_NOT_NONE_POP] = "_GUARD_IS_NOT_NONE_POP", + [_GUARD_IS_TRUE_POP] = "_GUARD_IS_TRUE_POP", + [_GUARD_KEYS_VERSION] = "_GUARD_KEYS_VERSION", + [_GUARD_NOT_EXHAUSTED_LIST] = "_GUARD_NOT_EXHAUSTED_LIST", + [_GUARD_NOT_EXHAUSTED_RANGE] = "_GUARD_NOT_EXHAUSTED_RANGE", + [_GUARD_NOT_EXHAUSTED_TUPLE] = "_GUARD_NOT_EXHAUSTED_TUPLE", + [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", + [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = "_INIT_CALL_BOUND_METHOD_EXACT_ARGS", + [_INIT_CALL_PY_EXACT_ARGS] = "_INIT_CALL_PY_EXACT_ARGS", + [_INSERT] = "_INSERT", + [_IS_NONE] = "_IS_NONE", + [_IS_OP] = "_IS_OP", + [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", + [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", + [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", + [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", + [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", + [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", + [_JUMP_TO_TOP] = "_JUMP_TO_TOP", + [_LIST_APPEND] = "_LIST_APPEND", + [_LIST_EXTEND] = "_LIST_EXTEND", + [_LOAD_ASSERTION_ERROR] = "_LOAD_ASSERTION_ERROR", + [_LOAD_ATTR] = "_LOAD_ATTR", + [_LOAD_ATTR_CLASS] = "_LOAD_ATTR_CLASS", + [_LOAD_ATTR_INSTANCE_VALUE] = "_LOAD_ATTR_INSTANCE_VALUE", + [_LOAD_ATTR_METHOD_LAZY_DICT] = "_LOAD_ATTR_METHOD_LAZY_DICT", + [_LOAD_ATTR_METHOD_NO_DICT] = "_LOAD_ATTR_METHOD_NO_DICT", + [_LOAD_ATTR_METHOD_WITH_VALUES] = "_LOAD_ATTR_METHOD_WITH_VALUES", + [_LOAD_ATTR_MODULE] = "_LOAD_ATTR_MODULE", + [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = "_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = "_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + [_LOAD_ATTR_SLOT] = "_LOAD_ATTR_SLOT", + [_LOAD_ATTR_WITH_HINT] = "_LOAD_ATTR_WITH_HINT", + [_LOAD_BUILD_CLASS] = "_LOAD_BUILD_CLASS", + [_LOAD_CONST] = "_LOAD_CONST", + [_LOAD_DEREF] = "_LOAD_DEREF", + [_LOAD_FAST] = "_LOAD_FAST", + [_LOAD_FAST_AND_CLEAR] = "_LOAD_FAST_AND_CLEAR", + [_LOAD_FAST_CHECK] = "_LOAD_FAST_CHECK", + [_LOAD_FAST_LOAD_FAST] = "_LOAD_FAST_LOAD_FAST", + [_LOAD_FROM_DICT_OR_DEREF] = "_LOAD_FROM_DICT_OR_DEREF", + [_LOAD_FROM_DICT_OR_GLOBALS] = "_LOAD_FROM_DICT_OR_GLOBALS", + [_LOAD_GLOBAL] = "_LOAD_GLOBAL", + [_LOAD_GLOBAL_BUILTINS] = "_LOAD_GLOBAL_BUILTINS", + [_LOAD_GLOBAL_MODULE] = "_LOAD_GLOBAL_MODULE", + [_LOAD_LOCALS] = "_LOAD_LOCALS", + [_LOAD_NAME] = "_LOAD_NAME", + [_LOAD_SUPER_ATTR_ATTR] = "_LOAD_SUPER_ATTR_ATTR", + [_LOAD_SUPER_ATTR_METHOD] = "_LOAD_SUPER_ATTR_METHOD", + [_MAKE_CELL] = "_MAKE_CELL", + [_MAKE_FUNCTION] = "_MAKE_FUNCTION", + [_MAP_ADD] = "_MAP_ADD", + [_MATCH_CLASS] = "_MATCH_CLASS", + [_MATCH_KEYS] = "_MATCH_KEYS", + [_MATCH_MAPPING] = "_MATCH_MAPPING", + [_MATCH_SEQUENCE] = "_MATCH_SEQUENCE", + [_NOP] = "_NOP", + [_POP_EXCEPT] = "_POP_EXCEPT", + [_POP_FRAME] = "_POP_FRAME", + [_POP_TOP] = "_POP_TOP", + [_PUSH_EXC_INFO] = "_PUSH_EXC_INFO", + [_PUSH_FRAME] = "_PUSH_FRAME", + [_PUSH_NULL] = "_PUSH_NULL", + [_RESUME_CHECK] = "_RESUME_CHECK", + [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", + [_SETUP_ANNOTATIONS] = "_SETUP_ANNOTATIONS", + [_SET_ADD] = "_SET_ADD", + [_SET_FUNCTION_ATTRIBUTE] = "_SET_FUNCTION_ATTRIBUTE", + [_SET_IP] = "_SET_IP", + [_SET_UPDATE] = "_SET_UPDATE", + [_STORE_ATTR] = "_STORE_ATTR", + [_STORE_ATTR_INSTANCE_VALUE] = "_STORE_ATTR_INSTANCE_VALUE", + [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", + [_STORE_DEREF] = "_STORE_DEREF", + [_STORE_FAST] = "_STORE_FAST", + [_STORE_FAST_LOAD_FAST] = "_STORE_FAST_LOAD_FAST", + [_STORE_FAST_STORE_FAST] = "_STORE_FAST_STORE_FAST", + [_STORE_GLOBAL] = "_STORE_GLOBAL", + [_STORE_NAME] = "_STORE_NAME", + [_STORE_SLICE] = "_STORE_SLICE", + [_STORE_SUBSCR] = "_STORE_SUBSCR", + [_STORE_SUBSCR_DICT] = "_STORE_SUBSCR_DICT", + [_STORE_SUBSCR_LIST_INT] = "_STORE_SUBSCR_LIST_INT", + [_SWAP] = "_SWAP", + [_TO_BOOL] = "_TO_BOOL", + [_TO_BOOL_ALWAYS_TRUE] = "_TO_BOOL_ALWAYS_TRUE", + [_TO_BOOL_BOOL] = "_TO_BOOL_BOOL", + [_TO_BOOL_INT] = "_TO_BOOL_INT", + [_TO_BOOL_LIST] = "_TO_BOOL_LIST", + [_TO_BOOL_NONE] = "_TO_BOOL_NONE", + [_TO_BOOL_STR] = "_TO_BOOL_STR", + [_UNARY_INVERT] = "_UNARY_INVERT", + [_UNARY_NEGATIVE] = "_UNARY_NEGATIVE", + [_UNARY_NOT] = "_UNARY_NOT", + [_UNPACK_EX] = "_UNPACK_EX", + [_UNPACK_SEQUENCE] = "_UNPACK_SEQUENCE", + [_UNPACK_SEQUENCE_LIST] = "_UNPACK_SEQUENCE_LIST", + [_UNPACK_SEQUENCE_TUPLE] = "_UNPACK_SEQUENCE_TUPLE", + [_UNPACK_SEQUENCE_TWO_TUPLE] = "_UNPACK_SEQUENCE_TWO_TUPLE", + [_WITH_EXCEPT_START] = "_WITH_EXCEPT_START", +}; +#endif // NEED_OPCODE_METADATA + + +#ifdef __cplusplus +} +#endif +#endif /* !Py_CORE_UOP_METADATA_H */ diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index e2e27ca00fd47b..fe969342ee79e7 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -231,7 +231,7 @@ extern "C" { #define SETUP_WITH 266 #define STORE_FAST_MAYBE_NULL 267 -#define HAVE_ARGUMENT 45 +#define HAVE_ARGUMENT 44 #define MIN_INSTRUMENTED_OPCODE 236 #ifdef __cplusplus diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 5dd06ae487dfcf..fdb099bd0c2ecf 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -1,8 +1,7 @@ -# This file is generated by Tools/cases_generator/generate_cases.py +# This file is generated by Tools/cases_generator/py_metadata_generator.py # from: # Python/bytecodes.c # Do not edit! - _specializations = { "RESUME": [ "RESUME_CHECK", @@ -23,6 +22,7 @@ "BINARY_OP_ADD_FLOAT", "BINARY_OP_SUBTRACT_FLOAT", "BINARY_OP_ADD_UNICODE", + "BINARY_OP_INPLACE_ADD_UNICODE", ], "BINARY_SUBSCR": [ "BINARY_SUBSCR_DICT", @@ -103,14 +103,11 @@ ], } -# An irregular case: -_specializations["BINARY_OP"].append("BINARY_OP_INPLACE_ADD_UNICODE") - _specialized_opmap = { - 'BINARY_OP_INPLACE_ADD_UNICODE': 3, 'BINARY_OP_ADD_FLOAT': 150, 'BINARY_OP_ADD_INT': 151, 'BINARY_OP_ADD_UNICODE': 152, + 'BINARY_OP_INPLACE_ADD_UNICODE': 3, 'BINARY_OP_MULTIPLY_FLOAT': 153, 'BINARY_OP_MULTIPLY_INT': 154, 'BINARY_OP_SUBTRACT_FLOAT': 155, @@ -181,6 +178,9 @@ opmap = { 'CACHE': 0, + 'RESERVED': 17, + 'RESUME': 149, + 'INSTRUMENTED_LINE': 254, 'BEFORE_ASYNC_WITH': 1, 'BEFORE_WITH': 2, 'BINARY_SLICE': 4, @@ -196,7 +196,6 @@ 'FORMAT_SIMPLE': 14, 'FORMAT_WITH_SPEC': 15, 'GET_AITER': 16, - 'RESERVED': 17, 'GET_ANEXT': 18, 'GET_ITER': 19, 'GET_LEN': 20, @@ -298,7 +297,6 @@ 'UNPACK_EX': 116, 'UNPACK_SEQUENCE': 117, 'YIELD_VALUE': 118, - 'RESUME': 149, 'INSTRUMENTED_RESUME': 236, 'INSTRUMENTED_END_FOR': 237, 'INSTRUMENTED_END_SEND': 238, @@ -317,7 +315,6 @@ 'INSTRUMENTED_POP_JUMP_IF_FALSE': 251, 'INSTRUMENTED_POP_JUMP_IF_NONE': 252, 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 253, - 'INSTRUMENTED_LINE': 254, 'JUMP': 256, 'JUMP_NO_INTERRUPT': 257, 'LOAD_CLOSURE': 258, @@ -331,5 +328,6 @@ 'SETUP_WITH': 266, 'STORE_FAST_MAYBE_NULL': 267, } + +HAVE_ARGUMENT = 44 MIN_INSTRUMENTED_OPCODE = 236 -HAVE_ARGUMENT = 45 diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 9f4731103c9413..5c8c0596610303 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -176,6 +176,7 @@ def f(): with temporary_optimizer(opt): f() exe = get_first_executor(f) + self.assertIsNotNone(exe) self.assertTrue(exe.is_valid()) _testinternalcapi.invalidate_executors(f.__code__) self.assertFalse(exe.is_valid()) @@ -196,7 +197,7 @@ def testfunc(x): self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertIn("_SET_IP", uops) - self.assertIn("LOAD_FAST", uops) + self.assertIn("_LOAD_FAST", uops) def test_extended_arg(self): "Check EXTENDED_ARG handling in superblock creation" @@ -243,7 +244,7 @@ def many_vars(): ex = get_first_executor(many_vars) self.assertIsNotNone(ex) - self.assertIn(("LOAD_FAST", 259, 0), list(ex)) + self.assertIn(("_LOAD_FAST", 259, 0), list(ex)) def test_unspecialized_unpack(self): # An example of an unspecialized opcode diff --git a/Makefile.pre.in b/Makefile.pre.in index 195fc0ddddecd3..92827ec3479526 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1588,23 +1588,28 @@ regen-cases: $(srcdir)/Tools/cases_generator/generate_cases.py \ $(CASESFLAG) \ -t $(srcdir)/Python/opcode_targets.h.new \ - -m $(srcdir)/Include/internal/pycore_opcode_metadata.h.new \ - -p $(srcdir)/Lib/_opcode_metadata.py.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) \ - $(srcdir)/Tools/cases_generator/opcode_id_generator.py -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) \ - $(srcdir)/Tools/cases_generator/uop_id_generator.py -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) \ - $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) \ - $(srcdir)/Tools/cases_generator/tier2_generator.py -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_id_generator.py \ + -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_id_generator.py \ + -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/py_metadata_generator.py \ + -o $(srcdir)/Lib/_opcode_metadata.py.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier1_generator.py \ + -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_generator.py \ + -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_metadata_generator.py \ + -o $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_metadata_generator.py -o \ + $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_ids.h $(srcdir)/Include/internal/pycore_uop_ids.h.new $(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new + $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_metadata.h $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Python/abstract_interp_cases.c.h $(srcdir)/Python/abstract_interp_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new diff --git a/Python/assemble.c b/Python/assemble.c index b6fb432aed4a3b..569454ebf3b9cb 100644 --- a/Python/assemble.c +++ b/Python/assemble.c @@ -4,7 +4,7 @@ #include "pycore_code.h" // write_location_entry_start() #include "pycore_compile.h" #include "pycore_opcode_utils.h" // IS_BACKWARDS_JUMP_OPCODE -#include "pycore_opcode_metadata.h" // IS_PSEUDO_INSTR, _PyOpcode_Caches +#include "pycore_opcode_metadata.h" // is_pseudo_target, _PyOpcode_Caches #define DEFAULT_CODE_SIZE 128 @@ -710,13 +710,13 @@ resolve_unconditional_jumps(instr_sequence *instrs) bool is_forward = (instr->i_oparg > i); switch(instr->i_opcode) { case JUMP: - assert(SAME_OPCODE_METADATA(JUMP, JUMP_FORWARD)); - assert(SAME_OPCODE_METADATA(JUMP, JUMP_BACKWARD)); + assert(is_pseudo_target(JUMP, JUMP_FORWARD)); + assert(is_pseudo_target(JUMP, JUMP_BACKWARD)); instr->i_opcode = is_forward ? JUMP_FORWARD : JUMP_BACKWARD; break; case JUMP_NO_INTERRUPT: - assert(SAME_OPCODE_METADATA(JUMP_NO_INTERRUPT, JUMP_FORWARD)); - assert(SAME_OPCODE_METADATA(JUMP_NO_INTERRUPT, JUMP_BACKWARD_NO_INTERRUPT)); + assert(is_pseudo_target(JUMP_NO_INTERRUPT, JUMP_FORWARD)); + assert(is_pseudo_target(JUMP_NO_INTERRUPT, JUMP_BACKWARD_NO_INTERRUPT)); instr->i_opcode = is_forward ? JUMP_FORWARD : JUMP_BACKWARD_NO_INTERRUPT; break; diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 19e2268046fcdc..82d7a71d4989a4 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -330,14 +330,14 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_TO_BOOL, (unused/2, value -- res)) { + op(_TO_BOOL, (value -- res)) { int err = PyObject_IsTrue(value); DECREF_INPUTS(); ERROR_IF(err < 0, error); res = err ? Py_True : Py_False; } - macro(TO_BOOL) = _SPECIALIZE_TO_BOOL + _TO_BOOL; + macro(TO_BOOL) = _SPECIALIZE_TO_BOOL + unused/2 + _TO_BOOL; inst(TO_BOOL_BOOL, (unused/1, unused/2, value -- value)) { DEOPT_IF(!PyBool_Check(value)); @@ -416,7 +416,7 @@ dummy_func( DEOPT_IF(!PyLong_CheckExact(right)); } - op(_BINARY_OP_MULTIPLY_INT, (unused/1, left, right -- res)) { + op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Multiply((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -424,7 +424,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - op(_BINARY_OP_ADD_INT, (unused/1, left, right -- res)) { + op(_BINARY_OP_ADD_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Add((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -432,7 +432,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - op(_BINARY_OP_SUBTRACT_INT, (unused/1, left, right -- res)) { + op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = _PyLong_Subtract((PyLongObject *)left, (PyLongObject *)right); _Py_DECREF_SPECIALIZED(right, (destructor)PyObject_Free); @@ -441,18 +441,18 @@ dummy_func( } macro(BINARY_OP_MULTIPLY_INT) = - _GUARD_BOTH_INT + _BINARY_OP_MULTIPLY_INT; + _GUARD_BOTH_INT + unused/1 + _BINARY_OP_MULTIPLY_INT; macro(BINARY_OP_ADD_INT) = - _GUARD_BOTH_INT + _BINARY_OP_ADD_INT; + _GUARD_BOTH_INT + unused/1 + _BINARY_OP_ADD_INT; macro(BINARY_OP_SUBTRACT_INT) = - _GUARD_BOTH_INT + _BINARY_OP_SUBTRACT_INT; + _GUARD_BOTH_INT + unused/1 + _BINARY_OP_SUBTRACT_INT; op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { DEOPT_IF(!PyFloat_CheckExact(left)); DEOPT_IF(!PyFloat_CheckExact(right)); } - op(_BINARY_OP_MULTIPLY_FLOAT, (unused/1, left, right -- res)) { + op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval * @@ -460,7 +460,7 @@ dummy_func( DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - op(_BINARY_OP_ADD_FLOAT, (unused/1, left, right -- res)) { + op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval + @@ -468,7 +468,7 @@ dummy_func( DECREF_INPUTS_AND_REUSE_FLOAT(left, right, dres, res); } - op(_BINARY_OP_SUBTRACT_FLOAT, (unused/1, left, right -- res)) { + op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { STAT_INC(BINARY_OP, hit); double dres = ((PyFloatObject *)left)->ob_fval - @@ -477,18 +477,18 @@ dummy_func( } macro(BINARY_OP_MULTIPLY_FLOAT) = - _GUARD_BOTH_FLOAT + _BINARY_OP_MULTIPLY_FLOAT; + _GUARD_BOTH_FLOAT + unused/1 + _BINARY_OP_MULTIPLY_FLOAT; macro(BINARY_OP_ADD_FLOAT) = - _GUARD_BOTH_FLOAT + _BINARY_OP_ADD_FLOAT; + _GUARD_BOTH_FLOAT + unused/1 + _BINARY_OP_ADD_FLOAT; macro(BINARY_OP_SUBTRACT_FLOAT) = - _GUARD_BOTH_FLOAT + _BINARY_OP_SUBTRACT_FLOAT; + _GUARD_BOTH_FLOAT + unused/1 + _BINARY_OP_SUBTRACT_FLOAT; op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { DEOPT_IF(!PyUnicode_CheckExact(left)); DEOPT_IF(!PyUnicode_CheckExact(right)); } - op(_BINARY_OP_ADD_UNICODE, (unused/1, left, right -- res)) { + op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { STAT_INC(BINARY_OP, hit); res = PyUnicode_Concat(left, right); _Py_DECREF_SPECIALIZED(left, _PyUnicode_ExactDealloc); @@ -497,7 +497,7 @@ dummy_func( } macro(BINARY_OP_ADD_UNICODE) = - _GUARD_BOTH_UNICODE + _BINARY_OP_ADD_UNICODE; + _GUARD_BOTH_UNICODE + unused/1 + _BINARY_OP_ADD_UNICODE; // This is a subtle one. It's a super-instruction for // BINARY_OP_ADD_UNICODE followed by STORE_FAST @@ -505,7 +505,7 @@ dummy_func( // So the inputs are the same as for all BINARY_OP // specializations, but there is no output. // At the end we just skip over the STORE_FAST. - op(_BINARY_OP_INPLACE_ADD_UNICODE, (unused/1, left, right --)) { + op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); @@ -533,7 +533,7 @@ dummy_func( } macro(BINARY_OP_INPLACE_ADD_UNICODE) = - _GUARD_BOTH_UNICODE + _BINARY_OP_INPLACE_ADD_UNICODE; + _GUARD_BOTH_UNICODE + unused/1 + _BINARY_OP_INPLACE_ADD_UNICODE; family(BINARY_SUBSCR, INLINE_CACHE_ENTRIES_BINARY_SUBSCR) = { BINARY_SUBSCR_DICT, @@ -1295,14 +1295,14 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_STORE_ATTR, (unused/3, v, owner --)) { + op(_STORE_ATTR, (v, owner --)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); int err = PyObject_SetAttr(owner, name, v); DECREF_INPUTS(); ERROR_IF(err, error); } - macro(STORE_ATTR) = _SPECIALIZE_STORE_ATTR + _STORE_ATTR; + macro(STORE_ATTR) = _SPECIALIZE_STORE_ATTR + unused/3 + _STORE_ATTR; inst(DELETE_ATTR, (owner --)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -1414,7 +1414,7 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_LOAD_GLOBAL, (unused/1, unused/1, unused/1 -- res, null if (oparg & 1))) { + op(_LOAD_GLOBAL, ( -- res, null if (oparg & 1))) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); if (PyDict_CheckExact(GLOBALS()) && PyDict_CheckExact(BUILTINS())) @@ -1451,7 +1451,12 @@ dummy_func( null = NULL; } - macro(LOAD_GLOBAL) = _SPECIALIZE_LOAD_GLOBAL + _LOAD_GLOBAL; + macro(LOAD_GLOBAL) = + _SPECIALIZE_LOAD_GLOBAL + + counter/1 + + globals_version/1 + + builtins_version/1 + + _LOAD_GLOBAL; op(_GUARD_GLOBALS_VERSION, (version/1 --)) { PyDictObject *dict = (PyDictObject *)GLOBALS(); @@ -1853,7 +1858,7 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_LOAD_ATTR, (unused/8, owner -- attr, self_or_null if (oparg & 1))) { + op(_LOAD_ATTR, (owner -- attr, self_or_null if (oparg & 1))) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); if (oparg & 1) { /* Designed to work in tandem with CALL, pushes two values. */ @@ -1886,7 +1891,10 @@ dummy_func( } } - macro(LOAD_ATTR) = _SPECIALIZE_LOAD_ATTR + _LOAD_ATTR; + macro(LOAD_ATTR) = + _SPECIALIZE_LOAD_ATTR + + unused/8 + + _LOAD_ATTR; pseudo(LOAD_METHOD) = { LOAD_ATTR, @@ -2369,7 +2377,7 @@ dummy_func( stack_pointer = _PyFrame_GetStackPointer(frame); } - replaced op(_POP_JUMP_IF_FALSE, (unused/1, cond -- )) { + replaced op(_POP_JUMP_IF_FALSE, (cond -- )) { assert(PyBool_Check(cond)); int flag = Py_IsFalse(cond); #if ENABLE_SPECIALIZATION @@ -2378,7 +2386,7 @@ dummy_func( JUMPBY(oparg * flag); } - replaced op(_POP_JUMP_IF_TRUE, (unused/1, cond -- )) { + replaced op(_POP_JUMP_IF_TRUE, (cond -- )) { assert(PyBool_Check(cond)); int flag = Py_IsTrue(cond); #if ENABLE_SPECIALIZATION @@ -2397,13 +2405,13 @@ dummy_func( } } - macro(POP_JUMP_IF_TRUE) = _POP_JUMP_IF_TRUE; + macro(POP_JUMP_IF_TRUE) = unused/1 + _POP_JUMP_IF_TRUE; - macro(POP_JUMP_IF_FALSE) = _POP_JUMP_IF_FALSE; + macro(POP_JUMP_IF_FALSE) = unused/1 + _POP_JUMP_IF_FALSE; - macro(POP_JUMP_IF_NONE) = _IS_NONE + _POP_JUMP_IF_TRUE; + macro(POP_JUMP_IF_NONE) = unused/1 + _IS_NONE + _POP_JUMP_IF_TRUE; - macro(POP_JUMP_IF_NOT_NONE) = _IS_NONE + _POP_JUMP_IF_FALSE; + macro(POP_JUMP_IF_NOT_NONE) = unused/1 + _IS_NONE + _POP_JUMP_IF_FALSE; inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { TIER_ONE_ONLY @@ -3010,7 +3018,7 @@ dummy_func( } // When calling Python, inline the call using DISPATCH_INLINED(). - op(_CALL, (unused/2, callable, self_or_null, args[oparg] -- res)) { + op(_CALL, (callable, self_or_null, args[oparg] -- res)) { // oparg counts all of the args, but *not* self: int total_args = oparg; if (self_or_null != NULL) { @@ -3079,7 +3087,7 @@ dummy_func( CHECK_EVAL_BREAKER(); } - macro(CALL) = _SPECIALIZE_CALL + _CALL; + macro(CALL) = _SPECIALIZE_CALL + unused/2 + _CALL; op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { DEOPT_IF(null != NULL); diff --git a/Python/ceval.c b/Python/ceval.c index 27304d31e27949..1fea9747488102 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -24,6 +24,7 @@ #include "pycore_sysmodule.h" // _PySys_Audit() #include "pycore_tuple.h" // _PyTuple_ITEMS() #include "pycore_typeobject.h" // _PySuper_Lookup() +#include "pycore_uop_ids.h" // Uops #include "pycore_uops.h" // _PyUOpExecutorObject #include "pycore_pyerrors.h" diff --git a/Python/compile.c b/Python/compile.c index 8b9e2f02048f11..65ac05ad58d4dd 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -796,35 +796,12 @@ stack_effect(int opcode, int oparg, int jump) // Specialized instructions are not supported. return PY_INVALID_STACK_EFFECT; } - int popped, pushed; - if (jump > 0) { - popped = _PyOpcode_num_popped(opcode, oparg, true); - pushed = _PyOpcode_num_pushed(opcode, oparg, true); - } - else { - popped = _PyOpcode_num_popped(opcode, oparg, false); - pushed = _PyOpcode_num_pushed(opcode, oparg, false); - } + int popped = _PyOpcode_num_popped(opcode, oparg); + int pushed = _PyOpcode_num_pushed(opcode, oparg); if (popped < 0 || pushed < 0) { return PY_INVALID_STACK_EFFECT; } - if (jump >= 0) { - return pushed - popped; - } - if (jump < 0) { - // Compute max(pushed - popped, alt_pushed - alt_popped) - int alt_popped = _PyOpcode_num_popped(opcode, oparg, true); - int alt_pushed = _PyOpcode_num_pushed(opcode, oparg, true); - if (alt_popped < 0 || alt_pushed < 0) { - return PY_INVALID_STACK_EFFECT; - } - int diff = pushed - popped; - int alt_diff = alt_pushed - alt_popped; - if (alt_diff > diff) { - return alt_diff; - } - return diff; - } + return pushed - popped; } // Pseudo ops @@ -1125,7 +1102,7 @@ compiler_addop_name(struct compiler_unit *u, location loc, arg <<= 1; } if (opcode == LOAD_METHOD) { - assert(SAME_OPCODE_METADATA(LOAD_METHOD, LOAD_ATTR)); + assert(is_pseudo_target(LOAD_METHOD, LOAD_ATTR)); opcode = LOAD_ATTR; arg <<= 1; arg |= 1; @@ -1135,18 +1112,18 @@ compiler_addop_name(struct compiler_unit *u, location loc, arg |= 2; } if (opcode == LOAD_SUPER_METHOD) { - assert(SAME_OPCODE_METADATA(LOAD_SUPER_METHOD, LOAD_SUPER_ATTR)); + assert(is_pseudo_target(LOAD_SUPER_METHOD, LOAD_SUPER_ATTR)); opcode = LOAD_SUPER_ATTR; arg <<= 2; arg |= 3; } if (opcode == LOAD_ZERO_SUPER_ATTR) { - assert(SAME_OPCODE_METADATA(LOAD_ZERO_SUPER_ATTR, LOAD_SUPER_ATTR)); + assert(is_pseudo_target(LOAD_ZERO_SUPER_ATTR, LOAD_SUPER_ATTR)); opcode = LOAD_SUPER_ATTR; arg <<= 2; } if (opcode == LOAD_ZERO_SUPER_METHOD) { - assert(SAME_OPCODE_METADATA(LOAD_ZERO_SUPER_METHOD, LOAD_SUPER_ATTR)); + assert(is_pseudo_target(LOAD_ZERO_SUPER_METHOD, LOAD_SUPER_ATTR)); opcode = LOAD_SUPER_ATTR; arg <<= 2; arg |= 1; diff --git a/Python/flowgraph.c b/Python/flowgraph.c index d2e3a7ae441c7f..e6c824a85ef51e 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -2258,11 +2258,11 @@ convert_pseudo_ops(basicblock *entryblock) INSTR_SET_OP0(instr, NOP); } else if (instr->i_opcode == LOAD_CLOSURE) { - assert(SAME_OPCODE_METADATA(LOAD_CLOSURE, LOAD_FAST)); + assert(is_pseudo_target(LOAD_CLOSURE, LOAD_FAST)); instr->i_opcode = LOAD_FAST; } else if (instr->i_opcode == STORE_FAST_MAYBE_NULL) { - assert(SAME_OPCODE_METADATA(STORE_FAST_MAYBE_NULL, STORE_FAST)); + assert(is_pseudo_target(STORE_FAST_MAYBE_NULL, STORE_FAST)); instr->i_opcode = STORE_FAST; } } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a274427a699a43..e935f33fa2131a 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -153,6 +153,7 @@ DEOPT_IF(!PyFloat_CheckExact(left), BINARY_OP); DEOPT_IF(!PyFloat_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_ADD_FLOAT { STAT_INC(BINARY_OP, hit); @@ -181,6 +182,7 @@ DEOPT_IF(!PyLong_CheckExact(left), BINARY_OP); DEOPT_IF(!PyLong_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_ADD_INT { STAT_INC(BINARY_OP, hit); @@ -209,6 +211,7 @@ DEOPT_IF(!PyUnicode_CheckExact(left), BINARY_OP); DEOPT_IF(!PyUnicode_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_ADD_UNICODE { STAT_INC(BINARY_OP, hit); @@ -236,6 +239,7 @@ DEOPT_IF(!PyUnicode_CheckExact(left), BINARY_OP); DEOPT_IF(!PyUnicode_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_INPLACE_ADD_UNICODE { TIER_ONE_ONLY @@ -282,6 +286,7 @@ DEOPT_IF(!PyFloat_CheckExact(left), BINARY_OP); DEOPT_IF(!PyFloat_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_MULTIPLY_FLOAT { STAT_INC(BINARY_OP, hit); @@ -310,6 +315,7 @@ DEOPT_IF(!PyLong_CheckExact(left), BINARY_OP); DEOPT_IF(!PyLong_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_MULTIPLY_INT { STAT_INC(BINARY_OP, hit); @@ -338,6 +344,7 @@ DEOPT_IF(!PyFloat_CheckExact(left), BINARY_OP); DEOPT_IF(!PyFloat_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_SUBTRACT_FLOAT { STAT_INC(BINARY_OP, hit); @@ -366,6 +373,7 @@ DEOPT_IF(!PyLong_CheckExact(left), BINARY_OP); DEOPT_IF(!PyLong_CheckExact(right), BINARY_OP); } + /* Skip 1 cache entry */ // _BINARY_OP_SUBTRACT_INT { STAT_INC(BINARY_OP, hit); @@ -763,6 +771,7 @@ DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); #endif /* ENABLE_SPECIALIZATION */ } + /* Skip 2 cache entries */ // _CALL { // oparg counts all of the args, but *not* self: @@ -3400,6 +3409,7 @@ DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); #endif /* ENABLE_SPECIALIZATION */ } + /* Skip 8 cache entries */ // _LOAD_ATTR { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1); @@ -4096,6 +4106,9 @@ DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); #endif /* ENABLE_SPECIALIZATION */ } + /* Skip 1 cache entry */ + /* Skip 1 cache entry */ + /* Skip 1 cache entry */ // _LOAD_GLOBAL { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -4564,6 +4577,7 @@ next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_FALSE); PyObject *cond; + /* Skip 1 cache entry */ cond = stack_pointer[-1]; assert(PyBool_Check(cond)); int flag = Py_IsFalse(cond); @@ -4582,6 +4596,7 @@ PyObject *value; PyObject *b; PyObject *cond; + /* Skip 1 cache entry */ // _IS_NONE value = stack_pointer[-1]; { @@ -4614,6 +4629,7 @@ PyObject *value; PyObject *b; PyObject *cond; + /* Skip 1 cache entry */ // _IS_NONE value = stack_pointer[-1]; { @@ -4644,6 +4660,7 @@ next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_TRUE); PyObject *cond; + /* Skip 1 cache entry */ cond = stack_pointer[-1]; assert(PyBool_Check(cond)); int flag = Py_IsTrue(cond); @@ -5117,6 +5134,7 @@ DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); #endif /* ENABLE_SPECIALIZATION */ } + /* Skip 3 cache entries */ // _STORE_ATTR v = stack_pointer[-2]; { @@ -5509,6 +5527,7 @@ DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); #endif /* ENABLE_SPECIALIZATION */ } + /* Skip 2 cache entries */ // _TO_BOOL { int err = PyObject_IsTrue(value); diff --git a/Python/optimizer.c b/Python/optimizer.c index d44e733bc346fa..0ff16191680a4b 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -6,14 +6,20 @@ #include "pycore_opcode_utils.h" // MAX_REAL_OPCODE #include "pycore_optimizer.h" // _Py_uop_analyze_and_optimize() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_uop_ids.h" #include "pycore_uops.h" #include "cpython/optimizer.h" #include #include #include +#define NEED_OPCODE_METADATA +#include "pycore_uop_metadata.h" // Uop tables +#undef NEED_OPCODE_METADATA + #define MAX_EXECUTORS_SIZE 256 + static bool has_space_for_executor(PyCodeObject *code, _Py_CODEUNIT *instr) { @@ -327,9 +333,6 @@ uop_dealloc(_PyUOpExecutorObject *self) { const char * _PyUOpName(int index) { - if (index <= MAX_REAL_OPCODE) { - return _PyOpcode_OpName[index]; - } return _PyOpcode_uop_name[index]; } @@ -388,7 +391,7 @@ PyTypeObject _PyUOpExecutor_Type = { /* TO DO -- Generate these tables */ static const uint16_t -_PyUOp_Replacements[OPCODE_METADATA_SIZE] = { +_PyUOp_Replacements[MAX_UOP_ID + 1] = { [_ITER_JUMP_RANGE] = _GUARD_NOT_EXHAUSTED_RANGE, [_ITER_JUMP_LIST] = _GUARD_NOT_EXHAUSTED_LIST, [_ITER_JUMP_TUPLE] = _GUARD_NOT_EXHAUSTED_TUPLE, @@ -629,14 +632,6 @@ translate_bytecode_to_trace( oparg += extras; } } - if (_PyUOp_Replacements[uop]) { - uop = _PyUOp_Replacements[uop]; - if (uop == _FOR_ITER_TIER_TWO) { - target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1; - assert(_PyCode_CODE(code)[target-1].op.code == END_FOR || - _PyCode_CODE(code)[target-1].op.code == INSTRUMENTED_END_FOR); - } - } break; case OPARG_CACHE_1: operand = read_u16(&instr[offset].cache); @@ -657,7 +652,15 @@ translate_bytecode_to_trace( oparg = offset; assert(uop == _SAVE_RETURN_OFFSET); break; - + case OPARG_REPLACED: + uop = _PyUOp_Replacements[uop]; + assert(uop != 0); + if (uop == _FOR_ITER_TIER_TWO) { + target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1; + assert(_PyCode_CODE(code)[target-1].op.code == END_FOR || + _PyCode_CODE(code)[target-1].op.code == INSTRUMENTED_END_FOR); + } + break; default: fprintf(stderr, "opcode=%d, oparg=%d; nuops=%d, i=%d; size=%d, offset=%d\n", @@ -799,7 +802,8 @@ compute_used(_PyUOpInstruction *buffer, uint32_t *used) } /* All other micro-ops fall through, so i+1 is reachable */ SET_BIT(used, i+1); - if (OPCODE_HAS_JUMP(opcode)) { + assert(opcode <= MAX_UOP_ID); + if (_PyUop_Flags[opcode] & HAS_JUMP_FLAG) { /* Mark target as reachable */ SET_BIT(used, buffer[i].oparg); } diff --git a/Python/specialize.c b/Python/specialize.c index 7c2a4a42b1dcc3..369b962a545f4e 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -10,6 +10,7 @@ #include "pycore_moduleobject.h" #include "pycore_object.h" #include "pycore_opcode_metadata.h" // _PyOpcode_Caches +#include "pycore_uop_metadata.h" // _PyOpcode_uop_name #include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START #include "pycore_pylifecycle.h" // _PyOS_URandomNonblock() #include "pycore_runtime.h" // _Py_ID() diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index e077eb0a8ed203..d7aca50d223748 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -11,11 +11,16 @@ class Properties: deopts: bool oparg: bool jumps: bool + eval_breaker: bool ends_with_eval_breaker: bool needs_this: bool always_exits: bool stores_sp: bool tier_one_only: bool + uses_co_consts: bool + uses_co_names: bool + uses_locals: bool + has_free: bool def dump(self, indent: str) -> None: print(indent, end="") @@ -30,11 +35,16 @@ def from_list(properties: list["Properties"]) -> "Properties": deopts=any(p.deopts for p in properties), oparg=any(p.oparg for p in properties), jumps=any(p.jumps for p in properties), + eval_breaker=any(p.eval_breaker for p in properties), ends_with_eval_breaker=any(p.ends_with_eval_breaker for p in properties), needs_this=any(p.needs_this for p in properties), always_exits=any(p.always_exits for p in properties), stores_sp=any(p.stores_sp for p in properties), tier_one_only=any(p.tier_one_only for p in properties), + uses_co_consts=any(p.uses_co_consts for p in properties), + uses_co_names=any(p.uses_co_names for p in properties), + uses_locals=any(p.uses_locals for p in properties), + has_free=any(p.has_free for p in properties), ) @@ -44,11 +54,16 @@ def from_list(properties: list["Properties"]) -> "Properties": deopts=False, oparg=False, jumps=False, + eval_breaker=False, ends_with_eval_breaker=False, needs_this=False, always_exits=False, stores_sp=False, tier_one_only=False, + uses_co_consts=False, + uses_co_names=False, + uses_locals=False, + has_free=False, ) @@ -142,6 +157,12 @@ def is_viable(self) -> bool: return False return True + def is_super(self) -> bool: + for tkn in self.body: + if tkn.kind == "IDENTIFIER" and tkn.text == "oparg1": + return True + return False + Part = Uop | Skip @@ -153,6 +174,7 @@ class Instruction: _properties: Properties | None is_target: bool = False family: Optional["Family"] = None + opcode: int = -1 @property def properties(self) -> Properties: @@ -171,16 +193,30 @@ def dump(self, indent: str) -> None: def size(self) -> int: return 1 + sum(part.size for part in self.parts) + def is_super(self) -> bool: + if len(self.parts) != 1: + return False + uop = self.parts[0] + if isinstance(uop, Uop): + return uop.is_super() + else: + return False + @dataclass class PseudoInstruction: name: str targets: list[Instruction] flags: list[str] + opcode: int = -1 def dump(self, indent: str) -> None: print(indent, self.name, "->", " or ".join([t.name for t in self.targets])) + @property + def properties(self) -> Properties: + return Properties.from_list([i.properties for i in self.targets]) + @dataclass class Family: @@ -198,12 +234,15 @@ class Analysis: uops: dict[str, Uop] families: dict[str, Family] pseudos: dict[str, PseudoInstruction] + opmap: dict[str, int] + have_arg: int + min_instrumented: int def analysis_error(message: str, tkn: lexer.Token) -> SyntaxError: # To do -- support file and line output # Construct a SyntaxError instance from message and token - return lexer.make_syntax_error(message, "", tkn.line, tkn.column, "") + return lexer.make_syntax_error(message, tkn.filename, tkn.line, tkn.column, "") def override_error( @@ -238,6 +277,11 @@ def analyze_caches(inputs: list[parser.InputEffect]) -> list[CacheEntry]: caches: list[parser.CacheEffect] = [ i for i in inputs if isinstance(i, parser.CacheEffect) ] + for cache in caches: + if cache.name == "unused": + raise analysis_error( + "Unused cache entry in op. Move to enclosing macro.", cache.tokens[0] + ) return [CacheEntry(i.name, int(i.size)) for i in caches] @@ -300,17 +344,28 @@ def always_exits(op: parser.InstDef) -> bool: def compute_properties(op: parser.InstDef) -> Properties: + has_free = ( + variable_used(op, "PyCell_New") + or variable_used(op, "PyCell_GET") + or variable_used(op, "PyCell_SET") + ) return Properties( escapes=makes_escaping_api_call(op), infallible=is_infallible(op), deopts=variable_used(op, "DEOPT_IF"), oparg=variable_used(op, "oparg"), jumps=variable_used(op, "JUMPBY"), + eval_breaker=variable_used(op, "CHECK_EVAL_BREAKER"), ends_with_eval_breaker=eval_breaker_at_end(op), needs_this=variable_used(op, "this_instr"), always_exits=always_exits(op), stores_sp=variable_used(op, "STORE_SP"), tier_one_only=variable_used(op, "TIER_ONE_ONLY"), + uses_co_consts=variable_used(op, "FRAME_CO_CONSTS"), + uses_co_names=variable_used(op, "FRAME_CO_NAMES"), + uses_locals=(variable_used(op, "GETLOCAL") or variable_used(op, "SETLOCAL")) + and not has_free, + has_free=has_free, ) @@ -417,6 +472,95 @@ def add_pseudo( ) +def assign_opcodes( + instructions: dict[str, Instruction], + families: dict[str, Family], + pseudos: dict[str, PseudoInstruction], +) -> tuple[dict[str, int], int, int]: + """Assigns opcodes, then returns the opmap, + have_arg and min_instrumented values""" + instmap: dict[str, int] = {} + + # 0 is reserved for cache entries. This helps debugging. + instmap["CACHE"] = 0 + + # 17 is reserved as it is the initial value for the specializing counter. + # This helps catch cases where we attempt to execute a cache. + instmap["RESERVED"] = 17 + + # 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py + instmap["RESUME"] = 149 + + # This is an historical oddity. + instmap["BINARY_OP_INPLACE_ADD_UNICODE"] = 3 + + instmap["INSTRUMENTED_LINE"] = 254 + + instrumented = [name for name in instructions if name.startswith("INSTRUMENTED")] + + # Special case: this instruction is implemented in ceval.c + # rather than bytecodes.c, so we need to add it explicitly + # here (at least until we add something to bytecodes.c to + # declare external instructions). + instrumented.append("INSTRUMENTED_LINE") + + specialized: set[str] = set() + no_arg: list[str] = [] + has_arg: list[str] = [] + + for family in families.values(): + specialized.update(inst.name for inst in family.members) + + for inst in instructions.values(): + name = inst.name + if name in specialized: + continue + if name in instrumented: + continue + if inst.properties.oparg: + has_arg.append(name) + else: + no_arg.append(name) + + # Specialized ops appear in their own section + # Instrumented opcodes are at the end of the valid range + min_internal = 150 + min_instrumented = 254 - (len(instrumented) - 1) + assert min_internal + len(specialized) < min_instrumented + + next_opcode = 1 + + def add_instruction(name: str) -> None: + nonlocal next_opcode + if name in instmap: + return # Pre-defined name + while next_opcode in instmap.values(): + next_opcode += 1 + instmap[name] = next_opcode + next_opcode += 1 + + for name in sorted(no_arg): + add_instruction(name) + for name in sorted(has_arg): + add_instruction(name) + # For compatibility + next_opcode = min_internal + for name in sorted(specialized): + add_instruction(name) + next_opcode = min_instrumented + for name in instrumented: + add_instruction(name) + + for name in instructions: + instructions[name].opcode = instmap[name] + + for op, name in enumerate(sorted(pseudos), 256): + instmap[name] = op + pseudos[name].opcode = op + + return instmap, len(no_arg), min_instrumented + + def analyze_forest(forest: list[parser.AstNode]) -> Analysis: instructions: dict[str, Instruction] = {} uops: dict[str, Uop] = {} @@ -460,10 +604,20 @@ def analyze_forest(forest: list[parser.AstNode]) -> Analysis: continue if target.text in instructions: instructions[target.text].is_target = True - # Hack + # Special case BINARY_OP_INPLACE_ADD_UNICODE + # BINARY_OP_INPLACE_ADD_UNICODE is not a normal family member, + # as it is the wrong size, but we need it to maintain an + # historical optimization. if "BINARY_OP_INPLACE_ADD_UNICODE" in instructions: - instructions["BINARY_OP_INPLACE_ADD_UNICODE"].family = families["BINARY_OP"] - return Analysis(instructions, uops, families, pseudos) + inst = instructions["BINARY_OP_INPLACE_ADD_UNICODE"] + inst.family = families["BINARY_OP"] + families["BINARY_OP"].members.append(inst) + opmap, first_arg, min_instrumented = assign_opcodes( + instructions, families, pseudos + ) + return Analysis( + instructions, uops, families, pseudos, opmap, first_arg, min_instrumented + ) def analyze_files(filenames: list[str]) -> Analysis: diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py index 67b1c9a169024c..069f0177a74018 100644 --- a/Tools/cases_generator/cwriter.py +++ b/Tools/cases_generator/cwriter.py @@ -1,5 +1,6 @@ +import contextlib from lexer import Token -from typing import TextIO +from typing import TextIO, Iterator class CWriter: @@ -44,9 +45,12 @@ def maybe_dedent(self, txt: str) -> None: def maybe_indent(self, txt: str) -> None: parens = txt.count("(") - txt.count(")") - if parens > 0 and self.last_token: - offset = self.last_token.end_column - 1 - if offset <= self.indents[-1] or offset > 40: + if parens > 0: + if self.last_token: + offset = self.last_token.end_column - 1 + if offset <= self.indents[-1] or offset > 40: + offset = self.indents[-1] + 4 + else: offset = self.indents[-1] + 4 self.indents.append(offset) if is_label(txt): @@ -54,6 +58,7 @@ def maybe_indent(self, txt: str) -> None: else: braces = txt.count("{") - txt.count("}") if braces > 0: + assert braces == 1 if 'extern "C"' in txt: self.indents.append(self.indents[-1]) else: @@ -114,6 +119,28 @@ def start_line(self) -> None: self.newline = True self.last_token = None + @contextlib.contextmanager + def header_guard(self, name: str) -> Iterator[None]: + self.out.write( + f""" +#ifndef {name} +#define {name} +#ifdef __cplusplus +extern "C" {{ +#endif + +""" + ) + yield + self.out.write( + f""" +#ifdef __cplusplus +}} +#endif +#endif /* !{name} */ +""" + ) + def is_label(txt: str) -> bool: return not txt.startswith("//") and txt.endswith(":") diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 50bc14a57fc584..bb027f3b09b654 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -840,7 +840,6 @@ def main() -> None: a.assign_opcode_ids() a.write_opcode_targets(args.opcode_targets_h) - a.write_metadata(args.metadata, args.pymetadata) a.write_abstract_interpreter_instructions( args.abstract_interpreter_cases, args.emit_line_directives ) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 1b565bff2c56f6..5a42a05c5c2ef2 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -2,14 +2,11 @@ from typing import TextIO from analyzer import ( - Analysis, Instruction, Uop, - Part, analyze_files, + Properties, Skip, - StackItem, - analysis_error, ) from cwriter import CWriter from typing import Callable, Mapping, TextIO, Iterator @@ -25,14 +22,16 @@ def root_relative_path(filename: str) -> str: try: return Path(filename).absolute().relative_to(ROOT).as_posix() except ValueError: + # Not relative to root, just return original path. return filename -def write_header(generator: str, sources: list[str], outfile: TextIO) -> None: + +def write_header(generator: str, sources: list[str], outfile: TextIO, comment: str = "//") -> None: outfile.write( - f"""// This file is generated by {root_relative_path(generator)} -// from: -// {", ".join(root_relative_path(src) for src in sources)} -// Do not edit! + f"""{comment} This file is generated by {root_relative_path(generator)} +{comment} from: +{comment} {", ".join(root_relative_path(src) for src in sources)} +{comment} Do not edit! """ ) @@ -186,3 +185,31 @@ def emit_tokens( replacement_functions[tkn.text](out, tkn, tkn_iter, uop, stack, inst) else: out.emit(tkn) + + +def cflags(p: Properties) -> str: + flags: list[str] = [] + if p.oparg: + flags.append("HAS_ARG_FLAG") + if p.uses_co_consts: + flags.append("HAS_CONST_FLAG") + if p.uses_co_names: + flags.append("HAS_NAME_FLAG") + if p.jumps: + flags.append("HAS_JUMP_FLAG") + if p.has_free: + flags.append("HAS_FREE_FLAG") + if p.uses_locals: + flags.append("HAS_LOCAL_FLAG") + if p.eval_breaker: + flags.append("HAS_EVAL_BREAK_FLAG") + if p.deopts: + flags.append("HAS_DEOPT_FLAG") + if not p.infallible: + flags.append("HAS_ERROR_FLAG") + if p.escapes: + flags.append("HAS_ESCAPES_FLAG") + if flags: + return " | ".join(flags) + else: + return "0" diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py index ddbb409bbced39..dbea3d0b622c87 100644 --- a/Tools/cases_generator/opcode_id_generator.py +++ b/Tools/cases_generator/opcode_id_generator.py @@ -24,111 +24,23 @@ DEFAULT_OUTPUT = ROOT / "Include/opcode_ids.h" -def generate_opcode_header(filenames: list[str], analysis: Analysis, outfile: TextIO) -> None: +def generate_opcode_header( + filenames: list[str], analysis: Analysis, outfile: TextIO +) -> None: write_header(__file__, filenames, outfile) out = CWriter(outfile, 0, False) - out.emit("\n") - instmap: dict[str, int] = {} + with out.header_guard("Py_OPCODE_IDS_H"): + out.emit("/* Instruction opcodes for compiled code */\n") - # 0 is reserved for cache entries. This helps debugging. - instmap["CACHE"] = 0 + def write_define(name: str, op: int) -> None: + out.emit(f"#define {name:<38} {op:>3}\n") - # 17 is reserved as it is the initial value for the specializing counter. - # This helps catch cases where we attempt to execute a cache. - instmap["RESERVED"] = 17 + for op, name in sorted([(op, name) for (name, op) in analysis.opmap.items()]): + write_define(name, op) - # 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py - instmap["RESUME"] = 149 - instmap["INSTRUMENTED_LINE"] = 254 - - instrumented = [ - name for name in analysis.instructions if name.startswith("INSTRUMENTED") - ] - - # Special case: this instruction is implemented in ceval.c - # rather than bytecodes.c, so we need to add it explicitly - # here (at least until we add something to bytecodes.c to - # declare external instructions). - instrumented.append("INSTRUMENTED_LINE") - - specialized: set[str] = set() - no_arg: list[str] = [] - has_arg: list[str] = [] - - for family in analysis.families.values(): - specialized.update(inst.name for inst in family.members) - - for inst in analysis.instructions.values(): - name = inst.name - if name in specialized: - continue - if name in instrumented: - continue - if inst.properties.oparg: - has_arg.append(name) - else: - no_arg.append(name) - - # Specialized ops appear in their own section - # Instrumented opcodes are at the end of the valid range - min_internal = 150 - min_instrumented = 254 - (len(instrumented) - 1) - assert min_internal + len(specialized) < min_instrumented - - next_opcode = 1 - - def add_instruction(name: str) -> None: - nonlocal next_opcode - if name in instmap: - return # Pre-defined name - while next_opcode in instmap.values(): - next_opcode += 1 - instmap[name] = next_opcode - next_opcode += 1 - - for name in sorted(no_arg): - add_instruction(name) - for name in sorted(has_arg): - add_instruction(name) - # For compatibility - next_opcode = min_internal - for name in sorted(specialized): - add_instruction(name) - next_opcode = min_instrumented - for name in instrumented: - add_instruction(name) - - for op, name in enumerate(sorted(analysis.pseudos), 256): - instmap[name] = op - - assert 255 not in instmap.values() - - out.emit( - """#ifndef Py_OPCODE_IDS_H -#define Py_OPCODE_IDS_H -#ifdef __cplusplus -extern "C" { -#endif - -/* Instruction opcodes for compiled code */ -""" - ) - - def write_define(name: str, op: int) -> None: - out.emit(f"#define {name:<38} {op:>3}\n") - - for op, name in sorted([(op, name) for (name, op) in instmap.items()]): - write_define(name, op) - - out.emit("\n") - write_define("HAVE_ARGUMENT", len(no_arg)) - write_define("MIN_INSTRUMENTED_OPCODE", min_instrumented) - - out.emit("\n") - out.emit("#ifdef __cplusplus\n") - out.emit("}\n") - out.emit("#endif\n") - out.emit("#endif /* !Py_OPCODE_IDS_H */\n") + out.emit("\n") + write_define("HAVE_ARGUMENT", analysis.have_arg) + write_define("MIN_INSTRUMENTED_OPCODE", analysis.min_instrumented) arg_parser = argparse.ArgumentParser( diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py new file mode 100644 index 00000000000000..427bb54e5fed59 --- /dev/null +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -0,0 +1,386 @@ +"""Generate uop metedata. +Reads the instruction definitions from bytecodes.c. +Writes the metadata to pycore_uop_metadata.h by default. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + analyze_files, + Skip, + Uop, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, + cflags, + StackOffset, +) +from cwriter import CWriter +from typing import TextIO +from stack import get_stack_effect + +# Constants used instead of size for macro expansions. +# Note: 1, 2, 4 must match actual cache entry sizes. +OPARG_KINDS = { + "OPARG_FULL": 0, + "OPARG_CACHE_1": 1, + "OPARG_CACHE_2": 2, + "OPARG_CACHE_4": 4, + "OPARG_TOP": 5, + "OPARG_BOTTOM": 6, + "OPARG_SAVE_RETURN_OFFSET": 7, + # Skip 8 as the other powers of 2 are sizes + "OPARG_REPLACED": 9, +} + +FLAGS = [ + "ARG", + "CONST", + "NAME", + "JUMP", + "FREE", + "LOCAL", + "EVAL_BREAK", + "DEOPT", + "ERROR", + "ESCAPES", +] + + +def generate_flag_macros(out: CWriter) -> None: + for i, flag in enumerate(FLAGS): + out.emit(f"#define HAS_{flag}_FLAG ({1< None: + for name, value in OPARG_KINDS.items(): + out.emit(f"#define {name} {value}\n") + out.emit("\n") + + +def emit_stack_effect_function( + out: CWriter, direction: str, data: list[tuple[str, str]] +) -> None: + out.emit(f"extern int _PyOpcode_num_{direction}(int opcode, int oparg);\n") + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit(f"int _PyOpcode_num_{direction}(int opcode, int oparg) {{\n") + out.emit("switch(opcode) {\n") + for name, effect in data: + out.emit(f"case {name}:\n") + out.emit(f" return {effect};\n") + out.emit("default:\n") + out.emit(" return -1;\n") + out.emit("}\n") + out.emit("}\n\n") + out.emit("#endif\n\n") + + +def generate_stack_effect_functions(analysis: Analysis, out: CWriter) -> None: + popped_data: list[tuple[str, str]] = [] + pushed_data: list[tuple[str, str]] = [] + for inst in analysis.instructions.values(): + stack = get_stack_effect(inst) + popped = (-stack.base_offset).to_c() + pushed = (stack.top_offset - stack.base_offset).to_c() + popped_data.append((inst.name, popped)) + pushed_data.append((inst.name, pushed)) + emit_stack_effect_function(out, "popped", sorted(popped_data)) + emit_stack_effect_function(out, "pushed", sorted(pushed_data)) + + +def generate_is_pseudo(analysis: Analysis, out: CWriter) -> None: + """Write the IS_PSEUDO_INSTR macro""" + out.emit("\n\n#define IS_PSEUDO_INSTR(OP) ( \\\n") + for op in analysis.pseudos: + out.emit(f"((OP) == {op}) || \\\n") + out.emit("0") + out.emit(")\n\n") + + +def get_format(inst: Instruction) -> str: + if inst.properties.oparg: + format = "INSTR_FMT_IB" + else: + format = "INSTR_FMT_IX" + if inst.size > 1: + format += "C" + format += "0" * (inst.size - 2) + return format + + +def generate_instruction_formats(analysis: Analysis, out: CWriter) -> None: + # Compute the set of all instruction formats. + formats: set[str] = set() + for inst in analysis.instructions.values(): + formats.add(get_format(inst)) + # Generate an enum for it + out.emit("enum InstructionFormat {\n") + next_id = 1 + for format in sorted(formats): + out.emit(f"{format} = {next_id},\n") + next_id += 1 + out.emit("};\n\n") + + +def generate_deopt_table(analysis: Analysis, out: CWriter) -> None: + out.emit("extern const uint8_t _PyOpcode_Deopt[256];\n") + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit("const uint8_t _PyOpcode_Deopt[256] = {\n") + deopts: list[tuple[str, str]] = [] + for inst in analysis.instructions.values(): + deopt = inst.name + if inst.family is not None: + deopt = inst.family.name + deopts.append((inst.name, deopt)) + deopts.append(("INSTRUMENTED_LINE", "INSTRUMENTED_LINE")) + for name, deopt in sorted(deopts): + out.emit(f"[{name}] = {deopt},\n") + out.emit("};\n\n") + out.emit("#endif // NEED_OPCODE_METADATA\n\n") + + +def generate_cache_table(analysis: Analysis, out: CWriter) -> None: + out.emit("extern const uint8_t _PyOpcode_Caches[256];\n") + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit("const uint8_t _PyOpcode_Caches[256] = {\n") + for inst in analysis.instructions.values(): + if inst.family and inst.family.name != inst.name: + continue + if inst.name.startswith("INSTRUMENTED"): + continue + if inst.size > 1: + out.emit(f"[{inst.name}] = {inst.size-1},\n") + out.emit("};\n") + out.emit("#endif\n\n") + + +def generate_name_table(analysis: Analysis, out: CWriter) -> None: + table_size = 256 + len(analysis.pseudos) + out.emit(f"extern const char *_PyOpcode_OpName[{table_size}];\n") + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit(f"const char *_PyOpcode_OpName[{table_size}] = {{\n") + names = list(analysis.instructions) + list(analysis.pseudos) + names.append("INSTRUMENTED_LINE") + for name in sorted(names): + out.emit(f'[{name}] = "{name}",\n') + out.emit("};\n") + out.emit("#endif\n\n") + + +def generate_metadata_table(analysis: Analysis, out: CWriter) -> None: + table_size = 256 + len(analysis.pseudos) + out.emit("struct opcode_metadata {\n") + out.emit("uint8_t valid_entry;\n") + out.emit("int8_t instr_format;\n") + out.emit("int16_t flags;\n") + out.emit("};\n\n") + out.emit( + f"extern const struct opcode_metadata _PyOpcode_opcode_metadata[{table_size}];\n" + ) + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit( + f"const struct opcode_metadata _PyOpcode_opcode_metadata[{table_size}] = {{\n" + ) + for inst in sorted(analysis.instructions.values(), key=lambda t: t.name): + out.emit( + f"[{inst.name}] = {{ true, {get_format(inst)}, {cflags(inst.properties)} }},\n" + ) + for pseudo in sorted(analysis.pseudos.values(), key=lambda t: t.name): + flags = cflags(pseudo.properties) + for flag in pseudo.flags: + if flags == "0": + flags = f"{flag}_FLAG" + else: + flags += f" | {flag}_FLAG" + out.emit(f"[{pseudo.name}] = {{ true, -1, {flags} }},\n") + out.emit("};\n") + out.emit("#endif\n\n") + + +def generate_expansion_table(analysis: Analysis, out: CWriter) -> None: + expansions_table: dict[str, list[tuple[str, int, int]]] = {} + for inst in sorted(analysis.instructions.values(), key=lambda t: t.name): + offset: int = 0 # Cache effect offset + expansions: list[tuple[str, int, int]] = [] # [(name, size, offset), ...] + if inst.is_super(): + pieces = inst.name.split("_") + assert len(pieces) == 4, f"{inst.name} doesn't look like a super-instr" + name1 = "_".join(pieces[:2]) + name2 = "_".join(pieces[2:]) + assert name1 in analysis.instructions, f"{name1} doesn't match any instr" + assert name2 in analysis.instructions, f"{name2} doesn't match any instr" + instr1 = analysis.instructions[name1] + instr2 = analysis.instructions[name2] + assert ( + len(instr1.parts) == 1 + ), f"{name1} is not a good superinstruction part" + assert ( + len(instr2.parts) == 1 + ), f"{name2} is not a good superinstruction part" + expansions.append((instr1.parts[0].name, OPARG_KINDS["OPARG_TOP"], 0)) + expansions.append((instr2.parts[0].name, OPARG_KINDS["OPARG_BOTTOM"], 0)) + elif not is_viable_expansion(inst): + continue + else: + for part in inst.parts: + size = part.size + if part.name == "_SAVE_RETURN_OFFSET": + size = OPARG_KINDS["OPARG_SAVE_RETURN_OFFSET"] + if isinstance(part, Uop): + # Skip specializations + if "specializing" in part.annotations: + continue + if "replaced" in part.annotations: + size = OPARG_KINDS["OPARG_REPLACED"] + expansions.append((part.name, size, offset if size else 0)) + offset += part.size + expansions_table[inst.name] = expansions + max_uops = max(len(ex) for ex in expansions_table.values()) + out.emit(f"#define MAX_UOP_PER_EXPANSION {max_uops}\n") + out.emit("struct opcode_macro_expansion {\n") + out.emit("int nuops;\n") + out.emit( + "struct { int16_t uop; int8_t size; int8_t offset; } uops[MAX_UOP_PER_EXPANSION];\n" + ) + out.emit("};\n") + out.emit( + "extern const struct opcode_macro_expansion _PyOpcode_macro_expansion[256];\n\n" + ) + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit("const struct opcode_macro_expansion\n") + out.emit("_PyOpcode_macro_expansion[256] = {\n") + for inst_name, expansions in expansions_table.items(): + uops = [ + f"{{ {name}, {size}, {offset} }}" for (name, size, offset) in expansions + ] + out.emit( + f'[{inst_name}] = {{ .nuops = {len(expansions)}, .uops = {{ {", ".join(uops)} }} }},\n' + ) + out.emit("};\n") + out.emit("#endif // NEED_OPCODE_METADATA\n\n") + + +def is_viable_expansion(inst: Instruction) -> bool: + "An instruction can be expanded if all its parts are viable for tier 2" + for part in inst.parts: + if isinstance(part, Uop): + # Skip specializing and replaced uops + if "specializing" in part.annotations: + continue + if "replaced" in part.annotations: + continue + if part.properties.tier_one_only or not part.is_viable(): + return False + return True + + +def generate_extra_cases(analysis: Analysis, out: CWriter) -> None: + out.emit("#define EXTRA_CASES \\\n") + valid_opcodes = set(analysis.opmap.values()) + for op in range(256): + if op not in valid_opcodes: + out.emit(f" case {op}: \\\n") + out.emit(" ;\n") + + +def generate_pseudo_targets(analysis: Analysis, out: CWriter) -> None: + table_size = len(analysis.pseudos) + max_targets = max(len(pseudo.targets) for pseudo in analysis.pseudos.values()) + out.emit("struct pseudo_targets {\n") + out.emit(f"uint8_t targets[{max_targets + 1}];\n") + out.emit("};\n") + out.emit( + f"extern const struct pseudo_targets _PyOpcode_PseudoTargets[{table_size}];\n" + ) + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit( + f"const struct pseudo_targets _PyOpcode_PseudoTargets[{table_size}] = {{\n" + ) + for pseudo in analysis.pseudos.values(): + targets = ["0"] * (max_targets + 1) + for i, target in enumerate(pseudo.targets): + targets[i] = target.name + out.emit(f"[{pseudo.name}-256] = {{ {{ {', '.join(targets)} }} }},\n") + out.emit("};\n\n") + out.emit("#endif // NEED_OPCODE_METADATA\n") + out.emit("static inline bool\n") + out.emit("is_pseudo_target(int pseudo, int target) {\n") + out.emit(f"if (pseudo < 256 || pseudo >= {256+table_size}) {{\n") + out.emit(f"return false;\n") + out.emit("}\n") + out.emit( + f"for (int i = 0; _PyOpcode_PseudoTargets[pseudo-256].targets[i]; i++) {{\n" + ) + out.emit( + f"if (_PyOpcode_PseudoTargets[pseudo-256].targets[i] == target) return true;\n" + ) + out.emit("}\n") + out.emit(f"return false;\n") + out.emit("}\n\n") + + +def generate_opcode_metadata( + filenames: list[str], analysis: Analysis, outfile: TextIO +) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 0, False) + with out.header_guard("Py_CORE_OPCODE_METADATA_H"): + out.emit("#ifndef Py_BUILD_CORE\n") + out.emit('# error "this header requires Py_BUILD_CORE define"\n') + out.emit("#endif\n\n") + out.emit("#include // bool\n") + out.emit('#include "opcode_ids.h"\n') + generate_is_pseudo(analysis, out) + out.emit('#include "pycore_uop_ids.h"\n') + generate_stack_effect_functions(analysis, out) + generate_instruction_formats(analysis, out) + table_size = 256 + len(analysis.pseudos) + out.emit("#define IS_VALID_OPCODE(OP) \\\n") + out.emit(f" (((OP) >= 0) && ((OP) < {table_size}) && \\\n") + out.emit(" (_PyOpcode_opcode_metadata[(OP)].valid_entry))\n\n") + generate_flag_macros(out) + generate_oparg_macros(out) + generate_metadata_table(analysis, out) + generate_expansion_table(analysis, out) + generate_name_table(analysis, out) + generate_cache_table(analysis, out) + generate_deopt_table(analysis, out) + generate_extra_cases(analysis, out) + generate_pseudo_targets(analysis, out) + + +arg_parser = argparse.ArgumentParser( + description="Generate the header file with opcode metadata.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + + +DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_metadata.h" + + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_opcode_metadata(args.input, data, outfile) diff --git a/Tools/cases_generator/py_metadata_generator.py b/Tools/cases_generator/py_metadata_generator.py new file mode 100644 index 00000000000000..43811fdacc8a9e --- /dev/null +++ b/Tools/cases_generator/py_metadata_generator.py @@ -0,0 +1,97 @@ +"""Generate uop metedata. +Reads the instruction definitions from bytecodes.c. +Writes the metadata to pycore_uop_metadata.h by default. +""" + +import argparse + +from analyzer import ( + Analysis, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + root_relative_path, + write_header, +) +from cwriter import CWriter +from typing import TextIO + + + +DEFAULT_OUTPUT = ROOT / "Lib/_opcode_metadata.py" + + +def get_specialized(analysis: Analysis) -> set[str]: + specialized: set[str] = set() + for family in analysis.families.values(): + for member in family.members: + specialized.add(member.name) + return specialized + + +def generate_specializations(analysis: Analysis, out: CWriter) -> None: + out.emit("_specializations = {\n") + for family in analysis.families.values(): + out.emit(f'"{family.name}": [\n') + for member in family.members: + out.emit(f' "{member.name}",\n') + out.emit("],\n") + out.emit("}\n\n") + + +def generate_specialized_opmap(analysis: Analysis, out: CWriter) -> None: + out.emit("_specialized_opmap = {\n") + names = [] + for family in analysis.families.values(): + for member in family.members: + if member.name == family.name: + continue + names.append(member.name) + for name in sorted(names): + out.emit(f"'{name}': {analysis.opmap[name]},\n") + out.emit("}\n\n") + + +def generate_opmap(analysis: Analysis, out: CWriter) -> None: + specialized = get_specialized(analysis) + out.emit("opmap = {\n") + for inst, op in analysis.opmap.items(): + if inst not in specialized: + out.emit(f"'{inst}': {analysis.opmap[inst]},\n") + out.emit("}\n\n") + + +def generate_py_metadata( + filenames: list[str], analysis: Analysis, outfile: TextIO +) -> None: + write_header(__file__, filenames, outfile, "#") + out = CWriter(outfile, 0, False) + generate_specializations(analysis, out) + generate_specialized_opmap(analysis, out) + generate_opmap(analysis, out) + out.emit(f"HAVE_ARGUMENT = {analysis.have_arg}\n") + out.emit(f"MIN_INSTRUMENTED_OPCODE = {analysis.min_instrumented}\n") + + +arg_parser = argparse.ArgumentParser( + description="Generate the Python file with opcode metadata.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_py_metadata(args.input, data, outfile) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 0b31ce4090f552..94fb82d1139b68 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -1,5 +1,5 @@ import sys -from analyzer import StackItem +from analyzer import StackItem, Instruction, Uop from dataclasses import dataclass from formatting import maybe_parenthesize from cwriter import CWriter @@ -15,13 +15,16 @@ def var_size(var: StackItem) -> str: else: return var.size - +@dataclass class StackOffset: "The stack offset of the virtual base of the stack from the physical stack pointer" - def __init__(self) -> None: - self.popped: list[str] = [] - self.pushed: list[str] = [] + popped: list[str] + pushed: list[str] + + @staticmethod + def empty() -> "StackOffset": + return StackOffset([], []) def pop(self, item: StackItem) -> None: self.popped.append(var_size(item)) @@ -29,6 +32,15 @@ def pop(self, item: StackItem) -> None: def push(self, item: StackItem) -> None: self.pushed.append(var_size(item)) + def __sub__(self, other: "StackOffset") -> "StackOffset": + return StackOffset( + self.popped + other.pushed, + self.pushed + other.popped + ) + + def __neg__(self) -> "StackOffset": + return StackOffset(self.pushed, self.popped) + def simplify(self) -> None: "Remove matching values from both the popped and pushed list" if not self.popped or not self.pushed: @@ -88,9 +100,9 @@ class SizeMismatch(Exception): class Stack: def __init__(self) -> None: - self.top_offset = StackOffset() - self.base_offset = StackOffset() - self.peek_offset = StackOffset() + self.top_offset = StackOffset.empty() + self.base_offset = StackOffset.empty() + self.peek_offset = StackOffset.empty() self.variables: list[StackItem] = [] self.defined: set[str] = set() @@ -166,3 +178,15 @@ def flush(self, out: CWriter) -> None: def as_comment(self) -> str: return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" + + +def get_stack_effect(inst: Instruction) -> Stack: + stack = Stack() + for uop in inst.parts: + if not isinstance(uop, Uop): + continue + for var in reversed(uop.stack.inputs): + stack.pop(var) + for i, var in enumerate(uop.stack.outputs): + stack.push(var) + return stack diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index 49cede978d821a..aba36ec74e5766 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -190,6 +190,7 @@ def generate_tier1_from_files( with open(outfilename, "w") as outfile: generate_tier1(filenames, data, outfile, lines) + if __name__ == "__main__": args = arg_parser.parse_args() if len(args.input) == 0: diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index a22fb6dd932503..7897b89b2752a7 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -103,13 +103,6 @@ def tier2_replace_deopt( TIER2_REPLACEMENT_FUNCTIONS["DEOPT_IF"] = tier2_replace_deopt -def is_super(uop: Uop) -> bool: - for tkn in uop.body: - if tkn.kind == "IDENTIFIER" and tkn.text == "oparg1": - return True - return False - - def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: try: out.start_line() @@ -123,7 +116,7 @@ def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: for cache in uop.caches: if cache.name != "unused": if cache.size == 4: - type = cast ="PyObject *" + type = cast = "PyObject *" else: type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" @@ -156,7 +149,7 @@ def generate_tier2( for name, uop in analysis.uops.items(): if uop.properties.tier_one_only: continue - if is_super(uop): + if uop.is_super(): continue if not uop.is_viable(): out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n") diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py index 277da25835f6fb..633249f1c6b1fe 100644 --- a/Tools/cases_generator/uop_id_generator.py +++ b/Tools/cases_generator/uop_id_generator.py @@ -24,50 +24,32 @@ DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_ids.h" -OMIT = {"_CACHE", "_RESERVED", "_EXTENDED_ARG"} - - def generate_uop_ids( filenames: list[str], analysis: Analysis, outfile: TextIO, distinct_namespace: bool ) -> None: write_header(__file__, filenames, outfile) out = CWriter(outfile, 0, False) - out.emit( - """#ifndef Py_CORE_UOP_IDS_H -#define Py_CORE_UOP_IDS_H -#ifdef __cplusplus -extern "C" { -#endif - -""" - ) - - next_id = 1 if distinct_namespace else 300 - # These two are first by convention - out.emit(f"#define _EXIT_TRACE {next_id}\n") - next_id += 1 - out.emit(f"#define _SET_IP {next_id}\n") - next_id += 1 - PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP"} - - for uop in analysis.uops.values(): - if uop.name in PRE_DEFINED: - continue - # TODO: We should omit all tier-1 only uops, but - # generate_cases.py still generates code for those. - if uop.name in OMIT: - continue - if uop.implicitly_created and not distinct_namespace: - out.emit(f"#define {uop.name} {uop.name[1:]}\n") - else: - out.emit(f"#define {uop.name} {next_id}\n") - next_id += 1 - - out.emit("\n") - out.emit("#ifdef __cplusplus\n") - out.emit("}\n") - out.emit("#endif\n") - out.emit("#endif /* !Py_OPCODE_IDS_H */\n") + with out.header_guard("Py_CORE_UOP_IDS_H"): + next_id = 1 if distinct_namespace else 300 + # These two are first by convention + out.emit(f"#define _EXIT_TRACE {next_id}\n") + next_id += 1 + out.emit(f"#define _SET_IP {next_id}\n") + next_id += 1 + PRE_DEFINED = {"_EXIT_TRACE", "_SET_IP"} + + for uop in analysis.uops.values(): + if uop.name in PRE_DEFINED: + continue + if uop.properties.tier_one_only: + continue + if uop.implicitly_created and not distinct_namespace: + out.emit(f"#define {uop.name} {uop.name[1:]}\n") + else: + out.emit(f"#define {uop.name} {next_id}\n") + next_id += 1 + + out.emit(f"#define MAX_UOP_ID {next_id-1}\n") arg_parser = argparse.ArgumentParser( diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py new file mode 100644 index 00000000000000..d4f3a096d2acc1 --- /dev/null +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -0,0 +1,73 @@ +"""Generate uop metedata. +Reads the instruction definitions from bytecodes.c. +Writes the metadata to pycore_uop_metadata.h by default. +""" + +import argparse + +from analyzer import ( + Analysis, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, + cflags, +) +from cwriter import CWriter +from typing import TextIO + + +DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_metadata.h" + + +def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: + out.emit("extern const uint16_t _PyUop_Flags[MAX_UOP_ID+1];\n") + out.emit("extern const char * const _PyOpcode_uop_name[MAX_UOP_ID+1];\n\n") + out.emit("#ifdef NEED_OPCODE_METADATA\n") + out.emit("const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {\n") + for uop in analysis.uops.values(): + if uop.is_viable() and not uop.properties.tier_one_only: + out.emit(f"[{uop.name}] = {cflags(uop.properties)},\n") + + out.emit("};\n\n") + out.emit("const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {\n") + for uop in sorted(analysis.uops.values(), key=lambda t: t.name): + if uop.is_viable() and not uop.properties.tier_one_only: + out.emit(f'[{uop.name}] = "{uop.name}",\n') + out.emit("};\n") + out.emit("#endif // NEED_OPCODE_METADATA\n\n") + + +def generate_uop_metadata( + filenames: list[str], analysis: Analysis, outfile: TextIO +) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 0, False) + with out.header_guard("Py_CORE_UOP_METADATA_H"): + out.emit("#include \n") + out.emit('#include "pycore_uop_ids.h"\n') + generate_names_and_flags(analysis, out) + + +arg_parser = argparse.ArgumentParser( + description="Generate the header file with uop metadata.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_uop_metadata(args.input, data, outfile) From de8a4e52a5f5e5f0c5057afd4c391afccfca57d3 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 20 Dec 2023 15:09:12 +0000 Subject: [PATCH 315/442] GH-111485: Generate `TARGET` table for computed goto dispatch. (GH-113319) --- Makefile.pre.in | 3 +- Python/opcode_targets.h | 3 +- Tools/cases_generator/generate_cases.py | 1 - Tools/cases_generator/target_generator.py | 54 +++++++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 Tools/cases_generator/target_generator.py diff --git a/Makefile.pre.in b/Makefile.pre.in index 92827ec3479526..95b2f246ed5bcb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1587,11 +1587,12 @@ regen-cases: $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/generate_cases.py \ $(CASESFLAG) \ - -t $(srcdir)/Python/opcode_targets.h.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_id_generator.py \ -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/target_generator.py \ + -o $(srcdir)/Python/opcode_targets.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_id_generator.py \ -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/py_metadata_generator.py \ diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index bcd6ea7564f9b3..e664e638bdb749 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -254,4 +254,5 @@ static void *opcode_targets[256] = { &&TARGET_INSTRUMENTED_POP_JUMP_IF_NONE, &&TARGET_INSTRUMENTED_POP_JUMP_IF_NOT_NONE, &&TARGET_INSTRUMENTED_LINE, - &&_unknown_opcode}; + &&_unknown_opcode, +}; diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index bb027f3b09b654..73b5fc2c9897e8 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -839,7 +839,6 @@ def main() -> None: # These raise OSError if output can't be written a.assign_opcode_ids() - a.write_opcode_targets(args.opcode_targets_h) a.write_abstract_interpreter_instructions( args.abstract_interpreter_cases, args.emit_line_directives ) diff --git a/Tools/cases_generator/target_generator.py b/Tools/cases_generator/target_generator.py new file mode 100644 index 00000000000000..44a699c92bbd22 --- /dev/null +++ b/Tools/cases_generator/target_generator.py @@ -0,0 +1,54 @@ +"""Generate targets for computed goto dispatch +Reads the instruction definitions from bytecodes.c. +Writes the table to opcode_targets.h by default. +""" + +import argparse + +from analyzer import ( + Analysis, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, +) +from cwriter import CWriter +from typing import TextIO + + +DEFAULT_OUTPUT = ROOT / "Python/opcode_targets.h" + + +def write_opcode_targets(analysis: Analysis, out: CWriter) -> None: + """Write header file that defines the jump target table""" + targets = ["&&_unknown_opcode,\n"] * 256 + for name, op in analysis.opmap.items(): + if op < 256: + targets[op] = f"&&TARGET_{name},\n" + out.emit("static void *opcode_targets[256] = {\n") + for target in targets: + out.emit(target) + out.emit("};\n") + +arg_parser = argparse.ArgumentParser( + description="Generate the file with dispatch targets.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + out = CWriter(outfile, 0, False) + write_opcode_targets(data, out) From 11ee912327ef51100d2a6b990249f25b6b1b435d Mon Sep 17 00:00:00 2001 From: Kir Date: Thu, 21 Dec 2023 02:21:23 +0900 Subject: [PATCH 316/442] gh-113255: Clarify docs for `typing.reveal_type` (#113286) Co-authored-by: AlexWaygood --- Doc/library/typing.rst | 23 +++++++++++------------ Lib/typing.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index ba2845eb17ddcc..63bd62d1f6679b 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2604,10 +2604,10 @@ Functions and decorators .. function:: reveal_type(obj, /) - Reveal the inferred static type of an expression. + Ask a static type checker to reveal the inferred type of an expression. When a static type checker encounters a call to this function, - it emits a diagnostic with the type of the argument. For example:: + it emits a diagnostic with the inferred type of the argument. For example:: x: int = 1 reveal_type(x) # Revealed type is "builtins.int" @@ -2615,22 +2615,21 @@ Functions and decorators This can be useful when you want to debug how your type checker handles a particular piece of code. - The function returns its argument unchanged, which allows using - it within an expression:: + At runtime, this function prints the runtime type of its argument to + :data:`sys.stderr` and returns the argument unchanged (allowing the call to + be used within an expression):: - x = reveal_type(1) # Revealed type is "builtins.int" + x = reveal_type(1) # prints "Runtime type is int" + print(x) # prints "1" + + Note that the runtime type may be different from (more or less specific + than) the type statically inferred by a type checker. Most type checkers support ``reveal_type()`` anywhere, even if the name is not imported from ``typing``. Importing the name from - ``typing`` allows your code to run without runtime errors and + ``typing``, however, allows your code to run without runtime errors and communicates intent more clearly. - At runtime, this function prints the runtime type of its argument to stderr - and returns it unchanged:: - - x = reveal_type(1) # prints "Runtime type is int" - print(x) # prints "1" - .. versionadded:: 3.11 .. decorator:: dataclass_transform(*, eq_default=True, order_default=False, \ diff --git a/Lib/typing.py b/Lib/typing.py index 61b88a560e9dc5..d7d793539b35b1 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3301,7 +3301,7 @@ def __enter__(self) -> 'TextIO': def reveal_type[T](obj: T, /) -> T: - """Reveal the inferred type of a variable. + """Ask a static type checker to reveal the inferred type of an expression. When a static type checker encounters a call to ``reveal_type()``, it will emit the inferred type of the argument:: @@ -3313,7 +3313,7 @@ def reveal_type[T](obj: T, /) -> T: will produce output similar to 'Revealed type is "builtins.int"'. At runtime, the function prints the runtime type of the - argument and returns it unchanged. + argument and returns the argument unchanged. """ print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr) return obj From b221e030100e565d4a027ce1c0152dac1b7ffbdf Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Wed, 20 Dec 2023 11:28:20 -0600 Subject: [PATCH 317/442] gh-113257: Automatically generate pip SBOM metadata from wheel (#113295) Co-authored-by: Hugo van Kemenade --- Misc/sbom.spdx.json | 2 +- Tools/build/generate_sbom.py | 116 ++++++++++++++++++++++++++++++++--- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 81f8486ea350c1..5b3cd04ffa7f74 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -1700,7 +1700,7 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be" + "checksumValue": "5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76" } ], "downloadLocation": "https://files.pythonhosted.org/packages/15/aa/3f4c7bcee2057a76562a5b33ecbd199be08cdb4443a02e26bd2c3cf6fc39/pip-23.3.2-py3-none-any.whl", diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index c02eb88b46532f..93d0d8a3762df3 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -1,12 +1,16 @@ """Tool for generating Software Bill of Materials (SBOM) for Python's dependencies""" - +import os import re import hashlib import json import glob import pathlib import subprocess +import sys import typing +from urllib.request import urlopen + +CPYTHON_ROOT_DIR = pathlib.Path(__file__).parent.parent.parent # Before adding a new entry to this list, double check that # the license expression is a valid SPDX license expression: @@ -43,15 +47,14 @@ class PackageFiles(typing.NamedTuple): # values to 'exclude' if we create new files within tracked # directories that aren't sourced from third-party packages. PACKAGE_TO_FILES = { + # NOTE: pip's entry in this structure is automatically generated in + # the 'discover_pip_sbom_package()' function below. "mpdecimal": PackageFiles( include=["Modules/_decimal/libmpdec/**"] ), "expat": PackageFiles( include=["Modules/expat/**"] ), - "pip": PackageFiles( - include=["Lib/ensurepip/_bundled/pip-23.3.2-py3-none-any.whl"] - ), "macholib": PackageFiles( include=["Lib/ctypes/macholib/**"], exclude=[ @@ -106,13 +109,106 @@ 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 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. + """ + 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] + + # 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}"] + ) + + # Wheel filename format puts the version right after the project name. + pip_version = pip_wheel_filename.split("-")[1] + pip_checksum_sha256 = hashlib.sha256( + (ensurepip_bundled_dir / pip_wheel_filename).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] + + # 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: + 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) + + # Remove pip from the existing SBOM packages if it's there + # and then overwrite its entry with our own generated one. + sbom_data["packages"] = [ + sbom_package + for sbom_package in sbom_data["packages"] + if sbom_package["name"] != "pip" + ] + sbom_data["packages"].append( + { + "SPDXID": spdx_id("SPDXRef-PACKAGE-pip"), + "name": "pip", + "versionInfo": pip_version, + "originator": "Organization: Python Packaging Authority", + "licenseConcluded": "MIT", + "downloadLocation": pip_download_url, + "checksums": [ + {"algorithm": "SHA256", "checksumValue": pip_checksum_sha256} + ], + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": f"cpe:2.3:a:pypa:pip:{pip_version}:*:*:*:*:*:*:*", + "referenceType": "cpe23Type", + }, + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceLocator": f"pkg:pypi/pip@{pip_version}", + "referenceType": "purl", + }, + ], + "primaryPackagePurpose": "SOURCE", + } + ) + + def main() -> None: - root_dir = pathlib.Path(__file__).parent.parent.parent - sbom_path = root_dir / "Misc/sbom.spdx.json" + sbom_path = CPYTHON_ROOT_DIR / "Misc/sbom.spdx.json" sbom_data = json.loads(sbom_path.read_bytes()) - # Make a bunch of assertions about the SBOM data to ensure it's consistent. + # Insert pip's SBOM metadata from the wheel. + discover_pip_sbom_package(sbom_data) + + # Ensure all packages in this tool are represented also in the SBOM file. assert {package["name"] for package in sbom_data["packages"]} == set(PACKAGE_TO_FILES) + + # Make a bunch of assertions about the SBOM data to ensure it's consistent. for package in sbom_data["packages"]: # Properties and ID must be properly formed. @@ -138,17 +234,17 @@ def main() -> None: for include in sorted(files.include): # Find all the paths and then filter them through .gitignore. - paths = glob.glob(include, root_dir=root_dir, recursive=True) + paths = glob.glob(include, root_dir=CPYTHON_ROOT_DIR, recursive=True) paths = filter_gitignored_paths(paths) assert paths, include # Make sure that every value returns something! for path in paths: # Skip directories and excluded files - if not (root_dir / path).is_file() or path in exclude: + if not (CPYTHON_ROOT_DIR / path).is_file() or path in exclude: continue # SPDX requires SHA1 to be used for files, but we provide SHA256 too. - data = (root_dir / path).read_bytes() + data = (CPYTHON_ROOT_DIR / path).read_bytes() checksum_sha1 = hashlib.sha1(data).hexdigest() checksum_sha256 = hashlib.sha256(data).hexdigest() From 713e42822f5c74d8420f641968c3bef4b10a513a Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 20 Dec 2023 10:05:48 -0800 Subject: [PATCH 318/442] GH-111485: Fix DEFAULT_OUTPUT in opcode_metadata_generator.py (#113324) --- Tools/cases_generator/opcode_metadata_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 427bb54e5fed59..9b7df9a54c7b3b 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -366,7 +366,7 @@ def generate_opcode_metadata( ) -DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_uop_metadata.h" +DEFAULT_OUTPUT = ROOT / "Include/internal/pycore_opcode_metadata.h" arg_parser.add_argument( From a3e8afe0a3b5868440501edf579d1d4711c0fb18 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Wed, 20 Dec 2023 16:07:17 -0500 Subject: [PATCH 319/442] gh-113330: Fix mimalloc headers reference (#113331) The `MIMALLOC_HEADERS` variable is defined in the Makefile.pre.in, not the configure script, so we should use the `$(MIMALLOC_HEADERS)` syntax instead of the `@MIMALLOC_HEADERS@` syntax. --- Makefile.pre.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 95b2f246ed5bcb..4e2ec974b79eea 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1797,7 +1797,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/warnings.h \ $(srcdir)/Include/cpython/weakrefobject.h \ \ - @MIMALLOC_HEADERS@ \ + $(MIMALLOC_HEADERS) \ \ $(srcdir)/Include/internal/pycore_abstract.h \ $(srcdir)/Include/internal/pycore_asdl.h \ From 1ff02385944924db7e683a607da2882462594764 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Wed, 20 Dec 2023 23:09:01 +0000 Subject: [PATCH 320/442] GH-113214: Fix SSLProto exception handling in SSL-over-SSL scenarios (#113334) When wrapped, `_SSLProtocolTransport._force_close(exc)` is called just like in the unwrapped scenario `_SelectorTransport._force_close(exc)` or `_ProactorBasePipeTransport._force_close(exc)` would be called, except here the exception needs to be passed through the `SSLProtocol._abort()` method, which didn't accept an exception object. This commit ensures that this path works, in the same way that the uvloop implementation of SSLProto passes on the exception (on which the current implementation of SSLProto is based). --- Lib/asyncio/sslproto.py | 13 ++++++------- Lib/test/test_asyncio/test_sslproto.py | 15 ++++++++++++++- ...2023-12-20-21-18-51.gh-issue-113214.JcV9Mn.rst | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-20-21-18-51.gh-issue-113214.JcV9Mn.rst diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 3eb65a8a08b5a0..cbb6527d0b28e0 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -243,13 +243,12 @@ def abort(self): The protocol's connection_lost() method will (eventually) be called with None as its argument. """ - self._closed = True - if self._ssl_protocol is not None: - self._ssl_protocol._abort() + self._force_close(None) def _force_close(self, exc): self._closed = True - self._ssl_protocol._abort(exc) + if self._ssl_protocol is not None: + self._ssl_protocol._abort(exc) def _test__append_write_backlog(self, data): # for test only @@ -614,7 +613,7 @@ def _start_shutdown(self): if self._app_transport is not None: self._app_transport._closed = True if self._state == SSLProtocolState.DO_HANDSHAKE: - self._abort() + self._abort(None) else: self._set_state(SSLProtocolState.FLUSHING) self._shutdown_timeout_handle = self._loop.call_later( @@ -661,10 +660,10 @@ def _on_shutdown_complete(self, shutdown_exc): else: self._loop.call_soon(self._transport.close) - def _abort(self): + def _abort(self, exc): self._set_state(SSLProtocolState.UNWRAPPED) if self._transport is not None: - self._transport.abort() + self._transport._force_close(exc) # Outgoing flow diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py index 37d015339761c6..f5f0afeab51c9e 100644 --- a/Lib/test/test_asyncio/test_sslproto.py +++ b/Lib/test/test_asyncio/test_sslproto.py @@ -47,6 +47,7 @@ def connection_made(self, ssl_proto, *, do_handshake=None): sslobj = mock.Mock() # emulate reading decompressed data sslobj.read.side_effect = ssl.SSLWantReadError + sslobj.write.side_effect = ssl.SSLWantReadError if do_handshake is not None: sslobj.do_handshake = do_handshake ssl_proto._sslobj = sslobj @@ -120,7 +121,19 @@ def test_close_during_handshake(self): test_utils.run_briefly(self.loop) ssl_proto._app_transport.close() - self.assertTrue(transport.abort.called) + self.assertTrue(transport._force_close.called) + + def test_close_during_ssl_over_ssl(self): + # gh-113214: passing exceptions from the inner wrapped SSL protocol to the + # shim transport provided by the outer SSL protocol should not raise + # attribute errors + outer = self.ssl_protocol(proto=self.ssl_protocol()) + self.connection_made(outer) + # Closing the outer app transport should not raise an exception + messages = [] + self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) + outer._app_transport.close() + self.assertEqual(messages, []) def test_get_extra_info_on_closed_connection(self): waiter = self.loop.create_future() diff --git a/Misc/NEWS.d/next/Library/2023-12-20-21-18-51.gh-issue-113214.JcV9Mn.rst b/Misc/NEWS.d/next/Library/2023-12-20-21-18-51.gh-issue-113214.JcV9Mn.rst new file mode 100644 index 00000000000000..6db74cda166e92 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-20-21-18-51.gh-issue-113214.JcV9Mn.rst @@ -0,0 +1 @@ +Fix an ``AttributeError`` during asyncio SSL protocol aborts in SSL-over-SSL scenarios. From a2dd0e7038ad65a2464541f91604524d871d98a7 Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Wed, 20 Dec 2023 20:46:41 -0500 Subject: [PATCH 321/442] gh-111375: Use `NULL` rather than `None` in the exception stack to indicate that an exception was handled (#113302) --- .../2023-12-19-22-03-43.gh-issue-111375.M9vuA6.rst | 2 ++ Objects/frameobject.c | 2 +- Python/bytecodes.c | 2 +- Python/errors.c | 6 +++--- Python/executor_cases.c.h | 2 +- Python/generated_cases.c.h | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-19-22-03-43.gh-issue-111375.M9vuA6.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-19-22-03-43.gh-issue-111375.M9vuA6.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-19-22-03-43.gh-issue-111375.M9vuA6.rst new file mode 100644 index 00000000000000..fbb517173451f8 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-19-22-03-43.gh-issue-111375.M9vuA6.rst @@ -0,0 +1,2 @@ +Only use ``NULL`` in the exception stack to indicate an exception was +handled. Patch by Carey Metcalfe. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index be330a775872c2..cafe4ef6141d9a 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -811,7 +811,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore PyObject *exc = _PyFrame_StackPop(f->f_frame); assert(PyExceptionInstance_Check(exc) || exc == Py_None); PyThreadState *tstate = _PyThreadState_GET(); - Py_XSETREF(tstate->exc_info->exc_value, exc); + Py_XSETREF(tstate->exc_info->exc_value, exc == Py_None ? NULL : exc); } else { PyObject *v = _PyFrame_StackPop(f->f_frame); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 82d7a71d4989a4..29e1dab184ef4e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1100,7 +1100,7 @@ dummy_func( inst(POP_EXCEPT, (exc_value -- )) { _PyErr_StackItem *exc_info = tstate->exc_info; - Py_XSETREF(exc_info->exc_value, exc_value); + Py_XSETREF(exc_info->exc_value, exc_value == Py_None ? NULL : exc_value); } inst(RERAISE, (values[oparg], exc -- values[oparg])) { diff --git a/Python/errors.c b/Python/errors.c index ed5eec5c261970..e5f176a5dd208e 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -121,11 +121,11 @@ _PyErr_GetTopmostException(PyThreadState *tstate) _PyErr_StackItem *exc_info = tstate->exc_info; assert(exc_info); - while ((exc_info->exc_value == NULL || exc_info->exc_value == Py_None) && - exc_info->previous_item != NULL) + while (exc_info->exc_value == NULL && exc_info->previous_item != NULL) { exc_info = exc_info->previous_item; } + assert(!Py_IsNone(exc_info->exc_value)); return exc_info; } @@ -592,7 +592,7 @@ PyErr_GetHandledException(void) void _PyErr_SetHandledException(PyThreadState *tstate, PyObject *exc) { - Py_XSETREF(tstate->exc_info->exc_value, Py_XNewRef(exc)); + Py_XSETREF(tstate->exc_info->exc_value, Py_XNewRef(exc == Py_None ? NULL : exc)); } void diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7cc29c8e644d8d..69adb20ed46595 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -806,7 +806,7 @@ PyObject *exc_value; exc_value = stack_pointer[-1]; _PyErr_StackItem *exc_info = tstate->exc_info; - Py_XSETREF(exc_info->exc_value, exc_value); + Py_XSETREF(exc_info->exc_value, exc_value == Py_None ? NULL : exc_value); stack_pointer += -1; break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index e935f33fa2131a..f94dc6164dde25 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4567,7 +4567,7 @@ PyObject *exc_value; exc_value = stack_pointer[-1]; _PyErr_StackItem *exc_info = tstate->exc_info; - Py_XSETREF(exc_info->exc_value, exc_value); + Py_XSETREF(exc_info->exc_value, exc_value == Py_None ? NULL : exc_value); stack_pointer += -1; DISPATCH(); } From 103c4ea27464cef8d1793dab347f5ff3629dc243 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 20 Dec 2023 23:32:13 -0800 Subject: [PATCH 322/442] gh-112305: Fix check-clean-src to detect frozen_modules .h files. (#113344) A typo left this check broken so many of us who do out-of-tree builds were seeing strange failures due to bad `Python/frozen_modules/*.h` files being picked up from the source tree and used at build time from different Python versions leading to errors like: `Fatal Python error: _PyImport_InitCore: failed to initialize importlib` Or similar once our build got to an "invoke the interpreter" bootstrapping step due to incorrect bytecode being embedded. --- Makefile.pre.in | 6 ++++-- .../Build/2023-12-21-05-35-06.gh-issue-112305.VfqQPx.rst | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2023-12-21-05-35-06.gh-issue-112305.VfqQPx.rst diff --git a/Makefile.pre.in b/Makefile.pre.in index 4e2ec974b79eea..954cde8cba5bcf 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -657,11 +657,13 @@ check-clean-src: @if test -n "$(VPATH)" -a \( \ -f "$(srcdir)/$(BUILDPYTHON)" \ -o -f "$(srcdir)/Programs/python.o" \ - -o -f "$(srcdir)\Python/frozen_modules/importlib._bootstrap.h" \ + -o -f "$(srcdir)/Python/frozen_modules/importlib._bootstrap.h" \ \); then \ echo "Error: The source directory ($(srcdir)) is not clean" ; \ echo "Building Python out of the source tree (in $(abs_builddir)) requires a clean source tree ($(abs_srcdir))" ; \ - echo "Try to run: make -C \"$(srcdir)\" clean" ; \ + echo "Build artifacts such as .o files, executables, and Python/frozen_modules/*.h must not exist within $(srcdir)." ; \ + echo "Try to run:" ; \ + echo " (cd \"$(srcdir)\" && make clean || git clean -fdx -e Doc/venv)" ; \ exit 1; \ fi diff --git a/Misc/NEWS.d/next/Build/2023-12-21-05-35-06.gh-issue-112305.VfqQPx.rst b/Misc/NEWS.d/next/Build/2023-12-21-05-35-06.gh-issue-112305.VfqQPx.rst new file mode 100644 index 00000000000000..2df3207f4e6f6c --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-12-21-05-35-06.gh-issue-112305.VfqQPx.rst @@ -0,0 +1,3 @@ +Fixed the ``check-clean-src`` step performed on out of tree builds to detect +errant ``$(srcdir)/Python/frozen_modules/*.h`` files and recommend +appropriate source tree cleanup steps to get a working build again. From 24f8ff28a3847b3b9f92f3aaa5346e92399a246a Mon Sep 17 00:00:00 2001 From: Jan Brasna <1784648+janbrasna@users.noreply.github.com> Date: Thu, 21 Dec 2023 08:50:25 +0100 Subject: [PATCH 323/442] Docs: OpenSSL wording ambiguity (#113296) --- Doc/library/ssl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 0db233e2dde33c..e8709b516ae07a 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -25,7 +25,7 @@ probably additional platforms, as long as OpenSSL is installed on that platform. Some behavior may be platform dependent, since calls are made to the operating system socket APIs. The installed version of OpenSSL may also - cause variations in behavior. For example, TLSv1.3 with OpenSSL version + cause variations in behavior. For example, TLSv1.3 comes with OpenSSL version 1.1.1. .. warning:: From 4b90b5d857ba88c7d85483e188a73323df9ba05c Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 21 Dec 2023 10:28:43 +0100 Subject: [PATCH 324/442] Docs: update URL in Argument Clinic CLI help text (#113351) The Argument Clinic docs was moved to the devguide earlier in 2023. --- Tools/clinic/clinic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 345459cf2fd623..45b01a260cb4cb 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -6564,7 +6564,7 @@ def create_cli() -> argparse.ArgumentParser: with writing argument parsing code for builtins and providing introspection signatures ("docstrings") for CPython builtins. -For more information see https://docs.python.org/3/howto/clinic.html""") +For more information see https://devguide.python.org/development-tools/clinic/""") cmdline.add_argument("-f", "--force", action='store_true', help="force output regeneration") cmdline.add_argument("-o", "--output", type=str, From fae096cd4b7025f91473546ca6a243c86374dd6a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 21 Dec 2023 13:10:41 +0100 Subject: [PATCH 325/442] gh-113336: Remove the 'version' directive from Argument Clinic (#113341) The 'version' directive was introduced with gh-63929 in Nov 2013. It has not been in use in the CPython code base, and the 'version' variable has never been bumped. --- Lib/test/test_clinic.py | 65 ----------------------------------------- Tools/clinic/clinic.py | 52 --------------------------------- 2 files changed, 117 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 714fa6d7cb328f..f3f73a174aeb13 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -8,7 +8,6 @@ from test.support.os_helper import TESTFN, unlink from textwrap import dedent from unittest import TestCase -import contextlib import inspect import os.path import re @@ -264,70 +263,6 @@ def converter_init(self): ) self.expect_failure(raw, err) - @staticmethod - @contextlib.contextmanager - def _clinic_version(new_version): - """Helper for test_version_*() tests""" - _saved = clinic.version - clinic.version = new_version - try: - yield - finally: - clinic.version = _saved - - def test_version_directive(self): - dataset = ( - # (clinic version, required version) - ('3', '2'), # required version < clinic version - ('3.1', '3.0'), # required version < clinic version - ('1.2b0', '1.2a7'), # required version < clinic version - ('5', '5'), # required version == clinic version - ('6.1', '6.1'), # required version == clinic version - ('1.2b3', '1.2b3'), # required version == clinic version - ) - for clinic_version, required_version in dataset: - with self.subTest(clinic_version=clinic_version, - required_version=required_version): - with self._clinic_version(clinic_version): - block = dedent(f""" - /*[clinic input] - version {required_version} - [clinic start generated code]*/ - """) - self.clinic.parse(block) - - def test_version_directive_insufficient_version(self): - with self._clinic_version('4'): - err = ( - "Insufficient Clinic version!\n" - " Version: 4\n" - " Required: 5" - ) - block = """ - /*[clinic input] - version 5 - [clinic start generated code]*/ - """ - self.expect_failure(block, err) - - def test_version_directive_illegal_char(self): - err = "Illegal character 'v' in version string 'v5'" - block = """ - /*[clinic input] - version v5 - [clinic start generated code]*/ - """ - self.expect_failure(block, err) - - def test_version_directive_unsupported_string(self): - err = "Unsupported version string: '.-'" - block = """ - /*[clinic input] - version .- - [clinic start generated code]*/ - """ - self.expect_failure(block, err) - def test_clone_mismatch(self): err = "'kind' of function and cloned function don't match!" block = """ diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 45b01a260cb4cb..bf3199257f65c9 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -61,8 +61,6 @@ # and keyword-only # -version = '1' - NO_VARARG = "PY_SSIZE_T_MAX" CLINIC_PREFIX = "__clinic_" CLINIC_PREFIXED_ARGS = { @@ -375,49 +373,6 @@ def pprint_words(items: list[str]) -> str: return ", ".join(items[:-1]) + " and " + items[-1] -def version_splitter(s: str) -> tuple[int, ...]: - """Splits a version string into a tuple of integers. - - The following ASCII characters are allowed, and employ - the following conversions: - a -> -3 - b -> -2 - c -> -1 - (This permits Python-style version strings such as "1.4b3".) - """ - version: list[int] = [] - accumulator: list[str] = [] - def flush() -> None: - if not accumulator: - fail(f'Unsupported version string: {s!r}') - version.append(int(''.join(accumulator))) - accumulator.clear() - - for c in s: - if c.isdigit(): - accumulator.append(c) - elif c == '.': - flush() - elif c in 'abc': - flush() - version.append('abc'.index(c) - 3) - else: - fail(f'Illegal character {c!r} in version string {s!r}') - flush() - return tuple(version) - -def version_comparator(version1: str, version2: str) -> Literal[-1, 0, 1]: - iterator = itertools.zip_longest( - version_splitter(version1), version_splitter(version2), fillvalue=0 - ) - for a, b in iterator: - if a < b: - return -1 - if a > b: - return 1 - return 0 - - class CRenderData: def __init__(self) -> None: @@ -5262,13 +5217,6 @@ def reset(self) -> None: self.critical_section = False self.target_critical_section = [] - def directive_version(self, required: str) -> None: - global version - if version_comparator(version, required) < 0: - fail("Insufficient Clinic version!\n" - f" Version: {version}\n" - f" Required: {required}") - def directive_module(self, name: str) -> None: fields = name.split('.')[:-1] module, cls = self.clinic._module_and_class(fields) From 723f4d66982e4d2c54f8e874d6084ab7b2ff5833 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 21 Dec 2023 12:46:28 +0000 Subject: [PATCH 326/442] GH-111485: Delete the old generator code. (GH-113321) --- .gitattributes | 1 - Include/internal/pycore_opcode_metadata.h | 46 +- Lib/test/test_generated_cases.py | 77 +- Makefile.pre.in | 8 +- Python/abstract_interp_cases.c.h | 967 ---------------------- Python/executor_cases.c.h | 44 +- Python/generated_cases.c.h | 60 +- Tools/build/deepfreeze.py | 2 +- Tools/c-analyzer/cpython/_parser.py | 1 - Tools/cases_generator/analysis.py | 487 ----------- Tools/cases_generator/analyzer.py | 76 +- Tools/cases_generator/flags.py | 191 ----- Tools/cases_generator/formatting.py | 206 ----- Tools/cases_generator/generate_cases.py | 848 ------------------- Tools/cases_generator/instructions.py | 355 -------- Tools/cases_generator/parser.py | 12 +- Tools/cases_generator/stack.py | 18 +- Tools/cases_generator/stacking.py | 534 ------------ 18 files changed, 210 insertions(+), 3723 deletions(-) delete mode 100644 Python/abstract_interp_cases.c.h delete mode 100644 Tools/cases_generator/analysis.py delete mode 100644 Tools/cases_generator/flags.py delete mode 100644 Tools/cases_generator/formatting.py delete mode 100644 Tools/cases_generator/generate_cases.py delete mode 100644 Tools/cases_generator/instructions.py delete mode 100644 Tools/cases_generator/stacking.py diff --git a/.gitattributes b/.gitattributes index 22afffb05abb20..2a48df079e1aeb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -94,7 +94,6 @@ Programs/test_frozenmain.h generated Python/Python-ast.c generated Python/executor_cases.c.h generated Python/generated_cases.c.h generated -Python/abstract_interp_cases.c.h generated Python/opcode_targets.h generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index a9e0cd1238929f..7d39e4bc03099c 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -82,7 +82,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) { case BUILD_SET: return oparg; case BUILD_SLICE: - return 2 + (((oparg == 3) ? 1 : 0)); + return 2 + ((oparg == 3) ? 1 : 0); case BUILD_STRING: return oparg; case BUILD_TUPLE: @@ -104,7 +104,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) { case CALL_BUILTIN_O: return 2 + oparg; case CALL_FUNCTION_EX: - return 3 + ((oparg & 1)); + return 3 + (oparg & 1); case CALL_INTRINSIC_1: return 1; case CALL_INTRINSIC_2: @@ -519,7 +519,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case CALL_ALLOC_AND_ENTER_INIT: return 1; case CALL_BOUND_METHOD_EXACT_ARGS: - return (((0) ? 1 : 0)); + return ((0) ? 1 : 0); case CALL_BUILTIN_CLASS: return 1; case CALL_BUILTIN_FAST: @@ -551,7 +551,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case CALL_METHOD_DESCRIPTOR_O: return 1; case CALL_PY_EXACT_ARGS: - return (((0) ? 1 : 0)); + return ((0) ? 1 : 0); case CALL_PY_WITH_DEFAULTS: return 1; case CALL_STR_1: @@ -659,7 +659,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case INSTRUMENTED_JUMP_FORWARD: return 0; case INSTRUMENTED_LOAD_SUPER_ATTR: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case INSTRUMENTED_POP_JUMP_IF_FALSE: return 0; case INSTRUMENTED_POP_JUMP_IF_NONE: @@ -693,31 +693,31 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case LOAD_ASSERTION_ERROR: return 1; case LOAD_ATTR: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_ATTR_CLASS: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: - return 1 + (((0) ? 1 : 0)); + return 1 + ((0) ? 1 : 0); case LOAD_ATTR_INSTANCE_VALUE: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_ATTR_METHOD_LAZY_DICT: - return 1 + (((1) ? 1 : 0)); + return 1 + ((1) ? 1 : 0); case LOAD_ATTR_METHOD_NO_DICT: - return 1 + (((1) ? 1 : 0)); + return 1 + ((1) ? 1 : 0); case LOAD_ATTR_METHOD_WITH_VALUES: - return 1 + (((1) ? 1 : 0)); + return 1 + ((1) ? 1 : 0); case LOAD_ATTR_MODULE: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_ATTR_NONDESCRIPTOR_NO_DICT: - return 1 + (((0) ? 1 : 0)); + return 1 + ((0) ? 1 : 0); case LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: - return 1 + (((0) ? 1 : 0)); + return 1 + ((0) ? 1 : 0); case LOAD_ATTR_PROPERTY: - return 1 + (((0) ? 1 : 0)); + return 1 + ((0) ? 1 : 0); case LOAD_ATTR_SLOT: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_ATTR_WITH_HINT: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_BUILD_CLASS: return 1; case LOAD_CONST: @@ -737,19 +737,19 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case LOAD_FROM_DICT_OR_GLOBALS: return 1; case LOAD_GLOBAL: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_GLOBAL_BUILTIN: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_GLOBAL_MODULE: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_LOCALS: return 1; case LOAD_NAME: return 1; case LOAD_SUPER_ATTR: - return 1 + ((oparg & 1)); + return 1 + (oparg & 1); case LOAD_SUPER_ATTR_ATTR: - return 1 + (((0) ? 1 : 0)); + return 1 + ((0) ? 1 : 0); case LOAD_SUPER_ATTR_METHOD: return 2; case MAKE_CELL: diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 3541a4e70b9a56..3b2f579be684b7 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -15,9 +15,8 @@ def skip_if_different_mount_drives(): root_drive = os.path.splitroot(ROOT)[0] cwd_drive = os.path.splitroot(os.getcwd())[0] if root_drive != cwd_drive: - # generate_cases.py uses relpath() which raises ValueError if ROOT - # and the current working different have different mount drives - # (on Windows). + # May raise ValueError if ROOT and the current working + # different have different mount drives (on Windows). raise unittest.SkipTest( f"the current working directory and the Python source code " f"directory have different mount drives " @@ -28,10 +27,9 @@ def skip_if_different_mount_drives(): test_tools.skip_if_missing('cases_generator') with test_tools.imports_under_tool('cases_generator'): - import generate_cases - import analysis - import formatting - from parsing import StackEffect + from analyzer import StackItem + import parser + from stack import Stack import tier1_generator @@ -43,37 +41,24 @@ def handle_stderr(): class TestEffects(unittest.TestCase): def test_effect_sizes(self): - input_effects = [ - x := StackEffect("x", "", "", ""), - y := StackEffect("y", "", "", "oparg"), - z := StackEffect("z", "", "", "oparg*2"), + stack = Stack() + inputs = [ + x:= StackItem("x", None, "", "1"), + y:= StackItem("y", None, "", "oparg"), + z:= StackItem("z", None, "", "oparg*2"), ] - output_effects = [ - StackEffect("a", "", "", ""), - StackEffect("b", "", "", "oparg*4"), - StackEffect("c", "", "", ""), + outputs = [ + StackItem("x", None, "", "1"), + StackItem("b", None, "", "oparg*4"), + StackItem("c", None, "", "1"), ] - other_effects = [ - StackEffect("p", "", "", "oparg<<1"), - StackEffect("q", "", "", ""), - StackEffect("r", "", "", ""), - ] - self.assertEqual(formatting.effect_size(x), (1, "")) - self.assertEqual(formatting.effect_size(y), (0, "oparg")) - self.assertEqual(formatting.effect_size(z), (0, "oparg*2")) - - self.assertEqual( - formatting.list_effect_size(input_effects), - (1, "oparg + oparg*2"), - ) - self.assertEqual( - formatting.list_effect_size(output_effects), - (2, "oparg*4"), - ) - self.assertEqual( - formatting.list_effect_size(other_effects), - (2, "(oparg<<1)"), - ) + stack.pop(z) + stack.pop(y) + stack.pop(x) + for out in outputs: + stack.push(out) + self.assertEqual(stack.base_offset.to_c(), "-1 - oparg*2 - oparg") + self.assertEqual(stack.top_offset.to_c(), "1 - oparg*2 - oparg + oparg*4") class TestGeneratedCases(unittest.TestCase): @@ -104,9 +89,9 @@ def tearDown(self) -> None: def run_cases_test(self, input: str, expected: str): with open(self.temp_input_filename, "w+") as temp_input: - temp_input.write(analysis.BEGIN_MARKER) + temp_input.write(parser.BEGIN_MARKER) temp_input.write(input) - temp_input.write(analysis.END_MARKER) + temp_input.write(parser.END_MARKER) temp_input.flush() with handle_stderr(): @@ -636,13 +621,13 @@ def test_cond_effect(self): PyObject *output = NULL; PyObject *zz; cc = stack_pointer[-1]; - if ((oparg & 1) == 1) { input = stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))]; } - aa = stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))]; + if ((oparg & 1) == 1) { input = stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0)]; } + aa = stack_pointer[-2 - (((oparg & 1) == 1) ? 1 : 0)]; output = spam(oparg, input); - stack_pointer[-2 - ((((oparg & 1) == 1) ? 1 : 0))] = xx; - if (oparg & 2) stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0))] = output; - stack_pointer[-1 - ((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0))] = zz; - stack_pointer += -((((oparg & 1) == 1) ? 1 : 0)) + (((oparg & 2) ? 1 : 0)); + stack_pointer[-2 - (((oparg & 1) == 1) ? 1 : 0)] = xx; + if (oparg & 2) stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0)] = output; + stack_pointer[-1 - (((oparg & 1) == 1) ? 1 : 0) + ((oparg & 2) ? 1 : 0)] = zz; + stack_pointer += -(((oparg & 1) == 1) ? 1 : 0) + ((oparg & 2) ? 1 : 0); DISPATCH(); } """ @@ -682,8 +667,8 @@ def test_macro_cond_effect(self): } stack_pointer[-3] = deep; if (oparg) stack_pointer[-2] = extra; - stack_pointer[-2 + (((oparg) ? 1 : 0))] = res; - stack_pointer += -1 + (((oparg) ? 1 : 0)); + stack_pointer[-2 + ((oparg) ? 1 : 0)] = res; + stack_pointer += -1 + ((oparg) ? 1 : 0); DISPATCH(); } """ diff --git a/Makefile.pre.in b/Makefile.pre.in index 954cde8cba5bcf..cf3763ef546468 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1585,12 +1585,6 @@ Objects/mimalloc/page.o: $(srcdir)/Objects/mimalloc/page-queue.c regen-cases: # Regenerate various files from Python/bytecodes.c # Pass CASESFLAG=-l to insert #line directives in the output - PYTHONPATH=$(srcdir)/Tools/cases_generator \ - $(PYTHON_FOR_REGEN) \ - $(srcdir)/Tools/cases_generator/generate_cases.py \ - $(CASESFLAG) \ - -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ - $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_id_generator.py \ -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/target_generator.py \ @@ -1614,7 +1608,6 @@ regen-cases: $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_metadata.h $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new - $(UPDATE_FILE) $(srcdir)/Python/abstract_interp_cases.c.h $(srcdir)/Python/abstract_interp_cases.c.h.new $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new Python/compile.o: $(srcdir)/Include/internal/pycore_opcode_metadata.h @@ -1896,6 +1889,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_unicodeobject.h \ $(srcdir)/Include/internal/pycore_unicodeobject_generated.h \ $(srcdir)/Include/internal/pycore_uops.h \ + $(srcdir)/Include/internal/pycore_uop_metadata.h \ $(srcdir)/Include/internal/pycore_warnings.h \ $(srcdir)/Include/internal/pycore_weakref.h \ $(DTRACE_HEADERS) \ diff --git a/Python/abstract_interp_cases.c.h b/Python/abstract_interp_cases.c.h deleted file mode 100644 index 96ac0aabd1b59f..00000000000000 --- a/Python/abstract_interp_cases.c.h +++ /dev/null @@ -1,967 +0,0 @@ -// This file is generated by Tools/cases_generator/generate_cases.py -// from: -// Python/bytecodes.c -// Do not edit! - - case NOP: { - break; - } - - case RESUME_CHECK: { - break; - } - - case POP_TOP: { - STACK_SHRINK(1); - break; - } - - case PUSH_NULL: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case END_SEND: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case UNARY_NEGATIVE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case UNARY_NOT: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _TO_BOOL: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case TO_BOOL_BOOL: { - break; - } - - case TO_BOOL_INT: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case TO_BOOL_LIST: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case TO_BOOL_NONE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case TO_BOOL_STR: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case TO_BOOL_ALWAYS_TRUE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case UNARY_INVERT: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _GUARD_BOTH_INT: { - break; - } - - case _GUARD_BOTH_FLOAT: { - break; - } - - case _BINARY_OP_MULTIPLY_FLOAT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _BINARY_OP_ADD_FLOAT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _BINARY_OP_SUBTRACT_FLOAT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _GUARD_BOTH_UNICODE: { - break; - } - - case _BINARY_OP_ADD_UNICODE: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _BINARY_SUBSCR: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BINARY_SLICE: { - STACK_SHRINK(2); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case STORE_SLICE: { - STACK_SHRINK(4); - break; - } - - case BINARY_SUBSCR_LIST_INT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BINARY_SUBSCR_STR_INT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BINARY_SUBSCR_TUPLE_INT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BINARY_SUBSCR_DICT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LIST_APPEND: { - STACK_SHRINK(1); - break; - } - - case SET_ADD: { - STACK_SHRINK(1); - break; - } - - case _STORE_SUBSCR: { - STACK_SHRINK(3); - break; - } - - case STORE_SUBSCR_LIST_INT: { - STACK_SHRINK(3); - break; - } - - case STORE_SUBSCR_DICT: { - STACK_SHRINK(3); - break; - } - - case DELETE_SUBSCR: { - STACK_SHRINK(2); - break; - } - - case CALL_INTRINSIC_1: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_INTRINSIC_2: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _POP_FRAME: { - STACK_SHRINK(1); - break; - } - - case GET_AITER: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case GET_ANEXT: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case GET_AWAITABLE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case POP_EXCEPT: { - STACK_SHRINK(1); - break; - } - - case LOAD_ASSERTION_ERROR: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LOAD_BUILD_CLASS: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case STORE_NAME: { - STACK_SHRINK(1); - break; - } - - case DELETE_NAME: { - break; - } - - case _UNPACK_SEQUENCE: { - STACK_SHRINK(1); - STACK_GROW(oparg); - break; - } - - case UNPACK_SEQUENCE_TWO_TUPLE: { - STACK_SHRINK(1); - STACK_GROW(oparg); - break; - } - - case UNPACK_SEQUENCE_TUPLE: { - STACK_SHRINK(1); - STACK_GROW(oparg); - break; - } - - case UNPACK_SEQUENCE_LIST: { - STACK_SHRINK(1); - STACK_GROW(oparg); - break; - } - - case UNPACK_EX: { - STACK_GROW((oparg & 0xFF) + (oparg >> 8)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg >> 8))), true); - break; - } - - case _STORE_ATTR: { - STACK_SHRINK(2); - break; - } - - case DELETE_ATTR: { - STACK_SHRINK(1); - break; - } - - case STORE_GLOBAL: { - STACK_SHRINK(1); - break; - } - - case DELETE_GLOBAL: { - break; - } - - case LOAD_LOCALS: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LOAD_FROM_DICT_OR_GLOBALS: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LOAD_NAME: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _LOAD_GLOBAL: { - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _GUARD_GLOBALS_VERSION: { - break; - } - - case _GUARD_BUILTINS_VERSION: { - break; - } - - case _LOAD_GLOBAL_MODULE: { - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _LOAD_GLOBAL_BUILTINS: { - STACK_GROW(1); - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case DELETE_FAST: { - break; - } - - case MAKE_CELL: { - break; - } - - case DELETE_DEREF: { - break; - } - - case LOAD_FROM_DICT_OR_DEREF: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LOAD_DEREF: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case STORE_DEREF: { - STACK_SHRINK(1); - break; - } - - case COPY_FREE_VARS: { - break; - } - - case BUILD_STRING: { - STACK_SHRINK(oparg); - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BUILD_TUPLE: { - STACK_SHRINK(oparg); - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BUILD_LIST: { - STACK_SHRINK(oparg); - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case LIST_EXTEND: { - STACK_SHRINK(1); - break; - } - - case SET_UPDATE: { - STACK_SHRINK(1); - break; - } - - case BUILD_SET: { - STACK_SHRINK(oparg); - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BUILD_MAP: { - STACK_SHRINK(oparg*2); - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case SETUP_ANNOTATIONS: { - break; - } - - case BUILD_CONST_KEY_MAP: { - STACK_SHRINK(oparg); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case DICT_UPDATE: { - STACK_SHRINK(1); - break; - } - - case DICT_MERGE: { - STACK_SHRINK(1); - break; - } - - case MAP_ADD: { - STACK_SHRINK(2); - break; - } - - case LOAD_SUPER_ATTR_ATTR: { - STACK_SHRINK(2); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(0)), true); - break; - } - - case LOAD_SUPER_ATTR_METHOD: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _LOAD_ATTR: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _GUARD_TYPE_VERSION: { - break; - } - - case _CHECK_MANAGED_OBJECT_HAS_VALUES: { - break; - } - - case _LOAD_ATTR_INSTANCE_VALUE: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _CHECK_ATTR_MODULE: { - break; - } - - case _LOAD_ATTR_MODULE: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _CHECK_ATTR_WITH_HINT: { - break; - } - - case _LOAD_ATTR_WITH_HINT: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _LOAD_ATTR_SLOT: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _CHECK_ATTR_CLASS: { - break; - } - - case _LOAD_ATTR_CLASS: { - STACK_GROW(((oparg & 1) ? 1 : 0)); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - (oparg & 1 ? 1 : 0))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-(oparg & 1 ? 1 : 0))), true); - break; - } - - case _GUARD_DORV_VALUES: { - break; - } - - case _STORE_ATTR_INSTANCE_VALUE: { - STACK_SHRINK(2); - break; - } - - case _STORE_ATTR_SLOT: { - STACK_SHRINK(2); - break; - } - - case _COMPARE_OP: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case COMPARE_OP_FLOAT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case COMPARE_OP_INT: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case COMPARE_OP_STR: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case IS_OP: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CONTAINS_OP: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CHECK_EG_MATCH: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CHECK_EXC_MATCH: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _IS_NONE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case GET_LEN: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case MATCH_CLASS: { - STACK_SHRINK(2); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case MATCH_MAPPING: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case MATCH_SEQUENCE: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case MATCH_KEYS: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case GET_ITER: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case GET_YIELD_FROM_ITER: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _FOR_ITER_TIER_TWO: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _ITER_CHECK_LIST: { - break; - } - - case _GUARD_NOT_EXHAUSTED_LIST: { - break; - } - - case _ITER_NEXT_LIST: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _ITER_CHECK_TUPLE: { - break; - } - - case _GUARD_NOT_EXHAUSTED_TUPLE: { - break; - } - - case _ITER_NEXT_TUPLE: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _ITER_CHECK_RANGE: { - break; - } - - case _GUARD_NOT_EXHAUSTED_RANGE: { - break; - } - - case _ITER_NEXT_RANGE: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BEFORE_ASYNC_WITH: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BEFORE_WITH: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case WITH_EXCEPT_START: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case PUSH_EXC_INFO: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT: { - break; - } - - case _GUARD_KEYS_VERSION: { - break; - } - - case _LOAD_ATTR_METHOD_WITH_VALUES: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _LOAD_ATTR_METHOD_NO_DICT: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(0)), true); - break; - } - - case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(0)), true); - break; - } - - case _CHECK_ATTR_METHOD_LAZY_DICT: { - break; - } - - case _LOAD_ATTR_METHOD_LAZY_DICT: { - STACK_GROW(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { - break; - } - - case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2 - oparg)), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - oparg)), true); - break; - } - - case _CHECK_PEP_523: { - break; - } - - case _CHECK_FUNCTION_EXACT_ARGS: { - break; - } - - case _CHECK_STACK_SPACE: { - break; - } - - case _INIT_CALL_PY_EXACT_ARGS: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _PUSH_FRAME: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(0)), true); - break; - } - - case CALL_TYPE_1: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_STR_1: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_TUPLE_1: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case EXIT_INIT_CHECK: { - STACK_SHRINK(1); - break; - } - - case CALL_BUILTIN_CLASS: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_BUILTIN_O: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_BUILTIN_FAST: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_BUILTIN_FAST_WITH_KEYWORDS: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_LEN: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_ISINSTANCE: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_METHOD_DESCRIPTOR_O: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_METHOD_DESCRIPTOR_NOARGS: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CALL_METHOD_DESCRIPTOR_FAST: { - STACK_SHRINK(oparg); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case MAKE_FUNCTION: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case SET_FUNCTION_ATTRIBUTE: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case BUILD_SLICE: { - STACK_SHRINK(((oparg == 3) ? 1 : 0)); - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case CONVERT_VALUE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case FORMAT_SIMPLE: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case FORMAT_WITH_SPEC: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _BINARY_OP: { - STACK_SHRINK(1); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case SWAP: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-2 - (oparg-2))), true); - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1)), true); - break; - } - - case _GUARD_IS_TRUE_POP: { - STACK_SHRINK(1); - break; - } - - case _GUARD_IS_FALSE_POP: { - STACK_SHRINK(1); - break; - } - - case _GUARD_IS_NONE_POP: { - STACK_SHRINK(1); - break; - } - - case _GUARD_IS_NOT_NONE_POP: { - STACK_SHRINK(1); - break; - } - - case _JUMP_TO_TOP: { - break; - } - - case _SET_IP: { - break; - } - - case _SAVE_RETURN_OFFSET: { - break; - } - - case _EXIT_TRACE: { - break; - } - - case _INSERT: { - PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)PARTITIONNODE_NULLROOT, PEEK(-(-1 - oparg)), true); - break; - } - - case _CHECK_VALIDITY: { - break; - } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 69adb20ed46595..14fb3a05a9f674 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1127,7 +1127,7 @@ null = NULL; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); break; } @@ -1163,7 +1163,7 @@ null = NULL; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); break; } @@ -1181,7 +1181,7 @@ null = NULL; stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); break; } @@ -1534,7 +1534,7 @@ Py_DECREF(self); if (attr == NULL) goto pop_3_error_tier_two; stack_pointer[-3] = attr; - stack_pointer += -2 + (((0) ? 1 : 0)); + stack_pointer += -2 + ((0) ? 1 : 0); break; } @@ -1613,7 +1613,7 @@ } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -1653,7 +1653,7 @@ Py_DECREF(owner); stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -1687,7 +1687,7 @@ Py_DECREF(owner); stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -1731,7 +1731,7 @@ Py_DECREF(owner); stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -1751,7 +1751,7 @@ Py_DECREF(owner); stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -1779,7 +1779,7 @@ Py_DECREF(owner); stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); break; } @@ -2468,7 +2468,7 @@ self = owner; stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); break; } @@ -2488,7 +2488,7 @@ self = owner; stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); break; } @@ -2504,7 +2504,7 @@ Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); break; } @@ -2521,7 +2521,7 @@ Py_DECREF(owner); attr = Py_NewRef(descr); stack_pointer[-1] = attr; - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); break; } @@ -2551,7 +2551,7 @@ self = owner; stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); break; } @@ -2663,7 +2663,7 @@ goto exit_unwind; } #endif - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); break; } @@ -3199,16 +3199,16 @@ PyObject *start; PyObject *slice; oparg = CURRENT_OPARG(); - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } - stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; - start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; + if (oparg == 3) { step = stack_pointer[-((oparg == 3) ? 1 : 0)]; } + stop = stack_pointer[-1 - ((oparg == 3) ? 1 : 0)]; + start = stack_pointer[-2 - ((oparg == 3) ? 1 : 0)]; slice = PySlice_New(start, stop, step); Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - if (slice == NULL) { stack_pointer += -2 - (((oparg == 3) ? 1 : 0)); goto error_tier_two; } - stack_pointer[-2 - (((oparg == 3) ? 1 : 0))] = slice; - stack_pointer += -1 - (((oparg == 3) ? 1 : 0)); + if (slice == NULL) { stack_pointer += -2 - ((oparg == 3) ? 1 : 0); goto error_tier_two; } + stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; + stack_pointer += -1 - ((oparg == 3) ? 1 : 0); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index f94dc6164dde25..ce31967b7912d7 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -691,16 +691,16 @@ PyObject *stop; PyObject *start; PyObject *slice; - if (oparg == 3) { step = stack_pointer[-(((oparg == 3) ? 1 : 0))]; } - stop = stack_pointer[-1 - (((oparg == 3) ? 1 : 0))]; - start = stack_pointer[-2 - (((oparg == 3) ? 1 : 0))]; + if (oparg == 3) { step = stack_pointer[-((oparg == 3) ? 1 : 0)]; } + stop = stack_pointer[-1 - ((oparg == 3) ? 1 : 0)]; + start = stack_pointer[-2 - ((oparg == 3) ? 1 : 0)]; slice = PySlice_New(start, stop, step); Py_DECREF(start); Py_DECREF(stop); Py_XDECREF(step); - if (slice == NULL) { stack_pointer += -2 - (((oparg == 3) ? 1 : 0)); goto error; } - stack_pointer[-2 - (((oparg == 3) ? 1 : 0))] = slice; - stack_pointer += -1 - (((oparg == 3) ? 1 : 0)); + if (slice == NULL) { stack_pointer += -2 - ((oparg == 3) ? 1 : 0); goto error; } + stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; + stack_pointer += -1 - ((oparg == 3) ? 1 : 0); DISPATCH(); } @@ -1004,7 +1004,7 @@ } #endif } - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -1185,9 +1185,9 @@ PyObject *callargs; PyObject *func; PyObject *result; - if (oparg & 1) { kwargs = stack_pointer[-((oparg & 1))]; } - callargs = stack_pointer[-1 - ((oparg & 1))]; - func = stack_pointer[-3 - ((oparg & 1))]; + if (oparg & 1) { kwargs = stack_pointer[-(oparg & 1)]; } + callargs = stack_pointer[-1 - (oparg & 1)]; + func = stack_pointer[-3 - (oparg & 1)]; // DICT_MERGE is called before this opcode if there are kwargs. // It converts all dict subtypes in kwargs into regular dicts. assert(kwargs == NULL || PyDict_CheckExact(kwargs)); @@ -1253,9 +1253,9 @@ Py_DECREF(callargs); Py_XDECREF(kwargs); assert(PEEK(2 + (oparg & 1)) == NULL); - if (result == NULL) { stack_pointer += -3 - ((oparg & 1)); goto error; } - stack_pointer[-3 - ((oparg & 1))] = result; - stack_pointer += -2 - ((oparg & 1)); + if (result == NULL) { stack_pointer += -3 - (oparg & 1); goto error; } + stack_pointer[-3 - (oparg & 1)] = result; + stack_pointer += -2 - (oparg & 1); CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1754,7 +1754,7 @@ } #endif } - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -3445,7 +3445,7 @@ } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -3478,7 +3478,7 @@ } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -3555,7 +3555,7 @@ /* Skip 5 cache entries */ stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -3597,7 +3597,7 @@ } stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); DISPATCH(); } @@ -3632,7 +3632,7 @@ } stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); DISPATCH(); } @@ -3679,7 +3679,7 @@ } stack_pointer[-1] = attr; if (1) stack_pointer[0] = self; - stack_pointer += (((1) ? 1 : 0)); + stack_pointer += ((1) ? 1 : 0); DISPATCH(); } @@ -3718,7 +3718,7 @@ /* Skip 5 cache entries */ stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -3750,7 +3750,7 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -3793,7 +3793,7 @@ attr = Py_NewRef(descr); } stack_pointer[-1] = attr; - stack_pointer += (((0) ? 1 : 0)); + stack_pointer += ((0) ? 1 : 0); DISPATCH(); } @@ -3861,7 +3861,7 @@ /* Skip 5 cache entries */ stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -3917,7 +3917,7 @@ /* Skip 5 cache entries */ stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; - stack_pointer += ((oparg & 1)); + stack_pointer += (oparg & 1); DISPATCH(); } @@ -4148,7 +4148,7 @@ } stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); DISPATCH(); } @@ -4189,7 +4189,7 @@ } stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); DISPATCH(); } @@ -4223,7 +4223,7 @@ } stack_pointer[0] = res; if (oparg & 1) stack_pointer[1] = null; - stack_pointer += 1 + ((oparg & 1)); + stack_pointer += 1 + (oparg & 1); DISPATCH(); } @@ -4351,7 +4351,7 @@ } stack_pointer[-3] = attr; if (oparg & 1) stack_pointer[-2] = null; - stack_pointer += -2 + ((oparg & 1)); + stack_pointer += -2 + (oparg & 1); DISPATCH(); } @@ -4379,7 +4379,7 @@ Py_DECREF(self); if (attr == NULL) goto pop_3_error; stack_pointer[-3] = attr; - stack_pointer += -2 + (((0) ? 1 : 0)); + stack_pointer += -2 + ((0) ? 1 : 0); DISPATCH(); } diff --git a/Tools/build/deepfreeze.py b/Tools/build/deepfreeze.py index 218c64e13374e6..05633e3f77af49 100644 --- a/Tools/build/deepfreeze.py +++ b/Tools/build/deepfreeze.py @@ -21,7 +21,7 @@ verbose = False -# This must be kept in sync with Tools/cases_generator/generate_cases.py +# This must be kept in sync with Tools/cases_generator/analyzer.py RESUME = 149 def isprintable(b: bytes) -> bool: diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 04388fb54caa6c..239ed4e0266a75 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -84,7 +84,6 @@ def clean_lines(text): Python/frozen_modules/*.h Python/generated_cases.c.h Python/executor_cases.c.h -Python/abstract_interp_cases.c.h # not actually source Python/bytecodes.c diff --git a/Tools/cases_generator/analysis.py b/Tools/cases_generator/analysis.py deleted file mode 100644 index 26d92c13cd82ab..00000000000000 --- a/Tools/cases_generator/analysis.py +++ /dev/null @@ -1,487 +0,0 @@ -import re -import sys -import typing - -from _typing_backports import assert_never -from flags import InstructionFlags, variable_used -from formatting import prettify_filename, UNUSED -from instructions import ( - ActiveCacheEffect, - Component, - Instruction, - InstructionOrCacheEffect, - MacroInstruction, - MacroParts, - PseudoInstruction, -) -import parsing -from parsing import StackEffect - -BEGIN_MARKER = "// BEGIN BYTECODES //" -END_MARKER = "// END BYTECODES //" - -RESERVED_WORDS = { - "co_consts": "Use FRAME_CO_CONSTS.", - "co_names": "Use FRAME_CO_NAMES.", -} - -RE_GO_TO_INSTR = r"^\s*GO_TO_INSTRUCTION\((\w+)\);\s*(?://.*)?$" - - -class Analyzer: - """Parse input, analyze it, and write to output.""" - - input_filenames: list[str] - errors: int = 0 - warnings: int = 0 - - def __init__(self, input_filenames: list[str]): - self.input_filenames = input_filenames - - def message(self, msg: str, node: parsing.Node) -> None: - lineno = 0 - filename = "" - if context := node.context: - filename = context.owner.filename - # Use line number of first non-comment in the node - for token in context.owner.tokens[context.begin : context.end]: - lineno = token.line - if token.kind != "COMMENT": - break - print(f"{filename}:{lineno}: {msg}", file=sys.stderr) - - def error(self, msg: str, node: parsing.Node) -> None: - self.message("error: " + msg, node) - self.errors += 1 - - def warning(self, msg: str, node: parsing.Node) -> None: - self.message("warning: " + msg, node) - self.warnings += 1 - - def note(self, msg: str, node: parsing.Node) -> None: - self.message("note: " + msg, node) - - everything: list[ - parsing.InstDef - | parsing.Macro - | parsing.Pseudo - ] - instrs: dict[str, Instruction] # Includes ops - macros: dict[str, parsing.Macro] - macro_instrs: dict[str, MacroInstruction] - families: dict[str, parsing.Family] - pseudos: dict[str, parsing.Pseudo] - pseudo_instrs: dict[str, PseudoInstruction] - - def parse(self) -> None: - """Parse the source text. - - We only want the parser to see the stuff between the - begin and end markers. - """ - - self.everything = [] - self.instrs = {} - self.macros = {} - self.families = {} - self.pseudos = {} - - instrs_idx: dict[str, int] = dict() - - for filename in self.input_filenames: - self.parse_file(filename, instrs_idx) - - files = " + ".join(self.input_filenames) - n_instrs = len(set(self.instrs) & set(self.macros)) - n_ops = len(self.instrs) - n_instrs - print( - f"Read {n_instrs} instructions, {n_ops} ops, " - f"{len(self.macros)} macros, {len(self.pseudos)} pseudos, " - f"and {len(self.families)} families from {files}", - file=sys.stderr, - ) - - def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None: - with open(filename) as file: - src = file.read() - - psr = parsing.Parser(src, filename=prettify_filename(filename)) - - # Skip until begin marker - while tkn := psr.next(raw=True): - if tkn.text == BEGIN_MARKER: - break - else: - raise psr.make_syntax_error( - f"Couldn't find {BEGIN_MARKER!r} in {psr.filename}" - ) - start = psr.getpos() - - # Find end marker, then delete everything after it - while tkn := psr.next(raw=True): - if tkn.text == END_MARKER: - break - del psr.tokens[psr.getpos() - 1 :] - - # Parse from start - psr.setpos(start) - thing: parsing.Node | None - thing_first_token = psr.peek() - while thing := psr.definition(): - thing = typing.cast( - parsing.InstDef | parsing.Macro | parsing.Pseudo | parsing.Family, thing - ) - if ws := [w for w in RESERVED_WORDS if variable_used(thing, w)]: - self.error( - f"'{ws[0]}' is a reserved word. {RESERVED_WORDS[ws[0]]}", thing - ) - - match thing: - case parsing.InstDef(name=name): - macro: parsing.Macro | None = None - if thing.kind == "inst" and "override" not in thing.annotations: - macro = parsing.Macro(name, [parsing.OpName(name)]) - if name in self.instrs: - if "override" not in thing.annotations: - raise psr.make_syntax_error( - f"Duplicate definition of '{name}' @ {thing.context} " - f"previous definition @ {self.instrs[name].inst.context}", - thing_first_token, - ) - self.everything[instrs_idx[name]] = thing - if name not in self.instrs and "override" in thing.annotations: - raise psr.make_syntax_error( - f"Definition of '{name}' @ {thing.context} is supposed to be " - "an override but no previous definition exists.", - thing_first_token, - ) - self.instrs[name] = Instruction(thing) - instrs_idx[name] = len(self.everything) - self.everything.append(thing) - if macro is not None: - self.macros[macro.name] = macro - self.everything.append(macro) - case parsing.Macro(name): - self.macros[name] = thing - self.everything.append(thing) - case parsing.Family(name): - self.families[name] = thing - case parsing.Pseudo(name): - self.pseudos[name] = thing - self.everything.append(thing) - case _: - assert_never(thing) - if not psr.eof(): - raise psr.make_syntax_error(f"Extra stuff at the end of {filename}") - - def analyze(self) -> None: - """Analyze the inputs. - - Raises SystemExit if there is an error. - """ - self.analyze_macros_and_pseudos() - self.map_families() - self.mark_predictions() - self.check_families() - - def mark_predictions(self) -> None: - """Mark the instructions that need PREDICTED() labels.""" - # Start with family heads - for family in self.families.values(): - if family.name in self.instrs: - self.instrs[family.name].predicted = True - if family.name in self.macro_instrs: - self.macro_instrs[family.name].predicted = True - # Also look for GO_TO_INSTRUCTION() calls - for instr in self.instrs.values(): - targets: set[str] = set() - for line in instr.block_text: - if m := re.match(RE_GO_TO_INSTR, line): - targets.add(m.group(1)) - for target in targets: - if target_instr := self.instrs.get(target): - target_instr.predicted = True - if target_macro := self.macro_instrs.get(target): - target_macro.predicted = True - if not target_instr and not target_macro: - self.error( - f"Unknown instruction {target!r} predicted in {instr.name!r}", - instr.inst, # TODO: Use better location - ) - - def map_families(self) -> None: - """Link instruction names back to their family, if they have one.""" - for family in self.families.values(): - for member in [family.name] + family.members: - if member_instr := self.instrs.get(member): - if ( - member_instr.family is not family - and member_instr.family is not None - ): - self.error( - f"Instruction {member} is a member of multiple families " - f"({member_instr.family.name}, {family.name}).", - family, - ) - else: - member_instr.family = family - if member_mac := self.macro_instrs.get(member): - assert member_mac.family is None, (member, member_mac.family.name) - member_mac.family = family - if not member_instr and not member_mac: - self.error( - f"Unknown instruction {member!r} referenced in family {family.name!r}", - family, - ) - # A sanctioned exception: - # This opcode is a member of the family but it doesn't pass the checks. - if mac := self.macro_instrs.get("BINARY_OP_INPLACE_ADD_UNICODE"): - mac.family = self.families.get("BINARY_OP") - - def check_families(self) -> None: - """Check each family: - - - Must have at least 2 members (including head) - - Head and all members must be known instructions - - Head and all members must have the same cache, input and output effects - """ - for family in self.families.values(): - if family.name not in self.macro_instrs and family.name not in self.instrs: - self.error( - f"Family {family.name!r} has unknown instruction {family.name!r}", - family, - ) - members = [ - member - for member in family.members - if member in self.instrs or member in self.macro_instrs - ] - if members != family.members: - unknown = set(family.members) - set(members) - self.error( - f"Family {family.name!r} has unknown members: {unknown}", family - ) - expected_effects = self.effect_counts(family.name) - for member in members: - member_effects = self.effect_counts(member) - if member_effects != expected_effects: - self.error( - f"Family {family.name!r} has inconsistent " - f"(cache, input, output) effects:\n" - f" {family.name} = {expected_effects}; " - f"{member} = {member_effects}", - family, - ) - - def effect_counts(self, name: str) -> tuple[int, int, int]: - if mac := self.macro_instrs.get(name): - cache = mac.cache_offset - input, output = 0, 0 - for part in mac.parts: - if isinstance(part, Component): - # A component may pop what the previous component pushed, - # so we offset the input/output counts by that. - delta_i = len(part.instr.input_effects) - delta_o = len(part.instr.output_effects) - offset = min(delta_i, output) - input += delta_i - offset - output += delta_o - offset - else: - assert False, f"Unknown instruction {name!r}" - return cache, input, output - - def analyze_macros_and_pseudos(self) -> None: - """Analyze each macro and pseudo instruction.""" - self.macro_instrs = {} - self.pseudo_instrs = {} - for name, macro in self.macros.items(): - self.macro_instrs[name] = mac = self.analyze_macro(macro) - self.check_macro_consistency(mac) - for name, pseudo in self.pseudos.items(): - self.pseudo_instrs[name] = self.analyze_pseudo(pseudo) - - # TODO: Merge with similar code in stacking.py, write_components() - def check_macro_consistency(self, mac: MacroInstruction) -> None: - def get_var_names(instr: Instruction) -> dict[str, StackEffect]: - vars: dict[str, StackEffect] = {} - for eff in instr.input_effects + instr.output_effects: - if eff.name == UNUSED: - continue - if eff.name in vars: - if vars[eff.name] != eff: - self.error( - f"Instruction {instr.name!r} has " - f"inconsistent type/cond/size for variable " - f"{eff.name!r}: {vars[eff.name]} vs {eff}", - instr.inst, - ) - else: - vars[eff.name] = eff - return vars - - all_vars: dict[str, StackEffect] = {} - # print("Checking", mac.name) - prevop: Instruction | None = None - for part in mac.parts: - if not isinstance(part, Component): - continue - vars = get_var_names(part.instr) - # print(" //", part.instr.name, "//", vars) - for name, eff in vars.items(): - if name in all_vars: - if all_vars[name] != eff: - self.error( - f"Macro {mac.name!r} has " - f"inconsistent type/cond/size for variable " - f"{name!r}: " - f"{all_vars[name]} vs {eff} in {part.instr.name!r}", - mac.macro, - ) - else: - all_vars[name] = eff - if prevop is not None: - pushes = list(prevop.output_effects) - pops = list(reversed(part.instr.input_effects)) - copies: list[tuple[StackEffect, StackEffect]] = [] - while pushes and pops and pushes[-1] == pops[0]: - src, dst = pushes.pop(), pops.pop(0) - if src.name == dst.name or dst.name == UNUSED: - continue - copies.append((src, dst)) - reads = set(copy[0].name for copy in copies) - writes = set(copy[1].name for copy in copies) - if reads & writes: - self.error( - f"Macro {mac.name!r} has conflicting copies " - f"(source of one copy is destination of another): " - f"{reads & writes}", - mac.macro, - ) - prevop = part.instr - - def analyze_macro(self, macro: parsing.Macro) -> MacroInstruction: - components = self.check_macro_components(macro) - parts: MacroParts = [] - flags = InstructionFlags.newEmpty() - offset = 0 - for component in components: - match component: - case parsing.CacheEffect() as ceffect: - parts.append(ceffect) - offset += ceffect.size - case Instruction() as instr: - part, offset = self.analyze_instruction(instr, offset) - parts.append(part) - if instr.name != "_SAVE_RETURN_OFFSET": - # _SAVE_RETURN_OFFSET's oparg does not transfer - flags.add(instr.instr_flags) - case _: - assert_never(component) - format = "IB" if flags.HAS_ARG_FLAG else "IX" - if offset: - format += "C" + "0" * (offset - 1) - return MacroInstruction(macro.name, format, flags, macro, parts, offset) - - def analyze_pseudo(self, pseudo: parsing.Pseudo) -> PseudoInstruction: - targets: list[Instruction | MacroInstruction] = [] - for target_name in pseudo.targets: - if target_name in self.instrs: - targets.append(self.instrs[target_name]) - else: - targets.append(self.macro_instrs[target_name]) - assert targets - ignored_flags = {"HAS_EVAL_BREAK_FLAG", "HAS_DEOPT_FLAG", "HAS_ERROR_FLAG", - "HAS_ESCAPES_FLAG"} - assert len({t.instr_flags.bitmap(ignore=ignored_flags) for t in targets}) == 1 - - flags = InstructionFlags(**{f"{f}_FLAG" : True for f in pseudo.flags}) - for t in targets: - flags.add(t.instr_flags) - return PseudoInstruction(pseudo.name, targets, flags) - - def analyze_instruction( - self, instr: Instruction, offset: int - ) -> tuple[Component, int]: - active_effects: list[ActiveCacheEffect] = [] - for ceffect in instr.cache_effects: - if ceffect.name != UNUSED: - active_effects.append(ActiveCacheEffect(ceffect, offset)) - offset += ceffect.size - return ( - Component(instr, active_effects), - offset, - ) - - def check_macro_components( - self, macro: parsing.Macro - ) -> list[InstructionOrCacheEffect]: - components: list[InstructionOrCacheEffect] = [] - for uop in macro.uops: - match uop: - case parsing.OpName(name): - if name not in self.instrs: - self.error(f"Unknown instruction {name!r}", macro) - else: - components.append(self.instrs[name]) - case parsing.CacheEffect(): - components.append(uop) - case _: - assert_never(uop) - return components - - def report_non_viable_uops(self, jsonfile: str) -> None: - print("The following ops are not viable uops:") - skips = { - "CACHE", - "RESERVED", - "INTERPRETER_EXIT", - "JUMP_BACKWARD", - "LOAD_FAST_LOAD_FAST", - "LOAD_CONST_LOAD_FAST", - "STORE_FAST_STORE_FAST", - "POP_JUMP_IF_TRUE", - "POP_JUMP_IF_FALSE", - "_ITER_JUMP_LIST", - "_ITER_JUMP_TUPLE", - "_ITER_JUMP_RANGE", - } - try: - # Secret feature: if bmraw.json exists, print and sort by execution count - counts = load_execution_counts(jsonfile) - except FileNotFoundError as err: - counts = {} - non_viable = [ - instr - for instr in self.instrs.values() - if instr.name not in skips - and not instr.name.startswith("INSTRUMENTED_") - and not instr.is_viable_uop() - ] - non_viable.sort(key=lambda instr: (-counts.get(instr.name, 0), instr.name)) - for instr in non_viable: - if instr.name in counts: - scount = f"{counts[instr.name]:,}" - else: - scount = "" - print(f" {scount:>15} {instr.name:<35}", end="") - if instr.name in self.families: - print(" (unspecialized)", end="") - elif instr.family is not None: - print(f" (specialization of {instr.family.name})", end="") - print() - - -def load_execution_counts(jsonfile: str) -> dict[str, int]: - import json - - with open(jsonfile) as f: - jsondata = json.load(f) - - # Look for keys like "opcode[LOAD_FAST].execution_count" - prefix = "opcode[" - suffix = "].execution_count" - res: dict[str, int] = {} - for key, value in jsondata.items(): - if key.startswith(prefix) and key.endswith(suffix): - res[key[len(prefix) : -len(suffix)]] = value - return res diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index d7aca50d223748..82ef8888bfcee5 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -302,7 +302,81 @@ def is_infallible(op: parser.InstDef) -> bool: ) -from flags import makes_escaping_api_call +NON_ESCAPING_FUNCTIONS = ( + "Py_INCREF", + "_PyDictOrValues_IsValues", + "_PyObject_DictOrValuesPointer", + "_PyDictOrValues_GetValues", + "_PyObject_MakeInstanceAttributesFromDict", + "Py_DECREF", + "_Py_DECREF_SPECIALIZED", + "DECREF_INPUTS_AND_REUSE_FLOAT", + "PyUnicode_Append", + "_PyLong_IsZero", + "Py_SIZE", + "Py_TYPE", + "PyList_GET_ITEM", + "PyTuple_GET_ITEM", + "PyList_GET_SIZE", + "PyTuple_GET_SIZE", + "Py_ARRAY_LENGTH", + "Py_Unicode_GET_LENGTH", + "PyUnicode_READ_CHAR", + "_Py_SINGLETON", + "PyUnicode_GET_LENGTH", + "_PyLong_IsCompact", + "_PyLong_IsNonNegativeCompact", + "_PyLong_CompactValue", + "_Py_NewRef", + "_Py_IsImmortal", + "_Py_STR", + "_PyLong_Add", + "_PyLong_Multiply", + "_PyLong_Subtract", + "Py_NewRef", + "_PyList_ITEMS", + "_PyTuple_ITEMS", + "_PyList_AppendTakeRef", + "_Py_atomic_load_uintptr_relaxed", + "_PyFrame_GetCode", + "_PyThreadState_HasStackSpace", +) + +ESCAPING_FUNCTIONS = ( + "import_name", + "import_from", +) + + +def makes_escaping_api_call(instr: parser.InstDef) -> bool: + if "CALL_INTRINSIC" in instr.name: + return True + tkns = iter(instr.tokens) + for tkn in tkns: + if tkn.kind != lexer.IDENTIFIER: + continue + try: + next_tkn = next(tkns) + except StopIteration: + return False + if next_tkn.kind != lexer.LPAREN: + continue + if tkn.text in ESCAPING_FUNCTIONS: + return True + if not tkn.text.startswith("Py") and not tkn.text.startswith("_Py"): + continue + if tkn.text.endswith("Check"): + continue + if tkn.text.startswith("Py_Is"): + continue + if tkn.text.endswith("CheckExact"): + continue + if tkn.text in NON_ESCAPING_FUNCTIONS: + continue + return True + return False + + EXITS = { "DISPATCH", diff --git a/Tools/cases_generator/flags.py b/Tools/cases_generator/flags.py deleted file mode 100644 index bf76112159e38e..00000000000000 --- a/Tools/cases_generator/flags.py +++ /dev/null @@ -1,191 +0,0 @@ -import dataclasses - -from formatting import Formatter -import lexer as lx -import parsing -from typing import AbstractSet - -NON_ESCAPING_FUNCTIONS = ( - "Py_INCREF", - "_PyDictOrValues_IsValues", - "_PyObject_DictOrValuesPointer", - "_PyDictOrValues_GetValues", - "_PyObject_MakeInstanceAttributesFromDict", - "Py_DECREF", - "_Py_DECREF_SPECIALIZED", - "DECREF_INPUTS_AND_REUSE_FLOAT", - "PyUnicode_Append", - "_PyLong_IsZero", - "Py_SIZE", - "Py_TYPE", - "PyList_GET_ITEM", - "PyTuple_GET_ITEM", - "PyList_GET_SIZE", - "PyTuple_GET_SIZE", - "Py_ARRAY_LENGTH", - "Py_Unicode_GET_LENGTH", - "PyUnicode_READ_CHAR", - "_Py_SINGLETON", - "PyUnicode_GET_LENGTH", - "_PyLong_IsCompact", - "_PyLong_IsNonNegativeCompact", - "_PyLong_CompactValue", - "_Py_NewRef", - "_Py_IsImmortal", - "_Py_STR", - "_PyLong_Add", - "_PyLong_Multiply", - "_PyLong_Subtract", - "Py_NewRef", - "_PyList_ITEMS", - "_PyTuple_ITEMS", - "_PyList_AppendTakeRef", - "_Py_atomic_load_uintptr_relaxed", - "_PyFrame_GetCode", - "_PyThreadState_HasStackSpace", -) - -ESCAPING_FUNCTIONS = ( - "import_name", - "import_from", -) - - -def makes_escaping_api_call(instr: parsing.InstDef) -> bool: - if "CALL_INTRINSIC" in instr.name: - return True - tkns = iter(instr.tokens) - for tkn in tkns: - if tkn.kind != lx.IDENTIFIER: - continue - try: - next_tkn = next(tkns) - except StopIteration: - return False - if next_tkn.kind != lx.LPAREN: - continue - if tkn.text in ESCAPING_FUNCTIONS: - return True - if not tkn.text.startswith("Py") and not tkn.text.startswith("_Py"): - continue - if tkn.text.endswith("Check"): - continue - if tkn.text.startswith("Py_Is"): - continue - if tkn.text.endswith("CheckExact"): - continue - if tkn.text in NON_ESCAPING_FUNCTIONS: - continue - return True - return False - - -@dataclasses.dataclass -class InstructionFlags: - """Construct and manipulate instruction flags""" - - HAS_ARG_FLAG: bool = False - HAS_CONST_FLAG: bool = False - HAS_NAME_FLAG: bool = False - HAS_JUMP_FLAG: bool = False - HAS_FREE_FLAG: bool = False - HAS_LOCAL_FLAG: bool = False - HAS_EVAL_BREAK_FLAG: bool = False - HAS_DEOPT_FLAG: bool = False - HAS_ERROR_FLAG: bool = False - HAS_ESCAPES_FLAG: bool = False - - def __post_init__(self) -> None: - self.bitmask = {name: (1 << i) for i, name in enumerate(self.names())} - - @staticmethod - def fromInstruction(instr: parsing.InstDef) -> "InstructionFlags": - has_free = ( - variable_used(instr, "PyCell_New") - or variable_used(instr, "PyCell_GET") - or variable_used(instr, "PyCell_SET") - ) - - return InstructionFlags( - HAS_ARG_FLAG=variable_used(instr, "oparg"), - HAS_CONST_FLAG=variable_used(instr, "FRAME_CO_CONSTS"), - HAS_NAME_FLAG=variable_used(instr, "FRAME_CO_NAMES"), - HAS_JUMP_FLAG=variable_used(instr, "JUMPBY"), - HAS_FREE_FLAG=has_free, - HAS_LOCAL_FLAG=( - variable_used(instr, "GETLOCAL") or variable_used(instr, "SETLOCAL") - ) - and not has_free, - HAS_EVAL_BREAK_FLAG=variable_used(instr, "CHECK_EVAL_BREAKER"), - HAS_DEOPT_FLAG=variable_used(instr, "DEOPT_IF"), - HAS_ERROR_FLAG=( - variable_used(instr, "ERROR_IF") - or variable_used(instr, "error") - or variable_used(instr, "pop_1_error") - or variable_used(instr, "exception_unwind") - or variable_used(instr, "resume_with_error") - ), - HAS_ESCAPES_FLAG=makes_escaping_api_call(instr), - ) - - @staticmethod - def newEmpty() -> "InstructionFlags": - return InstructionFlags() - - def add(self, other: "InstructionFlags") -> None: - for name, value in dataclasses.asdict(other).items(): - if value: - setattr(self, name, value) - - def names(self, value: bool | None = None) -> list[str]: - if value is None: - return list(dataclasses.asdict(self).keys()) - return [n for n, v in dataclasses.asdict(self).items() if v == value] - - def bitmap(self, ignore: AbstractSet[str] = frozenset()) -> int: - flags = 0 - assert all(hasattr(self, name) for name in ignore) - for name in self.names(): - if getattr(self, name) and name not in ignore: - flags |= self.bitmask[name] - return flags - - @classmethod - def emit_macros(cls, out: Formatter) -> None: - flags = cls.newEmpty() - for name, value in flags.bitmask.items(): - out.emit(f"#define {name} ({value})") - - for name, value in flags.bitmask.items(): - out.emit( - f"#define OPCODE_{name[:-len('_FLAG')]}(OP) " - f"(_PyOpcode_opcode_metadata[OP].flags & ({name}))" - ) - - -def variable_used(node: parsing.Node, name: str) -> bool: - """Determine whether a variable with a given name is used in a node.""" - return any( - token.kind == "IDENTIFIER" and token.text == name for token in node.tokens - ) - - -def variable_used_unspecialized(node: parsing.Node, name: str) -> bool: - """Like variable_used(), but skips #if ENABLE_SPECIALIZATION blocks.""" - tokens: list[lx.Token] = [] - skipping = False - for i, token in enumerate(node.tokens): - if token.kind == "CMACRO": - text = "".join(token.text.split()) - # TODO: Handle nested #if - if text == "#if": - if i + 1 < len(node.tokens) and node.tokens[i + 1].text in ( - "ENABLE_SPECIALIZATION", - "TIER_ONE", - ): - skipping = True - elif text in ("#else", "#endif"): - skipping = False - if not skipping: - tokens.append(token) - return any(token.kind == "IDENTIFIER" and token.text == name for token in tokens) diff --git a/Tools/cases_generator/formatting.py b/Tools/cases_generator/formatting.py deleted file mode 100644 index 4fd9172d20c274..00000000000000 --- a/Tools/cases_generator/formatting.py +++ /dev/null @@ -1,206 +0,0 @@ -import contextlib -import re -import typing -from collections.abc import Iterator - -from parsing import StackEffect, Family - -UNUSED = "unused" - - -class Formatter: - """Wraps an output stream with the ability to indent etc.""" - - stream: typing.TextIO - prefix: str - emit_line_directives: bool = False - lineno: int # Next line number, 1-based - filename: str # Slightly improved stream.filename - nominal_lineno: int - nominal_filename: str - - def __init__( - self, - stream: typing.TextIO, - indent: int, - emit_line_directives: bool = False, - comment: str = "//", - ) -> None: - self.stream = stream - self.prefix = " " * indent - self.emit_line_directives = emit_line_directives - self.comment = comment - self.lineno = 1 - self.filename = prettify_filename(self.stream.name) - self.nominal_lineno = 1 - self.nominal_filename = self.filename - - def write_raw(self, s: str) -> None: - self.stream.write(s) - newlines = s.count("\n") - self.lineno += newlines - self.nominal_lineno += newlines - - def emit(self, arg: str) -> None: - if arg: - self.write_raw(f"{self.prefix}{arg}\n") - else: - self.write_raw("\n") - - def set_lineno(self, lineno: int, filename: str) -> None: - if self.emit_line_directives: - if lineno != self.nominal_lineno or filename != self.nominal_filename: - self.emit(f'#line {lineno} "{filename}"') - self.nominal_lineno = lineno - self.nominal_filename = filename - - def reset_lineno(self) -> None: - if self.lineno != self.nominal_lineno or self.filename != self.nominal_filename: - self.set_lineno(self.lineno + 1, self.filename) - - @contextlib.contextmanager - def indent(self) -> Iterator[None]: - self.prefix += " " - yield - self.prefix = self.prefix[:-4] - - @contextlib.contextmanager - def block(self, head: str, tail: str = "") -> Iterator[None]: - if head: - self.emit(head + " {") - else: - self.emit("{") - with self.indent(): - yield - self.emit("}" + tail) - - def stack_adjust( - self, - input_effects: list[StackEffect], - output_effects: list[StackEffect], - ) -> None: - shrink, isym = list_effect_size(input_effects) - grow, osym = list_effect_size(output_effects) - diff = grow - shrink - if isym and isym != osym: - self.emit(f"STACK_SHRINK({isym});") - if diff < 0: - self.emit(f"STACK_SHRINK({-diff});") - if diff > 0: - self.emit(f"STACK_GROW({diff});") - if osym and osym != isym: - self.emit(f"STACK_GROW({osym});") - - def declare(self, dst: StackEffect, src: StackEffect | None) -> None: - if dst.name == UNUSED or dst.cond == "0": - return - typ = f"{dst.type}" if dst.type else "PyObject *" - if src: - cast = self.cast(dst, src) - initexpr = f"{cast}{src.name}" - if src.cond and src.cond != "1": - initexpr = f"{parenthesize_cond(src.cond)} ? {initexpr} : NULL" - init = f" = {initexpr}" - elif dst.cond and dst.cond != "1": - init = " = NULL" - else: - init = "" - sepa = "" if typ.endswith("*") else " " - self.emit(f"{typ}{sepa}{dst.name}{init};") - - def assign(self, dst: StackEffect, src: StackEffect) -> None: - if src.name == UNUSED or dst.name == UNUSED: - return - cast = self.cast(dst, src) - if re.match(r"^REG\(oparg(\d+)\)$", dst.name): - self.emit(f"Py_XSETREF({dst.name}, {cast}{src.name});") - else: - stmt = f"{dst.name} = {cast}{src.name};" - if src.cond and src.cond != "1": - if src.cond == "0": - # It will not be executed - return - stmt = f"if ({src.cond}) {{ {stmt} }}" - self.emit(stmt) - - def cast(self, dst: StackEffect, src: StackEffect) -> str: - return f"({dst.type or 'PyObject *'})" if src.type != dst.type else "" - - def static_assert_family_size( - self, name: str, family: Family | None, cache_offset: int - ) -> None: - """Emit a static_assert for the size of a family, if known. - - This will fail at compile time if the cache size computed from - the instruction definition does not match the size of the struct - used by specialize.c. - """ - if family and name == family.name: - cache_size = family.size - if cache_size: - self.emit( - f"static_assert({cache_size} == {cache_offset}, " - f'"incorrect cache size");' - ) - - -def prettify_filename(filename: str) -> str: - # Make filename more user-friendly and less platform-specific, - # it is only used for error reporting at this point. - filename = filename.replace("\\", "/") - if filename.startswith("./"): - filename = filename[2:] - if filename.endswith(".new"): - filename = filename[:-4] - return filename - - -def list_effect_size(effects: list[StackEffect]) -> tuple[int, str]: - numeric = 0 - symbolic: list[str] = [] - for effect in effects: - diff, sym = effect_size(effect) - numeric += diff - if sym: - symbolic.append(maybe_parenthesize(sym)) - return numeric, " + ".join(symbolic) - - -def effect_size(effect: StackEffect) -> tuple[int, str]: - """Return the 'size' impact of a stack effect. - - Returns a tuple (numeric, symbolic) where: - - - numeric is an int giving the statically analyzable size of the effect - - symbolic is a string representing a variable effect (e.g. 'oparg*2') - - At most one of these will be non-zero / non-empty. - """ - if effect.size: - assert not effect.cond, "Array effects cannot have a condition" - return 0, effect.size - elif effect.cond: - if effect.cond in ("0", "1"): - return int(effect.cond), "" - return 0, f"{maybe_parenthesize(effect.cond)} ? 1 : 0" - else: - return 1, "" - - -def maybe_parenthesize(sym: str) -> str: - """Add parentheses around a string if it contains an operator. - - An exception is made for '*' which is common and harmless - in the context where the symbolic size is used. - """ - if re.match(r"^[\s\w*]+$", sym): - return sym - else: - return f"({sym})" - - -def parenthesize_cond(cond: str) -> str: - """Parenthesize a condition, but only if it contains ?: itself.""" - if "?" in cond: - cond = f"({cond})" - return cond diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py deleted file mode 100644 index 73b5fc2c9897e8..00000000000000 --- a/Tools/cases_generator/generate_cases.py +++ /dev/null @@ -1,848 +0,0 @@ -"""Generate the main interpreter switch. -Reads the instruction definitions from bytecodes.c. -Writes the cases to generated_cases.c.h, which is #included in ceval.c. -""" - -import argparse -import contextlib -import itertools -import os -import posixpath -import sys -import textwrap -import typing -from collections.abc import Iterator - -import stacking # Early import to avoid circular import -from _typing_backports import assert_never -from analysis import Analyzer -from formatting import Formatter, list_effect_size -from flags import InstructionFlags, variable_used -from instructions import ( - AnyInstruction, - AbstractInstruction, - Component, - Instruction, - MacroInstruction, - MacroParts, - PseudoInstruction, - TIER_ONE, - TIER_TWO, -) -import parsing -from parsing import StackEffect - - -HERE = os.path.dirname(__file__) -ROOT = os.path.join(HERE, "../..") -THIS = os.path.relpath(__file__, ROOT).replace(os.path.sep, posixpath.sep) - -DEFAULT_INPUT = os.path.relpath(os.path.join(ROOT, "Python/bytecodes.c")) -DEFAULT_OUTPUT = os.path.relpath(os.path.join(ROOT, "Python/generated_cases.c.h")) -DEFAULT_OPCODE_IDS_H_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Include/opcode_ids.h") -) -DEFAULT_OPCODE_TARGETS_H_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Python/opcode_targets.h") -) -DEFAULT_METADATA_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Include/internal/pycore_opcode_metadata.h") -) -DEFAULT_PYMETADATA_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Lib/_opcode_metadata.py") -) -DEFAULT_EXECUTOR_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Python/executor_cases.c.h") -) -DEFAULT_ABSTRACT_INTERPRETER_OUTPUT = os.path.relpath( - os.path.join(ROOT, "Python/abstract_interp_cases.c.h") -) - -# Constants used instead of size for macro expansions. -# Note: 1, 2, 4 must match actual cache entry sizes. -OPARG_SIZES = { - "OPARG_FULL": 0, - "OPARG_CACHE_1": 1, - "OPARG_CACHE_2": 2, - "OPARG_CACHE_4": 4, - "OPARG_TOP": 5, - "OPARG_BOTTOM": 6, - "OPARG_SAVE_RETURN_OFFSET": 7, -} - -INSTR_FMT_PREFIX = "INSTR_FMT_" - -# TODO: generate all these after updating the DSL -SPECIALLY_HANDLED_ABSTRACT_INSTR = { - "LOAD_FAST", - "LOAD_FAST_CHECK", - "LOAD_FAST_AND_CLEAR", - "LOAD_CONST", - "STORE_FAST", - "STORE_FAST_MAYBE_NULL", - "COPY", - # Arithmetic - "_BINARY_OP_MULTIPLY_INT", - "_BINARY_OP_ADD_INT", - "_BINARY_OP_SUBTRACT_INT", -} - -arg_parser = argparse.ArgumentParser( - description="Generate the code for the interpreter switch.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, -) - -arg_parser.add_argument( - "-v", - "--viable", - help="Print list of non-viable uops and exit", - action="store_true", -) -arg_parser.add_argument( - "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT -) -arg_parser.add_argument( - "-t", - "--opcode_targets_h", - type=str, - help="File with opcode targets for computed gotos", - default=DEFAULT_OPCODE_TARGETS_H_OUTPUT, -) -arg_parser.add_argument( - "-m", - "--metadata", - type=str, - help="Generated C metadata", - default=DEFAULT_METADATA_OUTPUT, -) -arg_parser.add_argument( - "-p", - "--pymetadata", - type=str, - help="Generated Python metadata", - default=DEFAULT_PYMETADATA_OUTPUT, -) -arg_parser.add_argument( - "-l", "--emit-line-directives", help="Emit #line directives", action="store_true" -) -arg_parser.add_argument( - "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" -) -arg_parser.add_argument( - "-a", - "--abstract-interpreter-cases", - type=str, - help="Write abstract interpreter cases to this file", - default=DEFAULT_ABSTRACT_INTERPRETER_OUTPUT, -) - - -class Generator(Analyzer): - def get_stack_effect_info( - self, thing: parsing.InstDef | parsing.Macro | parsing.Pseudo - ) -> tuple[AnyInstruction | None, str, str]: - def effect_str(effects: list[StackEffect]) -> str: - n_effect, sym_effect = list_effect_size(effects) - if sym_effect: - return f"{sym_effect} + {n_effect}" if n_effect else sym_effect - return str(n_effect) - - instr: AnyInstruction | None - popped: str | None = None - pushed: str | None = None - match thing: - case parsing.InstDef(): - instr = self.instrs[thing.name] - popped = effect_str(instr.input_effects) - pushed = effect_str(instr.output_effects) - case parsing.Macro(): - instr = self.macro_instrs[thing.name] - popped, pushed = stacking.get_stack_effect_info_for_macro(instr) - case parsing.Pseudo(): - instr = self.pseudo_instrs[thing.name] - # Calculate stack effect, and check that it's the same - # for all targets. - for target in self.pseudos[thing.name].targets: - target_instr = self.instrs.get(target) - if target_instr is None: - macro_instr = self.macro_instrs[target] - popped, pushed = stacking.get_stack_effect_info_for_macro(macro_instr) - else: - target_popped = effect_str(target_instr.input_effects) - target_pushed = effect_str(target_instr.output_effects) - if popped is None: - popped, pushed = target_popped, target_pushed - else: - assert popped == target_popped - assert pushed == target_pushed - case _: - assert_never(thing) - assert popped is not None and pushed is not None - return instr, popped, pushed - - @contextlib.contextmanager - def metadata_item(self, signature: str, open: str, close: str) -> Iterator[None]: - self.out.emit("") - self.out.emit(f"extern {signature};") - self.out.emit("#ifdef NEED_OPCODE_METADATA") - with self.out.block(f"{signature} {open}", close): - yield - self.out.emit("#endif // NEED_OPCODE_METADATA") - - def write_stack_effect_functions(self) -> None: - popped_data: list[tuple[AnyInstruction, str]] = [] - pushed_data: list[tuple[AnyInstruction, str]] = [] - for thing in self.everything: - if isinstance(thing, parsing.Macro) and thing.name in self.instrs: - continue - instr, popped, pushed = self.get_stack_effect_info(thing) - if instr is not None: - popped_data.append((instr, popped)) - pushed_data.append((instr, pushed)) - - def write_function( - direction: str, data: list[tuple[AnyInstruction, str]] - ) -> None: - with self.metadata_item( - f"int _PyOpcode_num_{direction}(int opcode, int oparg, bool jump)", - "", - "", - ): - with self.out.block("switch(opcode)"): - effects = [(instr.name, effect) for instr, effect in data] - for name, effect in sorted(effects): - self.out.emit(f"case {name}:") - self.out.emit(f" return {effect};") - self.out.emit("default:") - self.out.emit(" return -1;") - - write_function("popped", popped_data) - write_function("pushed", pushed_data) - self.out.emit("") - - def from_source_files(self) -> str: - filenames = [] - for filename in self.input_filenames: - try: - filename = os.path.relpath(filename, ROOT) - except ValueError: - # May happen on Windows if root and temp on different volumes - pass - filenames.append(filename.replace(os.path.sep, posixpath.sep)) - paths = f"\n{self.out.comment} ".join(filenames) - return f"{self.out.comment} from:\n{self.out.comment} {paths}\n" - - def write_provenance_header(self) -> None: - self.out.write_raw(f"{self.out.comment} This file is generated by {THIS}\n") - self.out.write_raw(self.from_source_files()) - self.out.write_raw(f"{self.out.comment} Do not edit!\n") - - def assign_opcode_ids(self) -> None: - """Assign IDs to opcodes""" - - ops: list[tuple[bool, str]] = [] # (has_arg, name) for each opcode - instrumented_ops: list[str] = [] - - specialized_ops: set[str] = set() - for name, family in self.families.items(): - specialized_ops.update(family.members) - - for instr in self.macro_instrs.values(): - name = instr.name - if name in specialized_ops: - continue - if name.startswith("INSTRUMENTED_"): - instrumented_ops.append(name) - else: - ops.append((instr.instr_flags.HAS_ARG_FLAG, name)) - - # Special case: this instruction is implemented in ceval.c - # rather than bytecodes.c, so we need to add it explicitly - # here (at least until we add something to bytecodes.c to - # declare external instructions). - instrumented_ops.append("INSTRUMENTED_LINE") - - # assert lists are unique - assert len(set(ops)) == len(ops) - assert len(set(instrumented_ops)) == len(instrumented_ops) - - opname: list[str | None] = [None] * 512 - opmap: dict[str, int] = {} - markers: dict[str, int] = {} - - def map_op(op: int, name: str) -> None: - assert op < len(opname) - assert opname[op] is None, (op, name) - assert name not in opmap - opname[op] = name - opmap[name] = op - - # 0 is reserved for cache entries. This helps debugging. - map_op(0, "CACHE") - - # 17 is reserved as it is the initial value for the specializing counter. - # This helps catch cases where we attempt to execute a cache. - map_op(17, "RESERVED") - - # 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py - map_op(149, "RESUME") - - # Specialized ops appear in their own section - # Instrumented opcodes are at the end of the valid range - min_internal = 150 - min_instrumented = 254 - (len(instrumented_ops) - 1) - assert min_internal + len(specialized_ops) < min_instrumented - - next_opcode = 1 - for has_arg, name in sorted(ops): - if name in opmap: - continue # an anchored name, like CACHE - map_op(next_opcode, name) - if has_arg and "HAVE_ARGUMENT" not in markers: - markers["HAVE_ARGUMENT"] = next_opcode - - while opname[next_opcode] is not None: - next_opcode += 1 - - assert next_opcode < min_internal, next_opcode - - for i, op in enumerate(sorted(specialized_ops)): - map_op(min_internal + i, op) - - markers["MIN_INSTRUMENTED_OPCODE"] = min_instrumented - for i, op in enumerate(instrumented_ops): - map_op(min_instrumented + i, op) - - # Pseudo opcodes are after the valid range - for i, op in enumerate(sorted(self.pseudos)): - map_op(256 + i, op) - - assert 255 not in opmap.values() # 255 is reserved - self.opmap = opmap - self.markers = markers - - def write_opcode_targets(self, opcode_targets_filename: str) -> None: - """Write header file that defines the jump target table""" - - with open(opcode_targets_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 0) - - with self.out.block("static void *opcode_targets[256] =", ";"): - targets = ["_unknown_opcode"] * 256 - for name, op in self.opmap.items(): - if op < 256: - targets[op] = f"TARGET_{name}" - f.write(",\n".join([f" &&{s}" for s in targets])) - - def write_metadata(self, metadata_filename: str, pymetadata_filename: str) -> None: - """Write instruction metadata to output file.""" - - # Compute the set of all instruction formats. - all_formats: set[str] = set() - for thing in self.everything: - format: str | None = None - match thing: - case parsing.InstDef(): - format = self.instrs[thing.name].instr_fmt - case parsing.Macro(): - format = self.macro_instrs[thing.name].instr_fmt - case parsing.Pseudo(): - # Pseudo instructions exist only in the compiler, - # so do not have a format - continue - case _: - assert_never(thing) - assert format is not None - all_formats.add(format) - - # Turn it into a sorted list of enum values. - format_enums = [INSTR_FMT_PREFIX + format for format in sorted(all_formats)] - - with open(metadata_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 0) - - self.write_provenance_header() - - self.out.emit("") - self.out.emit("#ifndef Py_BUILD_CORE") - self.out.emit('# error "this header requires Py_BUILD_CORE define"') - self.out.emit("#endif") - self.out.emit("") - self.out.emit("#include // bool") - - self.write_pseudo_instrs() - - self.out.emit("") - self.out.emit('#include "pycore_uop_ids.h"') - - self.write_stack_effect_functions() - - # Write the enum definition for instruction formats. - with self.out.block("enum InstructionFormat", ";"): - for enum in format_enums: - self.out.emit(enum + ",") - - self.out.emit("") - self.out.emit( - "#define IS_VALID_OPCODE(OP) \\\n" - " (((OP) >= 0) && ((OP) < OPCODE_METADATA_SIZE) && \\\n" - " (_PyOpcode_opcode_metadata[(OP)].valid_entry))" - ) - - self.out.emit("") - InstructionFlags.emit_macros(self.out) - - self.out.emit("") - with self.out.block("struct opcode_metadata", ";"): - self.out.emit("bool valid_entry;") - self.out.emit("enum InstructionFormat instr_format;") - self.out.emit("int flags;") - self.out.emit("") - - with self.out.block("struct opcode_macro_expansion", ";"): - self.out.emit("int nuops;") - self.out.emit( - "struct { int16_t uop; int8_t size; int8_t offset; } uops[12];" - ) - self.out.emit("") - - for key, value in OPARG_SIZES.items(): - self.out.emit(f"#define {key} {value}") - self.out.emit("") - - self.out.emit( - "#define OPCODE_METADATA_FLAGS(OP) " - "(_PyOpcode_opcode_metadata[(OP)].flags & (HAS_ARG_FLAG | HAS_JUMP_FLAG))" - ) - self.out.emit("#define SAME_OPCODE_METADATA(OP1, OP2) \\") - self.out.emit( - " (OPCODE_METADATA_FLAGS(OP1) == OPCODE_METADATA_FLAGS(OP2))" - ) - self.out.emit("") - - # Write metadata array declaration - self.out.emit("#define OPCODE_METADATA_SIZE 512") - self.out.emit("#define OPCODE_UOP_NAME_SIZE 512") - self.out.emit("#define OPCODE_MACRO_EXPANSION_SIZE 256") - - with self.metadata_item( - "const struct opcode_metadata " - "_PyOpcode_opcode_metadata[OPCODE_METADATA_SIZE]", - "=", - ";", - ): - # Write metadata for each instruction - sorted_things = sorted(self.everything, key = lambda t:t.name) - for thing in sorted_things: - match thing: - case parsing.InstDef(): - self.write_metadata_for_inst(self.instrs[thing.name]) - case parsing.Macro(): - if thing.name not in self.instrs: - self.write_metadata_for_macro( - self.macro_instrs[thing.name] - ) - case parsing.Pseudo(): - self.write_metadata_for_pseudo( - self.pseudo_instrs[thing.name] - ) - case _: - assert_never(thing) - - with self.metadata_item( - "const struct opcode_macro_expansion " - "_PyOpcode_macro_expansion[OPCODE_MACRO_EXPANSION_SIZE]", - "=", - ";", - ): - # Write macro expansion for each non-pseudo instruction - for mac in sorted(self.macro_instrs.values(), key=lambda t: t.name): - if is_super_instruction(mac): - # Special-case the heck out of super-instructions - self.write_super_expansions(mac.name) - else: - self.write_macro_expansions( - mac.name, mac.parts, mac.cache_offset - ) - - with self.metadata_item( - "const char * const _PyOpcode_uop_name[OPCODE_UOP_NAME_SIZE]", "=", ";" - ): - self.write_uop_items(lambda name, counter: f'[{name}] = "{name}",') - - with self.metadata_item( - f"const char *const _PyOpcode_OpName[{1 + max(self.opmap.values())}]", - "=", - ";", - ): - for name in sorted(self.opmap): - self.out.emit(f'[{name}] = "{name}",') - - with self.metadata_item( - f"const uint8_t _PyOpcode_Caches[256]", - "=", - ";", - ): - family_member_names: set[str] = set() - for family in self.families.values(): - family_member_names.update(family.members) - for mac in self.macro_instrs.values(): - if ( - mac.cache_offset > 0 - and mac.name not in family_member_names - and not mac.name.startswith("INSTRUMENTED_") - ): - self.out.emit(f"[{mac.name}] = {mac.cache_offset},") - - deoptcodes = {} - for name, op in self.opmap.items(): - if op < 256: - deoptcodes[name] = name - for name, family in self.families.items(): - for m in family.members: - deoptcodes[m] = name - # special case: - deoptcodes["BINARY_OP_INPLACE_ADD_UNICODE"] = "BINARY_OP" - - with self.metadata_item(f"const uint8_t _PyOpcode_Deopt[256]", "=", ";"): - for opt, deopt in sorted(deoptcodes.items()): - self.out.emit(f"[{opt}] = {deopt},") - - self.out.emit("") - self.out.emit("#define EXTRA_CASES \\") - valid_opcodes = set(self.opmap.values()) - with self.out.indent(): - for op in range(256): - if op not in valid_opcodes: - self.out.emit(f"case {op}: \\") - self.out.emit(" ;\n") - - with open(pymetadata_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 0, comment="#") - - self.write_provenance_header() - - # emit specializations - specialized_ops = set() - - self.out.emit("") - self.out.emit("_specializations = {") - for name, family in self.families.items(): - with self.out.indent(): - self.out.emit(f'"{family.name}": [') - with self.out.indent(): - for m in family.members: - self.out.emit(f'"{m}",') - specialized_ops.update(family.members) - self.out.emit(f"],") - self.out.emit("}") - - # Handle special case - self.out.emit("") - self.out.emit("# An irregular case:") - self.out.emit( - '_specializations["BINARY_OP"].append(' - '"BINARY_OP_INPLACE_ADD_UNICODE")' - ) - specialized_ops.add("BINARY_OP_INPLACE_ADD_UNICODE") - - ops = sorted((id, name) for (name, id) in self.opmap.items()) - # emit specialized opmap - self.out.emit("") - with self.out.block("_specialized_opmap ="): - for op, name in ops: - if name in specialized_ops: - self.out.emit(f"'{name}': {op},") - - # emit opmap - self.out.emit("") - with self.out.block("opmap ="): - for op, name in ops: - if name not in specialized_ops: - self.out.emit(f"'{name}': {op},") - - for name in ["MIN_INSTRUMENTED_OPCODE", "HAVE_ARGUMENT"]: - self.out.emit(f"{name} = {self.markers[name]}") - - def write_pseudo_instrs(self) -> None: - """Write the IS_PSEUDO_INSTR macro""" - self.out.emit("\n\n#define IS_PSEUDO_INSTR(OP) ( \\") - for op in self.pseudos: - self.out.emit(f" ((OP) == {op}) || \\") - self.out.emit(f" 0)") - - def write_uop_items(self, make_text: typing.Callable[[str, int], str]) -> None: - """Write '#define XXX NNN' for each uop""" - counter = 300 # TODO: Avoid collision with pseudo instructions - seen = set() - - def add(name: str) -> None: - if name in seen: - return - nonlocal counter - self.out.emit(make_text(name, counter)) - counter += 1 - seen.add(name) - - # These two are first by convention - add("_EXIT_TRACE") - add("_SET_IP") - - for instr in sorted(self.instrs.values(), key=lambda t:t.name): - # Skip ops that are also macros -- those are desugared inst()s - if instr.name not in self.macros: - add(instr.name) - - def write_macro_expansions( - self, name: str, parts: MacroParts, cache_offset: int - ) -> None: - """Write the macro expansions for a macro-instruction.""" - # TODO: Refactor to share code with write_cody(), is_viaible_uop(), etc. - offset = 0 # Cache effect offset - expansions: list[tuple[str, int, int]] = [] # [(name, size, offset), ...] - for part in parts: - if isinstance(part, Component): - # Skip specializations - if "specializing" in part.instr.annotations: - continue - # All other component instructions must be viable uops - if not part.instr.is_viable_uop() and "replaced" not in part.instr.annotations: - # This note just reminds us about macros that cannot - # be expanded to Tier 2 uops. It is not an error. - # Suppress it using 'replaced op(...)' for macros having - # manual translation in translate_bytecode_to_trace() - # in Python/optimizer.c. - if len(parts) > 1 or part.instr.name != name: - self.note( - f"Part {part.instr.name} of {name} is not a viable uop", - part.instr.inst, - ) - return - if not part.active_caches: - if part.instr.name == "_SAVE_RETURN_OFFSET": - size, offset = OPARG_SIZES["OPARG_SAVE_RETURN_OFFSET"], cache_offset - else: - size, offset = OPARG_SIZES["OPARG_FULL"], 0 - else: - # If this assert triggers, is_viable_uops() lied - assert len(part.active_caches) == 1, (name, part.instr.name) - cache = part.active_caches[0] - size, offset = cache.effect.size, cache.offset - expansions.append((part.instr.name, size, offset)) - assert len(expansions) > 0, f"Macro {name} has empty expansion?!" - self.write_expansions(name, expansions) - - def write_super_expansions(self, name: str) -> None: - """Write special macro expansions for super-instructions. - - If you get an assertion failure here, you probably have accidentally - violated one of the assumptions here. - - - A super-instruction's name is of the form FIRST_SECOND where - FIRST and SECOND are regular instructions whose name has the - form FOO_BAR. Thus, there must be exactly 3 underscores. - Example: LOAD_CONST_STORE_FAST. - - - A super-instruction's body uses `oparg1 and `oparg2`, and no - other instruction's body uses those variable names. - - - A super-instruction has no active (used) cache entries. - - In the expansion, the first instruction's operand is all but the - bottom 4 bits of the super-instruction's oparg, and the second - instruction's operand is the bottom 4 bits. We use the special - size codes OPARG_TOP and OPARG_BOTTOM for these. - """ - pieces = name.split("_") - assert len(pieces) == 4, f"{name} doesn't look like a super-instr" - name1 = "_".join(pieces[:2]) - name2 = "_".join(pieces[2:]) - assert name1 in self.instrs, f"{name1} doesn't match any instr" - assert name2 in self.instrs, f"{name2} doesn't match any instr" - instr1 = self.instrs[name1] - instr2 = self.instrs[name2] - assert not instr1.active_caches, f"{name1} has active caches" - assert not instr2.active_caches, f"{name2} has active caches" - expansions: list[tuple[str, int, int]] = [ - (name1, OPARG_SIZES["OPARG_TOP"], 0), - (name2, OPARG_SIZES["OPARG_BOTTOM"], 0), - ] - self.write_expansions(name, expansions) - - def write_expansions( - self, name: str, expansions: list[tuple[str, int, int]] - ) -> None: - pieces = [ - f"{{ {name}, {size}, {offset} }}" for name, size, offset in expansions - ] - self.out.emit( - f"[{name}] = " - f"{{ .nuops = {len(pieces)}, .uops = {{ {', '.join(pieces)} }} }}," - ) - - def emit_metadata_entry(self, name: str, fmt: str | None, flags: InstructionFlags) -> None: - flag_names = flags.names(value=True) - if not flag_names: - flag_names.append("0") - fmt_macro = "0" if fmt is None else INSTR_FMT_PREFIX + fmt - self.out.emit( - f"[{name}] = {{ true, {fmt_macro}," - f" {' | '.join(flag_names)} }}," - ) - - def write_metadata_for_inst(self, instr: Instruction) -> None: - """Write metadata for a single instruction.""" - self.emit_metadata_entry(instr.name, instr.instr_fmt, instr.instr_flags) - - def write_metadata_for_macro(self, mac: MacroInstruction) -> None: - """Write metadata for a macro-instruction.""" - self.emit_metadata_entry(mac.name, mac.instr_fmt, mac.instr_flags) - - def write_metadata_for_pseudo(self, ps: PseudoInstruction) -> None: - """Write metadata for a macro-instruction.""" - self.emit_metadata_entry(ps.name, None, ps.instr_flags) - - def write_instructions( - self, output_filename: str, emit_line_directives: bool - ) -> None: - """Write instructions to output file.""" - with open(output_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 8, emit_line_directives) - - self.write_provenance_header() - - self.out.write_raw("\n") - self.out.write_raw("#ifdef TIER_TWO\n") - self.out.write_raw(" #error \"This file is for Tier 1 only\"\n") - self.out.write_raw("#endif\n") - self.out.write_raw("#define TIER_ONE 1\n") - - # Write and count instructions of all kinds - n_macros = 0 - cases = [] - for thing in self.everything: - match thing: - case parsing.InstDef(): - pass - case parsing.Macro(): - n_macros += 1 - mac = self.macro_instrs[thing.name] - cases.append((mac.name, mac)) - case parsing.Pseudo(): - pass - case _: - assert_never(thing) - cases.sort() - for _, mac in cases: - stacking.write_macro_instr(mac, self.out) - - self.out.write_raw("\n") - self.out.write_raw("#undef TIER_ONE\n") - - print( - f"Wrote {n_macros} cases to {output_filename}", - file=sys.stderr, - ) - - def write_executor_instructions( - self, executor_filename: str, emit_line_directives: bool - ) -> None: - """Generate cases for the Tier 2 interpreter.""" - n_uops = 0 - with open(executor_filename, "w") as f: - self.out = Formatter(f, 8, emit_line_directives) - self.write_provenance_header() - - self.out.write_raw("\n") - self.out.write_raw("#ifdef TIER_ONE\n") - self.out.write_raw(" #error \"This file is for Tier 2 only\"\n") - self.out.write_raw("#endif\n") - self.out.write_raw("#define TIER_TWO 2\n") - - for instr in self.instrs.values(): - if instr.is_viable_uop(): - n_uops += 1 - self.out.emit("") - with self.out.block(f"case {instr.name}:"): - if instr.instr_flags.HAS_ARG_FLAG: - self.out.emit("oparg = CURRENT_OPARG();") - stacking.write_single_instr(instr, self.out, tier=TIER_TWO) - if instr.check_eval_breaker: - self.out.emit("CHECK_EVAL_BREAKER();") - self.out.emit("break;") - - self.out.write_raw("\n") - self.out.write_raw("#undef TIER_TWO\n") - - print( - f"Wrote {n_uops} cases to {executor_filename}", - file=sys.stderr, - ) - - def write_abstract_interpreter_instructions( - self, abstract_interpreter_filename: str, emit_line_directives: bool - ) -> None: - """Generate cases for the Tier 2 abstract interpreter/analzyer.""" - with open(abstract_interpreter_filename, "w") as f: - self.out = Formatter(f, 8, emit_line_directives) - self.write_provenance_header() - for instr in self.instrs.values(): - instr = AbstractInstruction(instr.inst) - if ( - instr.is_viable_uop() - and instr.name not in SPECIALLY_HANDLED_ABSTRACT_INSTR - ): - self.out.emit("") - with self.out.block(f"case {instr.name}:"): - instr.write(self.out, tier=TIER_TWO) - self.out.emit("break;") - print( - f"Wrote some stuff to {abstract_interpreter_filename}", - file=sys.stderr, - ) - - -def is_super_instruction(mac: MacroInstruction) -> bool: - if ( - len(mac.parts) == 1 - and isinstance(mac.parts[0], Component) - and variable_used(mac.parts[0].instr.inst, "oparg1") - ): - assert variable_used(mac.parts[0].instr.inst, "oparg2") - return True - else: - return False - - -def main() -> None: - """Parse command line, parse input, analyze, write output.""" - args = arg_parser.parse_args() # Prints message and sys.exit(2) on error - if len(args.input) == 0: - args.input.append(DEFAULT_INPUT) - - # Raises OSError if input unreadable - a = Generator(args.input) - - a.parse() # Raises SyntaxError on failure - a.analyze() # Prints messages and sets a.errors on failure - if a.errors: - sys.exit(f"Found {a.errors} errors") - if args.viable: - # Load execution counts from bmraw.json, if it exists - a.report_non_viable_uops("bmraw.json") - return - - # These raise OSError if output can't be written - - a.assign_opcode_ids() - a.write_abstract_interpreter_instructions( - args.abstract_interpreter_cases, args.emit_line_directives - ) - - -if __name__ == "__main__": - main() diff --git a/Tools/cases_generator/instructions.py b/Tools/cases_generator/instructions.py deleted file mode 100644 index 149a08810e4ae5..00000000000000 --- a/Tools/cases_generator/instructions.py +++ /dev/null @@ -1,355 +0,0 @@ -import dataclasses -import re -import typing - -from flags import InstructionFlags, variable_used, variable_used_unspecialized -from formatting import ( - Formatter, - UNUSED, - list_effect_size, -) -import lexer as lx -import parsing -from parsing import StackEffect -import stacking - -BITS_PER_CODE_UNIT = 16 - - -@dataclasses.dataclass -class ActiveCacheEffect: - """Wraps a CacheEffect that is actually used, in context.""" - - effect: parsing.CacheEffect - offset: int - - -FORBIDDEN_NAMES_IN_UOPS = ( - "next_instr", - "oparg1", # Proxy for super-instructions like LOAD_FAST_LOAD_FAST - "JUMPBY", - "DISPATCH", - "TIER_ONE_ONLY", -) - - -# Interpreter tiers -TIER_ONE: typing.Final = 1 # Specializing adaptive interpreter (PEP 659) -TIER_TWO: typing.Final = 2 # Experimental tracing interpreter -Tiers: typing.TypeAlias = typing.Literal[1, 2] - - -@dataclasses.dataclass -class Instruction: - """An instruction with additional data and code.""" - - # Parts of the underlying instruction definition - inst: parsing.InstDef - name: str - annotations: list[str] - block: parsing.Block - block_text: list[str] # Block.text, less curlies, less PREDICT() calls - block_line: int # First line of block in original code - - # Computed by constructor - always_exits: str # If the block always exits, its last line; else "" - has_deopt: bool - needs_this_instr: bool - cache_offset: int - cache_effects: list[parsing.CacheEffect] - input_effects: list[StackEffect] - output_effects: list[StackEffect] - unmoved_names: frozenset[str] - instr_fmt: str - instr_flags: InstructionFlags - active_caches: list[ActiveCacheEffect] - - # Set later - family: parsing.Family | None = None - predicted: bool = False - - def __init__(self, inst: parsing.InstDef): - self.inst = inst - self.name = inst.name - self.annotations = inst.annotations - self.block = inst.block - self.block_text, self.check_eval_breaker, self.block_line = extract_block_text( - self.block - ) - self.always_exits = always_exits(self.block_text) - self.has_deopt = variable_used(self.inst, "DEOPT_IF") - self.cache_effects = [ - effect for effect in inst.inputs if isinstance(effect, parsing.CacheEffect) - ] - self.cache_offset = sum(c.size for c in self.cache_effects) - self.needs_this_instr = variable_used(self.inst, "this_instr") or any(c.name != UNUSED for c in self.cache_effects) - self.input_effects = [ - effect for effect in inst.inputs if isinstance(effect, StackEffect) - ] - self.output_effects = inst.outputs # For consistency/completeness - unmoved_names: set[str] = set() - for ieffect, oeffect in zip(self.input_effects, self.output_effects): - if ieffect == oeffect and ieffect.name == oeffect.name: - unmoved_names.add(ieffect.name) - else: - break - self.unmoved_names = frozenset(unmoved_names) - - self.instr_flags = InstructionFlags.fromInstruction(inst) - - self.active_caches = [] - offset = 0 - for effect in self.cache_effects: - if effect.name != UNUSED: - self.active_caches.append(ActiveCacheEffect(effect, offset)) - offset += effect.size - - if self.instr_flags.HAS_ARG_FLAG: - fmt = "IB" - else: - fmt = "IX" - if offset: - fmt += "C" + "0" * (offset - 1) - self.instr_fmt = fmt - - def is_viable_uop(self) -> bool: - """Whether this instruction is viable as a uop.""" - dprint: typing.Callable[..., None] = lambda *args, **kwargs: None - if "FRAME" in self.name: - dprint = print - - if self.name == "_EXIT_TRACE": - return True # This has 'return frame' but it's okay - if self.name == "_SAVE_RETURN_OFFSET": - return True # Adjusts next_instr, but only in tier 1 code - if self.always_exits: - dprint(f"Skipping {self.name} because it always exits: {self.always_exits}") - return False - if len(self.active_caches) > 1: - # print(f"Skipping {self.name} because it has >1 cache entries") - return False - res = True - for forbidden in FORBIDDEN_NAMES_IN_UOPS: - # NOTE: To disallow unspecialized uops, use - # if variable_used(self.inst, forbidden): - if variable_used_unspecialized(self.inst, forbidden): - dprint(f"Skipping {self.name} because it uses {forbidden}") - res = False - return res - - def write_body( - self, - out: Formatter, - dedent: int, - active_caches: list[ActiveCacheEffect], - tier: Tiers, - family: parsing.Family | None, - ) -> None: - """Write the instruction body.""" - # Write cache effect variable declarations and initializations - for active in active_caches: - ceffect = active.effect - bits = ceffect.size * BITS_PER_CODE_UNIT - if bits == 64: - # NOTE: We assume that 64-bit data in the cache - # is always an object pointer. - # If this becomes false, we need a way to specify - # syntactically what type the cache data is. - typ = "PyObject *" - func = "read_obj" - else: - typ = f"uint{bits}_t " - func = f"read_u{bits}" - if tier == TIER_ONE: - out.emit( - f"{typ}{ceffect.name} = " - f"{func}(&this_instr[{active.offset + 1}].cache);" - ) - else: - out.emit(f"{typ}{ceffect.name} = ({typ.strip()})CURRENT_OPERAND();") - - # Write the body, substituting a goto for ERROR_IF() and other stuff - assert dedent <= 0 - extra = " " * -dedent - names_to_skip = self.unmoved_names | frozenset({UNUSED, "null"}) - offset = 0 - context = self.block.context - assert context is not None and context.owner is not None - filename = context.owner.filename - for line in self.block_text: - out.set_lineno(self.block_line + offset, filename) - offset += 1 - if m := re.match(r"(\s*)ERROR_IF\((.+), (\w+)\);\s*(?://.*)?$", line): - space, cond, label = m.groups() - space = extra + space - # ERROR_IF() must pop the inputs from the stack. - # The code block is responsible for DECREF()ing them. - # NOTE: If the label doesn't exist, just add it to ceval.c. - - # Don't pop common input/output effects at the bottom! - # These aren't DECREF'ed so they can stay. - ieffs = list(self.input_effects) - oeffs = list(self.output_effects) - while ( - ieffs - and oeffs - and ieffs[0] == oeffs[0] - and ieffs[0].name == oeffs[0].name - ): - ieffs.pop(0) - oeffs.pop(0) - ninputs, symbolic = list_effect_size(ieffs) - if ninputs: - label = f"pop_{ninputs}_{label}" - if tier == TIER_TWO: - label = label + "_tier_two" - if symbolic: - out.write_raw( - f"{space}if ({cond}) {{ STACK_SHRINK({symbolic}); goto {label}; }}\n" - ) - else: - out.write_raw(f"{space}if ({cond}) goto {label};\n") - elif m := re.match(r"(\s*)DEOPT_IF\((.+)\);\s*(?://.*)?$", line): - space, cond = m.groups() - space = extra + space - target = family.name if family else self.name - out.write_raw(f"{space}DEOPT_IF({cond}, {target});\n") - elif "DEOPT" in line: - filename = context.owner.filename - lineno = context.owner.tokens[context.begin].line - print(f"{filename}:{lineno}: ERROR: DEOPT_IF() must be all on one line") - out.write_raw(extra + line) - elif m := re.match(r"(\s*)DECREF_INPUTS\(\);\s*(?://.*)?$", line): - out.reset_lineno() - space = extra + m.group(1) - for ieff in self.input_effects: - if ieff.name in names_to_skip: - continue - if ieff.size: - out.write_raw( - f"{space}for (int _i = {ieff.size}; --_i >= 0;) {{\n" - ) - out.write_raw(f"{space} Py_DECREF({ieff.name}[_i]);\n") - out.write_raw(f"{space}}}\n") - else: - decref = "XDECREF" if ieff.cond else "DECREF" - out.write_raw(f"{space}Py_{decref}({ieff.name});\n") - else: - out.write_raw(extra + line) - out.reset_lineno() - - -InstructionOrCacheEffect = Instruction | parsing.CacheEffect - - -# Instruction used for abstract interpretation. -class AbstractInstruction(Instruction): - def __init__(self, inst: parsing.InstDef): - super().__init__(inst) - - def write(self, out: Formatter, tier: Tiers = TIER_ONE) -> None: - """Write one abstract instruction, sans prologue and epilogue.""" - stacking.write_single_instr_for_abstract_interp(self, out) - - def write_body( - self, - out: Formatter, - dedent: int, - active_caches: list[ActiveCacheEffect], - tier: Tiers, - family: parsing.Family | None, - ) -> None: - pass - - -@dataclasses.dataclass -class Component: - instr: Instruction - active_caches: list[ActiveCacheEffect] - - -MacroParts = list[Component | parsing.CacheEffect] - - -@dataclasses.dataclass -class MacroInstruction: - """A macro instruction.""" - - name: str - instr_fmt: str - instr_flags: InstructionFlags - macro: parsing.Macro - parts: MacroParts - cache_offset: int - # Set later - predicted: bool = False - family: parsing.Family | None = None - - -@dataclasses.dataclass -class PseudoInstruction: - """A pseudo instruction.""" - - name: str - targets: list[Instruction | MacroInstruction] - instr_flags: InstructionFlags - - -AnyInstruction = Instruction | MacroInstruction | PseudoInstruction - - -def extract_block_text(block: parsing.Block) -> tuple[list[str], bool, int]: - # Get lines of text with proper dedent - blocklines = block.text.splitlines(True) - first_token: lx.Token = block.tokens[0] # IndexError means the context is broken - block_line = first_token.begin[0] - - # Remove blank lines from both ends - while blocklines and not blocklines[0].strip(): - blocklines.pop(0) - block_line += 1 - while blocklines and not blocklines[-1].strip(): - blocklines.pop() - - # Remove leading and trailing braces - assert blocklines and blocklines[0].strip() == "{" - assert blocklines and blocklines[-1].strip() == "}" - blocklines.pop() - blocklines.pop(0) - block_line += 1 - - # Remove trailing blank lines - while blocklines and not blocklines[-1].strip(): - blocklines.pop() - - # Separate CHECK_EVAL_BREAKER() macro from end - check_eval_breaker = ( - blocklines != [] and blocklines[-1].strip() == "CHECK_EVAL_BREAKER();" - ) - if check_eval_breaker: - del blocklines[-1] - - return blocklines, check_eval_breaker, block_line - - -def always_exits(lines: list[str]) -> str: - """Determine whether a block always ends in a return/goto/etc.""" - if not lines: - return "" - line = lines[-1].rstrip() - # Indent must match exactly (TODO: Do something better) - if line[:12] != " " * 12: - return "" - line = line[12:] - if line.startswith( - ( - "goto ", - "return ", - "DISPATCH", - "GO_TO_", - "Py_UNREACHABLE()", - "ERROR_IF(true, ", - ) - ): - return line - return "" diff --git a/Tools/cases_generator/parser.py b/Tools/cases_generator/parser.py index fe4e8e476eadee..2b77d14d21143f 100644 --- a/Tools/cases_generator/parser.py +++ b/Tools/cases_generator/parser.py @@ -11,7 +11,17 @@ OpName, AstNode, ) -from formatting import prettify_filename + + +def prettify_filename(filename: str) -> str: + # Make filename more user-friendly and less platform-specific, + # it is only used for error reporting at this point. + filename = filename.replace("\\", "/") + if filename.startswith("./"): + filename = filename[2:] + if filename.endswith(".new"): + filename = filename[:-4] + return filename BEGIN_MARKER = "// BEGIN BYTECODES //" diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 94fb82d1139b68..d351037a663ca2 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -1,10 +1,24 @@ -import sys +import re from analyzer import StackItem, Instruction, Uop from dataclasses import dataclass -from formatting import maybe_parenthesize from cwriter import CWriter +def maybe_parenthesize(sym: str) -> str: + """Add parentheses around a string if it contains an operator + and is not already parenthesized. + + An exception is made for '*' which is common and harmless + in the context where the symbolic size is used. + """ + if sym.startswith("(") and sym.endswith(")"): + return sym + if re.match(r"^[\s\w*]+$", sym): + return sym + else: + return f"({sym})" + + def var_size(var: StackItem) -> str: if var.condition: # Special case simplification diff --git a/Tools/cases_generator/stacking.py b/Tools/cases_generator/stacking.py deleted file mode 100644 index 123e38c524f49d..00000000000000 --- a/Tools/cases_generator/stacking.py +++ /dev/null @@ -1,534 +0,0 @@ -import dataclasses -import typing - -from flags import variable_used_unspecialized -from formatting import ( - Formatter, - UNUSED, - maybe_parenthesize, - parenthesize_cond, -) -from instructions import ( - ActiveCacheEffect, - Instruction, - MacroInstruction, - Component, - Tiers, - TIER_ONE, -) -from parsing import StackEffect, CacheEffect, Family - - -@dataclasses.dataclass -class StackOffset: - """Represent the stack offset for a PEEK or POKE. - - - At stack_pointer[0], deep and high are both empty. - (Note that that is an invalid stack reference.) - - Below stack top, only deep is non-empty. - - Above stack top, only high is non-empty. - - In complex cases, both deep and high may be non-empty. - - All this would be much simpler if all stack entries were the same - size, but with conditional and array effects, they aren't. - The offsets are each represented by a list of StackEffect objects. - The name in the StackEffects is unused. - """ - - deep: list[StackEffect] = dataclasses.field(default_factory=list) - high: list[StackEffect] = dataclasses.field(default_factory=list) - - def clone(self) -> "StackOffset": - return StackOffset(list(self.deep), list(self.high)) - - def negate(self) -> "StackOffset": - return StackOffset(list(self.high), list(self.deep)) - - def deeper(self, eff: StackEffect) -> None: - if eff in self.high: - self.high.remove(eff) - else: - self.deep.append(eff) - - def higher(self, eff: StackEffect) -> None: - if eff in self.deep: - self.deep.remove(eff) - else: - self.high.append(eff) - - def as_terms(self) -> list[tuple[str, str]]: - num = 0 - terms: list[tuple[str, str]] = [] - for eff in self.deep: - if eff.size: - terms.append(("-", maybe_parenthesize(eff.size))) - elif eff.cond and eff.cond not in ("0", "1"): - terms.append(("-", f"({parenthesize_cond(eff.cond)} ? 1 : 0)")) - elif eff.cond != "0": - num -= 1 - for eff in self.high: - if eff.size: - terms.append(("+", maybe_parenthesize(eff.size))) - elif eff.cond and eff.cond not in ("0", "1"): - terms.append(("+", f"({parenthesize_cond(eff.cond)} ? 1 : 0)")) - elif eff.cond != "0": - num += 1 - if num < 0: - terms.insert(0, ("-", str(-num))) - elif num > 0: - terms.append(("+", str(num))) - return terms - - def as_index(self) -> str: - terms = self.as_terms() - return make_index(terms) - - def equivalent_to(self, other: "StackOffset") -> bool: - if self.deep == other.deep and self.high == other.high: - return True - deep = list(self.deep) - for x in other.deep: - try: - deep.remove(x) - except ValueError: - return False - if deep: - return False - high = list(self.high) - for x in other.high: - try: - high.remove(x) - except ValueError: - return False - if high: - return False - return True - - -def make_index(terms: list[tuple[str, str]]) -> str: - # Produce an index expression from the terms honoring PEP 8, - # surrounding binary ops with spaces but not unary minus - index = "" - for sign, term in terms: - if index: - index += f" {sign} {term}" - elif sign == "+": - index = term - else: - index = sign + term - return index or "0" - - -@dataclasses.dataclass -class StackItem: - offset: StackOffset - effect: StackEffect - - def as_variable(self, lax: bool = False) -> str: - """Return e.g. stack_pointer[-1].""" - terms = self.offset.as_terms() - if self.effect.size: - terms.insert(0, ("+", "stack_pointer")) - index = make_index(terms) - if self.effect.size: - res = index - else: - res = f"stack_pointer[{index}]" - if not lax: - # Check that we're not reading or writing above stack top. - # Skip this for output variable initialization (lax=True). - assert ( - self.effect in self.offset.deep and not self.offset.high - ), f"Push or pop above current stack level: {res}" - return res - - def as_stack_effect(self, lax: bool = False) -> StackEffect: - return StackEffect( - self.as_variable(lax=lax), - self.effect.type if self.effect.size else "", - self.effect.cond, - self.effect.size, - ) - - -@dataclasses.dataclass -class CopyItem: - src: StackItem - dst: StackItem - - -class EffectManager: - """Manage stack effects and offsets for an instruction.""" - - instr: Instruction - active_caches: list[ActiveCacheEffect] - peeks: list[StackItem] - pokes: list[StackItem] - copies: list[CopyItem] # See merge() - # Track offsets from stack pointer - min_offset: StackOffset - final_offset: StackOffset - # Link to previous manager - pred: "EffectManager | None" = None - - def __init__( - self, - instr: Instruction, - active_caches: list[ActiveCacheEffect], - pred: "EffectManager | None" = None, - ): - self.instr = instr - self.active_caches = active_caches - self.peeks = [] - self.pokes = [] - self.copies = [] - self.final_offset = pred.final_offset.clone() if pred else StackOffset() - for eff in reversed(instr.input_effects): - self.final_offset.deeper(eff) - self.peeks.append(StackItem(offset=self.final_offset.clone(), effect=eff)) - self.min_offset = self.final_offset.clone() - for eff in instr.output_effects: - self.pokes.append(StackItem(offset=self.final_offset.clone(), effect=eff)) - self.final_offset.higher(eff) - - self.pred = pred - while pred: - # Replace push(x) + pop(y) with copy(x, y). - # Check that the sources and destinations are disjoint. - sources: set[str] = set() - destinations: set[str] = set() - while ( - pred.pokes - and self.peeks - and pred.pokes[-1].effect == self.peeks[0].effect - ): - src = pred.pokes.pop(-1) - dst = self.peeks.pop(0) - assert src.offset.equivalent_to(dst.offset), (src, dst) - pred.final_offset.deeper(src.effect) - if dst.effect.name != src.effect.name: - if dst.effect.name != UNUSED: - destinations.add(dst.effect.name) - if src.effect.name != UNUSED: - sources.add(src.effect.name) - self.copies.append(CopyItem(src, dst)) - # TODO: Turn this into an error (pass an Analyzer instance?) - assert sources & destinations == set(), ( - pred.instr.name, - self.instr.name, - sources, - destinations, - ) - # See if we can get more copies of a earlier predecessor. - if self.peeks and not pred.pokes and not pred.peeks: - pred = pred.pred - else: - pred = None # Break - - # Fix up patterns of copies through UNUSED, - # e.g. cp(a, UNUSED) + cp(UNUSED, b) -> cp(a, b). - if any(copy.src.effect.name == UNUSED for copy in self.copies): - pred = self.pred - while pred is not None: - for copy in self.copies: - if copy.src.effect.name == UNUSED: - for pred_copy in pred.copies: - if pred_copy.dst == copy.src: - copy.src = pred_copy.src - break - pred = pred.pred - - def adjust_deeper(self, eff: StackEffect) -> None: - for peek in self.peeks: - peek.offset.deeper(eff) - for poke in self.pokes: - poke.offset.deeper(eff) - for copy in self.copies: - copy.src.offset.deeper(eff) - copy.dst.offset.deeper(eff) - self.min_offset.deeper(eff) - self.final_offset.deeper(eff) - - def adjust_higher(self, eff: StackEffect) -> None: - for peek in self.peeks: - peek.offset.higher(eff) - for poke in self.pokes: - poke.offset.higher(eff) - for copy in self.copies: - copy.src.offset.higher(eff) - copy.dst.offset.higher(eff) - self.min_offset.higher(eff) - self.final_offset.higher(eff) - - def adjust(self, offset: StackOffset) -> None: - deep = list(offset.deep) - high = list(offset.high) - for down in deep: - self.adjust_deeper(down) - for up in high: - self.adjust_higher(up) - - def adjust_inverse(self, offset: StackOffset) -> None: - deep = list(offset.deep) - high = list(offset.high) - for down in deep: - self.adjust_higher(down) - for up in high: - self.adjust_deeper(up) - - def collect_vars(self) -> dict[str, StackEffect]: - """Collect all variables, skipping unused ones.""" - vars: dict[str, StackEffect] = {} - - def add(eff: StackEffect) -> None: - if eff.name != UNUSED: - if eff.name in vars: - # TODO: Make this an error - assert vars[eff.name] == eff, ( - self.instr.name, - eff.name, - vars[eff.name], - eff, - ) - else: - vars[eff.name] = eff - - for copy in self.copies: - add(copy.src.effect) - add(copy.dst.effect) - for peek in self.peeks: - add(peek.effect) - for poke in self.pokes: - add(poke.effect) - - return vars - - -def less_than(a: StackOffset, b: StackOffset) -> bool: - # TODO: Handle more cases - if a.high != b.high: - return False - return a.deep[: len(b.deep)] == b.deep - - -def get_managers(parts: list[Component]) -> list[EffectManager]: - managers: list[EffectManager] = [] - pred: EffectManager | None = None - for part in parts: - mgr = EffectManager(part.instr, part.active_caches, pred) - managers.append(mgr) - pred = mgr - return managers - - -def get_stack_effect_info_for_macro(mac: MacroInstruction) -> tuple[str, str]: - """Get the stack effect info for a macro instruction. - - Returns a tuple (popped, pushed) where each is a string giving a - symbolic expression for the number of values popped/pushed. - """ - parts = [part for part in mac.parts if isinstance(part, Component)] - managers = get_managers(parts) - popped = StackOffset() - for mgr in managers: - if less_than(mgr.min_offset, popped): - popped = mgr.min_offset.clone() - # Compute pushed = final - popped - pushed = managers[-1].final_offset.clone() - for effect in popped.deep: - pushed.higher(effect) - for effect in popped.high: - pushed.deeper(effect) - return popped.negate().as_index(), pushed.as_index() - - -def write_single_instr( - instr: Instruction, out: Formatter, tier: Tiers = TIER_ONE -) -> None: - try: - write_components( - [Component(instr, instr.active_caches)], - out, - tier, - 0, - instr.family, - ) - except AssertionError as err: - raise AssertionError(f"Error writing instruction {instr.name}") from err - - -def write_macro_instr(mac: MacroInstruction, out: Formatter) -> None: - parts = [ - part - for part in mac.parts - if isinstance(part, Component) and part.instr.name != "_SET_IP" - ] - out.emit("") - with out.block(f"TARGET({mac.name})"): - needs_this = any(part.instr.needs_this_instr for part in parts) - if needs_this and not mac.predicted: - out.emit(f"_Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr;") - else: - out.emit(f"frame->instr_ptr = next_instr;") - out.emit(f"next_instr += {mac.cache_offset+1};") - out.emit(f"INSTRUCTION_STATS({mac.name});") - if mac.predicted: - out.emit(f"PREDICTED({mac.name});") - if needs_this: - out.emit(f"_Py_CODEUNIT *this_instr = next_instr - {mac.cache_offset+1};") - out.static_assert_family_size(mac.name, mac.family, mac.cache_offset) - try: - next_instr_is_set = write_components( - parts, out, TIER_ONE, mac.cache_offset, mac.family - ) - except AssertionError as err: - raise AssertionError(f"Error writing macro {mac.name}") from err - if not parts[-1].instr.always_exits: - if parts[-1].instr.check_eval_breaker: - out.emit("CHECK_EVAL_BREAKER();") - out.emit("DISPATCH();") - - -def write_components( - parts: list[Component], - out: Formatter, - tier: Tiers, - cache_offset: int, - family: Family | None, -) -> bool: - managers = get_managers(parts) - - all_vars: dict[str, StackEffect] = {} - for mgr in managers: - for name, eff in mgr.collect_vars().items(): - if name in all_vars: - # TODO: Turn this into an error -- variable conflict - assert all_vars[name] == eff, ( - name, - mgr.instr.name, - all_vars[name], - eff, - ) - else: - all_vars[name] = eff - - # Declare all variables - for name, eff in all_vars.items(): - out.declare(eff, None) - - next_instr_is_set = False - for mgr in managers: - if len(parts) > 1: - out.emit(f"// {mgr.instr.name}") - - for copy in mgr.copies: - copy_src_effect = copy.src.effect - if copy_src_effect.name != copy.dst.effect.name: - if copy_src_effect.name == UNUSED: - copy_src_effect = copy.src.as_stack_effect() - out.assign(copy.dst.effect, copy_src_effect) - for peek in mgr.peeks: - out.assign( - peek.effect, - peek.as_stack_effect(), - ) - # Initialize array outputs - for poke in mgr.pokes: - if poke.effect.size and poke.effect.name not in mgr.instr.unmoved_names: - out.assign( - poke.effect, - poke.as_stack_effect(lax=True), - ) - - if mgr.instr.name in ("_PUSH_FRAME", "_POP_FRAME"): - # Adjust stack to min_offset. - # This means that all input effects of this instruction - # are materialized, but not its output effects. - # That's as intended, since these two are so special. - out.stack_adjust(mgr.min_offset.deep, mgr.min_offset.high) - # However, for tier 2, pretend the stack is at final offset. - mgr.adjust_inverse(mgr.final_offset) - if tier == TIER_ONE: - # TODO: Check in analyzer that _{PUSH,POP}_FRAME is last. - assert ( - mgr is managers[-1] - ), f"Expected {mgr.instr.name!r} to be the last uop" - assert_no_pokes(managers) - - if mgr.instr.name == "_SAVE_RETURN_OFFSET": - next_instr_is_set = True - if tier == TIER_ONE: - assert_no_pokes(managers) - - if len(parts) == 1: - mgr.instr.write_body(out, 0, mgr.active_caches, tier, family) - else: - with out.block(""): - mgr.instr.write_body(out, -4, mgr.active_caches, tier, family) - - if mgr is managers[-1] and not next_instr_is_set and not mgr.instr.always_exits: - # Adjust the stack to its final depth, *then* write the - # pokes for all preceding uops. - # Note that for array output effects we may still write - # past the stack top. - out.stack_adjust(mgr.final_offset.deep, mgr.final_offset.high) - write_all_pokes(mgr.final_offset, managers, out) - - return next_instr_is_set - - -def assert_no_pokes(managers: list[EffectManager]) -> None: - for mgr in managers: - for poke in mgr.pokes: - if not poke.effect.size and poke.effect.name not in mgr.instr.unmoved_names: - assert ( - poke.effect.name == UNUSED - ), f"Unexpected poke of {poke.effect.name} in {mgr.instr.name!r}" - - -def write_all_pokes( - offset: StackOffset, managers: list[EffectManager], out: Formatter -) -> None: - # Emit all remaining pushes (pokes) - for m in managers: - m.adjust_inverse(offset) - write_pokes(m, out) - - -def write_pokes(mgr: EffectManager, out: Formatter) -> None: - for poke in mgr.pokes: - if not poke.effect.size and poke.effect.name not in mgr.instr.unmoved_names: - out.assign( - poke.as_stack_effect(), - poke.effect, - ) - - -def write_single_instr_for_abstract_interp(instr: Instruction, out: Formatter) -> None: - try: - _write_components_for_abstract_interp( - [Component(instr, instr.active_caches)], - out, - ) - except AssertionError as err: - raise AssertionError( - f"Error writing abstract instruction {instr.name}" - ) from err - - -def _write_components_for_abstract_interp( - parts: list[Component], - out: Formatter, -) -> None: - managers = get_managers(parts) - for mgr in managers: - if mgr is managers[-1]: - out.stack_adjust(mgr.final_offset.deep, mgr.final_offset.high) - mgr.adjust_inverse(mgr.final_offset) - # NULL out the output stack effects - for poke in mgr.pokes: - if not poke.effect.size and poke.effect.name not in mgr.instr.unmoved_names: - out.emit( - f"PARTITIONNODE_OVERWRITE((_Py_PARTITIONNODE_t *)" - f"PARTITIONNODE_NULLROOT, PEEK(-({poke.offset.as_index()})), true);" - ) From ca8b1d09585c032416c5d4905f707550eecf327f Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 21 Dec 2023 14:43:38 +0100 Subject: [PATCH 327/442] gh-87277: Don't look for X11 browsers on macOS in webbrowser (#24480) The installation of XQuartz on macOS will unconditionally set the $DISPLAY variable. The X11 server will be launched when a program tries to access the display. This results in launching the X11 server when using the webbrowser module, even though X11 browsers won't be used in practice. --- Lib/test/test_webbrowser.py | 11 +++++++++++ Lib/webbrowser.py | 7 ++++++- .../2023-12-21-09-41-42.gh-issue-87277.IF6EZZ.rst | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-21-09-41-42.gh-issue-87277.IF6EZZ.rst diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 2d695bc883131f..ca481c57c3d972 100644 --- a/Lib/test/test_webbrowser.py +++ b/Lib/test/test_webbrowser.py @@ -272,6 +272,17 @@ def test_register_preferred(self): self._check_registration(preferred=True) + @unittest.skipUnless(sys.platform == "darwin", "macOS specific test") + def test_no_xdg_settings_on_macOS(self): + # On macOS webbrowser should not use xdg-settings to + # look for X11 based browsers (for those users with + # XQuartz installed) + with mock.patch("subprocess.check_output") as ck_o: + webbrowser.register_standard_browsers() + + ck_o.assert_not_called() + + class ImportTest(unittest.TestCase): def test_register(self): webbrowser = import_helper.import_fresh_module('webbrowser') diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 8b0628745c57fc..6f9c6a6de177e6 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -495,7 +495,12 @@ def register_standard_browsers(): register("microsoft-edge", None, Edge("MicrosoftEdge.exe")) else: # Prefer X browsers if present - if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"): + # + # NOTE: Do not check for X11 browser on macOS, + # XQuartz installation sets a DISPLAY environment variable and will + # autostart when someone tries to access the display. Mac users in + # general don't need an X11 browser. + if sys.platform != "darwin" and (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")): try: cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) diff --git a/Misc/NEWS.d/next/macOS/2023-12-21-09-41-42.gh-issue-87277.IF6EZZ.rst b/Misc/NEWS.d/next/macOS/2023-12-21-09-41-42.gh-issue-87277.IF6EZZ.rst new file mode 100644 index 00000000000000..4ae55c0293198a --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-21-09-41-42.gh-issue-87277.IF6EZZ.rst @@ -0,0 +1,3 @@ +webbrowser: Don't look for X11 browsers on macOS. Those are generally not +used and probing for them can result in starting XQuartz even if it isn't +used otherwise. From 526d0a9b6eafb95c425838715a4961d97dc600da Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 21 Dec 2023 15:22:39 +0100 Subject: [PATCH 328/442] gh-110383: Improve accuracy of str.split() and str.rsplit() docstrings (#113355) Clarify split direction in the docstring body, instead of in the 'maxsplit' param docstring. --- Objects/clinic/unicodeobject.c.h | 8 +++++--- Objects/unicodeobject.c | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Objects/clinic/unicodeobject.c.h b/Objects/clinic/unicodeobject.c.h index 7711434f17c2bc..3e5167d9242fe4 100644 --- a/Objects/clinic/unicodeobject.c.h +++ b/Objects/clinic/unicodeobject.c.h @@ -954,9 +954,11 @@ PyDoc_STRVAR(unicode_split__doc__, " character (including \\n \\r \\t \\f and spaces) and will discard\n" " empty strings from the result.\n" " maxsplit\n" -" Maximum number of splits (starting from the left).\n" +" Maximum number of splits.\n" " -1 (the default value) means no limit.\n" "\n" +"Splitting starts at the front of the string and works to the end.\n" +"\n" "Note, str.split() is mainly useful for data that has been intentionally\n" "delimited. With natural text that includes punctuation, consider using\n" "the regular expression module."); @@ -1078,7 +1080,7 @@ PyDoc_STRVAR(unicode_rsplit__doc__, " character (including \\n \\r \\t \\f and spaces) and will discard\n" " empty strings from the result.\n" " maxsplit\n" -" Maximum number of splits (starting from the left).\n" +" Maximum number of splits.\n" " -1 (the default value) means no limit.\n" "\n" "Splitting starts at the end of the string and works to the front."); @@ -1505,4 +1507,4 @@ unicode_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=873d8b3d09af3095 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1aab29bab5201c78 input=a9049054013a1b77]*/ diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 836e14fd5d5dea..ad87206b2a8200 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -12504,11 +12504,13 @@ str.split as unicode_split character (including \n \r \t \f and spaces) and will discard empty strings from the result. maxsplit: Py_ssize_t = -1 - Maximum number of splits (starting from the left). + Maximum number of splits. -1 (the default value) means no limit. Return a list of the substrings in the string, using sep as the separator string. +Splitting starts at the front of the string and works to the end. + Note, str.split() is mainly useful for data that has been intentionally delimited. With natural text that includes punctuation, consider using the regular expression module. @@ -12517,7 +12519,7 @@ the regular expression module. static PyObject * unicode_split_impl(PyObject *self, PyObject *sep, Py_ssize_t maxsplit) -/*[clinic end generated code: output=3a65b1db356948dc input=07b9040d98c5fe8d]*/ +/*[clinic end generated code: output=3a65b1db356948dc input=a29bcc0c7a5af0eb]*/ { if (sep == Py_None) return split(self, NULL, maxsplit); From df1eec3dae3b1eddff819fd70f58b03b3fbd0eda Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 21 Dec 2023 16:28:00 +0100 Subject: [PATCH 329/442] gh-65701: document that freeze doesn't work with framework builds on macOS (#113352) * gh-65701: document that freeze doesn't work with framework builds on macOS The framework install is inherently incompatible with freeze. Document that that freeze doesn't work with framework builds and bail out early when trying to run freeze anyway. Co-authored-by: Erlend E. Aasland --- .../next/macOS/2023-12-21-10-20-41.gh-issue-65701.Q2hNbN.rst | 2 ++ Tools/freeze/README | 5 +++++ Tools/freeze/freeze.py | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-21-10-20-41.gh-issue-65701.Q2hNbN.rst diff --git a/Misc/NEWS.d/next/macOS/2023-12-21-10-20-41.gh-issue-65701.Q2hNbN.rst b/Misc/NEWS.d/next/macOS/2023-12-21-10-20-41.gh-issue-65701.Q2hNbN.rst new file mode 100644 index 00000000000000..870b84a4d1af80 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-21-10-20-41.gh-issue-65701.Q2hNbN.rst @@ -0,0 +1,2 @@ +The :program:`freeze` tool doesn't work with framework builds of Python. +Document this and bail out early when running the tool with such a build. diff --git a/Tools/freeze/README b/Tools/freeze/README index 9b3ea1f2c723b1..516077bc7daa89 100644 --- a/Tools/freeze/README +++ b/Tools/freeze/README @@ -218,6 +218,11 @@ source tree). It is possible to create frozen programs that don't have a console window, by specifying the option '-s windows'. See the Usage below. +Usage under macOS +----------------- + +On macOS the freeze tool is not supported for framework builds. + Usage ----- diff --git a/Tools/freeze/freeze.py b/Tools/freeze/freeze.py index bc5e43f4853deb..de9772732cdb5d 100755 --- a/Tools/freeze/freeze.py +++ b/Tools/freeze/freeze.py @@ -136,6 +136,11 @@ def main(): makefile = 'Makefile' subsystem = 'console' + if sys.platform == "darwin" and sysconfig.get_config_var("PYTHONFRAMEWORK"): + print(f"{sys.argv[0]} cannot be used with framework builds of Python", file=sys.stderr) + sys.exit(1) + + # parse command line by first replacing any "-i" options with the # file contents. pos = 1 From 5f7a80fd02158d9c655eff4202498f5cab9b2ca4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 21 Dec 2023 20:12:07 +0200 Subject: [PATCH 330/442] gh-113325: Remove a debugging print accidentally left in test_symtable (GH-113368) --- Lib/test/test_symtable.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_symtable.py b/Lib/test/test_symtable.py index 987e9e32afc325..92b78a8086a83d 100644 --- a/Lib/test/test_symtable.py +++ b/Lib/test/test_symtable.py @@ -337,7 +337,6 @@ def test_stdin(self): symtable.main(['-']) self.assertEqual(stdout.getvalue(), out) lines = out.splitlines() - print(out) self.assertIn("symbol table for module from file '':", lines) From 6b70c3dc5ab2f290fcdbe474bcb7d6fdf29eae4c Mon Sep 17 00:00:00 2001 From: Namhyung Kim Date: Thu, 21 Dec 2023 11:28:55 -0800 Subject: [PATCH 331/442] gh-113343: Fix error check on mmap(2) (#113342) Fix error check on mmap(2) It should check MAP_FAILED instead of NULL for error. On mmap(2) man page: RETURN VALUE On success, mmap() returns a pointer to the mapped area. On error, the value MAP_FAILED (that is, (void *) -1) is returned, and errno is set to indicate the error. --- Python/perf_trampoline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/perf_trampoline.c b/Python/perf_trampoline.c index 540b650192ed34..750ba18d3510ed 100644 --- a/Python/perf_trampoline.c +++ b/Python/perf_trampoline.c @@ -247,7 +247,7 @@ new_code_arena(void) mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, // fd (not used here) 0); // offset (not used here) - if (!memory) { + if (memory == MAP_FAILED) { PyErr_SetFromErrno(PyExc_OSError); PyErr_FormatUnraisable("Failed to create new mmap for perf trampoline"); perf_status = PERF_STATUS_FAILED; From 2d91409c690b113493e3e81efc880301d2949f5f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 21 Dec 2023 15:04:05 -0500 Subject: [PATCH 332/442] gh-113174: Sync with importlib_metadata 7.0 (#113175) * Sync with importlib_metadata 7.0.0 * Add blurb * Update docs to reflect changes. * Link datamodel docs for object.__getitem__ Co-authored-by: Alex Waygood * Add what's new for removed __getattr__ * Link datamodel docs for object.__getitem__ Co-authored-by: Alex Waygood * Add exclamation point, as that seems to be used for other classes. --------- Co-authored-by: Alex Waygood --- Doc/library/importlib.metadata.rst | 30 ++- Doc/whatsnew/3.13.rst | 4 + Lib/importlib/metadata/__init__.py | 183 ++++++++++-------- Lib/importlib/metadata/_adapters.py | 2 +- Lib/importlib/metadata/_meta.py | 2 +- Lib/importlib/metadata/diagnose.py | 21 ++ Lib/test/test_importlib/_path.py | 33 ++-- Lib/test/test_importlib/fixtures.py | 84 ++++---- Lib/test/test_importlib/test_main.py | 45 ++++- ...-12-15-09-51-41.gh-issue-113175.RHsNwE.rst | 5 + 10 files changed, 265 insertions(+), 144 deletions(-) create mode 100644 Lib/importlib/metadata/diagnose.py create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-09-51-41.gh-issue-113175.RHsNwE.rst diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 1df7d8d772a274..cc4a0da92da60a 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -171,16 +171,18 @@ group. Read `the setuptools docs `_ for more information on entry points, their definition, and usage. -*Compatibility Note* - -The "selectable" entry points were introduced in ``importlib_metadata`` -3.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted -no parameters and always returned a dictionary of entry points, keyed -by group. With ``importlib_metadata`` 5.0 and Python 3.12, -``entry_points`` always returns an ``EntryPoints`` object. See -`backports.entry_points_selectable `_ -for compatibility options. - +.. versionchanged:: 3.12 + The "selectable" entry points were introduced in ``importlib_metadata`` + 3.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted + no parameters and always returned a dictionary of entry points, keyed + by group. With ``importlib_metadata`` 5.0 and Python 3.12, + ``entry_points`` always returns an ``EntryPoints`` object. See + `backports.entry_points_selectable `_ + for compatibility options. + +.. versionchanged:: 3.13 + ``EntryPoint`` objects no longer present a tuple-like interface + (:meth:`~object.__getitem__`). .. _metadata: @@ -342,9 +344,17 @@ instance:: >>> dist.metadata['License'] # doctest: +SKIP 'MIT' +For editable packages, an origin property may present :pep:`610` +metadata:: + + >>> dist.origin.url + 'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl' + The full set of available metadata is not described here. See the `Core metadata specifications `_ for additional details. +.. versionadded:: 3.13 + The ``.origin`` property was added. Distribution Discovery ====================== diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 2c869cbe11396b..7dc02dacdc68f7 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1001,6 +1001,10 @@ importlib for migration advice. (Contributed by Jason R. Coombs in :gh:`106532`.) +* Remove deprecated :meth:`~object.__getitem__` access for + :class:`!importlib.metadata.EntryPoint` objects. + (Contributed by Jason R. Coombs in :gh:`113175`.) + locale ------ diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 5c09666b6a40d9..7b142e786e829e 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -3,7 +3,10 @@ import abc import csv import sys +import json import email +import types +import inspect import pathlib import zipfile import operator @@ -13,7 +16,6 @@ import itertools import posixpath import collections -import inspect from . import _adapters, _meta from ._collections import FreezableDefaultDict, Pair @@ -25,8 +27,7 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap -from typing import List, Mapping, Optional, cast - +from typing import Iterable, List, Mapping, Optional, Set, Union, cast __all__ = [ 'Distribution', @@ -47,11 +48,11 @@ class PackageNotFoundError(ModuleNotFoundError): """The package was not found.""" - def __str__(self): + def __str__(self) -> str: return f"No package metadata was found for {self.name}" @property - def name(self): + def name(self) -> str: # type: ignore[override] (name,) = self.args return name @@ -117,38 +118,11 @@ def read(text, filter_=None): yield Pair(name, value) @staticmethod - def valid(line): + def valid(line: str): return line and not line.startswith('#') -class DeprecatedTuple: - """ - Provide subscript item access for backward compatibility. - - >>> recwarn = getfixture('recwarn') - >>> ep = EntryPoint(name='name', value='value', group='group') - >>> ep[:] - ('name', 'value', 'group') - >>> ep[0] - 'name' - >>> len(recwarn) - 1 - """ - - # Do not remove prior to 2023-05-01 or Python 3.13 - _warn = functools.partial( - warnings.warn, - "EntryPoint tuple interface is deprecated. Access members by name.", - DeprecationWarning, - stacklevel=2, - ) - - def __getitem__(self, item): - self._warn() - return self._key()[item] - - -class EntryPoint(DeprecatedTuple): +class EntryPoint: """An entry point as defined by Python packaging conventions. See `the packaging docs on entry points @@ -192,7 +166,7 @@ class EntryPoint(DeprecatedTuple): dist: Optional['Distribution'] = None - def __init__(self, name, value, group): + def __init__(self, name: str, value: str, group: str) -> None: vars(self).update(name=name, value=value, group=group) def load(self): @@ -206,18 +180,21 @@ def load(self): return functools.reduce(getattr, attrs, module) @property - def module(self): + def module(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('module') @property - def attr(self): + def attr(self) -> str: match = self.pattern.match(self.value) + assert match is not None return match.group('attr') @property - def extras(self): + def extras(self) -> List[str]: match = self.pattern.match(self.value) + assert match is not None return re.findall(r'\w+', match.group('extras') or '') def _for(self, dist): @@ -265,7 +242,7 @@ def __repr__(self): f'group={self.group!r})' ) - def __hash__(self): + def __hash__(self) -> int: return hash(self._key()) @@ -276,7 +253,7 @@ class EntryPoints(tuple): __slots__ = () - def __getitem__(self, name): # -> EntryPoint: + def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] """ Get the EntryPoint in self matching name. """ @@ -285,6 +262,13 @@ def __getitem__(self, name): # -> EntryPoint: except StopIteration: raise KeyError(name) + def __repr__(self): + """ + Repr with classname and tuple constructor to + signal that we deviate from regular tuple behavior. + """ + return '%s(%r)' % (self.__class__.__name__, tuple(self)) + def select(self, **params): """ Select entry points from self that match the @@ -293,14 +277,14 @@ def select(self, **params): return EntryPoints(ep for ep in self if ep.matches(**params)) @property - def names(self): + def names(self) -> Set[str]: """ Return the set of all names of all entry points. """ return {ep.name for ep in self} @property - def groups(self): + def groups(self) -> Set[str]: """ Return the set of all groups of all entry points. """ @@ -321,24 +305,28 @@ def _from_text(text): class PackagePath(pathlib.PurePosixPath): """A reference to a path in a package""" - def read_text(self, encoding='utf-8'): + hash: Optional["FileHash"] + size: int + dist: "Distribution" + + def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override] with self.locate().open(encoding=encoding) as stream: return stream.read() - def read_binary(self): + def read_binary(self) -> bytes: with self.locate().open('rb') as stream: return stream.read() - def locate(self): + def locate(self) -> pathlib.Path: """Return a path-like object for this path""" return self.dist.locate_file(self) class FileHash: - def __init__(self, spec): + def __init__(self, spec: str) -> None: self.mode, _, self.value = spec.partition('=') - def __repr__(self): + def __repr__(self) -> str: return f'' @@ -373,14 +361,14 @@ def read_text(self, filename) -> Optional[str]: """ @abc.abstractmethod - def locate_file(self, path): + def locate_file(self, path: Union[str, os.PathLike[str]]) -> pathlib.Path: """ Given a path to a file in this distribution, return a path to it. """ @classmethod - def from_name(cls, name: str): + def from_name(cls, name: str) -> "Distribution": """Return the Distribution for the given package name. :param name: The name of the distribution package to search for. @@ -393,12 +381,12 @@ def from_name(cls, name: str): if not name: raise ValueError("A distribution name is required.") try: - return next(cls.discover(name=name)) + return next(iter(cls.discover(name=name))) except StopIteration: raise PackageNotFoundError(name) @classmethod - def discover(cls, **kwargs): + def discover(cls, **kwargs) -> Iterable["Distribution"]: """Return an iterable of Distribution objects for all packages. Pass a ``context`` or pass keyword arguments for constructing @@ -416,7 +404,7 @@ def discover(cls, **kwargs): ) @staticmethod - def at(path): + def at(path: Union[str, os.PathLike[str]]) -> "Distribution": """Return a Distribution for the indicated metadata path :param path: a string or path-like object @@ -451,7 +439,7 @@ def metadata(self) -> _meta.PackageMetadata: return _adapters.Message(email.message_from_string(text)) @property - def name(self): + def name(self) -> str: """Return the 'Name' metadata for the distribution package.""" return self.metadata['Name'] @@ -461,16 +449,16 @@ def _normalized_name(self): return Prepared.normalize(self.name) @property - def version(self): + def version(self) -> str: """Return the 'Version' metadata for the distribution package.""" return self.metadata['Version'] @property - def entry_points(self): + def entry_points(self) -> EntryPoints: return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) @property - def files(self): + def files(self) -> Optional[List[PackagePath]]: """Files in this distribution. :return: List of PackagePath for this distribution or None @@ -555,7 +543,7 @@ def _read_files_egginfo_sources(self): return text and map('"{}"'.format, text.splitlines()) @property - def requires(self): + def requires(self) -> Optional[List[str]]: """Generated requirements specified for this Distribution""" reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() return reqs and list(reqs) @@ -606,6 +594,16 @@ def url_req_space(req): space = url_req_space(section.value) yield section.value + space + quoted_marker(section.name) + @property + def origin(self): + return self._load_json('direct_url.json') + + def _load_json(self, filename): + return pass_none(json.loads)( + self.read_text(filename), + object_hook=lambda data: types.SimpleNamespace(**data), + ) + class DistributionFinder(MetaPathFinder): """ @@ -634,7 +632,7 @@ def __init__(self, **kwargs): vars(self).update(kwargs) @property - def path(self): + def path(self) -> List[str]: """ The sequence of directory path that a distribution finder should search. @@ -645,7 +643,7 @@ def path(self): return vars(self).get('path', sys.path) @abc.abstractmethod - def find_distributions(self, context=Context()): + def find_distributions(self, context=Context()) -> Iterable[Distribution]: """ Find distributions. @@ -774,7 +772,9 @@ def __bool__(self): class MetadataPathFinder(DistributionFinder): @classmethod - def find_distributions(cls, context=DistributionFinder.Context()): + def find_distributions( + cls, context=DistributionFinder.Context() + ) -> Iterable["PathDistribution"]: """ Find distributions. @@ -794,19 +794,19 @@ def _search_paths(cls, name, paths): path.search(prepared) for path in map(FastPath, paths) ) - def invalidate_caches(cls): + def invalidate_caches(cls) -> None: FastPath.__new__.cache_clear() class PathDistribution(Distribution): - def __init__(self, path: SimplePath): + def __init__(self, path: SimplePath) -> None: """Construct a distribution. :param path: SimplePath indicating the metadata directory. """ self._path = path - def read_text(self, filename): + def read_text(self, filename: Union[str, os.PathLike[str]]) -> Optional[str]: with suppress( FileNotFoundError, IsADirectoryError, @@ -816,9 +816,11 @@ def read_text(self, filename): ): return self._path.joinpath(filename).read_text(encoding='utf-8') + return None + read_text.__doc__ = Distribution.read_text.__doc__ - def locate_file(self, path): + def locate_file(self, path: Union[str, os.PathLike[str]]) -> pathlib.Path: return self._path.parent / path @property @@ -851,7 +853,7 @@ def _name_from_stem(stem): return name -def distribution(distribution_name): +def distribution(distribution_name: str) -> Distribution: """Get the ``Distribution`` instance for the named package. :param distribution_name: The name of the distribution package as a string. @@ -860,7 +862,7 @@ def distribution(distribution_name): return Distribution.from_name(distribution_name) -def distributions(**kwargs): +def distributions(**kwargs) -> Iterable[Distribution]: """Get all ``Distribution`` instances in the current environment. :return: An iterable of ``Distribution`` instances. @@ -868,7 +870,7 @@ def distributions(**kwargs): return Distribution.discover(**kwargs) -def metadata(distribution_name) -> _meta.PackageMetadata: +def metadata(distribution_name: str) -> _meta.PackageMetadata: """Get the metadata for the named package. :param distribution_name: The name of the distribution package to query. @@ -877,7 +879,7 @@ def metadata(distribution_name) -> _meta.PackageMetadata: return Distribution.from_name(distribution_name).metadata -def version(distribution_name): +def version(distribution_name: str) -> str: """Get the version string for the named package. :param distribution_name: The name of the distribution package to query. @@ -911,7 +913,7 @@ def entry_points(**params) -> EntryPoints: return EntryPoints(eps).select(**params) -def files(distribution_name): +def files(distribution_name: str) -> Optional[List[PackagePath]]: """Return a list of files for the named package. :param distribution_name: The name of the distribution package to query. @@ -920,11 +922,11 @@ def files(distribution_name): return distribution(distribution_name).files -def requires(distribution_name): +def requires(distribution_name: str) -> Optional[List[str]]: """ Return a list of requirements for the named package. - :return: An iterator of requirements, suitable for + :return: An iterable of requirements, suitable for packaging.requirement.Requirement. """ return distribution(distribution_name).requires @@ -951,13 +953,42 @@ def _top_level_declared(dist): return (dist.read_text('top_level.txt') or '').split() +def _topmost(name: PackagePath) -> Optional[str]: + """ + Return the top-most parent as long as there is a parent. + """ + top, *rest = name.parts + return top if rest else None + + +def _get_toplevel_name(name: PackagePath) -> str: + """ + Infer a possibly importable module name from a name presumed on + sys.path. + + >>> _get_toplevel_name(PackagePath('foo.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pyc')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo/__init__.py')) + 'foo' + >>> _get_toplevel_name(PackagePath('foo.pth')) + 'foo.pth' + >>> _get_toplevel_name(PackagePath('foo.dist-info')) + 'foo.dist-info' + """ + return _topmost(name) or ( + # python/typeshed#10328 + inspect.getmodulename(name) # type: ignore + or str(name) + ) + + def _top_level_inferred(dist): - opt_names = { - f.parts[0] if len(f.parts) > 1 else inspect.getmodulename(f) - for f in always_iterable(dist.files) - } + opt_names = set(map(_get_toplevel_name, always_iterable(dist.files))) - @pass_none def importable_name(name): return '.' not in name diff --git a/Lib/importlib/metadata/_adapters.py b/Lib/importlib/metadata/_adapters.py index 6aed69a30857e4..591168808953ba 100644 --- a/Lib/importlib/metadata/_adapters.py +++ b/Lib/importlib/metadata/_adapters.py @@ -53,7 +53,7 @@ def __iter__(self): def __getitem__(self, item): """ Warn users that a ``KeyError`` can be expected when a - mising key is supplied. Ref python/importlib_metadata#371. + missing key is supplied. Ref python/importlib_metadata#371. """ res = super().__getitem__(item) if res is None: diff --git a/Lib/importlib/metadata/_meta.py b/Lib/importlib/metadata/_meta.py index c9a7ef906a8a8c..f670016de7fef2 100644 --- a/Lib/importlib/metadata/_meta.py +++ b/Lib/importlib/metadata/_meta.py @@ -49,7 +49,7 @@ class SimplePath(Protocol[_T]): A minimal subset of pathlib.Path required by PathDistribution. """ - def joinpath(self) -> _T: + def joinpath(self, other: Union[str, _T]) -> _T: ... # pragma: no cover def __truediv__(self, other: Union[str, _T]) -> _T: diff --git a/Lib/importlib/metadata/diagnose.py b/Lib/importlib/metadata/diagnose.py new file mode 100644 index 00000000000000..e405471ac4d943 --- /dev/null +++ b/Lib/importlib/metadata/diagnose.py @@ -0,0 +1,21 @@ +import sys + +from . import Distribution + + +def inspect(path): + print("Inspecting", path) + dists = list(Distribution.discover(path=[path])) + if not dists: + return + print("Found", len(dists), "packages:", end=' ') + print(', '.join(dist.name for dist in dists)) + + +def run(): + for path in sys.path: + inspect(path) + + +if __name__ == '__main__': + run() diff --git a/Lib/test/test_importlib/_path.py b/Lib/test/test_importlib/_path.py index 71a704389b986e..25c799fa44cd55 100644 --- a/Lib/test/test_importlib/_path.py +++ b/Lib/test/test_importlib/_path.py @@ -1,17 +1,18 @@ -# from jaraco.path 3.5 +# from jaraco.path 3.7 import functools import pathlib -from typing import Dict, Union +from typing import Dict, Protocol, Union +from typing import runtime_checkable -try: - from typing import Protocol, runtime_checkable -except ImportError: # pragma: no cover - # Python 3.7 - from typing_extensions import Protocol, runtime_checkable # type: ignore + +class Symlink(str): + """ + A string indicating the target of a symlink. + """ -FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore +FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore @runtime_checkable @@ -28,6 +29,9 @@ def write_text(self, content, **kwargs): def write_bytes(self, content): ... # pragma: no cover + def symlink_to(self, target): + ... # pragma: no cover + def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker: return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore @@ -51,12 +55,16 @@ def build( ... "__init__.py": "", ... }, ... "baz.py": "# Some code", - ... } + ... "bar.py": Symlink("baz.py"), + ... }, + ... "bing": Symlink("foo"), ... } >>> target = getfixture('tmp_path') >>> build(spec, target) >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8') '# Some code' + >>> target.joinpath('bing/bar.py').read_text(encoding='utf-8') + '# Some code' """ for name, contents in spec.items(): create(contents, _ensure_tree_maker(prefix) / name) @@ -79,8 +87,8 @@ def _(content: str, path): @create.register -def _(content: str, path): - path.write_text(content, encoding='utf-8') +def _(content: Symlink, path): + path.symlink_to(content) class Recording: @@ -107,3 +115,6 @@ def write_text(self, content, **kwargs): def mkdir(self, **kwargs): return + + def symlink_to(self, target): + pass diff --git a/Lib/test/test_importlib/fixtures.py b/Lib/test/test_importlib/fixtures.py index 73e5da2ba92279..8c973356b5660d 100644 --- a/Lib/test/test_importlib/fixtures.py +++ b/Lib/test/test_importlib/fixtures.py @@ -1,6 +1,7 @@ import os import sys import copy +import json import shutil import pathlib import tempfile @@ -86,7 +87,15 @@ def setUp(self): self.fixtures.enter_context(self.add_sys_path(self.site_dir)) -class DistInfoPkg(OnSysPath, SiteDir): +class SiteBuilder(SiteDir): + def setUp(self): + super().setUp() + for cls in self.__class__.mro(): + with contextlib.suppress(AttributeError): + build_files(cls.files, prefix=self.site_dir) + + +class DistInfoPkg(OnSysPath, SiteBuilder): files: FilesSpec = { "distinfo_pkg-1.0.0.dist-info": { "METADATA": """ @@ -113,10 +122,6 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(DistInfoPkg.files, self.site_dir) - def make_uppercase(self): """ Rewrite metadata with everything uppercase. @@ -128,7 +133,28 @@ def make_uppercase(self): build_files(files, self.site_dir) -class DistInfoPkgWithDot(OnSysPath, SiteDir): +class DistInfoPkgEditable(DistInfoPkg): + """ + Package with a PEP 660 direct_url.json. + """ + + some_hash = '524127ce937f7cb65665130c695abd18ca386f60bb29687efb976faa1596fdcc' + files: FilesSpec = { + 'distinfo_pkg-1.0.0.dist-info': { + 'direct_url.json': json.dumps( + { + "archive_info": { + "hash": f"sha256={some_hash}", + "hashes": {"sha256": f"{some_hash}"}, + }, + "url": "file:///path/to/distinfo_pkg-1.0.0.editable-py3-none-any.whl", + } + ) + }, + } + + +class DistInfoPkgWithDot(OnSysPath, SiteBuilder): files: FilesSpec = { "pkg_dot-1.0.0.dist-info": { "METADATA": """ @@ -138,12 +164,8 @@ class DistInfoPkgWithDot(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(DistInfoPkgWithDot.files, self.site_dir) - -class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): +class DistInfoPkgWithDotLegacy(OnSysPath, SiteBuilder): files: FilesSpec = { "pkg.dot-1.0.0.dist-info": { "METADATA": """ @@ -159,18 +181,12 @@ class DistInfoPkgWithDotLegacy(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(DistInfoPkgWithDotLegacy.files, self.site_dir) - -class DistInfoPkgOffPath(SiteDir): - def setUp(self): - super().setUp() - build_files(DistInfoPkg.files, self.site_dir) +class DistInfoPkgOffPath(SiteBuilder): + files = DistInfoPkg.files -class EggInfoPkg(OnSysPath, SiteDir): +class EggInfoPkg(OnSysPath, SiteBuilder): files: FilesSpec = { "egginfo_pkg.egg-info": { "PKG-INFO": """ @@ -205,12 +221,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkg.files, prefix=self.site_dir) - -class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteDir): +class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteBuilder): files: FilesSpec = { "egg_with_module_pkg.egg-info": { "PKG-INFO": "Name: egg_with_module-pkg", @@ -240,12 +252,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgPipInstalledNoToplevel.files, prefix=self.site_dir) - -class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteDir): +class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder): files: FilesSpec = { "egg_with_no_modules_pkg.egg-info": { "PKG-INFO": "Name: egg_with_no_modules-pkg", @@ -270,12 +278,8 @@ class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteDir): }, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgPipInstalledNoModules.files, prefix=self.site_dir) - -class EggInfoPkgSourcesFallback(OnSysPath, SiteDir): +class EggInfoPkgSourcesFallback(OnSysPath, SiteBuilder): files: FilesSpec = { "sources_fallback_pkg.egg-info": { "PKG-INFO": "Name: sources_fallback-pkg", @@ -296,12 +300,8 @@ def main(): """, } - def setUp(self): - super().setUp() - build_files(EggInfoPkgSourcesFallback.files, prefix=self.site_dir) - -class EggInfoFile(OnSysPath, SiteDir): +class EggInfoFile(OnSysPath, SiteBuilder): files: FilesSpec = { "egginfo_file.egg-info": """ Metadata-Version: 1.0 @@ -317,10 +317,6 @@ class EggInfoFile(OnSysPath, SiteDir): """, } - def setUp(self): - super().setUp() - build_files(EggInfoFile.files, prefix=self.site_dir) - # dedent all text strings before writing orig = _path.create.registry[str] diff --git a/Lib/test/test_importlib/test_main.py b/Lib/test/test_importlib/test_main.py index 3b49227255eb58..1d3817151edf64 100644 --- a/Lib/test/test_importlib/test_main.py +++ b/Lib/test/test_importlib/test_main.py @@ -12,6 +12,7 @@ from . import fixtures from ._context import suppress +from ._path import Symlink from importlib.metadata import ( Distribution, EntryPoint, @@ -68,7 +69,7 @@ def test_abc_enforced(self): dict(name=''), ) def test_invalid_inputs_to_from_name(self, name): - with self.assertRaises(ValueError): + with self.assertRaises(Exception): Distribution.from_name(name) @@ -207,6 +208,20 @@ def test_invalid_usage(self): with self.assertRaises(ValueError): list(distributions(context='something', name='else')) + def test_interleaved_discovery(self): + """ + Ensure interleaved searches are safe. + + When the search is cached, it is possible for searches to be + interleaved, so make sure those use-cases are safe. + + Ref #293 + """ + dists = distributions() + next(dists) + version('egginfo-pkg') + next(dists) + class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase): def test_egg_info(self): @@ -388,6 +403,27 @@ def test_packages_distributions_all_module_types(self): assert not any(name.endswith('.dist-info') for name in distributions) + def test_packages_distributions_symlinked_top_level(self) -> None: + """ + Distribution is resolvable from a simple top-level symlink in RECORD. + See #452. + """ + + files: fixtures.FilesSpec = { + "symlinked_pkg-1.0.0.dist-info": { + "METADATA": """ + Name: symlinked-pkg + Version: 1.0.0 + """, + "RECORD": "symlinked,,\n", + }, + ".symlink.target": {}, + "symlinked": Symlink(".symlink.target"), + } + + fixtures.build_files(files, self.site_dir) + assert packages_distributions()['symlinked'] == ['symlinked-pkg'] + class PackagesDistributionsEggTest( fixtures.EggInfoPkg, @@ -424,3 +460,10 @@ def import_names_from_package(package_name): # sources_fallback-pkg has one import ('sources_fallback') inferred from # SOURCES.txt (top_level.txt and installed-files.txt is missing) assert import_names_from_package('sources_fallback-pkg') == {'sources_fallback'} + + +class EditableDistributionTest(fixtures.DistInfoPkgEditable, unittest.TestCase): + def test_origin(self): + dist = Distribution.from_name('distinfo-pkg') + assert dist.origin.url.endswith('.whl') + assert dist.origin.archive_info.hashes.sha256 diff --git a/Misc/NEWS.d/next/Library/2023-12-15-09-51-41.gh-issue-113175.RHsNwE.rst b/Misc/NEWS.d/next/Library/2023-12-15-09-51-41.gh-issue-113175.RHsNwE.rst new file mode 100644 index 00000000000000..1b43803d1a7aa4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-09-51-41.gh-issue-113175.RHsNwE.rst @@ -0,0 +1,5 @@ +Sync with importlib_metadata 7.0, including improved type annotations, fixed +issue with symlinked packages in ``package_distributions``, added +``EntryPoints.__repr__``, introduced the ``diagnose`` script, added +``Distribution.origin`` property, and removed deprecated ``EntryPoint`` +access by numeric index (tuple behavior). From 61e818409567ce452af60605937cdedf582f6293 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:24:10 -0800 Subject: [PATCH 333/442] gh-95754: Better AttributeError on partially initialised module (#112577) Co-authored-by: Serhiy Storchaka --- Lib/test/test_import/__init__.py | 8 +++++++ .../data/circular_imports/import_cycle.py | 3 +++ ...3-12-01-08-16-10.gh-issue-95754.ae4gwy.rst | 1 + Objects/moduleobject.c | 24 +++++++++++++++++-- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 Lib/test/test_import/data/circular_imports/import_cycle.py create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index 48c0a43f29e27f..7b0126226c4aba 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1632,6 +1632,14 @@ def test_circular_from_import(self): str(cm.exception), ) + def test_circular_import(self): + with self.assertRaisesRegex( + AttributeError, + r"partially initialized module 'test.test_import.data.circular_imports.import_cycle' " + r"from '.*' has no attribute 'some_attribute' \(most likely due to a circular import\)" + ): + import test.test_import.data.circular_imports.import_cycle + def test_absolute_circular_submodule(self): with self.assertRaises(AttributeError) as cm: import test.test_import.data.circular_imports.subpkg2.parent diff --git a/Lib/test/test_import/data/circular_imports/import_cycle.py b/Lib/test/test_import/data/circular_imports/import_cycle.py new file mode 100644 index 00000000000000..cd9507b5f69e25 --- /dev/null +++ b/Lib/test/test_import/data/circular_imports/import_cycle.py @@ -0,0 +1,3 @@ +import test.test_import.data.circular_imports.import_cycle as m + +m.some_attribute diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst new file mode 100644 index 00000000000000..0884bc4a4be726 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-01-08-16-10.gh-issue-95754.ae4gwy.rst @@ -0,0 +1 @@ +Provide a better error message when accessing invalid attributes on partially initialized modules. The origin of the module being accessed is now included in the message to help with the common issue of shadowing other modules. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index e2741fef6debd3..3a1c516658dce7 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -788,7 +788,7 @@ PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) { // When suppress=1, this function suppresses AttributeError. - PyObject *attr, *mod_name, *getattr; + PyObject *attr, *mod_name, *getattr, *origin; attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress); if (attr) { return attr; @@ -831,11 +831,31 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) if (suppress != 1) { int rc = _PyModuleSpec_IsInitializing(spec); if (rc > 0) { - PyErr_Format(PyExc_AttributeError, + int valid_spec = PyObject_GetOptionalAttr(spec, &_Py_ID(origin), &origin); + if (valid_spec == -1) { + Py_XDECREF(spec); + Py_DECREF(mod_name); + return NULL; + } + if (valid_spec == 1 && !PyUnicode_Check(origin)) { + valid_spec = 0; + Py_DECREF(origin); + } + if (valid_spec == 1) { + PyErr_Format(PyExc_AttributeError, + "partially initialized " + "module '%U' from '%U' has no attribute '%U' " + "(most likely due to a circular import)", + mod_name, origin, name); + Py_DECREF(origin); + } + else { + PyErr_Format(PyExc_AttributeError, "partially initialized " "module '%U' has no attribute '%U' " "(most likely due to a circular import)", mod_name, name); + } } else if (rc == 0) { rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name); From 31d8757b6070010b0fb92a989b1812ecf303059f Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 21 Dec 2023 16:44:55 -0500 Subject: [PATCH 334/442] gh-113370: Add missing obmalloc.o dependencies on mimalloc (#113371) --- Makefile.pre.in | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index cf3763ef546468..6a64547e97d266 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1564,7 +1564,7 @@ Objects/dictobject.o: $(srcdir)/Objects/stringlib/eq.h Objects/setobject.o: $(srcdir)/Objects/stringlib/eq.h Objects/obmalloc.o: $(srcdir)/Objects/mimalloc/alloc.c \ - $(srcdir)/Objects/mimalloc/alloc-aligned.c \ + $(srcdir)/Objects/mimalloc/alloc-aligned.c \ $(srcdir)/Objects/mimalloc/alloc-posix.c \ $(srcdir)/Objects/mimalloc/arena.c \ $(srcdir)/Objects/mimalloc/bitmap.c \ @@ -1577,7 +1577,10 @@ Objects/obmalloc.o: $(srcdir)/Objects/mimalloc/alloc.c \ $(srcdir)/Objects/mimalloc/segment.c \ $(srcdir)/Objects/mimalloc/segment-map.c \ $(srcdir)/Objects/mimalloc/stats.c \ - $(srcdir)/Objects/mimalloc/prim/prim.c + $(srcdir)/Objects/mimalloc/prim/prim.c \ + $(srcdir)/Objects/mimalloc/prim/osx/prim.c \ + $(srcdir)/Objects/mimalloc/prim/unix/prim.c \ + $(srcdir)/Objects/mimalloc/prim/wasi/prim.c Objects/mimalloc/page.o: $(srcdir)/Objects/mimalloc/page-queue.c From 6a5b4736e548fc5827c3bcf1a14193f77e1a989d Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 21 Dec 2023 15:54:59 -0600 Subject: [PATCH 335/442] gh-113313: Note that slice support is not required for all sequences. (gh-113377) --- Doc/reference/datamodel.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index c774d75433420e..b3af5c6298d02d 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2808,9 +2808,9 @@ through the object's keys; for sequences, it should iterate through the values. .. method:: object.__getitem__(self, key) Called to implement evaluation of ``self[key]``. For :term:`sequence` types, - the accepted keys should be integers and slice objects. Note that the - special interpretation of negative indexes (if the class wishes to emulate a - :term:`sequence` type) is up to the :meth:`__getitem__` method. If *key* is + the accepted keys should be integers. Optionally, they may support + :class:`slice` objects as well. Negative index support is also optional. + If *key* is of an inappropriate type, :exc:`TypeError` may be raised; if *key* is a value outside the set of indexes for the sequence (after any special interpretation of negative values), :exc:`IndexError` should be raised. For From d058eaeed44766a8291013b275ad22f153935d3b Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 21 Dec 2023 16:08:35 -0600 Subject: [PATCH 336/442] gh-113157 gh-89519: Fix method descriptors (gh-113233) Restore behaviors before classmethod descriptor chaining was introduced. --- Lib/test/test_descr.py | 15 +++++++++++++++ Objects/classobject.c | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 4a3db80ca43c27..fd0af9b30a0a71 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5004,6 +5004,21 @@ class Child(Parent): gc.collect() self.assertEqual(Parent.__subclasses__(), []) + def test_instance_method_get_behavior(self): + # test case for gh-113157 + + class A: + def meth(self): + return self + + class B: + pass + + a = A() + b = B() + b.meth = a.meth.__get__(b, B) + self.assertEqual(b.meth(), a) + def test_attr_raise_through_property(self): # test case for gh-103272 class A: diff --git a/Objects/classobject.c b/Objects/classobject.c index 618d88894debbe..d7e520f556d9a0 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -319,6 +319,13 @@ method_traverse(PyMethodObject *im, visitproc visit, void *arg) return 0; } +static PyObject * +method_descr_get(PyObject *meth, PyObject *obj, PyObject *cls) +{ + Py_INCREF(meth); + return meth; +} + PyTypeObject PyMethod_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "method", @@ -339,6 +346,7 @@ PyTypeObject PyMethod_Type = { .tp_methods = method_methods, .tp_members = method_memberlist, .tp_getset = method_getset, + .tp_descr_get = method_descr_get, .tp_new = method_new, }; From 7de9855410d034b2b7624a057dbf7c3f58ee5328 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 21 Dec 2023 23:14:24 +0000 Subject: [PATCH 337/442] Bump mypy to 1.8.0 (#113385) --- Tools/clinic/clinic.py | 2 +- Tools/requirements-dev.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index bf3199257f65c9..e8ba805bd53b45 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -5815,11 +5815,11 @@ def parse_parameter(self, line: str) -> None: parameter_name = parameter.arg name, legacy, kwargs = self.parse_converter(parameter.annotation) + value: object if not default: if self.parameter_state is ParamState.OPTIONAL: fail(f"Can't have a parameter without a default ({parameter_name!r}) " "after a parameter with a default!") - value: Sentinels | Null if is_vararg: value = NULL kwargs.setdefault('c_default', "NULL") diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index 3a2e62f70bbb60..b89f86a35d6115 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -1,6 +1,6 @@ # Requirements file for external linters and checks we run on # Tools/clinic, Tools/cases_generator/, and Tools/peg_generator/ in CI -mypy==1.7.1 +mypy==1.8.0 # needed for peg_generator: types-psutil==5.9.5.17 From 9afb0e1606cad41ed57c42ea0a53ac90433f211b Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 21 Dec 2023 19:38:27 -0500 Subject: [PATCH 338/442] gh-112027: Don't print mimalloc warning after mmap() call (gh-113372) gh-112027: Don't print mimalloc warning after mmap This changes the warning to a "verbose"-level message in prim.c. The address passed to mmap is only a hint -- it's normal for mmap() to sometimes not respect the hint and return a different address. --- Objects/mimalloc/prim/unix/prim.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/mimalloc/prim/unix/prim.c b/Objects/mimalloc/prim/unix/prim.c index cffbb2d0b4d7b2..2152017e01fb43 100644 --- a/Objects/mimalloc/prim/unix/prim.c +++ b/Objects/mimalloc/prim/unix/prim.c @@ -170,7 +170,7 @@ static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int p p = mmap(addr, size, protect_flags, flags | MAP_ALIGNED(n), fd, 0); if (p==MAP_FAILED || !_mi_is_aligned(p,try_alignment)) { int err = errno; - _mi_warning_message("unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, addr); + _mi_verbose_message("unable to directly request aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, addr); } if (p!=MAP_FAILED) return p; // fall back to regular mmap @@ -195,7 +195,7 @@ static void* unix_mmap_prim(void* addr, size_t size, size_t try_alignment, int p #else int err = errno; #endif - _mi_warning_message("unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, hint); + _mi_verbose_message("unable to directly request hinted aligned OS memory (error: %d (0x%x), size: 0x%zx bytes, alignment: 0x%zx, hint address: %p)\n", err, err, size, try_alignment, hint); } if (p!=MAP_FAILED) return p; // fall back to regular mmap From c31943af16f885c8cf5d5a690c25c366afdb2862 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Fri, 22 Dec 2023 01:50:26 +0000 Subject: [PATCH 339/442] gh-113297: Fix segfault in compiler for with statement with 19 context managers (#113327) --- Lib/test/test_syntax.py | 26 +++++++++++++++++++ ...-12-20-18-27-11.gh-issue-113297.BZyAI_.rst | 1 + Python/flowgraph.c | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-20-18-27-11.gh-issue-113297.BZyAI_.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index ece1366076798c..8b3ca69c9fe155 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2017,6 +2017,7 @@ def f(x: *b) import re import doctest +import textwrap import unittest from test import support @@ -2279,6 +2280,31 @@ def test_nested_named_except_blocks(self): code += f"{' '*4*12}pass" self._check_error(code, "too many statically nested blocks") + @support.cpython_only + def test_with_statement_many_context_managers(self): + # See gh-113297 + + def get_code(n): + code = textwrap.dedent(""" + def bug(): + with ( + a + """) + for i in range(n): + code += f" as a{i}, a\n" + code += "): yield a" + return code + + CO_MAXBLOCKS = 20 # static nesting limit of the compiler + + for n in range(CO_MAXBLOCKS): + with self.subTest(f"within range: {n=}"): + compile(get_code(n), "", "exec") + + for n in range(CO_MAXBLOCKS, CO_MAXBLOCKS + 5): + with self.subTest(f"out of range: {n=}"): + self._check_error(get_code(n), "too many statically nested blocks") + def test_barry_as_flufl_with_syntax_errors(self): # The "barry_as_flufl" rule can produce some "bugs-at-a-distance" if # is reading the wrong token in the presence of syntax errors later diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-20-18-27-11.gh-issue-113297.BZyAI_.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-20-18-27-11.gh-issue-113297.BZyAI_.rst new file mode 100644 index 00000000000000..b6aee1f241fd23 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-20-18-27-11.gh-issue-113297.BZyAI_.rst @@ -0,0 +1 @@ +Fix segfault in the compiler on with statement with 19 context managers. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index e6c824a85ef51e..0e6ffbc32e1526 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -648,7 +648,7 @@ mark_except_handlers(basicblock *entryblock) { struct _PyCfgExceptStack { - basicblock *handlers[CO_MAXBLOCKS+1]; + basicblock *handlers[CO_MAXBLOCKS+2]; int depth; }; @@ -661,6 +661,7 @@ push_except_block(struct _PyCfgExceptStack *stack, cfg_instr *setup) { if (opcode == SETUP_WITH || opcode == SETUP_CLEANUP) { target->b_preserve_lasti = 1; } + assert(stack->depth <= CO_MAXBLOCKS); stack->handlers[++stack->depth] = target; return target; } From bee627c1e29a070562d1a540a6e513d0daa322f5 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Fri, 22 Dec 2023 11:35:54 +0100 Subject: [PATCH 340/442] gh-113384: Skip test_freeze for framework builds on macOS (#113390) --- Lib/test/test_tools/test_freeze.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 671ec2961e7f8f..0e7ed67de71067 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -14,6 +14,8 @@ @support.requires_zlib() @unittest.skipIf(sys.platform.startswith('win'), 'not supported on Windows') +@unittest.skipIf(sys.platform == 'darwin' and sys._framework, + 'not supported for frameworks builds on macOS') @support.skip_if_buildbot('not all buildbots have enough space') # gh-103053: Skip test if Python is built with Profile Guided Optimization # (PGO), since the test is just too slow in this case. From 5f665e99e0b8a52415f83c2416eaf28abaacc3ae Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Fri, 22 Dec 2023 12:09:16 +0100 Subject: [PATCH 341/442] gh-109989: Fix test_c_locale_coercion when PYTHONIOENCODING is set (#113378) * gh-109989: Fix test_c_locale_coercion when PYTHONIOENCODING is set This fixes the existing tests when PYTHONIOENCODING is set by unsetting PYTHONIOENCODING. Also add a test that explicitly checks what happens when PYTHONIOENCODING is set. Co-authored-by: Nikita Sobolev --- Lib/test/test_c_locale_coercion.py | 54 +++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_c_locale_coercion.py b/Lib/test/test_c_locale_coercion.py index 71f934756e26a1..7334a325ba22f0 100644 --- a/Lib/test/test_c_locale_coercion.py +++ b/Lib/test/test_c_locale_coercion.py @@ -112,12 +112,16 @@ class EncodingDetails(_EncodingDetails): ]) @classmethod - def get_expected_details(cls, coercion_expected, fs_encoding, stream_encoding, env_vars): + def get_expected_details(cls, coercion_expected, fs_encoding, stream_encoding, stream_errors, env_vars): """Returns expected child process details for a given encoding""" _stream = stream_encoding + ":{}" - # stdin and stdout should use surrogateescape either because the - # coercion triggered, or because the C locale was detected - stream_info = 2*[_stream.format("surrogateescape")] + if stream_errors is None: + # stdin and stdout should use surrogateescape either because the + # coercion triggered, or because the C locale was detected + stream_errors = "surrogateescape" + + stream_info = [_stream.format(stream_errors)] * 2 + # stderr should always use backslashreplace stream_info.append(_stream.format("backslashreplace")) expected_lang = env_vars.get("LANG", "not set") @@ -210,6 +214,7 @@ def _check_child_encoding_details(self, env_vars, expected_fs_encoding, expected_stream_encoding, + expected_stream_errors, expected_warnings, coercion_expected): """Check the C locale handling for the given process environment @@ -225,6 +230,7 @@ def _check_child_encoding_details(self, coercion_expected, expected_fs_encoding, expected_stream_encoding, + expected_stream_errors, env_vars ) self.assertEqual(encoding_details, expected_details) @@ -257,6 +263,7 @@ def test_external_target_locale_configuration(self): "LC_CTYPE": "", "LC_ALL": "", "PYTHONCOERCECLOCALE": "", + "PYTHONIOENCODING": "", } for env_var in ("LANG", "LC_CTYPE"): for locale_to_set in AVAILABLE_TARGETS: @@ -273,10 +280,43 @@ def test_external_target_locale_configuration(self): self._check_child_encoding_details(var_dict, expected_fs_encoding, expected_stream_encoding, + expected_stream_errors=None, expected_warnings=None, coercion_expected=False) + def test_with_ioencoding(self): + # Explicitly setting a target locale should give the same behaviour as + # is seen when implicitly coercing to that target locale + self.maxDiff = None + + expected_fs_encoding = "utf-8" + expected_stream_encoding = "utf-8" + base_var_dict = { + "LANG": "", + "LC_CTYPE": "", + "LC_ALL": "", + "PYTHONCOERCECLOCALE": "", + "PYTHONIOENCODING": "UTF-8", + } + for env_var in ("LANG", "LC_CTYPE"): + for locale_to_set in AVAILABLE_TARGETS: + # XXX (ncoghlan): LANG=UTF-8 doesn't appear to work as + # expected, so skip that combination for now + # See https://bugs.python.org/issue30672 for discussion + if env_var == "LANG" and locale_to_set == "UTF-8": + continue + + with self.subTest(env_var=env_var, + configured_locale=locale_to_set): + var_dict = base_var_dict.copy() + var_dict[env_var] = locale_to_set + self._check_child_encoding_details(var_dict, + expected_fs_encoding, + expected_stream_encoding, + expected_stream_errors="strict", + expected_warnings=None, + coercion_expected=False) @support.cpython_only @unittest.skipUnless(sysconfig.get_config_var("PY_COERCE_C_LOCALE"), @@ -316,6 +356,7 @@ def _check_c_locale_coercion(self, "LC_CTYPE": "", "LC_ALL": "", "PYTHONCOERCECLOCALE": "", + "PYTHONIOENCODING": "", } base_var_dict.update(extra_vars) if coerce_c_locale is not None: @@ -340,6 +381,7 @@ def _check_c_locale_coercion(self, self._check_child_encoding_details(base_var_dict, fs_encoding, stream_encoding, + None, _expected_warnings, _coercion_expected) @@ -348,13 +390,15 @@ def _check_c_locale_coercion(self, for env_var in ("LANG", "LC_CTYPE"): with self.subTest(env_var=env_var, nominal_locale=locale_to_set, - PYTHONCOERCECLOCALE=coerce_c_locale): + PYTHONCOERCECLOCALE=coerce_c_locale, + PYTHONIOENCODING=""): var_dict = base_var_dict.copy() var_dict[env_var] = locale_to_set # Check behaviour on successful coercion self._check_child_encoding_details(var_dict, fs_encoding, stream_encoding, + None, expected_warnings, coercion_expected) From 45e09f921be55e23bed19b5db4c95ce7bd7aad6b Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 22 Dec 2023 14:25:25 +0000 Subject: [PATCH 342/442] GH-112215: Increase C recursion limit for non debug builds (GH-113397) --- Include/cpython/pystate.h | 4 +++- Lib/test/support/__init__.py | 23 +++++++------------ Lib/test/test_functools.py | 14 +++++++++++ Lib/test/test_json/test_recursion.py | 6 ++--- Lib/test/test_support.py | 2 +- Lib/test/test_xml_etree.py | 2 +- ...-12-15-16-26-01.gh-issue-112215.xJS6_6.rst | 3 +++ 7 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-15-16-26-01.gh-issue-112215.xJS6_6.rst diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 56172d231c44f4..ed7dd829d4b6f0 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -223,9 +223,11 @@ struct _ts { // layout, optimization, and WASI runtime. Wasmtime can handle about 700 // recursions, sometimes less. 500 is a more conservative limit. # define Py_C_RECURSION_LIMIT 500 +#elif defined(__s390x__) +# define Py_C_RECURSION_LIMIT 1200 #else // This value is duplicated in Lib/test/support/__init__.py -# define Py_C_RECURSION_LIMIT 1500 +# define Py_C_RECURSION_LIMIT 8000 #endif diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index b605951320dc8b..c8f73cede230d8 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2122,19 +2122,11 @@ def set_recursion_limit(limit): sys.setrecursionlimit(original_limit) def infinite_recursion(max_depth=None): - """Set a lower limit for tests that interact with infinite recursions - (e.g test_ast.ASTHelpers_Test.test_recursion_direct) since on some - debug windows builds, due to not enough functions being inlined the - stack size might not handle the default recursion limit (1000). See - bpo-11105 for details.""" if max_depth is None: - if not python_is_optimized() or Py_DEBUG: - # Python built without compiler optimizations or in debug mode - # usually consumes more stack memory per function call. - # Unoptimized number based on what works under a WASI debug build. - max_depth = 50 - else: - max_depth = 100 + # Pick a number large enough to cause problems + # but not take too long for code that can handle + # very deep recursion. + max_depth = 20_000 elif max_depth < 3: raise ValueError("max_depth must be at least 3, got {max_depth}") depth = get_recursion_depth() @@ -2373,8 +2365,6 @@ def adjust_int_max_str_digits(max_digits): finally: sys.set_int_max_str_digits(current) -#For recursion tests, easily exceeds default recursion limit -EXCEEDS_RECURSION_LIMIT = 5000 def _get_c_recursion_limit(): try: @@ -2382,11 +2372,14 @@ def _get_c_recursion_limit(): return _testcapi.Py_C_RECURSION_LIMIT except (ImportError, AttributeError): # Originally taken from Include/cpython/pystate.h . - return 1500 + return 8000 # The default C recursion limit. Py_C_RECURSION_LIMIT = _get_c_recursion_limit() +#For recursion tests, easily exceeds default recursion limit +EXCEEDS_RECURSION_LIMIT = Py_C_RECURSION_LIMIT * 3 + #Windows doesn't have os.uname() but it doesn't support s390x. skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', 'skipped on s390x') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index e4de2c5ede15f1..b95afe0bcc86e4 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1862,6 +1862,20 @@ def orig(): ... self.assertEqual(str(Signature.from_callable(lru.cache_info)), '()') self.assertEqual(str(Signature.from_callable(lru.cache_clear)), '()') + @support.skip_on_s390x + @unittest.skipIf(support.is_wasi, "WASI has limited C stack") + def test_lru_recursion(self): + + @self.module.lru_cache + def fib(n): + if n <= 1: + return n + return fib(n-1) + fib(n-2) + + if not support.Py_DEBUG: + with support.infinite_recursion(): + fib(2500) + @py_functools.lru_cache() def py_cached_func(x, y): diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 9919d7fbe54ef7..164ff2013eb552 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -85,10 +85,10 @@ def test_highly_nested_objects_encoding(self): for x in range(100000): l, d = [l], {'k':d} with self.assertRaises(RecursionError): - with support.infinite_recursion(): + with support.infinite_recursion(5000): self.dumps(l) with self.assertRaises(RecursionError): - with support.infinite_recursion(): + with support.infinite_recursion(5000): self.dumps(d) def test_endless_recursion(self): @@ -99,7 +99,7 @@ def default(self, o): return [o] with self.assertRaises(RecursionError): - with support.infinite_recursion(): + with support.infinite_recursion(1000): EndlessJSONEncoder(check_circular=False).encode(5j) diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index c34b0e5e015702..d160cbf0645b47 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -630,7 +630,7 @@ def recursive_function(depth): if depth: recursive_function(depth - 1) - for max_depth in (5, 25, 250): + for max_depth in (5, 25, 250, 2500): with support.infinite_recursion(max_depth): available = support.get_recursion_available() diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index b9e7937b0bbc00..80ee064896f59a 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -2535,7 +2535,7 @@ def __eq__(self, o): e.extend([ET.Element('bar')]) self.assertRaises(ValueError, e.remove, X('baz')) - @support.infinite_recursion(25) + @support.infinite_recursion() def test_recursive_repr(self): # Issue #25455 e = ET.Element('foo') diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-15-16-26-01.gh-issue-112215.xJS6_6.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-15-16-26-01.gh-issue-112215.xJS6_6.rst new file mode 100644 index 00000000000000..01ca1cc7f79b8f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-15-16-26-01.gh-issue-112215.xJS6_6.rst @@ -0,0 +1,3 @@ +Increase the C recursion limit by a factor of 3 for non-debug builds, except +for webassembly and s390 platforms which are unchanged. This mitigates some +regressions in 3.12 with deep recursion mixing builtin (C) and Python code. From 237e2cff00cca49db47bcb7ea13683a4d9ad1ea5 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 22 Dec 2023 15:11:16 +0000 Subject: [PATCH 343/442] GH-110109: Fix misleading `pathlib._abc.PurePathBase` repr (#113376) `PurePathBase.__repr__()` produces a string like `MyPath('/foo')`. This repr is incorrect/misleading when a subclass's `__init__()` method is customized, which I expect to be the very common. This commit moves the `__repr__()` method to `PurePath`, leaving `PurePathBase` with the default `object` repr. No user-facing changes because the `pathlib._abc` module remains private. --- Lib/pathlib/__init__.py | 3 +++ Lib/pathlib/_abc.py | 3 --- Lib/test/test_pathlib/test_pathlib.py | 12 ++++++++++++ Lib/test/test_pathlib/test_pathlib_abc.py | 13 +------------ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index b020d2db350da8..4a8bee601225ed 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -99,6 +99,9 @@ def __reduce__(self): # when pickling related paths. return (self.__class__, self.parts) + def __repr__(self): + return "{}({!r})".format(self.__class__.__name__, self.as_posix()) + def __fspath__(self): return str(self) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 4808d0e61f7038..fc4b6443448a5d 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -282,9 +282,6 @@ def as_posix(self): slashes.""" return str(self).replace(self.pathmod.sep, '/') - def __repr__(self): - return "{}({!r})".format(self.__class__.__name__, self.as_posix()) - @property def drive(self): """The drive prefix (letter or UNC path), if any.""" diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index 00cfdd37e568a6..d55ccd9e255fcc 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -69,6 +69,18 @@ def test_pickling_common(self): self.assertEqual(hash(pp), hash(p)) self.assertEqual(str(pp), str(p)) + def test_repr_common(self): + for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): + with self.subTest(pathstr=pathstr): + p = self.cls(pathstr) + clsname = p.__class__.__name__ + r = repr(p) + # The repr() is in the form ClassName("forward-slashes path"). + self.assertTrue(r.startswith(clsname + '('), r) + self.assertTrue(r.endswith(')'), r) + inner = r[len(clsname) + 1 : -1] + self.assertEqual(eval(inner), p.as_posix()) + def test_fspath_common(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 a272973d9c1d61..6748def91a1e6e 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -31,6 +31,7 @@ def test_magic_methods(self): self.assertFalse(hasattr(P, '__fspath__')) self.assertFalse(hasattr(P, '__bytes__')) self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__repr__, object.__repr__) self.assertIs(P.__hash__, object.__hash__) self.assertIs(P.__eq__, object.__eq__) self.assertIs(P.__lt__, object.__lt__) @@ -227,18 +228,6 @@ def test_as_posix_common(self): self.assertEqual(P(pathstr).as_posix(), pathstr) # Other tests for as_posix() are in test_equivalences(). - def test_repr_common(self): - for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): - with self.subTest(pathstr=pathstr): - p = self.cls(pathstr) - clsname = p.__class__.__name__ - r = repr(p) - # The repr() is in the form ClassName("forward-slashes path"). - self.assertTrue(r.startswith(clsname + '('), r) - self.assertTrue(r.endswith(')'), r) - inner = r[len(clsname) + 1 : -1] - self.assertEqual(eval(inner), p.as_posix()) - def test_eq_common(self): P = self.cls self.assertEqual(P('a/b'), P('a/b')) From 4a3d2419bb5b7cd862e3d909f53a2ef0a09cdcee Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Fri, 22 Dec 2023 16:12:08 +0100 Subject: [PATCH 344/442] gh-113212: Improve error message & document zero-arg super inside nested functions and generator expressions (GH-113307) --- Doc/library/functions.rst | 7 +++++ Lib/test/test_super.py | 27 +++++++++++++++++++ ...-12-20-08-54-54.gh-issue-113212.62AUlw.rst | 1 + Objects/typeobject.c | 19 ++++++++++--- 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-20-08-54-54.gh-issue-113212.62AUlw.rst diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index c731b6fd333275..4682ec9c924757 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1800,6 +1800,13 @@ are always available. They are listed here in alphabetical order. the second argument is a type, ``issubclass(type2, type)`` must be true (this is useful for classmethods). + When called directly within an ordinary method of a class, both arguments may + be omitted ("zero-argument :func:`!super`"). In this case, *type* will be the + enclosing class, and *obj* will be the first argument of the immediately + enclosing function (typically ``self``). (This means that zero-argument + :func:`!super` will not work as expected within nested functions, including + generator expressions, which implicitly create nested functions.) + There are two typical use cases for *super*. In a class hierarchy with single inheritance, *super* can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 43162c540b55ae..f8e968b9b56f82 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -396,6 +396,33 @@ def method(self): with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): C().method() + def test_supercheck_fail(self): + class C: + def method(self, type_, obj): + return super(type_, obj).method() + + c = C() + err_msg = ( + r"super\(type, obj\): obj \({} {}\) is not " + r"an instance or subtype of type \({}\)." + ) + + cases = ( + (int, c, int.__name__, C.__name__, "instance of"), + # obj is instance of type + (C, list(), C.__name__, list.__name__, "instance of"), + # obj is type itself + (C, list, C.__name__, list.__name__, "type"), + ) + + for case in cases: + with self.subTest(case=case): + type_, obj, type_str, obj_str, instance_or_type = case + regex = err_msg.format(instance_or_type, obj_str, type_str) + + with self.assertRaisesRegex(TypeError, regex): + c.method(type_, obj) + def test_super___class__(self): class C: def method(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-20-08-54-54.gh-issue-113212.62AUlw.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-20-08-54-54.gh-issue-113212.62AUlw.rst new file mode 100644 index 00000000000000..6edbc9c60d968c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-20-08-54-54.gh-issue-113212.62AUlw.rst @@ -0,0 +1 @@ +Improve :py:class:`super` error messages. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5261ef92413615..ea29a38d74ae3e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -10404,9 +10404,22 @@ supercheck(PyTypeObject *type, PyObject *obj) Py_XDECREF(class_attr); } - PyErr_SetString(PyExc_TypeError, - "super(type, obj): " - "obj must be an instance or subtype of type"); + const char *type_or_instance, *obj_str; + + if (PyType_Check(obj)) { + type_or_instance = "type"; + obj_str = ((PyTypeObject*)obj)->tp_name; + } + else { + type_or_instance = "instance of"; + obj_str = Py_TYPE(obj)->tp_name; + } + + PyErr_Format(PyExc_TypeError, + "super(type, obj): obj (%s %.200s) is not " + "an instance or subtype of type (%.200s).", + type_or_instance, obj_str, type->tp_name); + return NULL; } From ff5e131df5f374f72579b970856dc5646e8b836c Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 22 Dec 2023 17:49:09 +0000 Subject: [PATCH 345/442] GH-112855: Slightly improve tests for `pathlib.PurePath` pickling (#113243) Add a few more simple test cases, like non-anchored paths. Remove misplaced and indirect test that pickling doesn't change the `stat()` value. --- Lib/test/test_pathlib/test_pathlib.py | 18 ++++++++++-------- Lib/test/test_pathlib/test_pathlib_abc.py | 8 -------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index d55ccd9e255fcc..a448f6a1adefce 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -60,14 +60,16 @@ def test_div_nested(self): def test_pickling_common(self): P = self.cls - p = P('/a/b') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertIs(pp.__class__, p.__class__) - self.assertEqual(pp, p) - self.assertEqual(hash(pp), hash(p)) - self.assertEqual(str(pp), str(p)) + for pathstr in ('a', 'a/', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c', 'a/b/c/'): + with self.subTest(pathstr=pathstr): + p = P(pathstr) + for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): + dumped = pickle.dumps(p, proto) + pp = pickle.loads(dumped) + self.assertIs(pp.__class__, p.__class__) + self.assertEqual(pp, p) + self.assertEqual(hash(pp), hash(p)) + self.assertEqual(str(pp), str(p)) def test_repr_common(self): for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 6748def91a1e6e..42575a5e640d65 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -3,7 +3,6 @@ import os import errno import pathlib -import pickle import posixpath import stat import unittest @@ -1644,13 +1643,6 @@ def test_is_char_device_false(self): self.assertIs((P / 'fileA\udfff').is_char_device(), False) self.assertIs((P / 'fileA\x00').is_char_device(), False) - def test_pickling_common(self): - p = self.cls(self.base, 'fileA') - for proto in range(0, pickle.HIGHEST_PROTOCOL + 1): - dumped = pickle.dumps(p, proto) - pp = pickle.loads(dumped) - self.assertEqual(pp.stat(), p.stat()) - def test_parts_interning(self): P = self.cls p = P('/usr/bin/foo') From a0d3d3ec9ddd35514aa8db68b2fe9b8151cfc0a6 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 22 Dec 2023 18:09:50 +0000 Subject: [PATCH 346/442] GH-110109: pathlib ABCs: do not vary path syntax by host OS. (#113219) Change the value of `pathlib._abc.PurePathBase.pathmod` from `os.path` to `posixpath`. User subclasses of `PurePathBase` and `PathBase` previously used the host OS's path syntax, e.g. backslashes as separators on Windows. This is wrong in most use cases, and likely to catch developers out unless they test on both Windows and non-Windows machines. In this patch we change the default to POSIX syntax, regardless of OS. This is somewhat arguable (why not make all aspects of syntax abstract and individually configurable?) but an improvement all the same. This change has no effect on `PurePath`, `Path`, nor their subclasses. Only private APIs are affected. --- Lib/pathlib/__init__.py | 1 + Lib/pathlib/_abc.py | 3 +- Lib/test/test_pathlib/test_pathlib.py | 59 ++++++++++++++++ Lib/test/test_pathlib/test_pathlib_abc.py | 84 +++++++---------------- 4 files changed, 87 insertions(+), 60 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4a8bee601225ed..bfd2a924979746 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -59,6 +59,7 @@ class PurePath(_abc.PurePathBase): # path. It's set when `__hash__()` is called for the first time. '_hash', ) + pathmod = os.path def __new__(cls, *args, **kwargs): """Construct a PurePath from one or several strings and or existing diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index fc4b6443448a5d..43e2670c4d0258 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -1,7 +1,6 @@ import functools import io import ntpath -import os import posixpath import sys import warnings @@ -204,7 +203,7 @@ class PurePathBase: # work from occurring when `resolve()` calls `stat()` or `readlink()`. '_resolving', ) - pathmod = os.path + pathmod = posixpath def __init__(self, *paths): self._raw_paths = paths diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index a448f6a1adefce..db5f3b2634be97 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -2,8 +2,10 @@ import os import sys import errno +import ntpath import pathlib import pickle +import posixpath import socket import stat import tempfile @@ -39,6 +41,50 @@ class PurePathTest(test_pathlib_abc.DummyPurePathTest): cls = pathlib.PurePath + # Make sure any symbolic links in the base test path are resolved. + base = os.path.realpath(TESTFN) + + def test_concrete_class(self): + if self.cls is pathlib.PurePath: + expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath + else: + expected = self.cls + p = self.cls('a') + self.assertIs(type(p), expected) + + def test_concrete_pathmod(self): + if self.cls is pathlib.PurePosixPath: + expected = posixpath + elif self.cls is pathlib.PureWindowsPath: + expected = ntpath + else: + expected = os.path + p = self.cls('a') + self.assertIs(p.pathmod, expected) + + def test_different_pathmods_unequal(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + self.assertNotEqual(p, q) + + def test_different_pathmods_unordered(self): + p = self.cls('a') + if p.pathmod is posixpath: + q = pathlib.PureWindowsPath('a') + else: + q = pathlib.PurePosixPath('a') + with self.assertRaises(TypeError): + p < q + with self.assertRaises(TypeError): + p <= q + with self.assertRaises(TypeError): + p > q + with self.assertRaises(TypeError): + p >= q + def test_constructor_nested(self): P = self.cls P(FakePath("a/b/c")) @@ -958,6 +1004,19 @@ def tempdir(self): self.addCleanup(os_helper.rmtree, d) return d + def test_matches_pathbase_api(self): + our_names = {name for name in dir(self.cls) if name[0] != '_'} + path_names = {name for name in dir(pathlib._abc.PathBase) if name[0] != '_'} + self.assertEqual(our_names, path_names) + for attr_name in our_names: + if attr_name == 'pathmod': + # On Windows, Path.pathmod is ntpath, but PathBase.pathmod is + # posixpath, and so their docstrings differ. + continue + our_attr = getattr(self.cls, attr_name) + path_attr = getattr(pathlib._abc.PathBase, attr_name) + self.assertEqual(our_attr.__doc__, path_attr.__doc__) + def test_concrete_class(self): if self.cls is pathlib.Path: expected = pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 42575a5e640d65..37f4caa4da1028 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -38,6 +38,9 @@ def test_magic_methods(self): self.assertIs(P.__gt__, object.__gt__) self.assertIs(P.__ge__, object.__ge__) + def test_pathmod(self): + self.assertIs(self.cls.pathmod, posixpath) + class DummyPurePath(pathlib._abc.PurePathBase): def __eq__(self, other): @@ -52,8 +55,8 @@ def __hash__(self): class DummyPurePathTest(unittest.TestCase): cls = DummyPurePath - # Make sure any symbolic links in the base test path are resolved. - base = os.path.realpath(TESTFN) + # Use a base path that's unrelated to any real filesystem path. + base = f'/this/path/kills/fascists/{TESTFN}' # Keys are canonical paths, values are list of tuples of arguments # supposed to produce equal paths. @@ -86,37 +89,6 @@ def test_constructor_common(self): P('a/b/c') P('/a/b/c') - def test_concrete_class(self): - if self.cls is pathlib.PurePath: - expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath - else: - expected = self.cls - p = self.cls('a') - self.assertIs(type(p), expected) - - def test_different_pathmods_unequal(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - self.assertNotEqual(p, q) - - def test_different_pathmods_unordered(self): - p = self.cls('a') - if p.pathmod is posixpath: - q = pathlib.PureWindowsPath('a') - else: - q = pathlib.PurePosixPath('a') - with self.assertRaises(TypeError): - p < q - with self.assertRaises(TypeError): - p <= q - with self.assertRaises(TypeError): - p > q - with self.assertRaises(TypeError): - p >= q - 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 @@ -721,15 +693,6 @@ def test_fspath_common(self): def test_as_bytes_common(self): self.assertRaises(TypeError, bytes, self.cls()) - def test_matches_path_api(self): - our_names = {name for name in dir(self.cls) if name[0] != '_'} - path_names = {name for name in dir(pathlib.Path) if name[0] != '_'} - self.assertEqual(our_names, path_names) - for attr_name in our_names: - our_attr = getattr(self.cls, attr_name) - path_attr = getattr(pathlib.Path, attr_name) - self.assertEqual(our_attr.__doc__, path_attr.__doc__) - class DummyPathIO(io.BytesIO): """ @@ -905,11 +868,13 @@ def assertFileNotFound(self, func, *args, **kwargs): self.assertEqual(cm.exception.errno, errno.ENOENT) def assertEqualNormCase(self, path_a, path_b): - self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b)) + normcase = self.pathmod.normcase + self.assertEqual(normcase(path_a), normcase(path_b)) def test_samefile(self): - fileA_path = os.path.join(self.base, 'fileA') - fileB_path = os.path.join(self.base, 'dirB', 'fileB') + pathmod = self.pathmod + fileA_path = pathmod.join(self.base, 'fileA') + fileB_path = pathmod.join(self.base, 'dirB', 'fileB') p = self.cls(fileA_path) pp = self.cls(fileA_path) q = self.cls(fileB_path) @@ -918,7 +883,7 @@ def test_samefile(self): self.assertFalse(p.samefile(fileB_path)) self.assertFalse(p.samefile(q)) # Test the non-existent file case - non_existent = os.path.join(self.base, 'foo') + non_existent = pathmod.join(self.base, 'foo') r = self.cls(non_existent) self.assertRaises(FileNotFoundError, p.samefile, r) self.assertRaises(FileNotFoundError, p.samefile, non_existent) @@ -1379,14 +1344,15 @@ def test_resolve_common(self): p.resolve(strict=True) self.assertEqual(cm.exception.errno, errno.ENOENT) # Non-strict + pathmod = self.pathmod self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(self.base, 'foo')) + pathmod.join(self.base, 'foo')) p = P(self.base, 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.join(self.base, 'foo', 'in', 'spam')) + pathmod.join(self.base, 'foo', 'in', 'spam')) p = P(self.base, '..', 'foo', 'in', 'spam') self.assertEqualNormCase(str(p.resolve(strict=False)), - os.path.abspath(os.path.join('foo', 'in', 'spam'))) + pathmod.join(pathmod.dirname(self.base), 'foo', 'in', 'spam')) # These are all relative symlinks. p = P(self.base, 'dirB', 'fileB') self._check_resolve_relative(p, p) @@ -1401,7 +1367,7 @@ def test_resolve_common(self): self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in', 'spam'), False) p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): + if self.cls.pathmod is not posixpath: # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in', @@ -1421,7 +1387,7 @@ def test_resolve_common(self): self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'), False) p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam') - if os.name == 'nt' and isinstance(p, pathlib.Path): + if self.cls.pathmod is not posixpath: # In Windows, if linkY points to dirB, 'dirA\linkY\..' # resolves to 'dirA' without resolving linkY first. self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False) @@ -1434,10 +1400,11 @@ 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) - p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True) - p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True) + p.joinpath('1').symlink_to(pathmod.join('0', '0'), target_is_directory=True) + p.joinpath('2').symlink_to(pathmod.join('1', '1'), target_is_directory=True) q = p / '2' self.assertEqual(q.resolve(strict=True), p) r = q / '3' / '4' @@ -1454,7 +1421,7 @@ def _check_symlink_loop(self, *args): def test_resolve_loop(self): if not self.can_symlink: self.skipTest("symlinks required") - if os.name == 'nt' and issubclass(self.cls, pathlib.Path): + 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') @@ -1657,10 +1624,11 @@ def _check_complex_symlinks(self, link0_target): self.skipTest("symlinks required") # Test solving a non-looping chain of symlinks (issue #19887). + pathmod = self.pathmod P = self.cls(self.base) - P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True) - P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True) - P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True) + P.joinpath('link1').symlink_to(pathmod.join('link0', 'link0'), target_is_directory=True) + P.joinpath('link2').symlink_to(pathmod.join('link1', 'link1'), target_is_directory=True) + P.joinpath('link3').symlink_to(pathmod.join('link2', 'link2'), target_is_directory=True) P.joinpath('link0').symlink_to(link0_target, target_is_directory=True) # Resolve absolute paths. @@ -1707,7 +1675,7 @@ def test_complex_symlinks_relative(self): self._check_complex_symlinks('.') def test_complex_symlinks_relative_dot_dot(self): - self._check_complex_symlinks(os.path.join('dirA', '..')) + self._check_complex_symlinks(self.pathmod.join('dirA', '..')) def setUpWalk(self): # Build: From daa658aba5133b78db89c90056a8421bd6ac8703 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 22 Dec 2023 21:28:15 +0100 Subject: [PATCH 347/442] gh-113317: Argument Clinic: tear out internal text accumulator APIs (#113402) Replace the internal accumulator APIs by using lists of strings and join(). Yak-shaving for separating out formatting code into a separate file. Co-authored-by: Alex Waygood --- Lib/test/test_clinic.py | 30 ----- Tools/clinic/clinic.py | 247 +++++++++++++++------------------------- 2 files changed, 89 insertions(+), 188 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f3f73a174aeb13..199c9fec80205a 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3761,20 +3761,6 @@ def test_normalize_snippet(self): actual = clinic.normalize_snippet(snippet, indent=indent) self.assertEqual(actual, expected) - def test_accumulator(self): - acc = clinic.text_accumulator() - self.assertEqual(acc.output(), "") - acc.append("a") - self.assertEqual(acc.output(), "a") - self.assertEqual(acc.output(), "") - acc.append("b") - self.assertEqual(acc.output(), "b") - self.assertEqual(acc.output(), "") - acc.append("c") - acc.append("d") - self.assertEqual(acc.output(), "cd") - self.assertEqual(acc.output(), "") - def test_quoted_for_c_string(self): dataset = ( # input, expected @@ -3790,22 +3776,6 @@ def test_quoted_for_c_string(self): out = clinic.quoted_for_c_string(line) self.assertEqual(out, expected) - def test_rstrip_lines(self): - lines = ( - "a \n" - "b\n" - " c\n" - " d \n" - ) - expected = ( - "a\n" - "b\n" - " c\n" - " d\n" - ) - out = clinic.rstrip_lines(lines) - self.assertEqual(out, expected) - def test_format_escape(self): line = "{}, {a}" expected = "{{}}, {{a}}" diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index e8ba805bd53b45..4b00902052d5a5 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -105,42 +105,8 @@ def __repr__(self) -> str: sig_end_marker = '--' -Appender = Callable[[str], None] -Outputter = Callable[[], str] TemplateDict = dict[str, str] -class _TextAccumulator(NamedTuple): - text: list[str] - append: Appender - output: Outputter - -def _text_accumulator() -> _TextAccumulator: - text: list[str] = [] - def output() -> str: - s = ''.join(text) - text.clear() - return s - return _TextAccumulator(text, text.append, output) - - -class TextAccumulator(NamedTuple): - append: Appender - output: Outputter - -def text_accumulator() -> TextAccumulator: - """ - Creates a simple text accumulator / joiner. - - Returns a pair of callables: - append, output - "append" appends a string to the accumulator. - "output" returns the contents of the accumulator - joined together (''.join(accumulator)) and - empties the accumulator. - """ - text, append, output = _text_accumulator() - return TextAccumulator(append, output) - @dc.dataclass class ClinicError(Exception): @@ -264,14 +230,6 @@ def ensure_legal_c_identifier(s: str) -> str: return s + "_value" return s -def rstrip_lines(s: str) -> str: - text, add, output = _text_accumulator() - for line in s.split('\n'): - add(line.rstrip()) - add('\n') - text.pop() - return output() - def format_escape(s: str) -> str: # double up curly-braces, this string will be used # as part of a format_map() template later @@ -294,18 +252,16 @@ def linear_format(s: str, **kwargs: str) -> str: * A newline will be added to the end. """ - add, output = text_accumulator() + lines = [] for line in s.split('\n'): indent, curly, trailing = line.partition('{') if not curly: - add(line) - add('\n') + lines.extend([line, "\n"]) continue name, curly, trailing = trailing.partition('}') if not curly or name not in kwargs: - add(line) - add('\n') + lines.extend([line, "\n"]) continue if trailing: @@ -319,51 +275,32 @@ def linear_format(s: str, **kwargs: str) -> str: if not value: continue - value = textwrap.indent(rstrip_lines(value), indent) - add(value) - add('\n') + stripped = [line.rstrip() for line in value.split("\n")] + value = textwrap.indent("\n".join(stripped), indent) + lines.extend([value, "\n"]) - return output()[:-1] + return "".join(lines[:-1]) -def indent_all_lines(s: str, prefix: str) -> str: +def _add_prefix_and_suffix(s: str, prefix: str = "", suffix: str = "") -> str: """ - Returns 's', with 'prefix' prepended to all lines. + Return 's', with 'prefix' prepended and 'suffix' appended to all lines. - If the last line is empty, prefix is not prepended - to it. (If s is blank, returns s unchanged.) + If the last line is empty, it remains unchanged. + If s is blank, returns s unchanged. (textwrap.indent only adds to non-blank lines.) """ - split = s.split('\n') - last = split.pop() - final = [] - for line in split: - final.append(prefix) - final.append(line) - final.append('\n') + *split, last = s.split("\n") + lines = [prefix + line + suffix + "\n" for line in split] if last: - final.append(prefix) - final.append(last) - return ''.join(final) + lines.append(prefix + last + suffix) + return "".join(lines) -def suffix_all_lines(s: str, suffix: str) -> str: - """ - Returns 's', with 'suffix' appended to all lines. +def indent_all_lines(s: str, prefix: str) -> str: + return _add_prefix_and_suffix(s, prefix=prefix) - If the last line is empty, suffix is not appended - to it. (If s is blank, returns s unchanged.) - """ - split = s.split('\n') - last = split.pop() - final = [] - for line in split: - final.append(line) - final.append(suffix) - final.append('\n') - if last: - final.append(last) - final.append(suffix) - return ''.join(final) +def suffix_all_lines(s: str, suffix: str) -> str: + return _add_prefix_and_suffix(s, suffix=suffix) def pprint_words(items: list[str]) -> str: @@ -1068,21 +1005,21 @@ def docstring_for_c_string( self, f: Function ) -> str: - text, add, output = _text_accumulator() + lines = [] # turn docstring into a properly quoted C string for line in f.docstring.split('\n'): - add('"') - add(quoted_for_c_string(line)) - add('\\n"\n') + lines.append('"') + lines.append(quoted_for_c_string(line)) + lines.append('\\n"\n') - if text[-2] == sig_end_marker: + if lines[-2] == sig_end_marker: # If we only have a signature, add the blank line that the # __text_signature__ getter expects to be there. - add('"\\n"') + lines.append('"\\n"') else: - text.pop() - add('"') - return ''.join(text) + lines.pop() + lines.append('"') + return ''.join(lines) def output_templates( self, @@ -1183,8 +1120,8 @@ def parser_body( declarations: str = '' ) -> str: nonlocal parser_body_fields - add, output = text_accumulator() - add(prototype) + lines = [] + lines.append(prototype) parser_body_fields = fields preamble = normalize_snippet(""" @@ -1208,9 +1145,8 @@ def parser_body( }} """) for field in preamble, *fields, finale: - add('\n') - add(field) - return linear_format(output(), parser_declarations=declarations) + lines.append(field) + return linear_format("\n".join(lines), parser_declarations=declarations) fastcall = not new_or_init limited_capi = clinic.limited_capi @@ -1785,7 +1721,7 @@ def render_option_group_parsing( # Clinic prefers groups on the left. So in the above example, # five arguments would map to B+C, not C+D. - add, output = text_accumulator() + out = [] parameters = list(f.parameters.values()) if isinstance(parameters[0].converter, self_converter): del parameters[0] @@ -1817,14 +1753,14 @@ def render_option_group_parsing( nargs = 'PyTuple_Size(args)' else: nargs = 'PyTuple_GET_SIZE(args)' - add(f"switch ({nargs}) {{\n") + out.append(f"switch ({nargs}) {{\n") for subset in permute_optional_groups(left, required, right): count = len(subset) count_min = min(count_min, count) count_max = max(count_max, count) if count == 0: - add(""" case 0: + out.append(""" case 0: break; """) continue @@ -1856,14 +1792,15 @@ def render_option_group_parsing( """ s = linear_format(s, group_booleans=lines) s = s.format_map(d) - add(s) + out.append(s) - add(" default:\n") + out.append(" default:\n") s = ' PyErr_SetString(PyExc_TypeError, "{} requires {} to {} arguments");\n' - add(s.format(f.full_name, count_min, count_max)) - add(' goto exit;\n') - add("}") - template_dict['option_group_parsing'] = format_escape(output()) + out.append(s.format(f.full_name, count_min, count_max)) + out.append(' goto exit;\n') + out.append("}") + + template_dict['option_group_parsing'] = format_escape("".join(out)) def render_function( self, @@ -1873,7 +1810,6 @@ def render_function( if f is None or clinic is None: return "" - add, output = text_accumulator() data = CRenderData() assert f.parameters, "We should always have a 'self' at this point!" @@ -2202,7 +2138,7 @@ def _line(self, lookahead: bool = False) -> str: return line def parse_verbatim_block(self) -> Block: - add, output = text_accumulator() + lines = [] self.block_start_line_number = self.line_number while self.input: @@ -2211,12 +2147,12 @@ def parse_verbatim_block(self) -> Block: if dsl_name: self.dsl_name = dsl_name break - add(line) + lines.append(line) - return Block(output()) + return Block("".join(lines)) def parse_clinic_block(self, dsl_name: str) -> Block: - input_add, input_output = text_accumulator() + in_lines = [] self.block_start_line_number = self.line_number + 1 stop_line = self.language.stop_line.format(dsl_name=dsl_name) body_prefix = self.language.body_prefix.format(dsl_name=dsl_name) @@ -2244,7 +2180,7 @@ def is_stop_line(line: str) -> bool: line = line.lstrip() assert line.startswith(body_prefix) line = line.removeprefix(body_prefix) - input_add(line) + in_lines.append(line) # consume output and checksum line, if present. if self.last_dsl_name == dsl_name: @@ -2258,7 +2194,7 @@ def is_stop_line(line: str) -> bool: assert checksum_re is not None # scan forward for checksum line - output_add, output_output = text_accumulator() + out_lines = [] arguments = None while self.input: line = self._line(lookahead=True) @@ -2266,12 +2202,12 @@ def is_stop_line(line: str) -> bool: arguments = match.group(1) if match else None if arguments: break - output_add(line) + out_lines.append(line) if self.is_start_line(line): break output: str | None - output = output_output() + output = "".join(out_lines) if arguments: d = {} for field in shlex.split(arguments): @@ -2299,7 +2235,7 @@ def is_stop_line(line: str) -> bool: self.input.extend(reversed(output_lines)) output = None - return Block(input_output(), dsl_name, output=output) + return Block("".join(in_lines), dsl_name, output=output) @dc.dataclass(slots=True, frozen=True) @@ -2406,6 +2342,9 @@ def write(self, text: str) -> None: self.f.write(text) +TextAccumulator = list[str] + + class BufferSeries: """ Behaves like a "defaultlist". @@ -2419,26 +2358,26 @@ class BufferSeries: def __init__(self) -> None: self._start = 0 - self._array: list[_TextAccumulator] = [] - self._constructor = _text_accumulator + self._array: list[TextAccumulator] = [] - def __getitem__(self, i: int) -> _TextAccumulator: + def __getitem__(self, i: int) -> TextAccumulator: i -= self._start if i < 0: self._start += i - prefix = [self._constructor() for x in range(-i)] + prefix: list[TextAccumulator] = [[] for x in range(-i)] self._array = prefix + self._array i = 0 while i >= len(self._array): - self._array.append(self._constructor()) + self._array.append([]) return self._array[i] def clear(self) -> None: for ta in self._array: - ta.text.clear() + ta.clear() def dump(self) -> str: - texts = [ta.output() for ta in self._array] + texts = ["".join(ta) for ta in self._array] + self.clear() return "".join(texts) @@ -2624,7 +2563,7 @@ def __init__( 'impl_definition': d('block'), } - DestBufferType = dict[str, _TextAccumulator] + DestBufferType = dict[str, TextAccumulator] DestBufferList = list[DestBufferType] self.destination_buffers_stack: DestBufferList = [] @@ -2696,7 +2635,7 @@ def get_destination_buffer( self, name: str, item: int = 0 - ) -> _TextAccumulator: + ) -> TextAccumulator: d = self.get_destination(name) return d.buffers[item] @@ -3150,11 +3089,9 @@ def get_displayname(self, i: int) -> str: return f'argument {i}' def render_docstring(self) -> str: - add, out = text_accumulator() - add(f" {self.name}\n") - for line in self.docstring.split("\n"): - add(f" {line}\n") - return out().rstrip() + lines = [f" {self.name}"] + lines.extend(f" {line}" for line in self.docstring.split("\n")) + return "\n".join(lines).rstrip() CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"]) @@ -6220,15 +6157,15 @@ def state_function_docstring(self, line: str) -> None: def format_docstring_signature( self, f: Function, parameters: list[Parameter] ) -> str: - text, add, output = _text_accumulator() - add(f.displayname) + lines = [] + lines.append(f.displayname) if self.forced_text_signature: - add(self.forced_text_signature) + lines.append(self.forced_text_signature) elif f.kind in {GETTER, SETTER}: # @getter and @setter do not need signatures like a method or a function. return '' else: - add('(') + lines.append('(') # populate "right_bracket_count" field for every parameter assert parameters, "We should always have a self parameter. " + repr(f) @@ -6282,7 +6219,7 @@ def fix_right_bracket_count(desired: int) -> str: first_parameter = True last_p = parameters[-1] - line_length = len(''.join(text)) + line_length = len(''.join(lines)) indent = " " * line_length def add_parameter(text: str) -> None: nonlocal line_length @@ -6293,12 +6230,11 @@ def add_parameter(text: str) -> None: else: s = ' ' + text if line_length + len(s) >= 72: - add('\n') - add(indent) + lines.extend(["\n", indent]) line_length = len(indent) s = text line_length += len(s) - add(s) + lines.append(s) for p in parameters: if not p.converter.show_in_signature: @@ -6321,8 +6257,7 @@ def add_parameter(text: str) -> None: added_star = True add_parameter('*,') - p_add, p_output = text_accumulator() - p_add(fix_right_bracket_count(p.right_bracket_count)) + p_lines = [fix_right_bracket_count(p.right_bracket_count)] if isinstance(p.converter, self_converter): # annotate first parameter as being a "self". @@ -6340,30 +6275,31 @@ def add_parameter(text: str) -> None: # have a docstring.) if this is an __init__ # (or __new__), then this signature is for # calling the class to construct a new instance. - p_add('$') + p_lines.append('$') if p.is_vararg(): - p_add("*") + p_lines.append("*") name = p.converter.signature_name or p.name - p_add(name) + p_lines.append(name) if not p.is_vararg() and p.converter.is_optional(): - p_add('=') + p_lines.append('=') value = p.converter.py_default if not value: value = repr(p.converter.default) - p_add(value) + p_lines.append(value) if (p != last_p) or need_a_trailing_slash: - p_add(',') + p_lines.append(',') - add_parameter(p_output()) + p_output = "".join(p_lines) + add_parameter(p_output) - add(fix_right_bracket_count(0)) + lines.append(fix_right_bracket_count(0)) if need_a_trailing_slash: add_parameter('/') - add(')') + lines.append(')') # PEP 8 says: # @@ -6375,13 +6311,13 @@ def add_parameter(text: str) -> None: # therefore this is commented out: # # if f.return_converter.py_default: - # add(' -> ') - # add(f.return_converter.py_default) + # lines.append(' -> ') + # lines.append(f.return_converter.py_default) if not f.docstring_only: - add("\n" + sig_end_marker + "\n") + lines.append("\n" + sig_end_marker + "\n") - signature_line = output() + signature_line = "".join(lines) # now fix up the places where the brackets look wrong return signature_line.replace(', ]', ',] ') @@ -6389,12 +6325,7 @@ def add_parameter(text: str) -> None: @staticmethod def format_docstring_parameters(params: list[Parameter]) -> str: """Create substitution text for {parameters}""" - add, output = text_accumulator() - for p in params: - if p.docstring: - add(p.render_docstring()) - add('\n') - return output() + return "".join(p.render_docstring() + "\n" for p in params if p.docstring) def format_docstring(self) -> str: assert self.function is not None From d1c711e757213b38cff177ba4b4d8ae201da1d21 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Fri, 22 Dec 2023 20:59:17 +0000 Subject: [PATCH 348/442] GH-110109: Adjust `test_pathlib_abc` imports to ease backporting (#113411) This very boring patch reduces the number of changes needed in `test_pathlib_abc.py` when backporting to the external `pathlib_abc` package. --- Lib/test/test_pathlib/test_pathlib_abc.py | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 37f4caa4da1028..568a3183b40b8d 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -2,19 +2,20 @@ import io import os import errno -import pathlib -import posixpath import stat import unittest +from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase +import posixpath + from test.support import set_recursion_limit from test.support.os_helper import TESTFN class UnsupportedOperationTest(unittest.TestCase): def test_is_notimplemented(self): - self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError)) - self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError)) + self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError)) + self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError)) # @@ -23,7 +24,7 @@ def test_is_notimplemented(self): class PurePathBaseTest(unittest.TestCase): - cls = pathlib._abc.PurePathBase + cls = PurePathBase def test_magic_methods(self): P = self.cls @@ -42,7 +43,7 @@ def test_pathmod(self): self.assertIs(self.cls.pathmod, posixpath) -class DummyPurePath(pathlib._abc.PurePathBase): +class DummyPurePath(PurePathBase): def __eq__(self, other): if not isinstance(other, DummyPurePath): return NotImplemented @@ -637,12 +638,12 @@ def test_is_relative_to_common(self): # class PathBaseTest(PurePathBaseTest): - cls = pathlib._abc.PathBase + cls = PathBase def test_unsupported_operation(self): P = self.cls p = self.cls() - e = pathlib.UnsupportedOperation + e = UnsupportedOperation self.assertRaises(e, p.stat) self.assertRaises(e, p.lstat) self.assertRaises(e, p.exists) @@ -684,7 +685,7 @@ def test_unsupported_operation(self): self.assertRaises(e, p.as_uri) def test_as_uri_common(self): - e = pathlib.UnsupportedOperation + e = UnsupportedOperation self.assertRaises(e, self.cls().as_uri) def test_fspath_common(self): @@ -709,7 +710,7 @@ def close(self): super().close() -class DummyPath(pathlib._abc.PathBase): +class DummyPath(PathBase): """ Simple implementation of PathBase that keeps files and directories in memory. @@ -1325,7 +1326,7 @@ def test_readlink(self): def test_readlink_unsupported(self): P = self.cls(self.base) p = P / 'fileA' - with self.assertRaises(pathlib.UnsupportedOperation): + with self.assertRaises(UnsupportedOperation): q.readlink(p) def _check_resolve(self, p, expected, strict=True): @@ -1648,7 +1649,7 @@ def _check_complex_symlinks(self, link0_target): # Resolve relative paths. try: self.cls().absolute() - except pathlib.UnsupportedOperation: + except UnsupportedOperation: return old_path = os.getcwd() os.chdir(self.base) From 9c3ddf31a34295ebcef6dc49b9e0ddd75d0ea9f1 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 22 Dec 2023 23:35:16 +0100 Subject: [PATCH 349/442] gh-113317: Remove TextAccumulator type alias from clinic.py (#113413) Clean-up after gh-113402. --- Tools/clinic/clinic.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 4b00902052d5a5..92092e97000ad0 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2342,9 +2342,6 @@ def write(self, text: str) -> None: self.f.write(text) -TextAccumulator = list[str] - - class BufferSeries: """ Behaves like a "defaultlist". @@ -2358,13 +2355,13 @@ class BufferSeries: def __init__(self) -> None: self._start = 0 - self._array: list[TextAccumulator] = [] + self._array: list[list[str]] = [] - def __getitem__(self, i: int) -> TextAccumulator: + def __getitem__(self, i: int) -> list[str]: i -= self._start if i < 0: self._start += i - prefix: list[TextAccumulator] = [[] for x in range(-i)] + prefix: list[list[str]] = [[] for x in range(-i)] self._array = prefix + self._array i = 0 while i >= len(self._array): @@ -2563,7 +2560,7 @@ def __init__( 'impl_definition': d('block'), } - DestBufferType = dict[str, TextAccumulator] + DestBufferType = dict[str, list[str]] DestBufferList = list[DestBufferType] self.destination_buffers_stack: DestBufferList = [] @@ -2635,7 +2632,7 @@ def get_destination_buffer( self, name: str, item: int = 0 - ) -> TextAccumulator: + ) -> list[str]: d = self.get_destination(name) return d.buffers[item] From c3f92f6a7513340dfe2d82bfcd38eb77453e935d Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Dec 2023 01:37:39 +0100 Subject: [PATCH 350/442] gh-113317: Clean up Argument Clinic global namespace (#113414) Split up clinic.py by establishing libclinic as a support package for Argument Clinic. Get rid of clinic.py globals by either making them class members, or by putting them into libclinic. - Move INCLUDE_COMMENT_COLUMN to BlockPrinter - Move NO_VARARG to CLanguage - Move formatting helpers to libclinic - Move some constants to libclinic (and annotate them as Final) --- Lib/test/test_clinic.py | 29 ++--- Tools/clinic/clinic.py | 159 +++++++-------------------- Tools/clinic/libclinic/__init__.py | 40 +++++++ Tools/clinic/libclinic/formatting.py | 92 ++++++++++++++++ 4 files changed, 185 insertions(+), 135 deletions(-) create mode 100644 Tools/clinic/libclinic/__init__.py create mode 100644 Tools/clinic/libclinic/formatting.py diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 199c9fec80205a..cfb84bcaa3f3b7 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -16,6 +16,7 @@ test_tools.skip_if_missing('clinic') with test_tools.imports_under_tool('clinic'): + import libclinic import clinic from clinic import DSLParser @@ -3761,19 +3762,19 @@ def test_normalize_snippet(self): actual = clinic.normalize_snippet(snippet, indent=indent) self.assertEqual(actual, expected) - def test_quoted_for_c_string(self): + def test_escaped_docstring(self): dataset = ( # input, expected - (r"abc", r"abc"), - (r"\abc", r"\\abc"), - (r"\a\bc", r"\\a\\bc"), - (r"\a\\bc", r"\\a\\\\bc"), - (r'"abc"', r'\"abc\"'), - (r"'a'", r"\'a\'"), + (r"abc", r'"abc"'), + (r"\abc", r'"\\abc"'), + (r"\a\bc", r'"\\a\\bc"'), + (r"\a\\bc", r'"\\a\\\\bc"'), + (r'"abc"', r'"\"abc\""'), + (r"'a'", r'"\'a\'"'), ) for line, expected in dataset: with self.subTest(line=line, expected=expected): - out = clinic.quoted_for_c_string(line) + out = libclinic.docstring_for_c_string(line) self.assertEqual(out, expected) def test_format_escape(self): @@ -3784,7 +3785,7 @@ def test_format_escape(self): def test_indent_all_lines(self): # Blank lines are expected to be unchanged. - self.assertEqual(clinic.indent_all_lines("", prefix="bar"), "") + self.assertEqual(libclinic.indent_all_lines("", prefix="bar"), "") lines = ( "one\n" @@ -3794,7 +3795,7 @@ def test_indent_all_lines(self): "barone\n" "bartwo" ) - out = clinic.indent_all_lines(lines, prefix="bar") + out = libclinic.indent_all_lines(lines, prefix="bar") self.assertEqual(out, expected) # If last line is empty, expect it to be unchanged. @@ -3810,12 +3811,12 @@ def test_indent_all_lines(self): "bartwo\n" "" ) - out = clinic.indent_all_lines(lines, prefix="bar") + out = libclinic.indent_all_lines(lines, prefix="bar") self.assertEqual(out, expected) def test_suffix_all_lines(self): # Blank lines are expected to be unchanged. - self.assertEqual(clinic.suffix_all_lines("", suffix="foo"), "") + self.assertEqual(libclinic.suffix_all_lines("", suffix="foo"), "") lines = ( "one\n" @@ -3825,7 +3826,7 @@ def test_suffix_all_lines(self): "onefoo\n" "twofoo" ) - out = clinic.suffix_all_lines(lines, suffix="foo") + out = libclinic.suffix_all_lines(lines, suffix="foo") self.assertEqual(out, expected) # If last line is empty, expect it to be unchanged. @@ -3841,7 +3842,7 @@ def test_suffix_all_lines(self): "twofoo\n" "" ) - out = clinic.suffix_all_lines(lines, suffix="foo") + out = libclinic.suffix_all_lines(lines, suffix="foo") self.assertEqual(out, expected) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 92092e97000ad0..532c45f4b39c4e 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -50,6 +50,11 @@ overload, ) + +# Local imports. +import libclinic + + # TODO: # # soon: @@ -61,23 +66,6 @@ # and keyword-only # -NO_VARARG = "PY_SSIZE_T_MAX" -CLINIC_PREFIX = "__clinic_" -CLINIC_PREFIXED_ARGS = { - "_keywords", - "_parser", - "args", - "argsbuf", - "fastargs", - "kwargs", - "kwnames", - "nargs", - "noptargs", - "return_value", -} - -# '#include "header.h" // reason': column of '//' comment -INCLUDE_COMMENT_COLUMN = 35 # match '#define Py_LIMITED_API' LIMITED_CAPI_REGEX = re.compile(r'#define +Py_LIMITED_API') @@ -103,8 +91,6 @@ def __repr__(self) -> str: NULL = Null() -sig_end_marker = '--' - TemplateDict = dict[str, str] @@ -179,33 +165,6 @@ def fail( warn_or_fail(*args, filename=filename, line_number=line_number, fail=True) -def quoted_for_c_string(s: str) -> str: - for old, new in ( - ('\\', '\\\\'), # must be first! - ('"', '\\"'), - ("'", "\\'"), - ): - s = s.replace(old, new) - return s - -def c_repr(s: str) -> str: - return '"' + s + '"' - - -def wrapped_c_string_literal( - text: str, - *, - width: int = 72, - suffix: str = '', - initial_indent: int = 0, - subsequent_indent: int = 4 -) -> str: - wrapped = textwrap.wrap(text, width=width, replace_whitespace=False, - drop_whitespace=False, break_on_hyphens=False) - separator = '"' + suffix + '\n' + subsequent_indent * ' ' + '"' - return initial_indent * ' ' + '"' + separator.join(wrapped) + '"' - - is_legal_c_identifier = re.compile('^[A-Za-z_][A-Za-z0-9_]*$').match def is_legal_py_identifier(s: str) -> bool: @@ -251,7 +210,6 @@ def linear_format(s: str, **kwargs: str) -> str: by the indent of the source line. * A newline will be added to the end. """ - lines = [] for line in s.split('\n'): indent, curly, trailing = line.partition('{') @@ -281,34 +239,6 @@ def linear_format(s: str, **kwargs: str) -> str: return "".join(lines[:-1]) -def _add_prefix_and_suffix(s: str, prefix: str = "", suffix: str = "") -> str: - """ - Return 's', with 'prefix' prepended and 'suffix' appended to all lines. - - If the last line is empty, it remains unchanged. - If s is blank, returns s unchanged. - - (textwrap.indent only adds to non-blank lines.) - """ - *split, last = s.split("\n") - lines = [prefix + line + suffix + "\n" for line in split] - if last: - lines.append(prefix + last + suffix) - return "".join(lines) - -def indent_all_lines(s: str, prefix: str) -> str: - return _add_prefix_and_suffix(s, prefix=prefix) - -def suffix_all_lines(s: str, suffix: str) -> str: - return _add_prefix_and_suffix(s, suffix=suffix) - - -def pprint_words(items: list[str]) -> str: - if len(items) <= 2: - return " and ".join(items) - else: - return ", ".join(items[:-1]) + " and " + items[-1] - class CRenderData: def __init__(self) -> None: @@ -710,6 +640,8 @@ class CLanguage(Language): stop_line = "[{dsl_name} start generated code]*/" checksum_line = "/*[{dsl_name} end generated code: {arguments}]*/" + NO_VARARG: Final[str] = "PY_SSIZE_T_MAX" + PARSER_PROTOTYPE_KEYWORD: Final[str] = normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs) @@ -863,7 +795,7 @@ def compiler_deprecated_warning( code = self.COMPILER_DEPRECATION_WARNING_PROTOTYPE.format( major=minversion[0], minor=minversion[1], - message=c_repr(message), + message=libclinic.c_repr(message), ) return normalize_snippet(code) @@ -894,7 +826,7 @@ def deprecate_positional_use( params.values(), key=attrgetter("deprecated_positional") ): names = [repr(p.name) for p in group] - pstr = pprint_words(names) + pstr = libclinic.pprint_words(names) if len(names) == 1: message += ( f" Parameter {pstr} will become a keyword-only parameter " @@ -913,8 +845,8 @@ def deprecate_positional_use( code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, errcheck="", - message=wrapped_c_string_literal(message, width=64, - subsequent_indent=20), + message=libclinic.wrapped_c_string_literal(message, width=64, + subsequent_indent=20), ) return normalize_snippet(code, indent=4) @@ -963,7 +895,7 @@ def deprecate_keyword_use( else: condition = f"kwargs && PyDict_GET_SIZE(kwargs) && {condition}" names = [repr(p.name) for p in params.values()] - pstr = pprint_words(names) + pstr = libclinic.pprint_words(names) pl = 's' if len(params) != 1 else '' message = ( f"Passing keyword argument{pl} {pstr} to " @@ -974,7 +906,7 @@ def deprecate_keyword_use( params.values(), key=attrgetter("deprecated_keyword") ): names = [repr(p.name) for p in group] - pstr = pprint_words(names) + pstr = libclinic.pprint_words(names) pl = 's' if len(names) != 1 else '' message += ( f" Parameter{pl} {pstr} will become positional-only " @@ -996,31 +928,11 @@ def deprecate_keyword_use( code = self.DEPRECATION_WARNING_PROTOTYPE.format( condition=condition, errcheck=errcheck, - message=wrapped_c_string_literal(message, width=64, - subsequent_indent=20), + message=libclinic.wrapped_c_string_literal(message, width=64, + subsequent_indent=20), ) return normalize_snippet(code, indent=4) - def docstring_for_c_string( - self, - f: Function - ) -> str: - lines = [] - # turn docstring into a properly quoted C string - for line in f.docstring.split('\n'): - lines.append('"') - lines.append(quoted_for_c_string(line)) - lines.append('\\n"\n') - - if lines[-2] == sig_end_marker: - # If we only have a signature, add the blank line that the - # __text_signature__ getter expects to be there. - lines.append('"\\n"') - else: - lines.pop() - lines.append('"') - return ''.join(lines) - def output_templates( self, f: Function, @@ -1049,7 +961,7 @@ def output_templates( and not f.critical_section) new_or_init = f.kind.new_or_init - vararg: int | str = NO_VARARG + vararg: int | str = self.NO_VARARG pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0 for i, p in enumerate(parameters, 1): if p.is_keyword_only(): @@ -1057,12 +969,12 @@ def output_templates( if not p.is_optional(): min_kw_only = i - max_pos elif p.is_vararg(): - if vararg != NO_VARARG: + if vararg != self.NO_VARARG: fail("Too many var args") pseudo_args += 1 vararg = i - 1 else: - if vararg == NO_VARARG: + if vararg == self.NO_VARARG: max_pos = i if p.is_positional_only(): pos_only = i @@ -1271,7 +1183,7 @@ def parser_body( argname_fmt = 'PyTuple_GET_ITEM(args, %d)' left_args = f"{nargs} - {max_pos}" - max_args = NO_VARARG if (vararg != NO_VARARG) else max_pos + max_args = self.NO_VARARG if (vararg != self.NO_VARARG) else max_pos if limited_capi: parser_code = [] if nargs != 'nargs': @@ -1296,7 +1208,7 @@ def parser_body( }}}} """, indent=4)) - if max_args != NO_VARARG: + if max_args != self.NO_VARARG: pl = '' if max_args == 1 else 's' parser_code.append(normalize_snippet(f""" if ({nargs} > {max_args}) {{{{ @@ -1393,13 +1305,16 @@ def parser_body( if p.deprecated_keyword: deprecated_keywords[i] = p - has_optional_kw = (max(pos_only, min_pos) + min_kw_only < len(converters) - int(vararg != NO_VARARG)) + has_optional_kw = ( + max(pos_only, min_pos) + min_kw_only + < len(converters) - int(vararg != self.NO_VARARG) + ) if limited_capi: parser_code = None fastcall = False else: - if vararg == NO_VARARG: + if vararg == self.NO_VARARG: clinic.add_include('pycore_modsupport.h', '_PyArg_UnpackKeywords()') args_declaration = "_PyArg_UnpackKeywords", "%s, %s, %s" % ( @@ -1499,7 +1414,7 @@ def parser_body( else: label = 'skip_optional_kwonly' first_opt = max_pos + min_kw_only - if vararg != NO_VARARG: + if vararg != self.NO_VARARG: first_opt += 1 if i == first_opt: add_label = label @@ -1897,8 +1812,7 @@ def render_function( template_dict['methoddef_name'] = f.c_basename.upper() + "_METHODDEF" template_dict['c_basename'] = f.c_basename - template_dict['docstring'] = self.docstring_for_c_string(f) - + template_dict['docstring'] = libclinic.docstring_for_c_string(f.docstring) template_dict['self_name'] = template_dict['self_type'] = template_dict['self_type_check'] = '' template_dict['target_critical_section'] = ', '.join(f.target_critical_section) for converter in converters: @@ -1976,9 +1890,9 @@ def render_function( s = wrap_declarations(s) if clinic.line_prefix: - s = indent_all_lines(s, clinic.line_prefix) + s = libclinic.indent_all_lines(s, clinic.line_prefix) if clinic.line_suffix: - s = suffix_all_lines(s, clinic.line_suffix) + s = libclinic.suffix_all_lines(s, clinic.line_suffix) destination.append(s) @@ -2263,6 +2177,9 @@ class BlockPrinter: language: Language f: io.StringIO = dc.field(default_factory=io.StringIO) + # '#include "header.h" // reason': column of '//' comment + INCLUDE_COMMENT_COLUMN: Final[int] = 35 + def print_block( self, block: Block, @@ -2318,7 +2235,7 @@ def print_block( line = f'#include "{include.filename}"' if include.reason: comment = f'// {include.reason}\n' - line = line.ljust(INCLUDE_COMMENT_COLUMN - 1) + comment + line = line.ljust(self.INCLUDE_COMMENT_COLUMN - 1) + comment output += line if current_condition: @@ -3406,7 +3323,7 @@ def parse_argument(self, args: list[str]) -> None: args.append(self.converter) if self.encoding: - args.append(c_repr(self.encoding)) + args.append(libclinic.c_repr(self.encoding)) elif self.subclass_of: args.append(self.subclass_of) @@ -3584,8 +3501,8 @@ def set_template_dict(self, template_dict: TemplateDict) -> None: @property def parser_name(self) -> str: - if self.name in CLINIC_PREFIXED_ARGS: # bpo-39741 - return CLINIC_PREFIX + self.name + if self.name in libclinic.CLINIC_PREFIXED_ARGS: # bpo-39741 + return libclinic.CLINIC_PREFIX + self.name else: return self.name @@ -5867,7 +5784,7 @@ def bad_node(self, node: ast.AST) -> None: if isinstance(value, (bool, NoneType)): c_default = "Py_" + py_default elif isinstance(value, str): - c_default = c_repr(value) + c_default = libclinic.c_repr(value) else: c_default = py_default @@ -6312,7 +6229,7 @@ def add_parameter(text: str) -> None: # lines.append(f.return_converter.py_default) if not f.docstring_only: - lines.append("\n" + sig_end_marker + "\n") + lines.append("\n" + libclinic.SIG_END_MARKER + "\n") signature_line = "".join(lines) diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py new file mode 100644 index 00000000000000..32ab2259ce4226 --- /dev/null +++ b/Tools/clinic/libclinic/__init__.py @@ -0,0 +1,40 @@ +from typing import Final + +from .formatting import ( + c_repr, + docstring_for_c_string, + indent_all_lines, + pprint_words, + suffix_all_lines, + wrapped_c_string_literal, + SIG_END_MARKER, +) + + +__all__ = [ + # Formatting helpers + "c_repr", + "docstring_for_c_string", + "indent_all_lines", + "pprint_words", + "suffix_all_lines", + "wrapped_c_string_literal", + "SIG_END_MARKER", +] + + +CLINIC_PREFIX: Final = "__clinic_" +CLINIC_PREFIXED_ARGS: Final = frozenset( + { + "_keywords", + "_parser", + "args", + "argsbuf", + "fastargs", + "kwargs", + "kwnames", + "nargs", + "noptargs", + "return_value", + } +) diff --git a/Tools/clinic/libclinic/formatting.py b/Tools/clinic/libclinic/formatting.py new file mode 100644 index 00000000000000..691a8fc47ef037 --- /dev/null +++ b/Tools/clinic/libclinic/formatting.py @@ -0,0 +1,92 @@ +"""A collection of string formatting helpers.""" + +import textwrap +from typing import Final + + +SIG_END_MARKER: Final = "--" + + +def docstring_for_c_string(docstring: str) -> str: + lines = [] + # Turn docstring into a properly quoted C string. + for line in docstring.split("\n"): + lines.append('"') + lines.append(_quoted_for_c_string(line)) + lines.append('\\n"\n') + + if lines[-2] == SIG_END_MARKER: + # If we only have a signature, add the blank line that the + # __text_signature__ getter expects to be there. + lines.append('"\\n"') + else: + lines.pop() + lines.append('"') + return "".join(lines) + + +def _quoted_for_c_string(text: str) -> str: + """Helper for docstring_for_c_string().""" + for old, new in ( + ("\\", "\\\\"), # must be first! + ('"', '\\"'), + ("'", "\\'"), + ): + text = text.replace(old, new) + return text + + +def c_repr(text: str) -> str: + return '"' + text + '"' + + +def wrapped_c_string_literal( + text: str, + *, + width: int = 72, + suffix: str = "", + initial_indent: int = 0, + subsequent_indent: int = 4 +) -> str: + wrapped = textwrap.wrap( + text, + width=width, + replace_whitespace=False, + drop_whitespace=False, + break_on_hyphens=False, + ) + separator = c_repr(suffix + "\n" + subsequent_indent * " ") + return initial_indent * " " + c_repr(separator.join(wrapped)) + + +def _add_prefix_and_suffix( + text: str, + prefix: str = "", + suffix: str = "" +) -> str: + """Return 'text' with 'prefix' prepended and 'suffix' appended to all lines. + + If the last line is empty, it remains unchanged. + If text is blank, return text unchanged. + + (textwrap.indent only adds to non-blank lines.) + """ + *split, last = text.split("\n") + lines = [prefix + line + suffix + "\n" for line in split] + if last: + lines.append(prefix + last + suffix) + return "".join(lines) + + +def indent_all_lines(text: str, prefix: str) -> str: + return _add_prefix_and_suffix(text, prefix=prefix) + + +def suffix_all_lines(text: str, suffix: str) -> str: + return _add_prefix_and_suffix(text, suffix=suffix) + + +def pprint_words(items: list[str]) -> str: + if len(items) <= 2: + return " and ".join(items) + return ", ".join(items[:-1]) + " and " + items[-1] From 4e5b27e6a3be85853bd04d45128dd7cc706bb1c8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 23 Dec 2023 11:56:30 +0200 Subject: [PATCH 351/442] gh-81682: Fix test failures when CPython is built without docstrings (GH-113410) --- Lib/idlelib/idle_test/test_calltip.py | 6 ++++ Lib/test/test_capi/test_misc.py | 2 ++ Lib/test/test_coroutines.py | 11 ++++---- Lib/test/test_curses.py | 4 ++- Lib/test/test_functools.py | 2 ++ .../test_importlib/extension/test_loader.py | 4 ++- Lib/test/test_inspect/test_inspect.py | 5 +++- Lib/test/test_module/__init__.py | 2 +- Lib/test/test_pydoc.py | 28 +++++++++++++------ Lib/test/test_rlcompleter.py | 5 ++-- Lib/test/test_types.py | 4 ++- Lib/test/test_zoneinfo/test_zoneinfo.py | 3 ++ 12 files changed, 55 insertions(+), 21 deletions(-) diff --git a/Lib/idlelib/idle_test/test_calltip.py b/Lib/idlelib/idle_test/test_calltip.py index 1ccb63b9dbd65f..15e1ff3f3cf717 100644 --- a/Lib/idlelib/idle_test/test_calltip.py +++ b/Lib/idlelib/idle_test/test_calltip.py @@ -7,6 +7,7 @@ import types import re from idlelib.idle_test.mock_tk import Text +from test.support import MISSING_C_DOCSTRINGS # Test Class TC is used in multiple get_argspec test methods @@ -50,6 +51,8 @@ class Get_argspecTest(unittest.TestCase): # but a red buildbot is better than a user crash (as has happened). # For a simple mismatch, change the expected output to the actual. + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_builtins(self): def tiptest(obj, out): @@ -143,6 +146,8 @@ def f(): pass f.__doc__ = 'a'*300 self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}") + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_multiline_docstring(self): # Test fewer lines than max. self.assertEqual(get_spec(range), @@ -157,6 +162,7 @@ def test_multiline_docstring(self): bytes(int) -> bytes object of size given by the parameter initialized with null bytes bytes() -> empty bytes object''') + def test_multiline_docstring_2(self): # Test more than max lines def f(): pass f.__doc__ = 'a\n' * 15 diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 123813b949fb7d..67fbef4f269814 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -469,6 +469,8 @@ def __del__(self): del L self.assertEqual(PyList.num, 0) + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_heap_ctype_doc_and_text_signature(self): self.assertEqual(_testcapi.HeapDocCType.__doc__, "somedoc") self.assertEqual(_testcapi.HeapDocCType.__text_signature__, "(arg1, arg2)") diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 25c981d1511bc1..d848bfbd46c83b 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -953,11 +953,12 @@ async def b(): def test_corotype_1(self): ct = types.CoroutineType - self.assertIn('into coroutine', ct.send.__doc__) - self.assertIn('inside coroutine', ct.close.__doc__) - self.assertIn('in coroutine', ct.throw.__doc__) - self.assertIn('of the coroutine', ct.__dict__['__name__'].__doc__) - self.assertIn('of the coroutine', ct.__dict__['__qualname__'].__doc__) + if not support.MISSING_C_DOCSTRINGS: + self.assertIn('into coroutine', ct.send.__doc__) + self.assertIn('inside coroutine', ct.close.__doc__) + self.assertIn('in coroutine', ct.throw.__doc__) + self.assertIn('of the coroutine', ct.__dict__['__name__'].__doc__) + self.assertIn('of the coroutine', ct.__dict__['__qualname__'].__doc__) self.assertEqual(ct.__name__, 'coroutine') async def f(): pass diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 31bc108e7712ea..83d10dd8579074 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -8,7 +8,7 @@ from unittest.mock import MagicMock from test.support import (requires, verbose, SaveSignals, cpython_only, - check_disallow_instantiation) + check_disallow_instantiation, MISSING_C_DOCSTRINGS) from test.support.import_helper import import_module # Optionally test curses module. This currently requires that the @@ -1142,6 +1142,8 @@ def test_encoding(self): with self.assertRaises(TypeError): del stdscr.encoding + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_issue21088(self): stdscr = self.stdscr # diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index b95afe0bcc86e4..0ef45d3c670e85 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -939,6 +939,8 @@ def mycmp(x, y): self.assertRaises(TypeError, hash, k) self.assertNotIsInstance(k, collections.abc.Hashable) + @unittest.skipIf(support.MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_cmp_to_signature(self): self.assertEqual(str(Signature.from_callable(self.cmp_to_key)), '(mycmp)') diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index 64c8a5485106e3..84a0680e4ec653 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -9,6 +9,7 @@ import warnings import importlib.util import importlib +from test.support import MISSING_C_DOCSTRINGS class LoaderTests: @@ -373,7 +374,8 @@ def test_nonascii(self): with self.subTest(name): module = self.load_module_by_name(name) self.assertEqual(module.__name__, name) - self.assertEqual(module.__doc__, "Module named in %s" % lang) + if not MISSING_C_DOCSTRINGS: + self.assertEqual(module.__doc__, "Module named in %s" % lang) (Frozen_MultiPhaseExtensionModuleTests, diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 09d50859970c99..4611f62b293ff9 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -3990,6 +3990,8 @@ def foo(a, *, b:1): pass foo_sig = MySignature.from_callable(foo) self.assertIsInstance(foo_sig, MySignature) + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_signature_from_callable_class(self): # A regression test for a class inheriting its signature from `object`. class MySignature(inspect.Signature): pass @@ -4080,7 +4082,8 @@ def test_signature_eval_str(self): par('c', PORK, annotation="'MyClass'"), ))) - self.assertEqual(signature_func(isa.UnannotatedClass), sig()) + if not MISSING_C_DOCSTRINGS: + self.assertEqual(signature_func(isa.UnannotatedClass), sig()) self.assertEqual(signature_func(isa.unannotated_function), sig( parameters=( diff --git a/Lib/test/test_module/__init__.py b/Lib/test/test_module/__init__.py index d49c44df4d839d..98d1cbe824df12 100644 --- a/Lib/test/test_module/__init__.py +++ b/Lib/test/test_module/__init__.py @@ -30,7 +30,7 @@ def test_uninitialized(self): self.fail("__name__ = %s" % repr(s)) except AttributeError: pass - self.assertEqual(foo.__doc__, ModuleType.__doc__) + self.assertEqual(foo.__doc__, ModuleType.__doc__ or '') def test_uninitialized_missing_getattr(self): # Issue 8297 diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index eb50510e12b7b6..982ee60c0be4f7 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -32,7 +32,7 @@ from test.support import threading_helper from test.support import (reap_children, captured_output, captured_stdout, captured_stderr, is_emscripten, is_wasi, - requires_docstrings) + requires_docstrings, MISSING_C_DOCSTRINGS) from test.support.os_helper import (TESTFN, rmtree, unlink) from test import pydoc_mod @@ -906,12 +906,13 @@ class A(builtins.object) | ---------------------------------------------------------------------- | Data descriptors defined here: | - | __dict__ - | dictionary for instance variables + | __dict__%s | - | __weakref__ - | list of weak references to the object -''' % __name__) + | __weakref__%s +''' % (__name__, + '' if MISSING_C_DOCSTRINGS else '\n | dictionary for instance variables', + '' if MISSING_C_DOCSTRINGS else '\n | list of weak references to the object', + )) def func( arg1: Callable[[Annotated[int, 'Some doc']], str], @@ -1154,13 +1155,15 @@ def test_generic_alias(self): doc = pydoc.render_doc(typing.List[int], renderer=pydoc.plaintext) self.assertIn('_GenericAlias in module typing', doc) self.assertIn('List = class list(object)', doc) - self.assertIn(list.__doc__.strip().splitlines()[0], doc) + if not MISSING_C_DOCSTRINGS: + self.assertIn(list.__doc__.strip().splitlines()[0], doc) self.assertEqual(pydoc.describe(list[int]), 'GenericAlias') doc = pydoc.render_doc(list[int], renderer=pydoc.plaintext) self.assertIn('GenericAlias in module builtins', doc) self.assertIn('\nclass list(object)', doc) - self.assertIn(list.__doc__.strip().splitlines()[0], doc) + if not MISSING_C_DOCSTRINGS: + self.assertIn(list.__doc__.strip().splitlines()[0], doc) def test_union_type(self): self.assertEqual(pydoc.describe(typing.Union[int, str]), '_UnionGenericAlias') @@ -1174,7 +1177,8 @@ def test_union_type(self): doc = pydoc.render_doc(int | str, renderer=pydoc.plaintext) self.assertIn('UnionType in module types object', doc) self.assertIn('\nclass UnionType(builtins.object)', doc) - self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc) + if not MISSING_C_DOCSTRINGS: + self.assertIn(types.UnionType.__doc__.strip().splitlines()[0], doc) def test_special_form(self): self.assertEqual(pydoc.describe(typing.NoReturn), '_SpecialForm') @@ -1327,6 +1331,7 @@ def test_bound_builtin_classmethod_o(self): "__class_getitem__(object, /) method of builtins.type instance") @support.cpython_only + @requires_docstrings def test_module_level_callable_unrepresentable_default(self): import _testcapi builtin = _testcapi.func_with_unrepresentable_signature @@ -1334,6 +1339,7 @@ def test_module_level_callable_unrepresentable_default(self): "func_with_unrepresentable_signature(a, b=)") @support.cpython_only + @requires_docstrings def test_builtin_staticmethod_unrepresentable_default(self): self.assertEqual(self._get_summary_line(str.maketrans), "maketrans(x, y=, z=, /)") @@ -1343,6 +1349,7 @@ def test_builtin_staticmethod_unrepresentable_default(self): "staticmeth(a, b=)") @support.cpython_only + @requires_docstrings def test_unbound_builtin_method_unrepresentable_default(self): self.assertEqual(self._get_summary_line(dict.pop), "pop(self, key, default=, /)") @@ -1352,6 +1359,7 @@ def test_unbound_builtin_method_unrepresentable_default(self): "meth(self, /, a, b=)") @support.cpython_only + @requires_docstrings def test_bound_builtin_method_unrepresentable_default(self): self.assertEqual(self._get_summary_line({}.pop), "pop(key, default=, /) " @@ -1363,6 +1371,7 @@ def test_bound_builtin_method_unrepresentable_default(self): "method of _testcapi.DocStringUnrepresentableSignatureTest instance") @support.cpython_only + @requires_docstrings def test_unbound_builtin_classmethod_unrepresentable_default(self): import _testcapi cls = _testcapi.DocStringUnrepresentableSignatureTest @@ -1371,6 +1380,7 @@ def test_unbound_builtin_classmethod_unrepresentable_default(self): "classmeth(type, /, a, b=)") @support.cpython_only + @requires_docstrings def test_bound_builtin_classmethod_unrepresentable_default(self): import _testcapi cls = _testcapi.DocStringUnrepresentableSignatureTest diff --git a/Lib/test/test_rlcompleter.py b/Lib/test/test_rlcompleter.py index 7347fca71be2fe..273ce2cf5c7dd2 100644 --- a/Lib/test/test_rlcompleter.py +++ b/Lib/test/test_rlcompleter.py @@ -2,6 +2,7 @@ from unittest.mock import patch import builtins import rlcompleter +from test.support import MISSING_C_DOCSTRINGS class CompleteMe: """ Trivial class used in testing rlcompleter.Completer. """ @@ -40,12 +41,12 @@ def test_global_matches(self): # test with a customized namespace self.assertEqual(self.completer.global_matches('CompleteM'), - ['CompleteMe()']) + ['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()']) self.assertEqual(self.completer.global_matches('eg'), ['egg(']) # XXX: see issue5256 self.assertEqual(self.completer.global_matches('CompleteM'), - ['CompleteMe()']) + ['CompleteMe(' if MISSING_C_DOCSTRINGS else 'CompleteMe()']) def test_attr_matches(self): # test with builtins namespace diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index da32c4ea6477ce..bfecd8eb71220c 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1,6 +1,6 @@ # Python test set -- part 6, built-in types -from test.support import run_with_locale, cpython_only +from test.support import run_with_locale, cpython_only, MISSING_C_DOCSTRINGS import collections.abc from collections import namedtuple import copy @@ -598,6 +598,8 @@ def test_slot_wrapper_types(self): self.assertIsInstance(object.__lt__, types.WrapperDescriptorType) self.assertIsInstance(int.__lt__, types.WrapperDescriptorType) + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_dunder_get_signature(self): sig = inspect.signature(object.__init__.__get__) self.assertEqual(list(sig.parameters), ["instance", "owner"]) diff --git a/Lib/test/test_zoneinfo/test_zoneinfo.py b/Lib/test/test_zoneinfo/test_zoneinfo.py index 3766ceac8385f2..7b6b69d0109d88 100644 --- a/Lib/test/test_zoneinfo/test_zoneinfo.py +++ b/Lib/test/test_zoneinfo/test_zoneinfo.py @@ -17,6 +17,7 @@ from datetime import date, datetime, time, timedelta, timezone from functools import cached_property +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 @@ -404,6 +405,8 @@ def test_time_fixed_offset(self): class CZoneInfoTest(ZoneInfoTest): module = c_zoneinfo + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") def test_signatures(self): """Ensure that C module has valid method signatures.""" import inspect From bdc8d667ab545ccec0bf8c2769f5c5573ed293ea Mon Sep 17 00:00:00 2001 From: F-park <52167622+F-park@users.noreply.github.com> Date: Sat, 23 Dec 2023 18:44:27 +0800 Subject: [PATCH 352/442] gh-112925: Fix error in example of `datetime.time.fromisoformat` and add doctest marker (GH-112931) --- Doc/library/datetime.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 686b37754368c1..3674b4bd97d39d 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1799,6 +1799,8 @@ Other constructor: Examples:: + .. doctest:: + >>> from datetime import time >>> time.fromisoformat('04:23:01') datetime.time(4, 23, 1) @@ -1808,7 +1810,7 @@ Other constructor: datetime.time(4, 23, 1) >>> time.fromisoformat('04:23:01.000384') datetime.time(4, 23, 1, 384) - >>> time.fromisoformat('04:23:01,000') + >>> time.fromisoformat('04:23:01,000384') datetime.time(4, 23, 1, 384) >>> time.fromisoformat('04:23:01+04:00') datetime.time(4, 23, 1, tzinfo=datetime.timezone(datetime.timedelta(seconds=14400))) From 6e02d79f96b30bacdbc7a85e42040920b3dee915 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 23 Dec 2023 13:07:54 +0200 Subject: [PATCH 353/442] gh-113188: Fix shutil.copymode() on Windows (GH-113189) Previously it worked differently if dst is a symbolic link: it modified the permission bits of dst itself rather than the file it points to if follow_symlinks is true or src is not a symbolic link, and did nothing if follow_symlinks is false and src is a symbolic link. Also document similar changes in shutil.copystat(). --- Lib/shutil.py | 7 +++++- Lib/test/test_shutil.py | 25 +++++++++---------- ...-12-15-20-29-49.gh-issue-113188.AvoraB.rst | 6 +++++ 3 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index dc3aac3e07f910..c40f6ddae39a17 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -306,7 +306,12 @@ def copymode(src, dst, *, follow_symlinks=True): else: return else: - stat_func, chmod_func = _stat, os.chmod + stat_func = _stat + if os.name == 'nt' and os.path.islink(dst): + def chmod_func(*args): + os.chmod(*args, follow_symlinks=True) + else: + chmod_func = os.chmod st = stat_func(src) chmod_func(dst, stat.S_IMODE(st.st_mode)) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 5ce8e5d77fbbf3..cc5459aa08fe33 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1101,19 +1101,18 @@ def test_copymode_follow_symlinks(self): shutil.copymode(src, dst) self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) # On Windows, os.chmod does not follow symlinks (issue #15411) - if os.name != 'nt': - # follow src link - os.chmod(dst, stat.S_IRWXO) - shutil.copymode(src_link, dst) - self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - # follow dst link - os.chmod(dst, stat.S_IRWXO) - shutil.copymode(src, dst_link) - self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) - # follow both links - os.chmod(dst, stat.S_IRWXO) - shutil.copymode(src_link, dst_link) - self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + # follow src link + os.chmod(dst, stat.S_IRWXO) + shutil.copymode(src_link, dst) + self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + # follow dst link + os.chmod(dst, stat.S_IRWXO) + shutil.copymode(src, dst_link) + self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) + # follow both links + os.chmod(dst, stat.S_IRWXO) + shutil.copymode(src_link, dst_link) + self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode) @unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod') @os_helper.skip_unless_symlink diff --git a/Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst b/Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst new file mode 100644 index 00000000000000..17c69572d9f2b1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-20-29-49.gh-issue-113188.AvoraB.rst @@ -0,0 +1,6 @@ +Fix :func:`shutil.copymode` and :func:`shutil.copystat` on Windows. +Previously they worked differenly if *dst* is a symbolic link: +they modified the permission bits of *dst* itself +rather than the file it points to if *follow_symlinks* is true or *src* is +not a symbolic link, and did not modify the permission bits if +*follow_symlinks* is false and *src* is a symbolic link. From 593b4d81d276b428f926debfe70d56ba94edf0e1 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Sat, 23 Dec 2023 13:33:34 +0100 Subject: [PATCH 354/442] gh-74573: document that ndbm can silently corrupt databases on macOS (#113354) * gh-74573: document that ndbm can silently corrupt databases on macOS The system ndbm implementation on macOS has an undocumented limitation on the size of values and can silently corrupt database files when those are exceeded. Co-authored-by: Erlend E. Aasland --- Doc/library/dbm.rst | 7 +++++++ Doc/library/shelve.rst | 3 +++ .../macOS/2023-12-21-11-53-47.gh-issue-74573.MA6Vys.rst | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-21-11-53-47.gh-issue-74573.MA6Vys.rst diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 766847b971b645..cb95c61322582f 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -272,6 +272,13 @@ 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. +.. warning:: + + 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). + .. exception:: error Raised on :mod:`dbm.ndbm`-specific errors, such as I/O errors. :exc:`KeyError` is raised diff --git a/Doc/library/shelve.rst b/Doc/library/shelve.rst index 88802d717d7383..95c54991887022 100644 --- a/Doc/library/shelve.rst +++ b/Doc/library/shelve.rst @@ -113,6 +113,9 @@ Restrictions differs across Unix versions and requires knowledge about the database implementation used. +* On macOS :mod:`dbm.ndbm` can silently corrupt the database file on updates, + which can cause hard crashes when trying to read from the database. + .. class:: Shelf(dict, protocol=None, writeback=False, keyencoding='utf-8') diff --git a/Misc/NEWS.d/next/macOS/2023-12-21-11-53-47.gh-issue-74573.MA6Vys.rst b/Misc/NEWS.d/next/macOS/2023-12-21-11-53-47.gh-issue-74573.MA6Vys.rst new file mode 100644 index 00000000000000..96dcd4765d95da --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-21-11-53-47.gh-issue-74573.MA6Vys.rst @@ -0,0 +1,3 @@ +Document that :mod:`dbm.ndbm` can silently corrupt DBM files on updates when +exceeding undocumented platform limits, and can crash (segmentation fault) +when reading such a corrupted file. (FB8919203) From 8bce593a6317882da82693e6e8f7c49df0cf59a5 Mon Sep 17 00:00:00 2001 From: Jeff Allen Date: Sat, 23 Dec 2023 14:10:41 +0000 Subject: [PATCH 355/442] Fix trivial typo in test_interpreters (GH-113381) --- Lib/test/test_interpreters/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_interpreters/__main__.py b/Lib/test/test_interpreters/__main__.py index 8641229877b2be..40a23a297ec2b4 100644 --- a/Lib/test/test_interpreters/__main__.py +++ b/Lib/test/test_interpreters/__main__.py @@ -1,4 +1,4 @@ from . import load_tests import unittest -nittest.main() +unittest.main() From ca71987f4e3be56a369a1dd57763c6077b3c4899 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 23 Dec 2023 18:08:10 +0100 Subject: [PATCH 356/442] gh-113317: Move more formatting helpers into libclinic (#113438) Move the following global helpers into libclinic: - format_escape() - normalize_snippet() - wrap_declarations() Also move strip_leading_and_trailing_blank_lines() and make it internal to libclinic. --- Lib/test/test_clinic.py | 6 +- Tools/clinic/clinic.py | 212 ++++++++------------------- Tools/clinic/libclinic/__init__.py | 10 +- Tools/clinic/libclinic/formatting.py | 91 +++++++++++- 4 files changed, 160 insertions(+), 159 deletions(-) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index cfb84bcaa3f3b7..21f56fe0195e69 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3730,7 +3730,7 @@ def test_strip_leading_and_trailing_blank_lines(self): ) for lines, expected in dataset: with self.subTest(lines=lines, expected=expected): - out = clinic.strip_leading_and_trailing_blank_lines(lines) + out = libclinic.normalize_snippet(lines) self.assertEqual(out, expected) def test_normalize_snippet(self): @@ -3759,7 +3759,7 @@ def test_normalize_snippet(self): expected_outputs = {0: zero_indent, 4: four_indent, 8: eight_indent} for indent, expected in expected_outputs.items(): with self.subTest(indent=indent): - actual = clinic.normalize_snippet(snippet, indent=indent) + actual = libclinic.normalize_snippet(snippet, indent=indent) self.assertEqual(actual, expected) def test_escaped_docstring(self): @@ -3780,7 +3780,7 @@ def test_escaped_docstring(self): def test_format_escape(self): line = "{}, {a}" expected = "{{}}, {{a}}" - out = clinic.format_escape(line) + out = libclinic.format_escape(line) self.assertEqual(out, expected) def test_indent_all_lines(self): diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 532c45f4b39c4e..f004bec3cce8f6 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -189,12 +189,6 @@ def ensure_legal_c_identifier(s: str) -> str: return s + "_value" return s -def format_escape(s: str) -> str: - # double up curly-braces, this string will be used - # as part of a format_map() template later - s = s.replace('{', '{{') - s = s.replace('}', '}}') - return s def linear_format(s: str, **kwargs: str) -> str: """ @@ -475,34 +469,6 @@ def permute_optional_groups( return tuple(accumulator) -def strip_leading_and_trailing_blank_lines(s: str) -> str: - lines = s.rstrip().split('\n') - while lines: - line = lines[0] - if line.strip(): - break - del lines[0] - return '\n'.join(lines) - -@functools.lru_cache() -def normalize_snippet( - s: str, - *, - indent: int = 0 -) -> str: - """ - Reformats s: - * removes leading and trailing blank lines - * ensures that it does not end with a newline - * dedents so the first nonwhite character on any line is at column "indent" - """ - s = strip_leading_and_trailing_blank_lines(s) - s = textwrap.dedent(s) - if indent: - s = textwrap.indent(s, ' ' * indent) - return s - - def declare_parser( f: Function, *, @@ -573,62 +539,7 @@ def declare_parser( }}; #undef KWTUPLE """ % (format_ or fname) - return normalize_snippet(declarations) - - -def wrap_declarations( - text: str, - length: int = 78 -) -> str: - """ - A simple-minded text wrapper for C function declarations. - - It views a declaration line as looking like this: - xxxxxxxx(xxxxxxxxx,xxxxxxxxx) - If called with length=30, it would wrap that line into - xxxxxxxx(xxxxxxxxx, - xxxxxxxxx) - (If the declaration has zero or one parameters, this - function won't wrap it.) - - If this doesn't work properly, it's probably better to - start from scratch with a more sophisticated algorithm, - rather than try and improve/debug this dumb little function. - """ - lines = [] - for line in text.split('\n'): - prefix, _, after_l_paren = line.partition('(') - if not after_l_paren: - lines.append(line) - continue - in_paren, _, after_r_paren = after_l_paren.partition(')') - if not _: - lines.append(line) - continue - if ',' not in in_paren: - lines.append(line) - continue - parameters = [x.strip() + ", " for x in in_paren.split(',')] - prefix += "(" - if len(prefix) < length: - spaces = " " * len(prefix) - else: - spaces = " " * 4 - - while parameters: - line = prefix - first = True - while parameters: - if (not first and - (len(line) + len(parameters[0]) > length)): - break - line += parameters.pop(0) - first = False - if not parameters: - line = line.rstrip(", ") + ")" + after_r_paren - lines.append(line.rstrip()) - prefix = spaces - return "\n".join(lines) + return libclinic.normalize_snippet(declarations) class CLanguage(Language): @@ -642,67 +553,67 @@ class CLanguage(Language): NO_VARARG: Final[str] = "PY_SSIZE_T_MAX" - PARSER_PROTOTYPE_KEYWORD: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_KEYWORD: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs) """) - PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_KEYWORD___INIT__: Final[str] = libclinic.normalize_snippet(""" static int {c_basename}({self_type}{self_name}, PyObject *args, PyObject *kwargs) """) - PARSER_PROTOTYPE_VARARGS: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_VARARGS: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *args) """) - PARSER_PROTOTYPE_FASTCALL: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_FASTCALL: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs) """) - PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_FASTCALL_KEYWORDS: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) """) - PARSER_PROTOTYPE_DEF_CLASS: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_DEF_CLASS: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyTypeObject *{defining_class_name}, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) """) - PARSER_PROTOTYPE_NOARGS: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_NOARGS: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *Py_UNUSED(ignored)) """) - PARSER_PROTOTYPE_GETTER: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_GETTER: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, void *Py_UNUSED(context)) """) - PARSER_PROTOTYPE_SETTER: Final[str] = normalize_snippet(""" + PARSER_PROTOTYPE_SETTER: Final[str] = libclinic.normalize_snippet(""" static int {c_basename}({self_type}{self_name}, PyObject *value, void *Py_UNUSED(context)) """) - METH_O_PROTOTYPE: Final[str] = normalize_snippet(""" + METH_O_PROTOTYPE: Final[str] = libclinic.normalize_snippet(""" static PyObject * {c_basename}({impl_parameters}) """) - DOCSTRING_PROTOTYPE_VAR: Final[str] = normalize_snippet(""" + DOCSTRING_PROTOTYPE_VAR: Final[str] = libclinic.normalize_snippet(""" PyDoc_VAR({c_basename}__doc__); """) - DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet(""" + DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet(""" PyDoc_STRVAR({c_basename}__doc__, {docstring}); """) - GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = normalize_snippet(""" + GETSET_DOCSTRING_PROTOTYPE_STRVAR: Final[str] = libclinic.normalize_snippet(""" PyDoc_STRVAR({getset_basename}__doc__, {docstring}); #define {getset_basename}_HAS_DOCSTR """) - IMPL_DEFINITION_PROTOTYPE: Final[str] = normalize_snippet(""" + IMPL_DEFINITION_PROTOTYPE: Final[str] = libclinic.normalize_snippet(""" static {impl_return_type} {c_basename}_impl({impl_parameters}) """) - METHODDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + METHODDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r""" #define {methoddef_name} \ {{"{name}", {methoddef_cast}{c_basename}{methoddef_cast_end}, {methoddef_flags}, {c_basename}__doc__}}, """) - GETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + GETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r""" #if defined({getset_basename}_HAS_DOCSTR) # define {getset_basename}_DOCSTR {getset_basename}__doc__ #else @@ -715,7 +626,7 @@ class CLanguage(Language): # define {getset_name}_GETSETDEF {{"{name}", (getter){getset_basename}_get, NULL, {getset_basename}_DOCSTR}}, #endif """) - SETTERDEF_PROTOTYPE_DEFINE: Final[str] = normalize_snippet(r""" + SETTERDEF_PROTOTYPE_DEFINE: Final[str] = libclinic.normalize_snippet(r""" #if defined({getset_name}_HAS_DOCSTR) # define {getset_basename}_DOCSTR {getset_basename}__doc__ #else @@ -728,7 +639,7 @@ class CLanguage(Language): # define {getset_name}_GETSETDEF {{"{name}", NULL, (setter){getset_basename}_set, NULL}}, #endif """) - METHODDEF_PROTOTYPE_IFNDEF: Final[str] = normalize_snippet(""" + METHODDEF_PROTOTYPE_IFNDEF: Final[str] = libclinic.normalize_snippet(""" #ifndef {methoddef_name} #define {methoddef_name} #endif /* !defined({methoddef_name}) */ @@ -797,7 +708,7 @@ def compiler_deprecated_warning( minor=minversion[1], message=libclinic.c_repr(message), ) - return normalize_snippet(code) + return libclinic.normalize_snippet(code) def deprecate_positional_use( self, @@ -848,7 +759,7 @@ def deprecate_positional_use( message=libclinic.wrapped_c_string_literal(message, width=64, subsequent_indent=20), ) - return normalize_snippet(code, indent=4) + return libclinic.normalize_snippet(code, indent=4) def deprecate_keyword_use( self, @@ -931,7 +842,7 @@ def deprecate_keyword_use( message=libclinic.wrapped_c_string_literal(message, width=64, subsequent_indent=20), ) - return normalize_snippet(code, indent=4) + return libclinic.normalize_snippet(code, indent=4) def output_templates( self, @@ -1036,14 +947,14 @@ def parser_body( lines.append(prototype) parser_body_fields = fields - preamble = normalize_snippet(""" + preamble = libclinic.normalize_snippet(""" {{ {return_value_declaration} {parser_declarations} {declarations} {initializers} """) + "\n" - finale = normalize_snippet(""" + finale = libclinic.normalize_snippet(""" {modifications} {lock} {return_value} = {c_basename}_impl({impl_arguments}); @@ -1095,7 +1006,7 @@ def parser_body( parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS return_error = ('return NULL;' if simple_return else 'goto exit;') - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (nargs) {{ PyErr_SetString(PyExc_TypeError, "{name}() takes no arguments"); %s @@ -1135,7 +1046,7 @@ def parser_body( argname = 'arg' if parameters[0].name == argname: argname += '_' - parser_prototype = normalize_snippet(""" + parser_prototype = libclinic.normalize_snippet(""" static PyObject * {c_basename}({self_type}{self_name}, PyObject *%s) """ % argname) @@ -1149,7 +1060,7 @@ def parser_body( }} """ % argname parser_definition = parser_body(parser_prototype, - normalize_snippet(parsearg, indent=4)) + libclinic.normalize_snippet(parsearg, indent=4)) elif has_option_groups: # positional parameters with option groups @@ -1187,11 +1098,12 @@ def parser_body( if limited_capi: parser_code = [] if nargs != 'nargs': - parser_code.append(normalize_snippet(f'Py_ssize_t nargs = {nargs};', indent=4)) + nargs_def = f'Py_ssize_t nargs = {nargs};' + parser_code.append(libclinic.normalize_snippet(nargs_def, indent=4)) nargs = 'nargs' if min_pos == max_args: pl = '' if min_pos == 1 else 's' - parser_code.append(normalize_snippet(f""" + parser_code.append(libclinic.normalize_snippet(f""" if ({nargs} != {min_pos}) {{{{ PyErr_Format(PyExc_TypeError, "{{name}} expected {min_pos} argument{pl}, got %zd", {nargs}); goto exit; @@ -1201,7 +1113,7 @@ def parser_body( else: if min_pos: pl = '' if min_pos == 1 else 's' - parser_code.append(normalize_snippet(f""" + parser_code.append(libclinic.normalize_snippet(f""" if ({nargs} < {min_pos}) {{{{ PyErr_Format(PyExc_TypeError, "{{name}} expected at least {min_pos} argument{pl}, got %zd", {nargs}); goto exit; @@ -1210,7 +1122,7 @@ def parser_body( indent=4)) if max_args != self.NO_VARARG: pl = '' if max_args == 1 else 's' - parser_code.append(normalize_snippet(f""" + parser_code.append(libclinic.normalize_snippet(f""" if ({nargs} > {max_args}) {{{{ PyErr_Format(PyExc_TypeError, "{{name}} expected at most {max_args} argument{pl}, got %zd", {nargs}); goto exit; @@ -1220,7 +1132,7 @@ def parser_body( else: clinic.add_include('pycore_modsupport.h', '_PyArg_CheckPositional()') - parser_code = [normalize_snippet(f""" + parser_code = [libclinic.normalize_snippet(f""" if (!_PyArg_CheckPositional("{{name}}", {nargs}, {min_pos}, {max_args})) {{{{ goto exit; }}}} @@ -1230,7 +1142,7 @@ def parser_body( for i, p in enumerate(parameters): if p.is_vararg(): if fastcall: - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" %s = PyTuple_New(%s); if (!%s) {{ goto exit; @@ -1247,7 +1159,7 @@ def parser_body( max_pos ), indent=4)) else: - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" %s = PyTuple_GetSlice(%d, -1); """ % ( p.converter.parser_name, @@ -1263,12 +1175,12 @@ def parser_body( break if has_optional or p.is_optional(): has_optional = True - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" if (%s < %d) {{ goto skip_optional; }} """, indent=4) % (nargs, i + 1)) - parser_code.append(normalize_snippet(parsearg, indent=4)) + parser_code.append(libclinic.normalize_snippet(parsearg, indent=4)) if parser_code is not None: if has_optional: @@ -1279,7 +1191,7 @@ def parser_body( if fastcall: clinic.add_include('pycore_modsupport.h', '_PyArg_ParseStack()') - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (!_PyArg_ParseStack(args, nargs, "{format_units}:{name}", {parse_arguments})) {{ goto exit; @@ -1288,7 +1200,7 @@ def parser_body( else: flags = "METH_VARARGS" parser_prototype = self.PARSER_PROTOTYPE_VARARGS - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (!PyArg_ParseTuple(args, "{format_units}:{name}", {parse_arguments})) {{ goto exit; @@ -1343,7 +1255,7 @@ def parser_body( declarations += "\nPyObject *argsbuf[%s];" % len(converters) if has_optional_kw: declarations += "\nPy_ssize_t noptargs = %s + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - %d;" % (nargs, min_pos + min_kw_only) - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" args = %s(args, nargs, NULL, kwnames, &_parser, %s, argsbuf); if (!args) {{ goto exit; @@ -1361,7 +1273,7 @@ def parser_body( declarations += "\nPy_ssize_t nargs = PyTuple_GET_SIZE(args);" if has_optional_kw: declarations += "\nPy_ssize_t noptargs = %s + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - %d;" % (nargs, min_pos + min_kw_only) - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" fastargs = %s(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, %s, argsbuf); if (!fastargs) {{ goto exit; @@ -1394,19 +1306,19 @@ def parser_body( parser_code.append("%s:" % add_label) add_label = None if not p.is_optional(): - parser_code.append(normalize_snippet(parsearg, indent=4)) + parser_code.append(libclinic.normalize_snippet(parsearg, indent=4)) elif i < pos_only: add_label = 'skip_optional_posonly' - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" if (nargs < %d) {{ goto %s; }} """ % (i + 1, add_label), indent=4)) if has_optional_kw: - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" noptargs--; """, indent=4)) - parser_code.append(normalize_snippet(parsearg, indent=4)) + parser_code.append(libclinic.normalize_snippet(parsearg, indent=4)) else: if i < max_pos: label = 'skip_optional_pos' @@ -1418,20 +1330,20 @@ def parser_body( first_opt += 1 if i == first_opt: add_label = label - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" if (!noptargs) {{ goto %s; }} """ % add_label, indent=4)) if i + 1 == len(parameters): - parser_code.append(normalize_snippet(parsearg, indent=4)) + parser_code.append(libclinic.normalize_snippet(parsearg, indent=4)) else: add_label = label - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(""" if (%s) {{ """ % (argname_fmt % i), indent=4)) - parser_code.append(normalize_snippet(parsearg, indent=8)) - parser_code.append(normalize_snippet(""" + parser_code.append(libclinic.normalize_snippet(parsearg, indent=8)) + parser_code.append(libclinic.normalize_snippet(""" if (!--noptargs) {{ goto %s; }} @@ -1450,7 +1362,7 @@ def parser_body( assert not fastcall flags = "METH_VARARGS|METH_KEYWORDS" parser_prototype = self.PARSER_PROTOTYPE_KEYWORD - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (!PyArg_ParseTupleAndKeywords(args, kwargs, "{format_units}:{name}", _keywords, {parse_arguments})) goto exit; @@ -1462,7 +1374,7 @@ def parser_body( elif fastcall: clinic.add_include('pycore_modsupport.h', '_PyArg_ParseStackAndKeywords()') - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser{parse_arguments_comma} {parse_arguments})) {{ goto exit; @@ -1471,7 +1383,7 @@ def parser_body( else: clinic.add_include('pycore_modsupport.h', '_PyArg_ParseTupleAndKeywordsFast()') - parser_code = [normalize_snippet(""" + parser_code = [libclinic.normalize_snippet(""" if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, {parse_arguments})) {{ goto exit; @@ -1518,7 +1430,7 @@ def parser_body( declarations = '{base_type_ptr}' clinic.add_include('pycore_modsupport.h', '_PyArg_NoKeywords()') - fields.insert(0, normalize_snippet(""" + fields.insert(0, libclinic.normalize_snippet(""" if ({self_type_check}!_PyArg_NoKeywords("{name}", kwargs)) {{ goto exit; }} @@ -1526,7 +1438,7 @@ def parser_body( if not parses_positional: clinic.add_include('pycore_modsupport.h', '_PyArg_NoPositional()') - fields.insert(0, normalize_snippet(""" + fields.insert(0, libclinic.normalize_snippet(""" if ({self_type_check}!_PyArg_NoPositional("{name}", args)) {{ goto exit; }} @@ -1715,7 +1627,7 @@ def render_option_group_parsing( out.append(' goto exit;\n') out.append("}") - template_dict['option_group_parsing'] = format_escape("".join(out)) + template_dict['option_group_parsing'] = libclinic.format_escape("".join(out)) def render_function( self, @@ -1825,7 +1737,7 @@ def render_function( else: template_dict['impl_return_type'] = f.return_converter.type - template_dict['declarations'] = format_escape("\n".join(data.declarations)) + template_dict['declarations'] = libclinic.format_escape("\n".join(data.declarations)) template_dict['initializers'] = "\n\n".join(data.initializers) template_dict['modifications'] = '\n\n'.join(data.modifications) template_dict['keywords_c'] = ' '.join('"' + k + '",' @@ -1841,9 +1753,11 @@ def render_function( template_dict['parse_arguments_comma'] = ''; template_dict['impl_parameters'] = ", ".join(data.impl_parameters) template_dict['impl_arguments'] = ", ".join(data.impl_arguments) - template_dict['return_conversion'] = format_escape("".join(data.return_conversion).rstrip()) - template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip()) - template_dict['cleanup'] = format_escape("".join(data.cleanup)) + + template_dict['return_conversion'] = libclinic.format_escape("".join(data.return_conversion).rstrip()) + template_dict['post_parsing'] = libclinic.format_escape("".join(data.post_parsing).rstrip()) + template_dict['cleanup'] = libclinic.format_escape("".join(data.cleanup)) + template_dict['return_value'] = data.return_value template_dict['lock'] = "\n".join(data.lock) template_dict['unlock'] = "\n".join(data.unlock) @@ -1887,7 +1801,7 @@ def render_function( # mild hack: # reflow long impl declarations if name in {"impl_prototype", "impl_definition"}: - s = wrap_declarations(s) + s = libclinic.wrap_declarations(s) if clinic.line_prefix: s = libclinic.indent_all_lines(s, clinic.line_prefix) diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 32ab2259ce4226..0c3c6840901a42 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -1,25 +1,31 @@ from typing import Final from .formatting import ( + SIG_END_MARKER, c_repr, docstring_for_c_string, + format_escape, indent_all_lines, + normalize_snippet, pprint_words, suffix_all_lines, + wrap_declarations, wrapped_c_string_literal, - SIG_END_MARKER, ) __all__ = [ # Formatting helpers + "SIG_END_MARKER", "c_repr", "docstring_for_c_string", + "format_escape", "indent_all_lines", + "normalize_snippet", "pprint_words", "suffix_all_lines", + "wrap_declarations", "wrapped_c_string_literal", - "SIG_END_MARKER", ] diff --git a/Tools/clinic/libclinic/formatting.py b/Tools/clinic/libclinic/formatting.py index 691a8fc47ef037..8b3ad7ba566bc8 100644 --- a/Tools/clinic/libclinic/formatting.py +++ b/Tools/clinic/libclinic/formatting.py @@ -1,5 +1,6 @@ """A collection of string formatting helpers.""" +import functools import textwrap from typing import Final @@ -59,11 +60,7 @@ def wrapped_c_string_literal( return initial_indent * " " + c_repr(separator.join(wrapped)) -def _add_prefix_and_suffix( - text: str, - prefix: str = "", - suffix: str = "" -) -> str: +def _add_prefix_and_suffix(text: str, *, prefix: str = "", suffix: str = "") -> str: """Return 'text' with 'prefix' prepended and 'suffix' appended to all lines. If the last line is empty, it remains unchanged. @@ -90,3 +87,87 @@ def pprint_words(items: list[str]) -> str: if len(items) <= 2: return " and ".join(items) return ", ".join(items[:-1]) + " and " + items[-1] + + +def _strip_leading_and_trailing_blank_lines(text: str) -> str: + lines = text.rstrip().split("\n") + while lines: + line = lines[0] + if line.strip(): + break + del lines[0] + return "\n".join(lines) + + +@functools.lru_cache() +def normalize_snippet(text: str, *, indent: int = 0) -> str: + """ + Reformats 'text': + * removes leading and trailing blank lines + * ensures that it does not end with a newline + * dedents so the first nonwhite character on any line is at column "indent" + """ + text = _strip_leading_and_trailing_blank_lines(text) + text = textwrap.dedent(text) + if indent: + text = textwrap.indent(text, " " * indent) + return text + + +def format_escape(text: str) -> str: + # double up curly-braces, this string will be used + # as part of a format_map() template later + text = text.replace("{", "{{") + text = text.replace("}", "}}") + return text + + +def wrap_declarations(text: str, length: int = 78) -> str: + """ + A simple-minded text wrapper for C function declarations. + + It views a declaration line as looking like this: + xxxxxxxx(xxxxxxxxx,xxxxxxxxx) + If called with length=30, it would wrap that line into + xxxxxxxx(xxxxxxxxx, + xxxxxxxxx) + (If the declaration has zero or one parameters, this + function won't wrap it.) + + If this doesn't work properly, it's probably better to + start from scratch with a more sophisticated algorithm, + rather than try and improve/debug this dumb little function. + """ + lines = [] + for line in text.split("\n"): + prefix, _, after_l_paren = line.partition("(") + if not after_l_paren: + lines.append(line) + continue + in_paren, _, after_r_paren = after_l_paren.partition(")") + if not _: + lines.append(line) + continue + if "," not in in_paren: + lines.append(line) + continue + parameters = [x.strip() + ", " for x in in_paren.split(",")] + prefix += "(" + if len(prefix) < length: + spaces = " " * len(prefix) + else: + spaces = " " * 4 + + while parameters: + line = prefix + first = True + while parameters: + if not first and (len(line) + len(parameters[0]) > length): + break + line += parameters.pop(0) + first = False + if not parameters: + line = line.rstrip(", ") + ")" + after_r_paren + lines.append(line.rstrip()) + prefix = spaces + return "\n".join(lines) From 0187a7e4ec9550a6e35dd06b26f22863520242ab Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Sun, 24 Dec 2023 01:43:39 +0100 Subject: [PATCH 357/442] gh-112800: Ignore PermissionError on SubprocessTransport.close() in asyncio (#112803) In case the spawned process is setuid, we may not be able to send signals to it, in which case our .kill() call will raise PermissionError. Ignore that in order to avoid .close() raising an exception. Hopefully the process will exit as a result of receiving EOF on its stdin. --- Lib/asyncio/base_subprocess.py | 3 ++- .../Library/2023-12-06-16-01-33.gh-issue-112800.TNsGJ-.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-06-16-01-33.gh-issue-112800.TNsGJ-.rst diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py index 4c9b0dd5653c0c..6dbde2b696ad1f 100644 --- a/Lib/asyncio/base_subprocess.py +++ b/Lib/asyncio/base_subprocess.py @@ -115,7 +115,8 @@ def close(self): try: self._proc.kill() - except ProcessLookupError: + except (ProcessLookupError, PermissionError): + # the process may have already exited or may be running setuid pass # Don't clear the _proc reference yet: _post_init() may still run diff --git a/Misc/NEWS.d/next/Library/2023-12-06-16-01-33.gh-issue-112800.TNsGJ-.rst b/Misc/NEWS.d/next/Library/2023-12-06-16-01-33.gh-issue-112800.TNsGJ-.rst new file mode 100644 index 00000000000000..e88eac169177a9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-06-16-01-33.gh-issue-112800.TNsGJ-.rst @@ -0,0 +1,2 @@ +Fix :mod:`asyncio` ``SubprocessTransport.close()`` not to throw +``PermissionError`` when used with setuid executables. From 050783cb37d6a09d8238fa640814df8a915f6a68 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Sat, 23 Dec 2023 17:07:52 -0800 Subject: [PATCH 358/442] gh-112559: Avoid unnecessary conversion attempts to enum_klass in signal.py (#113040) --- Lib/signal.py | 6 +++-- Lib/test/test_asyncio/test_runners.py | 18 +++++++++++++ Lib/test/test_signal.py | 26 +++++++++++++++++++ ...-12-12-20-15-57.gh-issue-112559.IgXkje.rst | 3 +++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-12-20-15-57.gh-issue-112559.IgXkje.rst diff --git a/Lib/signal.py b/Lib/signal.py index 50b215b29d2fad..c8cd3d4f597ca5 100644 --- a/Lib/signal.py +++ b/Lib/signal.py @@ -22,9 +22,11 @@ def _int_to_enum(value, enum_klass): - """Convert a numeric value to an IntEnum member. - If it's not a known member, return the numeric value itself. + """Convert a possible numeric value to an IntEnum member. + If it's not a known member, return the value itself. """ + if not isinstance(value, int): + return value try: return enum_klass(value) except ValueError: diff --git a/Lib/test/test_asyncio/test_runners.py b/Lib/test/test_asyncio/test_runners.py index 13493d3c806d6a..266f057f0776c3 100644 --- a/Lib/test/test_asyncio/test_runners.py +++ b/Lib/test/test_asyncio/test_runners.py @@ -495,6 +495,24 @@ async def coro(): self.assertEqual(1, policy.set_event_loop.call_count) runner.close() + def test_no_repr_is_call_on_the_task_result(self): + # See https://github.com/python/cpython/issues/112559. + class MyResult: + def __init__(self): + self.repr_count = 0 + def __repr__(self): + self.repr_count += 1 + return super().__repr__() + + async def coro(): + return MyResult() + + + with asyncio.Runner() as runner: + result = runner.run(coro()) + + self.assertEqual(0, result.repr_count) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index acb7e9d4c6074d..637a0ca3b36972 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1,5 +1,6 @@ import enum import errno +import functools import inspect import os import random @@ -76,6 +77,9 @@ class PosixTests(unittest.TestCase): def trivial_signal_handler(self, *args): pass + def create_handler_with_partial(self, argument): + return functools.partial(self.trivial_signal_handler, argument) + def test_out_of_range_signal_number_raises_error(self): self.assertRaises(ValueError, signal.getsignal, 4242) @@ -96,6 +100,28 @@ def test_getsignal(self): signal.signal(signal.SIGHUP, hup) self.assertEqual(signal.getsignal(signal.SIGHUP), hup) + def test_no_repr_is_called_on_signal_handler(self): + # See https://github.com/python/cpython/issues/112559. + + class MyArgument: + def __init__(self): + self.repr_count = 0 + + def __repr__(self): + self.repr_count += 1 + return super().__repr__() + + argument = MyArgument() + self.assertEqual(0, argument.repr_count) + + handler = self.create_handler_with_partial(argument) + hup = signal.signal(signal.SIGHUP, handler) + self.assertIsInstance(hup, signal.Handlers) + self.assertEqual(signal.getsignal(signal.SIGHUP), handler) + signal.signal(signal.SIGHUP, hup) + self.assertEqual(signal.getsignal(signal.SIGHUP), hup) + self.assertEqual(0, argument.repr_count) + def test_strsignal(self): self.assertIn("Interrupt", signal.strsignal(signal.SIGINT)) self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) diff --git a/Misc/NEWS.d/next/Library/2023-12-12-20-15-57.gh-issue-112559.IgXkje.rst b/Misc/NEWS.d/next/Library/2023-12-12-20-15-57.gh-issue-112559.IgXkje.rst new file mode 100644 index 00000000000000..c08cb7c3ba5ea5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-12-20-15-57.gh-issue-112559.IgXkje.rst @@ -0,0 +1,3 @@ +:func:`signal.signal` and :func:`signal.getsignal` no longer call ``repr`` on +callable handlers. :func:`asyncio.run` and :meth:`asyncio.Runner.run` no longer +call ``repr`` on the task results. Patch by Yilei Yang. From 894f0e573d9eb49cd5864c44328f10a731852dab Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sun, 24 Dec 2023 11:57:41 +0300 Subject: [PATCH 359/442] gh-111784: Fix two segfaults in the elementtree module (GH-113405) First fix resolve situation when pyexpat module (which contains expat_CAPI capsule) deallocates before _elementtree, so we need to hold a strong reference to pyexpat module to. Second fix resolve situation when module state is deallocated before deallocation of XMLParser instances, which uses module state to clear some stuff. --- ...023-12-23-13-10-42.gh-issue-111784.Nb4L1j.rst | 5 +++++ Modules/_elementtree.c | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-23-13-10-42.gh-issue-111784.Nb4L1j.rst diff --git a/Misc/NEWS.d/next/Library/2023-12-23-13-10-42.gh-issue-111784.Nb4L1j.rst b/Misc/NEWS.d/next/Library/2023-12-23-13-10-42.gh-issue-111784.Nb4L1j.rst new file mode 100644 index 00000000000000..51ac0752cfae84 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-23-13-10-42.gh-issue-111784.Nb4L1j.rst @@ -0,0 +1,5 @@ +Fix segfaults in the ``_elementtree`` module. +Fix first segfault during deallocation of ``_elementtree.XMLParser`` instances by keeping strong reference +to ``pyexpat`` module in module state for capsule lifetime. +Fix second segfault which happens in the same deallocation process by keeping strong reference +to ``_elementtree`` module in ``XMLParser`` structure for ``_elementtree`` module lifetime. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index f9d5793f9b6497..5bf67870767698 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -98,6 +98,7 @@ typedef struct { PyTypeObject *TreeBuilder_Type; PyTypeObject *XMLParser_Type; + PyObject *expat_capsule; struct PyExpat_CAPI *expat_capi; } elementtreestate; @@ -155,6 +156,7 @@ elementtree_clear(PyObject *m) Py_CLEAR(st->ElementIter_Type); Py_CLEAR(st->TreeBuilder_Type); Py_CLEAR(st->XMLParser_Type); + Py_CLEAR(st->expat_capsule); st->expat_capi = NULL; return 0; @@ -175,6 +177,7 @@ elementtree_traverse(PyObject *m, visitproc visit, void *arg) Py_VISIT(st->ElementIter_Type); Py_VISIT(st->TreeBuilder_Type); Py_VISIT(st->XMLParser_Type); + Py_VISIT(st->expat_capsule); return 0; } @@ -3066,6 +3069,7 @@ typedef struct { PyObject *handle_close; elementtreestate *state; + PyObject *elementtree_module; } XMLParserObject; /* helpers */ @@ -3607,7 +3611,11 @@ xmlparser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->handle_start = self->handle_data = self->handle_end = NULL; self->handle_comment = self->handle_pi = self->handle_close = NULL; self->handle_doctype = NULL; - self->state = get_elementtree_state_by_type(type); + self->elementtree_module = PyType_GetModuleByDef(type, &elementtreemodule); + assert(self->elementtree_module != NULL); + Py_INCREF(self->elementtree_module); + // See gh-111784 for explanation why is reference to module needed here. + self->state = get_elementtree_state(self->elementtree_module); } return (PyObject *)self; } @@ -3784,6 +3792,7 @@ xmlparser_gc_clear(XMLParserObject *self) EXPAT(st, ParserFree)(parser); } + Py_CLEAR(self->elementtree_module); Py_CLEAR(self->handle_close); Py_CLEAR(self->handle_pi); Py_CLEAR(self->handle_comment); @@ -4343,7 +4352,10 @@ module_exec(PyObject *m) goto error; /* link against pyexpat */ - st->expat_capi = PyCapsule_Import(PyExpat_CAPSULE_NAME, 0); + if (!(st->expat_capsule = _PyImport_GetModuleAttrString("pyexpat", "expat_CAPI"))) + goto error; + if (!(st->expat_capi = PyCapsule_GetPointer(st->expat_capsule, PyExpat_CAPSULE_NAME))) + goto error; if (st->expat_capi) { /* check that it's usable */ if (strcmp(st->expat_capi->magic, PyExpat_CAPI_MAGIC) != 0 || From 08398631a0298dcf785ee7bd0e26c7844823ce59 Mon Sep 17 00:00:00 2001 From: Jeff Allen Date: Sun, 24 Dec 2023 09:43:44 +0000 Subject: [PATCH 360/442] gh-113028: Correctly memoize str in pickle when escapes added (GH-113436) This fixes a divergence between the Python and C implementations of pickle for protocol 0, such that it pickle.py fails to re-use the first pickled representation of strings involving characters that have to be escaped. --- Lib/pickle.py | 14 +++++++------- Lib/test/pickletester.py | 8 ++++++++ .../2023-12-23-16-51-17.gh-issue-113028.3Jmdoj.rst | 6 ++++++ 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-23-16-51-17.gh-issue-113028.3Jmdoj.rst diff --git a/Lib/pickle.py b/Lib/pickle.py index 4f5ad5b71e8899..988c0887341310 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -857,13 +857,13 @@ def save_str(self, obj): else: self.write(BINUNICODE + pack(" Date: Sun, 24 Dec 2023 18:04:12 +0800 Subject: [PATCH 361/442] gh-113421: Fix multiprocessing logger for "%(filename)s" (GH-113423) --- Lib/multiprocessing/util.py | 8 +++---- Lib/test/_test_multiprocessing.py | 23 +++++++++++++++++++ ...-12-23-16-10-07.gh-issue-113421.w7vs08.rst | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-23-16-10-07.gh-issue-113421.w7vs08.rst diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 28c77df1c32ea8..32871850ddec8b 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -43,19 +43,19 @@ def sub_debug(msg, *args): if _logger: - _logger.log(SUBDEBUG, msg, *args) + _logger.log(SUBDEBUG, msg, *args, stacklevel=2) def debug(msg, *args): if _logger: - _logger.log(DEBUG, msg, *args) + _logger.log(DEBUG, msg, *args, stacklevel=2) def info(msg, *args): if _logger: - _logger.log(INFO, msg, *args) + _logger.log(INFO, msg, *args, stacklevel=2) def sub_warning(msg, *args): if _logger: - _logger.log(SUBWARNING, msg, *args) + _logger.log(SUBWARNING, msg, *args, stacklevel=2) def get_logger(): ''' diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index a94eb6c0ae4b8e..8e4e0765d46809 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4724,6 +4724,29 @@ def test_level(self): root_logger.setLevel(root_level) logger.setLevel(level=LOG_LEVEL) + def test_filename(self): + logger = multiprocessing.get_logger() + original_level = logger.level + try: + logger.setLevel(util.DEBUG) + stream = io.StringIO() + handler = logging.StreamHandler(stream) + logging_format = '[%(levelname)s] [%(filename)s] %(message)s' + handler.setFormatter(logging.Formatter(logging_format)) + logger.addHandler(handler) + logger.info('1') + util.info('2') + logger.debug('3') + filename = os.path.basename(__file__) + log_record = stream.getvalue() + self.assertIn(f'[INFO] [{filename}] 1', log_record) + self.assertIn(f'[INFO] [{filename}] 2', log_record) + self.assertIn(f'[DEBUG] [{filename}] 3', log_record) + finally: + logger.setLevel(original_level) + logger.removeHandler(handler) + handler.close() + # class _TestLoggingProcessName(BaseTestCase): # diff --git a/Misc/NEWS.d/next/Library/2023-12-23-16-10-07.gh-issue-113421.w7vs08.rst b/Misc/NEWS.d/next/Library/2023-12-23-16-10-07.gh-issue-113421.w7vs08.rst new file mode 100644 index 00000000000000..2082fe6391d261 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-23-16-10-07.gh-issue-113421.w7vs08.rst @@ -0,0 +1 @@ +Fix multiprocessing logger for ``%(filename)s``. From 53330f167792a2947ab8b0faafb11019d7fb09b6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2023 12:31:23 +0200 Subject: [PATCH 362/442] gh-113440: Ignore the "ver" command failure with exit code 0xc0000142 (GH-113435) --- Lib/test/pythoninfo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 49e41ca6cdaf98..6dfb7f37e406a5 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -925,6 +925,8 @@ def collect_windows(info_add): stderr=subprocess.PIPE, text=True) output = proc.communicate()[0] + if proc.returncode == 0xc0000142: + return if proc.returncode: output = "" except OSError: From 1f06baeabd7ef64b7be6af7cb6fc03ec410b1aa2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2023 12:57:11 +0200 Subject: [PATCH 363/442] gh-113191: Add support of os.fchmod() on Windows (GH-113192) Also support a file descriptor in os.chmod(). --- Doc/library/os.rst | 8 +- Doc/whatsnew/3.13.rst | 4 + Lib/os.py | 1 + Lib/test/test_posix.py | 13 ++- ...-12-15-21-33-42.gh-issue-113191.Il155b.rst | 2 + Modules/clinic/posixmodule.c.h | 6 +- Modules/posixmodule.c | 80 +++++++++++++------ 7 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 1138cc1f249ee7..6b6e62a683ab18 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1001,11 +1001,14 @@ as internal buffering of data. .. audit-event:: os.chmod path,mode,dir_fd os.fchmod - .. availability:: Unix. + .. availability:: Unix, Windows. The function is limited on Emscripten and WASI, see :ref:`wasm-availability` for more information. + .. versionchanged:: 3.13 + Added support on Windows. + .. function:: fchown(fd, uid, gid) @@ -2077,7 +2080,8 @@ features: Accepts a :term:`path-like object`. .. versionchanged:: 3.13 - Added support for the *follow_symlinks* argument on Windows. + Added support for a file descriptor and the *follow_symlinks* argument + on Windows. .. function:: chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 7dc02dacdc68f7..b4cd106f5cac5f 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -289,6 +289,10 @@ os ``False`` on Windows. (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`) + * :func:`os.posix_spawn` now accepts ``env=None``, which makes the newly spawned process use the current process environment. (Contributed by Jakub Kulik in :gh:`113119`.) diff --git a/Lib/os.py b/Lib/os.py index 8c4b93250918eb..7f38e14e7bdd96 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -131,6 +131,7 @@ def _add(str, fn): _set = set() _add("HAVE_FCHDIR", "chdir") _add("HAVE_FCHMOD", "chmod") + _add("MS_WINDOWS", "chmod") _add("HAVE_FCHOWN", "chown") _add("HAVE_FDOPENDIR", "listdir") _add("HAVE_FDOPENDIR", "scandir") diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 55cc5e4c6e4f03..9c382ace806e0f 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -936,6 +936,7 @@ def test_utime(self): posix.utime(os_helper.TESTFN, (now, now)) def check_chmod(self, chmod_func, target, **kwargs): + closefd = not isinstance(target, int) mode = os.stat(target).st_mode try: new_mode = mode & ~(stat.S_IWOTH | stat.S_IWGRP | stat.S_IWUSR) @@ -943,7 +944,7 @@ def check_chmod(self, chmod_func, target, **kwargs): self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): try: - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass except PermissionError: pass @@ -951,10 +952,10 @@ def check_chmod(self, chmod_func, target, **kwargs): chmod_func(target, new_mode, **kwargs) self.assertEqual(os.stat(target).st_mode, new_mode) if stat.S_ISREG(mode): - with open(target, 'wb+'): + with open(target, 'wb+', closefd=closefd): pass finally: - posix.chmod(target, mode) + chmod_func(target, mode) @os_helper.skip_unless_working_chmod def test_chmod_file(self): @@ -971,6 +972,12 @@ def test_chmod_dir(self): target = self.tempdir() self.check_chmod(posix.chmod, target) + @os_helper.skip_unless_working_chmod + def test_fchmod_file(self): + with open(os_helper.TESTFN, 'wb+') as f: + self.check_chmod(posix.fchmod, f.fileno()) + self.check_chmod(posix.chmod, f.fileno()) + @unittest.skipUnless(hasattr(posix, 'lchmod'), 'test needs os.lchmod()') def test_lchmod_file(self): self.check_chmod(posix.lchmod, os_helper.TESTFN) diff --git a/Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst b/Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst new file mode 100644 index 00000000000000..13fe4ff5f6a8bd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-15-21-33-42.gh-issue-113191.Il155b.rst @@ -0,0 +1,2 @@ +Add support of :func:`os.fchmod` and a file descriptor in :func:`os.chmod` +on Windows. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index f36872a1eb7a0f..b7639af4b78a9d 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -601,7 +601,7 @@ os_chmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw return return_value; } -#if defined(HAVE_FCHMOD) +#if (defined(HAVE_FCHMOD) || defined(MS_WINDOWS)) PyDoc_STRVAR(os_fchmod__doc__, "fchmod($module, /, fd, mode)\n" @@ -676,7 +676,7 @@ os_fchmod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k return return_value; } -#endif /* defined(HAVE_FCHMOD) */ +#endif /* (defined(HAVE_FCHMOD) || defined(MS_WINDOWS)) */ #if (defined(HAVE_LCHMOD) || defined(MS_WINDOWS)) @@ -12422,4 +12422,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=1be15e60a553b40d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b82391c4f58231b6 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c7ee591f30c51f..c635fd4d993d57 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2855,6 +2855,8 @@ FTRUNCATE #ifdef MS_WINDOWS #undef PATH_HAVE_FTRUNCATE #define PATH_HAVE_FTRUNCATE 1 + #undef PATH_HAVE_FCHMOD + #define PATH_HAVE_FCHMOD 1 #endif /*[python input] @@ -3332,7 +3334,38 @@ win32_lchmod(LPCWSTR path, int mode) } return SetFileAttributesW(path, attr); } -#endif + +static int +win32_hchmod(HANDLE hfile, int mode) +{ + FILE_BASIC_INFO info; + if (!GetFileInformationByHandleEx(hfile, FileBasicInfo, + &info, sizeof(info))) + { + return 0; + } + if (mode & _S_IWRITE) { + info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + } + else { + info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + } + return SetFileInformationByHandle(hfile, FileBasicInfo, + &info, sizeof(info)); +} + +static int +win32_fchmod(int fd, int mode) +{ + HANDLE hfile = _Py_get_osfhandle_noraise(fd); + if (hfile == INVALID_HANDLE_VALUE) { + SetLastError(ERROR_INVALID_HANDLE); + return 0; + } + return win32_hchmod(hfile, mode); +} + +#endif /* MS_WINDOWS */ /*[clinic input] os.chmod @@ -3395,27 +3428,16 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, #ifdef MS_WINDOWS result = 0; Py_BEGIN_ALLOW_THREADS - if (follow_symlinks) { - HANDLE hfile; - FILE_BASIC_INFO info; - - hfile = CreateFileW(path->wide, - FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES, - 0, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (path->fd != -1) { + result = win32_fchmod(path->fd, mode); + } + else if (follow_symlinks) { + HANDLE hfile = CreateFileW(path->wide, + FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES, + 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileBasicInfo, - &info, sizeof(info))) - { - if (mode & _S_IWRITE) { - info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; - } - else { - info.FileAttributes |= FILE_ATTRIBUTE_READONLY; - } - result = SetFileInformationByHandle(hfile, FileBasicInfo, - &info, sizeof(info)); - } + result = win32_hchmod(hfile, mode); (void)CloseHandle(hfile); } } @@ -3511,7 +3533,7 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, } -#ifdef HAVE_FCHMOD +#if defined(HAVE_FCHMOD) || defined(MS_WINDOWS) /*[clinic input] os.fchmod @@ -3533,12 +3555,21 @@ os_fchmod_impl(PyObject *module, int fd, int mode) /*[clinic end generated code: output=afd9bc05b4e426b3 input=b5594618bbbc22df]*/ { int res; - int async_err = 0; if (PySys_Audit("os.chmod", "iii", fd, mode, -1) < 0) { return NULL; } +#ifdef MS_WINDOWS + res = 0; + Py_BEGIN_ALLOW_THREADS + res = win32_fchmod(fd, mode); + Py_END_ALLOW_THREADS + if (!res) { + return PyErr_SetFromWindowsErr(0); + } +#else /* MS_WINDOWS */ + int async_err = 0; do { Py_BEGIN_ALLOW_THREADS res = fchmod(fd, mode); @@ -3546,10 +3577,11 @@ os_fchmod_impl(PyObject *module, int fd, int mode) } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (res != 0) return (!async_err) ? posix_error() : NULL; +#endif /* MS_WINDOWS */ Py_RETURN_NONE; } -#endif /* HAVE_FCHMOD */ +#endif /* HAVE_FCHMOD || MS_WINDOWS */ #if defined(HAVE_LCHMOD) || defined(MS_WINDOWS) From 0d74e9683b8567df933e415abf747d9e0b4cd7ef Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sun, 24 Dec 2023 11:09:50 +0000 Subject: [PATCH 364/442] Add codeowners for `Lib/ensurepip/` (#112805) --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db28c2a231ae04..8038206441ab9b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -157,6 +157,8 @@ Doc/c-api/stable.rst @encukou **/*dataclasses* @ericvsmith +**/*ensurepip* @pfmoore @pradyunsg + **/*idlelib* @terryjreedy **/*typing* @JelleZijlstra @AlexWaygood From 0c574540e07792cef5487aef61ab38bfe404060f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Dec 2023 13:38:56 +0200 Subject: [PATCH 365/442] gh-113407: Fix import of unittest.mock when CPython is built without docstrings (GH-113408) --- Lib/unittest/mock.py | 16 +++++++++++----- ...023-12-22-20-49-52.gh-issue-113407.C_O13_.rst | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index c6b46eea657a21..2adb3d70662b1a 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2229,8 +2229,11 @@ def __get__(self, obj, _type=None): return self.create_mock() -_CODE_ATTRS = dir(CodeType) -_CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) +try: + _CODE_SIG = inspect.signature(partial(CodeType.__init__, None)) + _CODE_ATTRS = dir(CodeType) +except ValueError: + _CODE_SIG = None class AsyncMockMixin(Base): @@ -2250,9 +2253,12 @@ def __init__(self, /, *args, **kwargs): self.__dict__['_mock_await_count'] = 0 self.__dict__['_mock_await_args'] = None self.__dict__['_mock_await_args_list'] = _CallList() - code_mock = NonCallableMock(spec_set=_CODE_ATTRS) - code_mock.__dict__["_spec_class"] = CodeType - code_mock.__dict__["_spec_signature"] = _CODE_SIG + if _CODE_SIG: + code_mock = NonCallableMock(spec_set=_CODE_ATTRS) + code_mock.__dict__["_spec_class"] = CodeType + code_mock.__dict__["_spec_signature"] = _CODE_SIG + else: + code_mock = NonCallableMock(spec_set=CodeType) code_mock.co_flags = ( inspect.CO_COROUTINE + inspect.CO_VARARGS diff --git a/Misc/NEWS.d/next/Library/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst b/Misc/NEWS.d/next/Library/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst new file mode 100644 index 00000000000000..da00977f03cefd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-22-20-49-52.gh-issue-113407.C_O13_.rst @@ -0,0 +1 @@ +Fix import of :mod:`unittest.mock` when CPython is built without docstrings. From 9a35794fcbe994f0eb36c32d8f24d307837c9412 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Sun, 24 Dec 2023 18:07:34 +0000 Subject: [PATCH 366/442] GH-111485: Fix handling of FOR_ITER in Tier 2 (GH-113394) --- Python/optimizer.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 0ff16191680a4b..f27af14d967cd3 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -518,11 +518,11 @@ translate_bytecode_to_trace( uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; - uint32_t extras = 0; + uint32_t extended = 0; if (opcode == EXTENDED_ARG) { instr++; - extras += 1; + extended = 1; opcode = instr->op.code; oparg = (oparg << 8) | instr->op.arg; if (opcode == EXTENDED_ARG) { @@ -577,6 +577,7 @@ translate_bytecode_to_trace( } case JUMP_BACKWARD: + case JUMP_BACKWARD_NO_INTERRUPT: { if (instr + 2 - oparg == initial_instr && code == initial_code) { RESERVE(1); @@ -623,15 +624,7 @@ translate_bytecode_to_trace( int offset = expansion->uops[i].offset + 1; switch (expansion->uops[i].size) { case OPARG_FULL: - if (extras && OPCODE_HAS_JUMP(opcode)) { - if (opcode == JUMP_BACKWARD_NO_INTERRUPT) { - oparg -= extras; - } - else { - assert(opcode != JUMP_BACKWARD); - oparg += extras; - } - } + assert(opcode != JUMP_BACKWARD_NO_INTERRUPT && opcode != JUMP_BACKWARD); break; case OPARG_CACHE_1: operand = read_u16(&instr[offset].cache); @@ -656,7 +649,7 @@ 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; + 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); } From f7c5a7a0f9f8186a7fdc8b721468d022fe310b04 Mon Sep 17 00:00:00 2001 From: denballakh <47365157+denballakh@users.noreply.github.com> Date: Sun, 24 Dec 2023 23:28:39 +0300 Subject: [PATCH 367/442] fix bullet-list in `LOAD_SUPER_ATTR` documentation on `dis` page (#113461) --- Doc/library/dis.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5647021d6a9ba6..5823142cc75998 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1214,9 +1214,10 @@ iterations of the loop. ``super(cls, self).method()``, ``super(cls, self).attr``). It pops three values from the stack (from top of stack down): - - ``self``: the first argument to the current method - - ``cls``: the class within which the current method was defined - - the global ``super`` + + * ``self``: the first argument to the current method + * ``cls``: the class within which the current method was defined + * the global ``super`` With respect to its argument, it works similarly to :opcode:`LOAD_ATTR`, except that ``namei`` is shifted left by 2 bits instead of 1. From 3f5eb3e6c75b874db1a83c9d53e824e56fcad02e Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Mon, 25 Dec 2023 07:07:51 -0800 Subject: [PATCH 368/442] bpo-21360: mailbox.Maildir now ignores files with a leading dot (GH-11833) The maildir format specification states that files with a leading dot should be ignored. Co-authored-by: Serhiy Storchaka --- Doc/library/mailbox.rst | 3 +++ Doc/whatsnew/3.13.rst | 3 +++ Lib/mailbox.py | 2 ++ Lib/test/test_mailbox.py | 14 ++++++++++++++ .../2019-02-12-16-12-54.bpo-21360.gkSSfx.rst | 1 + 5 files changed, 23 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-02-12-16-12-54.bpo-21360.gkSSfx.rst diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index c98496d1fff993..3ffd0981a4a4f2 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -364,6 +364,9 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. The :attr:`!colon` attribute may also be set on a per-instance basis. + .. versionchanged:: 3.13 + :class:`Maildir` now ignores files with a leading dot. + :class:`!Maildir` instances have all of the methods of :class:`Mailbox` in addition to the following: diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b4cd106f5cac5f..c905cddc6cba62 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1163,6 +1163,9 @@ Changes in the Python API other "private" attributes. (See :gh:`112826`.) +* :class:`mailbox.Maildir` now ignores files with a leading dot. + (Contributed by Zackery Spytz in :gh:`65559`.) + Build Changes ============= diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 36afaded705d0a..574c01475dece6 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -590,6 +590,8 @@ def _refresh(self): for subdir in self._toc_mtimes: path = self._paths[subdir] for entry in os.listdir(path): + if entry.startswith('.'): + continue p = os.path.join(path, entry) if os.path.isdir(p): continue diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index 23fcbfac1f9c89..caa7eb3d829c68 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -681,6 +681,20 @@ def test_initialize_existing(self): self._box = mailbox.Maildir(self._path) self._check_basics() + def test_filename_leading_dot(self): + self.tearDown() + for subdir in '', 'tmp', 'new', 'cur': + os.mkdir(os.path.normpath(os.path.join(self._path, subdir))) + for subdir in 'tmp', 'new', 'cur': + fname = os.path.join(self._path, subdir, '.foo' + subdir) + with open(fname, 'wb') as f: + f.write(b"@") + self._box = mailbox.Maildir(self._path) + self.assertNotIn('.footmp', self._box) + self.assertNotIn('.foonew', self._box) + self.assertNotIn('.foocur', self._box) + self.assertEqual(list(self._box.iterkeys()), []) + def _check_basics(self, factory=None): # (Used by test_open_new() and test_open_existing().) self.assertEqual(self._box._path, os.path.abspath(self._path)) diff --git a/Misc/NEWS.d/next/Library/2019-02-12-16-12-54.bpo-21360.gkSSfx.rst b/Misc/NEWS.d/next/Library/2019-02-12-16-12-54.bpo-21360.gkSSfx.rst new file mode 100644 index 00000000000000..bc32b9fe4199f9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-02-12-16-12-54.bpo-21360.gkSSfx.rst @@ -0,0 +1 @@ +:class:`mailbox.Maildir` now ignores files with a leading dot. From 48c49739f5502fc7aa82f247ab2e4d7b55bdca62 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Mon, 25 Dec 2023 09:36:59 -0800 Subject: [PATCH 369/442] gh-106905: Use separate structs to track recursion depth in each PyAST_mod2obj call. (GH-113035) Co-authored-by: Gregory P. Smith [Google LLC] --- Include/internal/pycore_ast_state.h | 2 - ...-12-13-11-45-53.gh-issue-106905.5dslTN.rst | 7 + Parser/asdl_c.py | 53 +- Python/Python-ast.c | 689 ++++++++++-------- 4 files changed, 412 insertions(+), 339 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-12-13-11-45-53.gh-issue-106905.5dslTN.rst diff --git a/Include/internal/pycore_ast_state.h b/Include/internal/pycore_ast_state.h index 6ffd30aca7b11b..f1b1786264803b 100644 --- a/Include/internal/pycore_ast_state.h +++ b/Include/internal/pycore_ast_state.h @@ -16,8 +16,6 @@ extern "C" { struct ast_state { _PyOnceFlag once; int finalized; - int recursion_depth; - int recursion_limit; PyObject *AST_type; PyObject *Add_singleton; PyObject *Add_type; diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-12-13-11-45-53.gh-issue-106905.5dslTN.rst b/Misc/NEWS.d/next/Core and Builtins/2023-12-13-11-45-53.gh-issue-106905.5dslTN.rst new file mode 100644 index 00000000000000..e3a772f3354ecf --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-12-13-11-45-53.gh-issue-106905.5dslTN.rst @@ -0,0 +1,7 @@ +Use per AST-parser state rather than global state to track recursion depth +within the AST parser to prevent potential race condition due to +simultaneous parsing. + +The issue primarily showed up in 3.11 by multithreaded users of +:func:`ast.parse`. In 3.12 a change to when garbage collection can be +triggered prevented the race condition from occurring. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index c9bf08ed2e0f6d..4bb337349748cf 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -731,7 +731,7 @@ def emit_sequence_constructor(self, name, type): class PyTypesDeclareVisitor(PickleVisitor): def visitProduct(self, prod, name): - self.emit("static PyObject* ast2obj_%s(struct ast_state *state, void*);" % name, 0) + self.emit("static PyObject* ast2obj_%s(struct ast_state *state, struct validator *vstate, void*);" % name, 0) if prod.attributes: self.emit("static const char * const %s_attributes[] = {" % name, 0) for a in prod.attributes: @@ -752,7 +752,7 @@ def visitSum(self, sum, name): ptype = "void*" if is_simple(sum): ptype = get_c_type(name) - self.emit("static PyObject* ast2obj_%s(struct ast_state *state, %s);" % (name, ptype), 0) + self.emit("static PyObject* ast2obj_%s(struct ast_state *state, struct validator *vstate, %s);" % (name, ptype), 0) for t in sum.types: self.visitConstructor(t, name) @@ -984,7 +984,8 @@ def visitModule(self, mod): /* Conversion AST -> Python */ -static PyObject* ast2obj_list(struct ast_state *state, asdl_seq *seq, PyObject* (*func)(struct ast_state *state, void*)) +static PyObject* ast2obj_list(struct ast_state *state, struct validator *vstate, asdl_seq *seq, + PyObject* (*func)(struct ast_state *state, struct validator *vstate, void*)) { Py_ssize_t i, n = asdl_seq_LEN(seq); PyObject *result = PyList_New(n); @@ -992,7 +993,7 @@ def visitModule(self, mod): if (!result) return NULL; for (i = 0; i < n; i++) { - value = func(state, asdl_seq_GET_UNTYPED(seq, i)); + value = func(state, vstate, asdl_seq_GET_UNTYPED(seq, i)); if (!value) { Py_DECREF(result); return NULL; @@ -1002,7 +1003,7 @@ def visitModule(self, mod): return result; } -static PyObject* ast2obj_object(struct ast_state *Py_UNUSED(state), void *o) +static PyObject* ast2obj_object(struct ast_state *Py_UNUSED(state), struct validator *Py_UNUSED(vstate), void *o) { PyObject *op = (PyObject*)o; if (!op) { @@ -1014,7 +1015,7 @@ def visitModule(self, mod): #define ast2obj_identifier ast2obj_object #define ast2obj_string ast2obj_object -static PyObject* ast2obj_int(struct ast_state *Py_UNUSED(state), long b) +static PyObject* ast2obj_int(struct ast_state *Py_UNUSED(state), struct validator *Py_UNUSED(vstate), long b) { return PyLong_FromLong(b); } @@ -1116,8 +1117,6 @@ def visitModule(self, mod): for dfn in mod.dfns: self.visit(dfn) self.file.write(textwrap.dedent(''' - state->recursion_depth = 0; - state->recursion_limit = 0; return 0; } ''')) @@ -1260,7 +1259,7 @@ class ObjVisitor(PickleVisitor): def func_begin(self, name): ctype = get_c_type(name) self.emit("PyObject*", 0) - self.emit("ast2obj_%s(struct ast_state *state, void* _o)" % (name), 0) + self.emit("ast2obj_%s(struct ast_state *state, struct validator *vstate, void* _o)" % (name), 0) self.emit("{", 0) self.emit("%s o = (%s)_o;" % (ctype, ctype), 1) self.emit("PyObject *result = NULL, *value = NULL;", 1) @@ -1268,17 +1267,17 @@ def func_begin(self, name): self.emit('if (!o) {', 1) self.emit("Py_RETURN_NONE;", 2) self.emit("}", 1) - self.emit("if (++state->recursion_depth > state->recursion_limit) {", 1) + self.emit("if (++vstate->recursion_depth > vstate->recursion_limit) {", 1) self.emit("PyErr_SetString(PyExc_RecursionError,", 2) self.emit('"maximum recursion depth exceeded during ast construction");', 3) self.emit("return NULL;", 2) self.emit("}", 1) def func_end(self): - self.emit("state->recursion_depth--;", 1) + self.emit("vstate->recursion_depth--;", 1) self.emit("return result;", 1) self.emit("failed:", 0) - self.emit("state->recursion_depth--;", 1) + self.emit("vstate->recursion_depth--;", 1) self.emit("Py_XDECREF(value);", 1) self.emit("Py_XDECREF(result);", 1) self.emit("return NULL;", 1) @@ -1296,7 +1295,7 @@ def visitSum(self, sum, name): self.visitConstructor(t, i + 1, name) self.emit("}", 1) for a in sum.attributes: - self.emit("value = ast2obj_%s(state, o->%s);" % (a.type, a.name), 1) + self.emit("value = ast2obj_%s(state, vstate, o->%s);" % (a.type, a.name), 1) self.emit("if (!value) goto failed;", 1) self.emit('if (PyObject_SetAttr(result, state->%s, value) < 0)' % a.name, 1) self.emit('goto failed;', 2) @@ -1304,7 +1303,7 @@ def visitSum(self, sum, name): self.func_end() def simpleSum(self, sum, name): - self.emit("PyObject* ast2obj_%s(struct ast_state *state, %s_ty o)" % (name, name), 0) + self.emit("PyObject* ast2obj_%s(struct ast_state *state, struct validator *vstate, %s_ty o)" % (name, name), 0) self.emit("{", 0) self.emit("switch(o) {", 1) for t in sum.types: @@ -1322,7 +1321,7 @@ def visitProduct(self, prod, name): for field in prod.fields: self.visitField(field, name, 1, True) for a in prod.attributes: - self.emit("value = ast2obj_%s(state, o->%s);" % (a.type, a.name), 1) + self.emit("value = ast2obj_%s(state, vstate, o->%s);" % (a.type, a.name), 1) self.emit("if (!value) goto failed;", 1) self.emit("if (PyObject_SetAttr(result, state->%s, value) < 0)" % a.name, 1) self.emit('goto failed;', 2) @@ -1363,7 +1362,7 @@ def set(self, field, value, depth): self.emit("for(i = 0; i < n; i++)", depth+1) # This cannot fail, so no need for error handling self.emit( - "PyList_SET_ITEM(value, i, ast2obj_{0}(state, ({0}_ty)asdl_seq_GET({1}, i)));".format( + "PyList_SET_ITEM(value, i, ast2obj_{0}(state, vstate, ({0}_ty)asdl_seq_GET({1}, i)));".format( field.type, value ), @@ -1372,9 +1371,9 @@ def set(self, field, value, depth): ) self.emit("}", depth) else: - self.emit("value = ast2obj_list(state, (asdl_seq*)%s, ast2obj_%s);" % (value, field.type), depth) + self.emit("value = ast2obj_list(state, vstate, (asdl_seq*)%s, ast2obj_%s);" % (value, field.type), depth) else: - self.emit("value = ast2obj_%s(state, %s);" % (field.type, value), depth, reflow=False) + self.emit("value = ast2obj_%s(state, vstate, %s);" % (field.type, value), depth, reflow=False) class PartingShots(StaticVisitor): @@ -1394,18 +1393,19 @@ class PartingShots(StaticVisitor): if (!tstate) { return NULL; } - state->recursion_limit = Py_C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE; + struct validator vstate; + vstate.recursion_limit = Py_C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE; int recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE; - state->recursion_depth = starting_recursion_depth; + vstate.recursion_depth = starting_recursion_depth; - PyObject *result = ast2obj_mod(state, t); + PyObject *result = ast2obj_mod(state, &vstate, t); /* Check that the recursion depth counting balanced correctly */ - if (result && state->recursion_depth != starting_recursion_depth) { + if (result && vstate.recursion_depth != starting_recursion_depth) { PyErr_Format(PyExc_SystemError, "AST constructor recursion depth mismatch (before=%d, after=%d)", - starting_recursion_depth, state->recursion_depth); + starting_recursion_depth, vstate.recursion_depth); return NULL; } return result; @@ -1475,8 +1475,6 @@ def generate_ast_state(module_state, f): f.write('struct ast_state {\n') f.write(' _PyOnceFlag once;\n') f.write(' int finalized;\n') - f.write(' int recursion_depth;\n') - f.write(' int recursion_limit;\n') for s in module_state: f.write(' PyObject *' + s + ';\n') f.write('};') @@ -1539,6 +1537,11 @@ def generate_module_def(mod, metadata, f, internal_h): #include "pycore_pystate.h" // _PyInterpreterState_GET() #include + struct validator { + int recursion_depth; /* current recursion depth */ + int recursion_limit; /* recursion limit */ + }; + // Forward declaration static int init_types(struct ast_state *state); diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 75dc3e156aa945..699e1c157c591c 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -9,6 +9,11 @@ #include "pycore_pystate.h" // _PyInterpreterState_GET() #include +struct validator { + int recursion_depth; /* current recursion depth */ + int recursion_limit; /* recursion limit */ +}; + // Forward declaration static int init_types(struct ast_state *state); @@ -383,7 +388,8 @@ GENERATE_ASDL_SEQ_CONSTRUCTOR(pattern, pattern_ty) GENERATE_ASDL_SEQ_CONSTRUCTOR(type_ignore, type_ignore_ty) GENERATE_ASDL_SEQ_CONSTRUCTOR(type_param, type_param_ty) -static PyObject* ast2obj_mod(struct ast_state *state, void*); +static PyObject* ast2obj_mod(struct ast_state *state, struct validator *vstate, + void*); static const char * const Module_fields[]={ "body", "type_ignores", @@ -404,7 +410,8 @@ static const char * const stmt_attributes[] = { "end_lineno", "end_col_offset", }; -static PyObject* ast2obj_stmt(struct ast_state *state, void*); +static PyObject* ast2obj_stmt(struct ast_state *state, struct validator + *vstate, void*); static const char * const FunctionDef_fields[]={ "name", "args", @@ -539,7 +546,8 @@ static const char * const expr_attributes[] = { "end_lineno", "end_col_offset", }; -static PyObject* ast2obj_expr(struct ast_state *state, void*); +static PyObject* ast2obj_expr(struct ast_state *state, struct validator + *vstate, void*); static const char * const BoolOp_fields[]={ "op", "values", @@ -652,12 +660,18 @@ static const char * const Slice_fields[]={ "upper", "step", }; -static PyObject* ast2obj_expr_context(struct ast_state *state, expr_context_ty); -static PyObject* ast2obj_boolop(struct ast_state *state, boolop_ty); -static PyObject* ast2obj_operator(struct ast_state *state, operator_ty); -static PyObject* ast2obj_unaryop(struct ast_state *state, unaryop_ty); -static PyObject* ast2obj_cmpop(struct ast_state *state, cmpop_ty); -static PyObject* ast2obj_comprehension(struct ast_state *state, void*); +static PyObject* ast2obj_expr_context(struct ast_state *state, struct validator + *vstate, expr_context_ty); +static PyObject* ast2obj_boolop(struct ast_state *state, struct validator + *vstate, boolop_ty); +static PyObject* ast2obj_operator(struct ast_state *state, struct validator + *vstate, operator_ty); +static PyObject* ast2obj_unaryop(struct ast_state *state, struct validator + *vstate, unaryop_ty); +static PyObject* ast2obj_cmpop(struct ast_state *state, struct validator + *vstate, cmpop_ty); +static PyObject* ast2obj_comprehension(struct ast_state *state, struct + validator *vstate, void*); static const char * const comprehension_fields[]={ "target", "iter", @@ -670,13 +684,15 @@ static const char * const excepthandler_attributes[] = { "end_lineno", "end_col_offset", }; -static PyObject* ast2obj_excepthandler(struct ast_state *state, void*); +static PyObject* ast2obj_excepthandler(struct ast_state *state, struct + validator *vstate, void*); static const char * const ExceptHandler_fields[]={ "type", "name", "body", }; -static PyObject* ast2obj_arguments(struct ast_state *state, void*); +static PyObject* ast2obj_arguments(struct ast_state *state, struct validator + *vstate, void*); static const char * const arguments_fields[]={ "posonlyargs", "args", @@ -686,7 +702,8 @@ static const char * const arguments_fields[]={ "kwarg", "defaults", }; -static PyObject* ast2obj_arg(struct ast_state *state, void*); +static PyObject* ast2obj_arg(struct ast_state *state, struct validator *vstate, + void*); static const char * const arg_attributes[] = { "lineno", "col_offset", @@ -698,7 +715,8 @@ static const char * const arg_fields[]={ "annotation", "type_comment", }; -static PyObject* ast2obj_keyword(struct ast_state *state, void*); +static PyObject* ast2obj_keyword(struct ast_state *state, struct validator + *vstate, void*); static const char * const keyword_attributes[] = { "lineno", "col_offset", @@ -709,7 +727,8 @@ static const char * const keyword_fields[]={ "arg", "value", }; -static PyObject* ast2obj_alias(struct ast_state *state, void*); +static PyObject* ast2obj_alias(struct ast_state *state, struct validator + *vstate, void*); static const char * const alias_attributes[] = { "lineno", "col_offset", @@ -720,12 +739,14 @@ static const char * const alias_fields[]={ "name", "asname", }; -static PyObject* ast2obj_withitem(struct ast_state *state, void*); +static PyObject* ast2obj_withitem(struct ast_state *state, struct validator + *vstate, void*); static const char * const withitem_fields[]={ "context_expr", "optional_vars", }; -static PyObject* ast2obj_match_case(struct ast_state *state, void*); +static PyObject* ast2obj_match_case(struct ast_state *state, struct validator + *vstate, void*); static const char * const match_case_fields[]={ "pattern", "guard", @@ -737,7 +758,8 @@ static const char * const pattern_attributes[] = { "end_lineno", "end_col_offset", }; -static PyObject* ast2obj_pattern(struct ast_state *state, void*); +static PyObject* ast2obj_pattern(struct ast_state *state, struct validator + *vstate, void*); static const char * const MatchValue_fields[]={ "value", }; @@ -768,7 +790,8 @@ static const char * const MatchAs_fields[]={ static const char * const MatchOr_fields[]={ "patterns", }; -static PyObject* ast2obj_type_ignore(struct ast_state *state, void*); +static PyObject* ast2obj_type_ignore(struct ast_state *state, struct validator + *vstate, void*); static const char * const TypeIgnore_fields[]={ "lineno", "tag", @@ -779,7 +802,8 @@ static const char * const type_param_attributes[] = { "end_lineno", "end_col_offset", }; -static PyObject* ast2obj_type_param(struct ast_state *state, void*); +static PyObject* ast2obj_type_param(struct ast_state *state, struct validator + *vstate, void*); static const char * const TypeVar_fields[]={ "name", "bound", @@ -1008,7 +1032,8 @@ add_attributes(struct ast_state *state, PyObject *type, const char * const *attr /* Conversion AST -> Python */ -static PyObject* ast2obj_list(struct ast_state *state, asdl_seq *seq, PyObject* (*func)(struct ast_state *state, void*)) +static PyObject* ast2obj_list(struct ast_state *state, struct validator *vstate, asdl_seq *seq, + PyObject* (*func)(struct ast_state *state, struct validator *vstate, void*)) { Py_ssize_t i, n = asdl_seq_LEN(seq); PyObject *result = PyList_New(n); @@ -1016,7 +1041,7 @@ static PyObject* ast2obj_list(struct ast_state *state, asdl_seq *seq, PyObject* if (!result) return NULL; for (i = 0; i < n; i++) { - value = func(state, asdl_seq_GET_UNTYPED(seq, i)); + value = func(state, vstate, asdl_seq_GET_UNTYPED(seq, i)); if (!value) { Py_DECREF(result); return NULL; @@ -1026,7 +1051,7 @@ static PyObject* ast2obj_list(struct ast_state *state, asdl_seq *seq, PyObject* return result; } -static PyObject* ast2obj_object(struct ast_state *Py_UNUSED(state), void *o) +static PyObject* ast2obj_object(struct ast_state *Py_UNUSED(state), struct validator *Py_UNUSED(vstate), void *o) { PyObject *op = (PyObject*)o; if (!op) { @@ -1038,7 +1063,7 @@ static PyObject* ast2obj_object(struct ast_state *Py_UNUSED(state), void *o) #define ast2obj_identifier ast2obj_object #define ast2obj_string ast2obj_object -static PyObject* ast2obj_int(struct ast_state *Py_UNUSED(state), long b) +static PyObject* ast2obj_int(struct ast_state *Py_UNUSED(state), struct validator *Py_UNUSED(vstate), long b) { return PyLong_FromLong(b); } @@ -1914,8 +1939,6 @@ init_types(struct ast_state *state) "TypeVarTuple(identifier name)"); if (!state->TypeVarTuple_type) return -1; - state->recursion_depth = 0; - state->recursion_limit = 0; return 0; } @@ -3770,7 +3793,7 @@ _PyAST_TypeVarTuple(identifier name, int lineno, int col_offset, int PyObject* -ast2obj_mod(struct ast_state *state, void* _o) +ast2obj_mod(struct ast_state *state, struct validator *vstate, void* _o) { mod_ty o = (mod_ty)_o; PyObject *result = NULL, *value = NULL; @@ -3778,7 +3801,7 @@ ast2obj_mod(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -3788,12 +3811,14 @@ ast2obj_mod(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Module_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Module.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Module.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Module.type_ignores, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.Module.type_ignores, ast2obj_type_ignore); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_ignores, value) == -1) @@ -3804,7 +3829,7 @@ ast2obj_mod(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Interactive_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Interactive.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Interactive.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) @@ -3815,7 +3840,7 @@ ast2obj_mod(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Expression_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Expression.body); + value = ast2obj_expr(state, vstate, o->v.Expression.body); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; @@ -3825,30 +3850,31 @@ ast2obj_mod(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->FunctionType_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.FunctionType.argtypes, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.FunctionType.argtypes, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->argtypes, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.FunctionType.returns); + value = ast2obj_expr(state, vstate, o->v.FunctionType.returns); if (!value) goto failed; if (PyObject_SetAttr(result, state->returns, value) == -1) goto failed; Py_DECREF(value); break; } - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_stmt(struct ast_state *state, void* _o) +ast2obj_stmt(struct ast_state *state, struct validator *vstate, void* _o) { stmt_ty o = (stmt_ty)_o; PyObject *result = NULL, *value = NULL; @@ -3856,7 +3882,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -3866,39 +3892,41 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->FunctionDef_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.FunctionDef.name); + value = ast2obj_identifier(state, vstate, o->v.FunctionDef.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_arguments(state, o->v.FunctionDef.args); + value = ast2obj_arguments(state, vstate, o->v.FunctionDef.args); if (!value) goto failed; if (PyObject_SetAttr(result, state->args, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.FunctionDef.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.FunctionDef.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.FunctionDef.decorator_list, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.FunctionDef.decorator_list, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->decorator_list, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.FunctionDef.returns); + value = ast2obj_expr(state, vstate, o->v.FunctionDef.returns); if (!value) goto failed; if (PyObject_SetAttr(result, state->returns, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.FunctionDef.type_comment); + value = ast2obj_string(state, vstate, o->v.FunctionDef.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.FunctionDef.type_params, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.FunctionDef.type_params, ast2obj_type_param); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_params, value) == -1) @@ -3909,40 +3937,41 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->AsyncFunctionDef_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.AsyncFunctionDef.name); + value = ast2obj_identifier(state, vstate, o->v.AsyncFunctionDef.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_arguments(state, o->v.AsyncFunctionDef.args); + value = ast2obj_arguments(state, vstate, o->v.AsyncFunctionDef.args); if (!value) goto failed; if (PyObject_SetAttr(result, state->args, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.AsyncFunctionDef.body, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.AsyncFunctionDef.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncFunctionDef.decorator_list, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->decorator_list, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.AsyncFunctionDef.returns); + value = ast2obj_expr(state, vstate, o->v.AsyncFunctionDef.returns); if (!value) goto failed; if (PyObject_SetAttr(result, state->returns, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.AsyncFunctionDef.type_comment); + value = ast2obj_string(state, vstate, o->v.AsyncFunctionDef.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncFunctionDef.type_params, ast2obj_type_param); if (!value) goto failed; @@ -3954,36 +3983,38 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->ClassDef_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.ClassDef.name); + value = ast2obj_identifier(state, vstate, o->v.ClassDef.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ClassDef.bases, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.ClassDef.bases, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->bases, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ClassDef.keywords, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.ClassDef.keywords, ast2obj_keyword); if (!value) goto failed; if (PyObject_SetAttr(result, state->keywords, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ClassDef.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.ClassDef.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ClassDef.decorator_list, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.ClassDef.decorator_list, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->decorator_list, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ClassDef.type_params, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.ClassDef.type_params, ast2obj_type_param); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_params, value) == -1) @@ -3994,7 +4025,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Return_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Return.value); + value = ast2obj_expr(state, vstate, o->v.Return.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4004,7 +4035,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Delete_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Delete.targets, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Delete.targets, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->targets, value) == -1) @@ -4015,18 +4046,18 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Assign_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Assign.targets, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Assign.targets, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->targets, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Assign.value); + value = ast2obj_expr(state, vstate, o->v.Assign.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.Assign.type_comment); + value = ast2obj_string(state, vstate, o->v.Assign.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; @@ -4036,18 +4067,19 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->TypeAlias_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.TypeAlias.name); + value = ast2obj_expr(state, vstate, o->v.TypeAlias.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.TypeAlias.type_params, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.TypeAlias.type_params, ast2obj_type_param); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_params, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.TypeAlias.value); + value = ast2obj_expr(state, vstate, o->v.TypeAlias.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4057,17 +4089,17 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->AugAssign_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.AugAssign.target); + value = ast2obj_expr(state, vstate, o->v.AugAssign.target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_operator(state, o->v.AugAssign.op); + value = ast2obj_operator(state, vstate, o->v.AugAssign.op); if (!value) goto failed; if (PyObject_SetAttr(result, state->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.AugAssign.value); + value = ast2obj_expr(state, vstate, o->v.AugAssign.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4077,22 +4109,22 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->AnnAssign_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.AnnAssign.target); + value = ast2obj_expr(state, vstate, o->v.AnnAssign.target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.AnnAssign.annotation); + value = ast2obj_expr(state, vstate, o->v.AnnAssign.annotation); if (!value) goto failed; if (PyObject_SetAttr(result, state->annotation, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.AnnAssign.value); + value = ast2obj_expr(state, vstate, o->v.AnnAssign.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->v.AnnAssign.simple); + value = ast2obj_int(state, vstate, o->v.AnnAssign.simple); if (!value) goto failed; if (PyObject_SetAttr(result, state->simple, value) == -1) goto failed; @@ -4102,27 +4134,29 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->For_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.For.target); + value = ast2obj_expr(state, vstate, o->v.For.target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.For.iter); + value = ast2obj_expr(state, vstate, o->v.For.iter); if (!value) goto failed; if (PyObject_SetAttr(result, state->iter, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.For.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.For.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.For.orelse, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.For.orelse, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.For.type_comment); + value = ast2obj_string(state, vstate, o->v.For.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; @@ -4132,29 +4166,29 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->AsyncFor_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.AsyncFor.target); + value = ast2obj_expr(state, vstate, o->v.AsyncFor.target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.AsyncFor.iter); + value = ast2obj_expr(state, vstate, o->v.AsyncFor.iter); if (!value) goto failed; if (PyObject_SetAttr(result, state->iter, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.AsyncFor.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncFor.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.AsyncFor.orelse, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncFor.orelse, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.AsyncFor.type_comment); + value = ast2obj_string(state, vstate, o->v.AsyncFor.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; @@ -4164,17 +4198,19 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->While_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.While.test); + value = ast2obj_expr(state, vstate, o->v.While.test); if (!value) goto failed; if (PyObject_SetAttr(result, state->test, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.While.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.While.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.While.orelse, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.While.orelse, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; @@ -4184,17 +4220,19 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->If_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.If.test); + value = ast2obj_expr(state, vstate, o->v.If.test); if (!value) goto failed; if (PyObject_SetAttr(result, state->test, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.If.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.If.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.If.orelse, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.If.orelse, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; @@ -4204,18 +4242,19 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->With_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.With.items, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.With.items, ast2obj_withitem); if (!value) goto failed; if (PyObject_SetAttr(result, state->items, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.With.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.With.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.With.type_comment); + value = ast2obj_string(state, vstate, o->v.With.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; @@ -4225,19 +4264,19 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->AsyncWith_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.AsyncWith.items, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncWith.items, ast2obj_withitem); if (!value) goto failed; if (PyObject_SetAttr(result, state->items, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.AsyncWith.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.AsyncWith.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.AsyncWith.type_comment); + value = ast2obj_string(state, vstate, o->v.AsyncWith.type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; @@ -4247,12 +4286,12 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Match_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Match.subject); + value = ast2obj_expr(state, vstate, o->v.Match.subject); if (!value) goto failed; if (PyObject_SetAttr(result, state->subject, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Match.cases, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Match.cases, ast2obj_match_case); if (!value) goto failed; if (PyObject_SetAttr(result, state->cases, value) == -1) @@ -4263,12 +4302,12 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Raise_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Raise.exc); + value = ast2obj_expr(state, vstate, o->v.Raise.exc); if (!value) goto failed; if (PyObject_SetAttr(result, state->exc, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Raise.cause); + value = ast2obj_expr(state, vstate, o->v.Raise.cause); if (!value) goto failed; if (PyObject_SetAttr(result, state->cause, value) == -1) goto failed; @@ -4278,23 +4317,25 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Try_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Try.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Try.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Try.handlers, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Try.handlers, ast2obj_excepthandler); if (!value) goto failed; if (PyObject_SetAttr(result, state->handlers, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Try.orelse, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Try.orelse, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Try.finalbody, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Try.finalbody, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->finalbody, value) == -1) @@ -4305,24 +4346,25 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->TryStar_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.TryStar.body, + ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.handlers, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.TryStar.handlers, ast2obj_excepthandler); if (!value) goto failed; if (PyObject_SetAttr(result, state->handlers, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.orelse, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.TryStar.orelse, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.TryStar.finalbody, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.TryStar.finalbody, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->finalbody, value) == -1) @@ -4333,12 +4375,12 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Assert_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Assert.test); + value = ast2obj_expr(state, vstate, o->v.Assert.test); if (!value) goto failed; if (PyObject_SetAttr(result, state->test, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Assert.msg); + value = ast2obj_expr(state, vstate, o->v.Assert.msg); if (!value) goto failed; if (PyObject_SetAttr(result, state->msg, value) == -1) goto failed; @@ -4348,7 +4390,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Import_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Import.names, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Import.names, ast2obj_alias); if (!value) goto failed; if (PyObject_SetAttr(result, state->names, value) == -1) @@ -4359,18 +4401,18 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->ImportFrom_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.ImportFrom.module); + value = ast2obj_identifier(state, vstate, o->v.ImportFrom.module); if (!value) goto failed; if (PyObject_SetAttr(result, state->module, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ImportFrom.names, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.ImportFrom.names, ast2obj_alias); if (!value) goto failed; if (PyObject_SetAttr(result, state->names, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->v.ImportFrom.level); + value = ast2obj_int(state, vstate, o->v.ImportFrom.level); if (!value) goto failed; if (PyObject_SetAttr(result, state->level, value) == -1) goto failed; @@ -4380,7 +4422,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Global_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Global.names, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Global.names, ast2obj_identifier); if (!value) goto failed; if (PyObject_SetAttr(result, state->names, value) == -1) @@ -4391,7 +4433,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Nonlocal_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Nonlocal.names, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Nonlocal.names, ast2obj_identifier); if (!value) goto failed; if (PyObject_SetAttr(result, state->names, value) == -1) @@ -4402,7 +4444,7 @@ ast2obj_stmt(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Expr_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Expr.value); + value = ast2obj_expr(state, vstate, o->v.Expr.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4424,37 +4466,37 @@ ast2obj_stmt(struct ast_state *state, void* _o) if (!result) goto failed; break; } - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_expr(struct ast_state *state, void* _o) +ast2obj_expr(struct ast_state *state, struct validator *vstate, void* _o) { expr_ty o = (expr_ty)_o; PyObject *result = NULL, *value = NULL; @@ -4462,7 +4504,7 @@ ast2obj_expr(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -4472,12 +4514,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->BoolOp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_boolop(state, o->v.BoolOp.op); + value = ast2obj_boolop(state, vstate, o->v.BoolOp.op); if (!value) goto failed; if (PyObject_SetAttr(result, state->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.BoolOp.values, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.BoolOp.values, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->values, value) == -1) @@ -4488,12 +4530,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->NamedExpr_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.NamedExpr.target); + value = ast2obj_expr(state, vstate, o->v.NamedExpr.target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.NamedExpr.value); + value = ast2obj_expr(state, vstate, o->v.NamedExpr.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4503,17 +4545,17 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->BinOp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.BinOp.left); + value = ast2obj_expr(state, vstate, o->v.BinOp.left); if (!value) goto failed; if (PyObject_SetAttr(result, state->left, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_operator(state, o->v.BinOp.op); + value = ast2obj_operator(state, vstate, o->v.BinOp.op); if (!value) goto failed; if (PyObject_SetAttr(result, state->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.BinOp.right); + value = ast2obj_expr(state, vstate, o->v.BinOp.right); if (!value) goto failed; if (PyObject_SetAttr(result, state->right, value) == -1) goto failed; @@ -4523,12 +4565,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->UnaryOp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_unaryop(state, o->v.UnaryOp.op); + value = ast2obj_unaryop(state, vstate, o->v.UnaryOp.op); if (!value) goto failed; if (PyObject_SetAttr(result, state->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.UnaryOp.operand); + value = ast2obj_expr(state, vstate, o->v.UnaryOp.operand); if (!value) goto failed; if (PyObject_SetAttr(result, state->operand, value) == -1) goto failed; @@ -4538,12 +4580,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Lambda_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_arguments(state, o->v.Lambda.args); + value = ast2obj_arguments(state, vstate, o->v.Lambda.args); if (!value) goto failed; if (PyObject_SetAttr(result, state->args, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Lambda.body); + value = ast2obj_expr(state, vstate, o->v.Lambda.body); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; @@ -4553,17 +4595,17 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->IfExp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.IfExp.test); + value = ast2obj_expr(state, vstate, o->v.IfExp.test); if (!value) goto failed; if (PyObject_SetAttr(result, state->test, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.IfExp.body); + value = ast2obj_expr(state, vstate, o->v.IfExp.body); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.IfExp.orelse); + value = ast2obj_expr(state, vstate, o->v.IfExp.orelse); if (!value) goto failed; if (PyObject_SetAttr(result, state->orelse, value) == -1) goto failed; @@ -4573,12 +4615,14 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Dict_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Dict.keys, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Dict.keys, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->keys, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Dict.values, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Dict.values, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->values, value) == -1) goto failed; @@ -4588,7 +4632,8 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Set_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Set.elts, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Set.elts, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->elts, value) == -1) goto failed; @@ -4598,12 +4643,13 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->ListComp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.ListComp.elt); + value = ast2obj_expr(state, vstate, o->v.ListComp.elt); if (!value) goto failed; if (PyObject_SetAttr(result, state->elt, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ListComp.generators, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.ListComp.generators, ast2obj_comprehension); if (!value) goto failed; if (PyObject_SetAttr(result, state->generators, value) == -1) @@ -4614,12 +4660,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->SetComp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.SetComp.elt); + value = ast2obj_expr(state, vstate, o->v.SetComp.elt); if (!value) goto failed; if (PyObject_SetAttr(result, state->elt, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.SetComp.generators, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.SetComp.generators, ast2obj_comprehension); if (!value) goto failed; if (PyObject_SetAttr(result, state->generators, value) == -1) @@ -4630,17 +4676,18 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->DictComp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.DictComp.key); + value = ast2obj_expr(state, vstate, o->v.DictComp.key); if (!value) goto failed; if (PyObject_SetAttr(result, state->key, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.DictComp.value); + value = ast2obj_expr(state, vstate, o->v.DictComp.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.DictComp.generators, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.DictComp.generators, ast2obj_comprehension); if (!value) goto failed; if (PyObject_SetAttr(result, state->generators, value) == -1) @@ -4651,12 +4698,13 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->GeneratorExp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.GeneratorExp.elt); + value = ast2obj_expr(state, vstate, o->v.GeneratorExp.elt); if (!value) goto failed; if (PyObject_SetAttr(result, state->elt, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.GeneratorExp.generators, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.GeneratorExp.generators, ast2obj_comprehension); if (!value) goto failed; if (PyObject_SetAttr(result, state->generators, value) == -1) @@ -4667,7 +4715,7 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Await_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Await.value); + value = ast2obj_expr(state, vstate, o->v.Await.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4677,7 +4725,7 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Yield_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Yield.value); + value = ast2obj_expr(state, vstate, o->v.Yield.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4687,7 +4735,7 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->YieldFrom_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.YieldFrom.value); + value = ast2obj_expr(state, vstate, o->v.YieldFrom.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -4697,7 +4745,7 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Compare_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Compare.left); + value = ast2obj_expr(state, vstate, o->v.Compare.left); if (!value) goto failed; if (PyObject_SetAttr(result, state->left, value) == -1) goto failed; @@ -4707,14 +4755,14 @@ ast2obj_expr(struct ast_state *state, void* _o) value = PyList_New(n); if (!value) goto failed; for(i = 0; i < n; i++) - PyList_SET_ITEM(value, i, ast2obj_cmpop(state, (cmpop_ty)asdl_seq_GET(o->v.Compare.ops, i))); + PyList_SET_ITEM(value, i, ast2obj_cmpop(state, vstate, (cmpop_ty)asdl_seq_GET(o->v.Compare.ops, i))); } if (!value) goto failed; if (PyObject_SetAttr(result, state->ops, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Compare.comparators, - ast2obj_expr); + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.Compare.comparators, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->comparators, value) == -1) goto failed; @@ -4724,17 +4772,18 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Call_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Call.func); + value = ast2obj_expr(state, vstate, o->v.Call.func); if (!value) goto failed; if (PyObject_SetAttr(result, state->func, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Call.args, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Call.args, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->args, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.Call.keywords, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Call.keywords, ast2obj_keyword); if (!value) goto failed; if (PyObject_SetAttr(result, state->keywords, value) == -1) @@ -4745,17 +4794,17 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->FormattedValue_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.FormattedValue.value); + value = ast2obj_expr(state, vstate, o->v.FormattedValue.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->v.FormattedValue.conversion); + value = ast2obj_int(state, vstate, o->v.FormattedValue.conversion); if (!value) goto failed; if (PyObject_SetAttr(result, state->conversion, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.FormattedValue.format_spec); + value = ast2obj_expr(state, vstate, o->v.FormattedValue.format_spec); if (!value) goto failed; if (PyObject_SetAttr(result, state->format_spec, value) == -1) goto failed; @@ -4765,7 +4814,7 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->JoinedStr_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.JoinedStr.values, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.JoinedStr.values, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->values, value) == -1) @@ -4776,12 +4825,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Constant_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_constant(state, o->v.Constant.value); + value = ast2obj_constant(state, vstate, o->v.Constant.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.Constant.kind); + value = ast2obj_string(state, vstate, o->v.Constant.kind); if (!value) goto failed; if (PyObject_SetAttr(result, state->kind, value) == -1) goto failed; @@ -4791,17 +4840,17 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Attribute_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Attribute.value); + value = ast2obj_expr(state, vstate, o->v.Attribute.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_identifier(state, o->v.Attribute.attr); + value = ast2obj_identifier(state, vstate, o->v.Attribute.attr); if (!value) goto failed; if (PyObject_SetAttr(result, state->attr, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.Attribute.ctx); + value = ast2obj_expr_context(state, vstate, o->v.Attribute.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4811,17 +4860,17 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Subscript_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Subscript.value); + value = ast2obj_expr(state, vstate, o->v.Subscript.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Subscript.slice); + value = ast2obj_expr(state, vstate, o->v.Subscript.slice); if (!value) goto failed; if (PyObject_SetAttr(result, state->slice, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.Subscript.ctx); + value = ast2obj_expr_context(state, vstate, o->v.Subscript.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4831,12 +4880,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Starred_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Starred.value); + value = ast2obj_expr(state, vstate, o->v.Starred.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.Starred.ctx); + value = ast2obj_expr_context(state, vstate, o->v.Starred.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4846,12 +4895,12 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Name_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.Name.id); + value = ast2obj_identifier(state, vstate, o->v.Name.id); if (!value) goto failed; if (PyObject_SetAttr(result, state->id, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.Name.ctx); + value = ast2obj_expr_context(state, vstate, o->v.Name.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4861,12 +4910,13 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->List_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.List.elts, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.List.elts, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->elts, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.List.ctx); + value = ast2obj_expr_context(state, vstate, o->v.List.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4876,12 +4926,13 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Tuple_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.Tuple.elts, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.Tuple.elts, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->elts, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr_context(state, o->v.Tuple.ctx); + value = ast2obj_expr_context(state, vstate, o->v.Tuple.ctx); if (!value) goto failed; if (PyObject_SetAttr(result, state->ctx, value) == -1) goto failed; @@ -4891,53 +4942,54 @@ ast2obj_expr(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->Slice_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.Slice.lower); + value = ast2obj_expr(state, vstate, o->v.Slice.lower); if (!value) goto failed; if (PyObject_SetAttr(result, state->lower, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Slice.upper); + value = ast2obj_expr(state, vstate, o->v.Slice.upper); if (!value) goto failed; if (PyObject_SetAttr(result, state->upper, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.Slice.step); + value = ast2obj_expr(state, vstate, o->v.Slice.step); if (!value) goto failed; if (PyObject_SetAttr(result, state->step, value) == -1) goto failed; Py_DECREF(value); break; } - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } -PyObject* ast2obj_expr_context(struct ast_state *state, expr_context_ty o) +PyObject* ast2obj_expr_context(struct ast_state *state, struct validator + *vstate, expr_context_ty o) { switch(o) { case Load: @@ -4949,7 +5001,8 @@ PyObject* ast2obj_expr_context(struct ast_state *state, expr_context_ty o) } Py_UNREACHABLE(); } -PyObject* ast2obj_boolop(struct ast_state *state, boolop_ty o) +PyObject* ast2obj_boolop(struct ast_state *state, struct validator *vstate, + boolop_ty o) { switch(o) { case And: @@ -4959,7 +5012,8 @@ PyObject* ast2obj_boolop(struct ast_state *state, boolop_ty o) } Py_UNREACHABLE(); } -PyObject* ast2obj_operator(struct ast_state *state, operator_ty o) +PyObject* ast2obj_operator(struct ast_state *state, struct validator *vstate, + operator_ty o) { switch(o) { case Add: @@ -4991,7 +5045,8 @@ PyObject* ast2obj_operator(struct ast_state *state, operator_ty o) } Py_UNREACHABLE(); } -PyObject* ast2obj_unaryop(struct ast_state *state, unaryop_ty o) +PyObject* ast2obj_unaryop(struct ast_state *state, struct validator *vstate, + unaryop_ty o) { switch(o) { case Invert: @@ -5005,7 +5060,8 @@ PyObject* ast2obj_unaryop(struct ast_state *state, unaryop_ty o) } Py_UNREACHABLE(); } -PyObject* ast2obj_cmpop(struct ast_state *state, cmpop_ty o) +PyObject* ast2obj_cmpop(struct ast_state *state, struct validator *vstate, + cmpop_ty o) { switch(o) { case Eq: @@ -5032,7 +5088,8 @@ PyObject* ast2obj_cmpop(struct ast_state *state, cmpop_ty o) Py_UNREACHABLE(); } PyObject* -ast2obj_comprehension(struct ast_state *state, void* _o) +ast2obj_comprehension(struct ast_state *state, struct validator *vstate, void* + _o) { comprehension_ty o = (comprehension_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5040,7 +5097,7 @@ ast2obj_comprehension(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5048,37 +5105,38 @@ ast2obj_comprehension(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->comprehension_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_expr(state, o->target); + value = ast2obj_expr(state, vstate, o->target); if (!value) goto failed; if (PyObject_SetAttr(result, state->target, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->iter); + value = ast2obj_expr(state, vstate, o->iter); if (!value) goto failed; if (PyObject_SetAttr(result, state->iter, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->ifs, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->ifs, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->ifs, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->is_async); + value = ast2obj_int(state, vstate, o->is_async); if (!value) goto failed; if (PyObject_SetAttr(result, state->is_async, value) == -1) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_excepthandler(struct ast_state *state, void* _o) +ast2obj_excepthandler(struct ast_state *state, struct validator *vstate, void* + _o) { excepthandler_ty o = (excepthandler_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5086,7 +5144,7 @@ ast2obj_excepthandler(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5096,17 +5154,17 @@ ast2obj_excepthandler(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->ExceptHandler_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.ExceptHandler.type); + value = ast2obj_expr(state, vstate, o->v.ExceptHandler.type); if (!value) goto failed; if (PyObject_SetAttr(result, state->type, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_identifier(state, o->v.ExceptHandler.name); + value = ast2obj_identifier(state, vstate, o->v.ExceptHandler.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.ExceptHandler.body, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.ExceptHandler.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) @@ -5114,37 +5172,37 @@ ast2obj_excepthandler(struct ast_state *state, void* _o) Py_DECREF(value); break; } - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_arguments(struct ast_state *state, void* _o) +ast2obj_arguments(struct ast_state *state, struct validator *vstate, void* _o) { arguments_ty o = (arguments_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5152,7 +5210,7 @@ ast2obj_arguments(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5160,52 +5218,53 @@ ast2obj_arguments(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->arguments_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_list(state, (asdl_seq*)o->posonlyargs, ast2obj_arg); + value = ast2obj_list(state, vstate, (asdl_seq*)o->posonlyargs, ast2obj_arg); if (!value) goto failed; if (PyObject_SetAttr(result, state->posonlyargs, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->args, ast2obj_arg); + value = ast2obj_list(state, vstate, (asdl_seq*)o->args, ast2obj_arg); if (!value) goto failed; if (PyObject_SetAttr(result, state->args, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_arg(state, o->vararg); + value = ast2obj_arg(state, vstate, o->vararg); if (!value) goto failed; if (PyObject_SetAttr(result, state->vararg, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->kwonlyargs, ast2obj_arg); + value = ast2obj_list(state, vstate, (asdl_seq*)o->kwonlyargs, ast2obj_arg); if (!value) goto failed; if (PyObject_SetAttr(result, state->kwonlyargs, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->kw_defaults, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->kw_defaults, + ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->kw_defaults, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_arg(state, o->kwarg); + value = ast2obj_arg(state, vstate, o->kwarg); if (!value) goto failed; if (PyObject_SetAttr(result, state->kwarg, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->defaults, ast2obj_expr); + value = ast2obj_list(state, vstate, (asdl_seq*)o->defaults, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->defaults, value) == -1) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_arg(struct ast_state *state, void* _o) +ast2obj_arg(struct ast_state *state, struct validator *vstate, void* _o) { arg_ty o = (arg_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5213,7 +5272,7 @@ ast2obj_arg(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5221,52 +5280,52 @@ ast2obj_arg(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->arg_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_identifier(state, o->arg); + value = ast2obj_identifier(state, vstate, o->arg); if (!value) goto failed; if (PyObject_SetAttr(result, state->arg, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->annotation); + value = ast2obj_expr(state, vstate, o->annotation); if (!value) goto failed; if (PyObject_SetAttr(result, state->annotation, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->type_comment); + value = ast2obj_string(state, vstate, o->type_comment); if (!value) goto failed; if (PyObject_SetAttr(result, state->type_comment, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_keyword(struct ast_state *state, void* _o) +ast2obj_keyword(struct ast_state *state, struct validator *vstate, void* _o) { keyword_ty o = (keyword_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5274,7 +5333,7 @@ ast2obj_keyword(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5282,47 +5341,47 @@ ast2obj_keyword(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->keyword_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_identifier(state, o->arg); + value = ast2obj_identifier(state, vstate, o->arg); if (!value) goto failed; if (PyObject_SetAttr(result, state->arg, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->value); + value = ast2obj_expr(state, vstate, o->value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_alias(struct ast_state *state, void* _o) +ast2obj_alias(struct ast_state *state, struct validator *vstate, void* _o) { alias_ty o = (alias_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5330,7 +5389,7 @@ ast2obj_alias(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5338,47 +5397,47 @@ ast2obj_alias(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->alias_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_identifier(state, o->name); + value = ast2obj_identifier(state, vstate, o->name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_identifier(state, o->asname); + value = ast2obj_identifier(state, vstate, o->asname); if (!value) goto failed; if (PyObject_SetAttr(result, state->asname, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_withitem(struct ast_state *state, void* _o) +ast2obj_withitem(struct ast_state *state, struct validator *vstate, void* _o) { withitem_ty o = (withitem_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5386,7 +5445,7 @@ ast2obj_withitem(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5394,27 +5453,27 @@ ast2obj_withitem(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->withitem_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_expr(state, o->context_expr); + value = ast2obj_expr(state, vstate, o->context_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->context_expr, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->optional_vars); + value = ast2obj_expr(state, vstate, o->optional_vars); if (!value) goto failed; if (PyObject_SetAttr(result, state->optional_vars, value) == -1) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_match_case(struct ast_state *state, void* _o) +ast2obj_match_case(struct ast_state *state, struct validator *vstate, void* _o) { match_case_ty o = (match_case_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5422,7 +5481,7 @@ ast2obj_match_case(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5430,32 +5489,32 @@ ast2obj_match_case(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->match_case_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) return NULL; - value = ast2obj_pattern(state, o->pattern); + value = ast2obj_pattern(state, vstate, o->pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->pattern, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->guard); + value = ast2obj_expr(state, vstate, o->guard); if (!value) goto failed; if (PyObject_SetAttr(result, state->guard, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->body, ast2obj_stmt); + value = ast2obj_list(state, vstate, (asdl_seq*)o->body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttr(result, state->body, value) == -1) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_pattern(struct ast_state *state, void* _o) +ast2obj_pattern(struct ast_state *state, struct validator *vstate, void* _o) { pattern_ty o = (pattern_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5463,7 +5522,7 @@ ast2obj_pattern(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5473,7 +5532,7 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchValue_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.MatchValue.value); + value = ast2obj_expr(state, vstate, o->v.MatchValue.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -5483,7 +5542,7 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchSingleton_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_constant(state, o->v.MatchSingleton.value); + value = ast2obj_constant(state, vstate, o->v.MatchSingleton.value); if (!value) goto failed; if (PyObject_SetAttr(result, state->value, value) == -1) goto failed; @@ -5493,7 +5552,8 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchSequence_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.MatchSequence.patterns, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.MatchSequence.patterns, ast2obj_pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->patterns, value) == -1) @@ -5504,19 +5564,20 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchMapping_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.MatchMapping.keys, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.MatchMapping.keys, ast2obj_expr); if (!value) goto failed; if (PyObject_SetAttr(result, state->keys, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.MatchMapping.patterns, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.MatchMapping.patterns, ast2obj_pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->patterns, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_identifier(state, o->v.MatchMapping.rest); + value = ast2obj_identifier(state, vstate, o->v.MatchMapping.rest); if (!value) goto failed; if (PyObject_SetAttr(result, state->rest, value) == -1) goto failed; @@ -5526,24 +5587,27 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchClass_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(state, o->v.MatchClass.cls); + value = ast2obj_expr(state, vstate, o->v.MatchClass.cls); if (!value) goto failed; if (PyObject_SetAttr(result, state->cls, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.MatchClass.patterns, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.MatchClass.patterns, ast2obj_pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->patterns, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.MatchClass.kwd_attrs, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.MatchClass.kwd_attrs, ast2obj_identifier); if (!value) goto failed; if (PyObject_SetAttr(result, state->kwd_attrs, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_list(state, (asdl_seq*)o->v.MatchClass.kwd_patterns, + value = ast2obj_list(state, vstate, + (asdl_seq*)o->v.MatchClass.kwd_patterns, ast2obj_pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->kwd_patterns, value) == -1) @@ -5554,7 +5618,7 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchStar_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.MatchStar.name); + value = ast2obj_identifier(state, vstate, o->v.MatchStar.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; @@ -5564,12 +5628,12 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchAs_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_pattern(state, o->v.MatchAs.pattern); + value = ast2obj_pattern(state, vstate, o->v.MatchAs.pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->pattern, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_identifier(state, o->v.MatchAs.name); + value = ast2obj_identifier(state, vstate, o->v.MatchAs.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; @@ -5579,7 +5643,7 @@ ast2obj_pattern(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->MatchOr_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_list(state, (asdl_seq*)o->v.MatchOr.patterns, + value = ast2obj_list(state, vstate, (asdl_seq*)o->v.MatchOr.patterns, ast2obj_pattern); if (!value) goto failed; if (PyObject_SetAttr(result, state->patterns, value) == -1) @@ -5587,37 +5651,37 @@ ast2obj_pattern(struct ast_state *state, void* _o) Py_DECREF(value); break; } - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_type_ignore(struct ast_state *state, void* _o) +ast2obj_type_ignore(struct ast_state *state, struct validator *vstate, void* _o) { type_ignore_ty o = (type_ignore_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5625,7 +5689,7 @@ ast2obj_type_ignore(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5635,29 +5699,29 @@ ast2obj_type_ignore(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->TypeIgnore_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_int(state, o->v.TypeIgnore.lineno); + value = ast2obj_int(state, vstate, o->v.TypeIgnore.lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_string(state, o->v.TypeIgnore.tag); + value = ast2obj_string(state, vstate, o->v.TypeIgnore.tag); if (!value) goto failed; if (PyObject_SetAttr(result, state->tag, value) == -1) goto failed; Py_DECREF(value); break; } - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; } PyObject* -ast2obj_type_param(struct ast_state *state, void* _o) +ast2obj_type_param(struct ast_state *state, struct validator *vstate, void* _o) { type_param_ty o = (type_param_ty)_o; PyObject *result = NULL, *value = NULL; @@ -5665,7 +5729,7 @@ ast2obj_type_param(struct ast_state *state, void* _o) if (!o) { Py_RETURN_NONE; } - if (++state->recursion_depth > state->recursion_limit) { + if (++vstate->recursion_depth > vstate->recursion_limit) { PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during ast construction"); return NULL; @@ -5675,12 +5739,12 @@ ast2obj_type_param(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->TypeVar_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.TypeVar.name); + value = ast2obj_identifier(state, vstate, o->v.TypeVar.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(state, o->v.TypeVar.bound); + value = ast2obj_expr(state, vstate, o->v.TypeVar.bound); if (!value) goto failed; if (PyObject_SetAttr(result, state->bound, value) == -1) goto failed; @@ -5690,7 +5754,7 @@ ast2obj_type_param(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->ParamSpec_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.ParamSpec.name); + value = ast2obj_identifier(state, vstate, o->v.ParamSpec.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; @@ -5700,37 +5764,37 @@ ast2obj_type_param(struct ast_state *state, void* _o) tp = (PyTypeObject *)state->TypeVarTuple_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_identifier(state, o->v.TypeVarTuple.name); + value = ast2obj_identifier(state, vstate, o->v.TypeVarTuple.name); if (!value) goto failed; if (PyObject_SetAttr(result, state->name, value) == -1) goto failed; Py_DECREF(value); break; } - value = ast2obj_int(state, o->lineno); + value = ast2obj_int(state, vstate, o->lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->col_offset); + value = ast2obj_int(state, vstate, o->col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->col_offset, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_lineno); + value = ast2obj_int(state, vstate, o->end_lineno); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_lineno, value) < 0) goto failed; Py_DECREF(value); - value = ast2obj_int(state, o->end_col_offset); + value = ast2obj_int(state, vstate, o->end_col_offset); if (!value) goto failed; if (PyObject_SetAttr(result, state->end_col_offset, value) < 0) goto failed; Py_DECREF(value); - state->recursion_depth--; + vstate->recursion_depth--; return result; failed: - state->recursion_depth--; + vstate->recursion_depth--; Py_XDECREF(value); Py_XDECREF(result); return NULL; @@ -13090,18 +13154,19 @@ PyObject* PyAST_mod2obj(mod_ty t) if (!tstate) { return NULL; } - state->recursion_limit = Py_C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE; + struct validator vstate; + vstate.recursion_limit = Py_C_RECURSION_LIMIT * COMPILER_STACK_FRAME_SCALE; int recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; starting_recursion_depth = recursion_depth * COMPILER_STACK_FRAME_SCALE; - state->recursion_depth = starting_recursion_depth; + vstate.recursion_depth = starting_recursion_depth; - PyObject *result = ast2obj_mod(state, t); + PyObject *result = ast2obj_mod(state, &vstate, t); /* Check that the recursion depth counting balanced correctly */ - if (result && state->recursion_depth != starting_recursion_depth) { + if (result && vstate.recursion_depth != starting_recursion_depth) { PyErr_Format(PyExc_SystemError, "AST constructor recursion depth mismatch (before=%d, after=%d)", - starting_recursion_depth, state->recursion_depth); + starting_recursion_depth, vstate.recursion_depth); return NULL; } return result; From b5dc0f83adda0bde1b176c3a84cc31cd94254e73 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 25 Dec 2023 16:26:04 -0600 Subject: [PATCH 370/442] Misc minor improvements to the itertools recipes (gh-113477) --- Doc/library/itertools.rst | 164 +++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index c016fb76bfd0a0..5c8cc982a89a2c 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -803,11 +803,11 @@ which incur interpreter overhead. import random def take(n, iterable): - "Return first n items of the iterable as a list" + "Return first n items of the iterable as a list." return list(islice(iterable, n)) def prepend(value, iterable): - "Prepend a single value in front of an iterable" + "Prepend a single value in front of an iterable." # prepend(1, [2, 3, 4]) --> 1 2 3 4 return chain([value], iterable) @@ -825,15 +825,15 @@ which incur interpreter overhead. return starmap(func, repeat(args, times)) def flatten(list_of_lists): - "Flatten one level of nesting" + "Flatten one level of nesting." return chain.from_iterable(list_of_lists) def ncycles(iterable, n): - "Returns the sequence elements n times" + "Returns the sequence elements n times." return chain.from_iterable(repeat(tuple(iterable), n)) def tail(n, iterable): - "Return an iterator over the last n items" + "Return an iterator over the last n items." # tail(3, 'ABCDEFG') --> E F G return iter(collections.deque(iterable, maxlen=n)) @@ -848,7 +848,7 @@ which incur interpreter overhead. next(islice(iterator, n, n), None) def nth(iterable, n, default=None): - "Returns the nth item or a default value" + "Returns the nth item or a default value." return next(islice(iterable, n, None), default) def quantify(iterable, pred=bool): @@ -856,7 +856,7 @@ which incur interpreter overhead. return sum(map(pred, iterable)) def all_equal(iterable): - "Returns True if all the elements are equal to each other" + "Returns True if all the elements are equal to each other." g = groupby(iterable) return next(g, True) and not next(g, False) @@ -873,6 +873,30 @@ which incur interpreter overhead. # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x return next(filter(pred, iterable), default) + def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBcCAD', str.casefold) --> A B c D + seen = set() + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen.add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen.add(k) + yield element + + def unique_justseen(iterable, key=None): + "List unique elements, preserving order. Remember only the element just seen." + # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B + # unique_justseen('ABBcCAD', str.casefold) --> A B c A D + if key is None: + return map(operator.itemgetter(0), groupby(iterable)) + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + def iter_index(iterable, value, start=0, stop=None): "Return indices where a value occurs in a sequence or iterable." # iter_index('AABCADEAF', 'A') --> 0 1 4 7 @@ -893,31 +917,17 @@ which incur interpreter overhead. except ValueError: pass - def iter_except(func, exception, first=None): - """ Call a function repeatedly until an exception is raised. - - Converts a call-until-exception interface to an iterator interface. - Like builtins.iter(func, sentinel) but uses an exception instead - of a sentinel to end the loop. - - Examples: - iter_except(functools.partial(heappop, h), IndexError) # priority queue iterator - iter_except(d.popitem, KeyError) # non-blocking dict iterator - iter_except(d.popleft, IndexError) # non-blocking deque iterator - iter_except(q.get_nowait, Queue.Empty) # loop over a producer Queue - iter_except(s.pop, KeyError) # non-blocking set iterator - - """ - try: - if first is not None: - yield first() # For database APIs needing an initial cast to db.first() - while True: - yield func() - except exception: - pass + def sliding_window(iterable, n): + "Collect data into overlapping fixed-length chunks or blocks." + # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG + it = iter(iterable) + window = collections.deque(islice(it, n-1), maxlen=n) + for x in it: + window.append(x) + yield tuple(window) def grouper(iterable, n, *, incomplete='fill', fillvalue=None): - "Collect data into non-overlapping fixed-length chunks or blocks" + "Collect data into non-overlapping fixed-length chunks or blocks." # grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF @@ -932,16 +942,9 @@ which incur interpreter overhead. case _: raise ValueError('Expected fill, strict, or ignore') - def sliding_window(iterable, n): - # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG - it = iter(iterable) - window = collections.deque(islice(it, n-1), maxlen=n) - for x in it: - window.append(x) - yield tuple(window) - def roundrobin(*iterables): - "roundrobin('ABC', 'D', 'EF') --> A D E B F C" + "Visit input iterables in a cycle until each is exhausted." + # roundrobin('ABC', 'D', 'EF') --> A D E B F C # Recipe credited to George Sakkis num_active = len(iterables) nexts = cycle(iter(it).__next__ for it in iterables) @@ -964,11 +967,43 @@ which incur interpreter overhead. return filterfalse(pred, t1), filter(pred, t2) def subslices(seq): - "Return all contiguous non-empty subslices of a sequence" + "Return all contiguous non-empty subslices of a sequence." # subslices('ABCD') --> A AB ABC ABCD B BC BCD C CD D slices = starmap(slice, combinations(range(len(seq) + 1), 2)) return map(operator.getitem, repeat(seq), slices) + def iter_except(func, exception, first=None): + """ Call a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like builtins.iter(func, sentinel) but uses an exception instead + of a sentinel to end the loop. + + Priority queue iterator: + iter_except(functools.partial(heappop, h), IndexError) + + Non-blocking dictionary iterator: + iter_except(d.popitem, KeyError) + + Non-blocking deque iterator: + iter_except(d.popleft, IndexError) + + Non-blocking iterator over a producer Queue: + iter_except(q.get_nowait, Queue.Empty) + + Non-blocking set iterator: + iter_except(s.pop, KeyError) + + """ + try: + if first is not None: + # For database APIs needing an initial call to db.first() + yield first() + while True: + yield func() + except exception: + pass + def before_and_after(predicate, it): """ Variant of takewhile() that allows complete access to the remainder of the iterator. @@ -980,12 +1015,12 @@ which incur interpreter overhead. >>> ''.join(remainder) # takewhile() would lose the 'd' 'dEfGhI' - Note that the first iterator must be fully - consumed before the second iterator can - generate valid results. + Note that the true iterator must be fully consumed + before the remainder iterator can generate valid results. """ it = iter(it) transition = [] + def true_iterator(): for elem in it: if predicate(elem): @@ -993,41 +1028,8 @@ which incur interpreter overhead. else: transition.append(elem) return - def remainder_iterator(): - yield from transition - yield from it - return true_iterator(), remainder_iterator() - def unique_everseen(iterable, key=None): - "List unique elements, preserving order. Remember all elements ever seen." - # unique_everseen('AAAABBBCCDAABBB') --> A B C D - # unique_everseen('ABBcCAD', str.lower) --> A B c D - seen = set() - if key is None: - for element in filterfalse(seen.__contains__, iterable): - seen.add(element) - yield element - # For order preserving deduplication, - # a faster but non-lazy solution is: - # yield from dict.fromkeys(iterable) - else: - for element in iterable: - k = key(element) - if k not in seen: - seen.add(k) - yield element - # For use cases that allow the last matching element to be returned, - # a faster but non-lazy solution is: - # t1, t2 = tee(iterable) - # yield from dict(zip(map(key, t1), t2)).values() - - def unique_justseen(iterable, key=None): - "List unique elements, preserving order. Remember only the element just seen." - # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B - # unique_justseen('ABBcCAD', str.lower) --> A B c A D - if key is None: - return map(operator.itemgetter(0), groupby(iterable)) - return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + return true_iterator(), chain(transition, it) The following recipes have a more mathematical flavor: @@ -1562,16 +1564,16 @@ The following recipes have a more mathematical flavor: >>> list(unique_everseen('AAAABBBCCDAABBB')) ['A', 'B', 'C', 'D'] - >>> list(unique_everseen('ABBCcAD', str.lower)) + >>> list(unique_everseen('ABBCcAD', str.casefold)) ['A', 'B', 'C', 'D'] - >>> list(unique_everseen('ABBcCAD', str.lower)) + >>> list(unique_everseen('ABBcCAD', str.casefold)) ['A', 'B', 'c', 'D'] >>> list(unique_justseen('AAAABBBCCDAABBB')) ['A', 'B', 'C', 'D', 'A', 'B'] - >>> list(unique_justseen('ABBCcAD', str.lower)) + >>> list(unique_justseen('ABBCcAD', str.casefold)) ['A', 'B', 'C', 'A', 'D'] - >>> list(unique_justseen('ABBcCAD', str.lower)) + >>> list(unique_justseen('ABBcCAD', str.casefold)) ['A', 'B', 'c', 'A', 'D'] >>> d = dict(a=1, b=2, c=3) From e87cadc1ce194aae2c076e81298d6e8074f1bb45 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 26 Dec 2023 11:15:14 +0200 Subject: [PATCH 371/442] gh-66515: mailbox.MH now supports folders withou the ".mh_sequences" file (GH-804) (for example Claws Mail IMAP-cache folders). --- Doc/library/mailbox.rst | 4 ++++ Lib/mailbox.py | 9 ++++++--- Lib/test/test_mailbox.py | 13 +++++++++++++ .../2023-04-09-21-05-43.gh-issue-66515.0DS8Ya.rst | 3 +++ 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-09-21-05-43.gh-issue-66515.0DS8Ya.rst diff --git a/Doc/library/mailbox.rst b/Doc/library/mailbox.rst index 3ffd0981a4a4f2..fa5b273093f583 100644 --- a/Doc/library/mailbox.rst +++ b/Doc/library/mailbox.rst @@ -644,6 +644,10 @@ Supported mailbox formats are Maildir, mbox, MH, Babyl, and MMDF. :class:`!MH` instances have all of the methods of :class:`Mailbox` in addition to the following: + .. versionchanged:: 3.13 + + Supported folders that don't contain a :file:`.mh_sequences` file. + .. method:: list_folders() diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 574c01475dece6..0e1d49b399d077 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -1198,7 +1198,11 @@ def remove_folder(self, folder): def get_sequences(self): """Return a name-to-key-list dictionary to define each sequence.""" results = {} - with open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII') as f: + try: + f = open(os.path.join(self._path, '.mh_sequences'), 'r', encoding='ASCII') + except FileNotFoundError: + return results + with f: all_keys = set(self.keys()) for line in f: try: @@ -1221,9 +1225,8 @@ def get_sequences(self): def set_sequences(self, sequences): """Set sequences using the given name-to-key-list dictionary.""" - f = open(os.path.join(self._path, '.mh_sequences'), 'r+', encoding='ASCII') + f = open(os.path.join(self._path, '.mh_sequences'), 'w', encoding='ASCII') try: - os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) for name, keys in sequences.items(): if len(keys) == 0: continue diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index caa7eb3d829c68..8c350eb02ccc17 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -1347,6 +1347,19 @@ def test_sequences(self): self._box.remove(key1) self.assertEqual(self._box.get_sequences(), {'flagged':[key0]}) + self._box.set_sequences({'foo':[key0]}) + self.assertEqual(self._box.get_sequences(), {'foo':[key0]}) + + def test_no_dot_mh_sequences_file(self): + path = os.path.join(self._path, 'foo.bar') + os.mkdir(path) + box = self._factory(path) + self.assertEqual(os.listdir(path), []) + self.assertEqual(box.get_sequences(), {}) + self.assertEqual(os.listdir(path), []) + box.set_sequences({}) + self.assertEqual(os.listdir(path), ['.mh_sequences']) + def test_issue2625(self): msg0 = mailbox.MHMessage(self._template % 0) msg0.add_sequence('foo') diff --git a/Misc/NEWS.d/next/Library/2023-04-09-21-05-43.gh-issue-66515.0DS8Ya.rst b/Misc/NEWS.d/next/Library/2023-04-09-21-05-43.gh-issue-66515.0DS8Ya.rst new file mode 100644 index 00000000000000..b9c52f3b8db52c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-09-21-05-43.gh-issue-66515.0DS8Ya.rst @@ -0,0 +1,3 @@ +:class:`mailbox.MH` now supports folders that do not contain a +``.mh_sequences`` file (e.g. Claws Mail IMAP-cache folders). Patch by Serhiy +Storchaka. From 8a3d0e4a661e6c27e4c17c818ce4187a36579e5f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 26 Dec 2023 12:54:05 +0200 Subject: [PATCH 372/442] gh-113468: Remove the "_new_ suffix from class names in pydocfodder (GH-113469) --- Lib/test/pydocfodder.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/pydocfodder.py b/Lib/test/pydocfodder.py index d0750e5a43c0c0..a3ef2231243954 100644 --- a/Lib/test/pydocfodder.py +++ b/Lib/test/pydocfodder.py @@ -2,8 +2,8 @@ import types -class A_new: - "A new-style class." +class A: + "A class." def A_method(self): "Method defined in A." @@ -41,8 +41,8 @@ def _delx(self): A_int_alias = int -class B_new(A_new): - "A new-style class, derived from A_new." +class B(A): + "A class, derived from A." def AB_method(self): "Method defined in A and B." @@ -61,8 +61,8 @@ def BD_method(self): def BCD_method(self): "Method defined in B, C and D." -class C_new(A_new): - "A new-style class, derived from A_new." +class C(A): + "A class, derived from A." def AC_method(self): "Method defined in A and C." @@ -81,8 +81,8 @@ def C_method(self): def CD_method(self): "Method defined in C and D." -class D_new(B_new, C_new): - """A new-style class, derived from B_new and C_new. +class D(B, C): + """A class, derived from B and C. """ def AD_method(self): From 36adc79041f4d2764e1daf7db5bb478923e89a1f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 26 Dec 2023 14:02:13 +0200 Subject: [PATCH 373/442] Docs: make htmllive: open browser when ready (#113288) --- Doc/Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Doc/Makefile b/Doc/Makefile index 7af56e965e1be4..38fd60f2ae01d1 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -19,8 +19,12 @@ SPHINXERRORHANDLING = -W PAPEROPT_a4 = -D latex_elements.papersize=a4paper PAPEROPT_letter = -D latex_elements.papersize=letterpaper -ALLSPHINXOPTS = -b $(BUILDER) -d build/doctrees $(PAPEROPT_$(PAPER)) -j $(JOBS) \ - $(SPHINXOPTS) $(SPHINXERRORHANDLING) . build/$(BUILDER) $(SOURCES) +ALLSPHINXOPTS = -b $(BUILDER) \ + -d build/doctrees \ + -j $(JOBS) \ + $(PAPEROPT_$(PAPER)) \ + $(SPHINXOPTS) $(SPHINXERRORHANDLING) \ + . build/$(BUILDER) $(SOURCES) .PHONY: help help: @@ -142,7 +146,7 @@ htmlview: html .PHONY: htmllive htmllive: SPHINXBUILD = $(VENVDIR)/bin/sphinx-autobuild -htmllive: SPHINXOPTS = --re-ignore="/venv/" +htmllive: SPHINXOPTS = --re-ignore="/venv/" --open-browser --delay 0 htmllive: html .PHONY: clean From 8f5b9987066f46daa67b622d913ff2c51c949ed4 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 26 Dec 2023 16:48:33 +0000 Subject: [PATCH 374/442] gh-111971: Make _PyUnicode_FromId thread-safe in --disable-gil (gh-113489) --- Include/cpython/object.h | 4 ++++ Objects/unicodeobject.c | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 762e8a3b86ee1e..d6482f4bc689a1 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -39,6 +39,10 @@ typedef struct _Py_Identifier { // Index in PyInterpreterState.unicode.ids.array. It is process-wide // unique and must be initialized to -1. Py_ssize_t index; + // Hidden PyMutex struct for non free-threaded build. + struct { + uint8_t v; + } mutex; } _Py_Identifier; #ifndef Py_BUILD_CORE diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index ad87206b2a8200..4b03cc3f4da5fa 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1897,6 +1897,7 @@ PyUnicode_FromString(const char *u) PyObject * _PyUnicode_FromId(_Py_Identifier *id) { + PyMutex_Lock((PyMutex *)&id->mutex); PyInterpreterState *interp = _PyInterpreterState_GET(); struct _Py_unicode_ids *ids = &interp->unicode.ids; @@ -1923,14 +1924,14 @@ _PyUnicode_FromId(_Py_Identifier *id) obj = ids->array[index]; if (obj) { // Return a borrowed reference - return obj; + goto end; } } obj = PyUnicode_DecodeUTF8Stateful(id->string, strlen(id->string), NULL, NULL); if (!obj) { - return NULL; + goto end; } PyUnicode_InternInPlace(&obj); @@ -1941,7 +1942,8 @@ _PyUnicode_FromId(_Py_Identifier *id) PyObject **new_array = PyMem_Realloc(ids->array, new_size * item_size); if (new_array == NULL) { PyErr_NoMemory(); - return NULL; + obj = NULL; + goto end; } memset(&new_array[ids->size], 0, (new_size - ids->size) * item_size); ids->array = new_array; @@ -1951,6 +1953,8 @@ _PyUnicode_FromId(_Py_Identifier *id) // The array stores a strong reference ids->array[index] = obj; +end: + PyMutex_Unlock((PyMutex *)&id->mutex); // Return a borrowed reference return obj; } From acf3bcc8861983dcd6896682283a480450f9a1e3 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 26 Dec 2023 11:53:20 -0500 Subject: [PATCH 375/442] gh-112532: Use separate mimalloc heaps for GC objects (gh-113263) * gh-112532: Use separate mimalloc heaps for GC objects In `--disable-gil` builds, we now use four separate heaps in anticipation of using mimalloc to find GC objects when the GIL is disabled. To support this, we also make a few changes to mimalloc: * `mi_heap_t` and `mi_tld_t` initialization is split from allocation. This allows us to have a `mi_tld_t` per-`PyThreadState`, which is important to keep interpreter isolation, since the same OS thread may run in multiple interpreters (using different PyThreadStates.) * Heap abandoning (mi_heap_collect_ex) can now be called from a different thread than the one that created the heap. This is necessary because we may clear and delete the containing PyThreadStates from a different thread during finalization and after fork(). * Use enum instead of defines and guard mimalloc includes. * The enum typedef will be convenient for future PRs that use the type. * Guarding the mimalloc includes allows us to unconditionally include pycore_mimalloc.h from other header files that rely on things like `struct _mimalloc_thread_state`. * Only define _mimalloc_thread_state in Py_GIL_DISABLED builds --- Include/internal/mimalloc/mimalloc/internal.h | 2 + Include/internal/pycore_mimalloc.h | 26 +++++++++ Include/internal/pycore_pystate.h | 1 + Include/internal/pycore_tstate.h | 7 ++- Objects/mimalloc/heap.c | 29 +++++++--- Objects/mimalloc/init.c | 24 ++++---- Objects/obmalloc.c | 36 ++++++++++++ Python/pylifecycle.c | 4 ++ Python/pystate.c | 55 +++++++++++++++++++ 9 files changed, 161 insertions(+), 23 deletions(-) diff --git a/Include/internal/mimalloc/mimalloc/internal.h b/Include/internal/mimalloc/mimalloc/internal.h index f076bc6a40f977..cb6e211de5bb63 100644 --- a/Include/internal/mimalloc/mimalloc/internal.h +++ b/Include/internal/mimalloc/mimalloc/internal.h @@ -85,6 +85,7 @@ mi_threadid_t _mi_thread_id(void) mi_attr_noexcept; mi_heap_t* _mi_heap_main_get(void); // statically allocated main backing heap void _mi_thread_done(mi_heap_t* heap); void _mi_thread_data_collect(void); +void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap); // os.c void _mi_os_init(void); // called from process init @@ -170,6 +171,7 @@ size_t _mi_bin_size(uint8_t bin); // for stats uint8_t _mi_bin(size_t size); // for stats // "heap.c" +void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id); void _mi_heap_destroy_pages(mi_heap_t* heap); void _mi_heap_collect_abandon(mi_heap_t* heap); void _mi_heap_set_default_direct(mi_heap_t* heap); diff --git a/Include/internal/pycore_mimalloc.h b/Include/internal/pycore_mimalloc.h index c29dc82a42762a..adebb559dae658 100644 --- a/Include/internal/pycore_mimalloc.h +++ b/Include/internal/pycore_mimalloc.h @@ -9,11 +9,37 @@ # error "pycore_mimalloc.h must be included before mimalloc.h" #endif +typedef enum { + _Py_MIMALLOC_HEAP_MEM = 0, // PyMem_Malloc() and friends + _Py_MIMALLOC_HEAP_OBJECT = 1, // non-GC objects + _Py_MIMALLOC_HEAP_GC = 2, // GC objects without pre-header + _Py_MIMALLOC_HEAP_GC_PRE = 3, // GC objects with pre-header + _Py_MIMALLOC_HEAP_COUNT +} _Py_mimalloc_heap_id; + #include "pycore_pymem.h" + +#ifdef WITH_MIMALLOC #define MI_DEBUG_UNINIT PYMEM_CLEANBYTE #define MI_DEBUG_FREED PYMEM_DEADBYTE #define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE +#ifdef Py_DEBUG +# define MI_DEBUG 1 +#else +# define MI_DEBUG 0 +#endif #include "mimalloc.h" +#include "mimalloc/types.h" +#include "mimalloc/internal.h" +#endif + +#ifdef Py_GIL_DISABLED +struct _mimalloc_thread_state { + mi_heap_t *current_object_heap; + mi_heap_t heaps[_Py_MIMALLOC_HEAP_COUNT]; + mi_tld_t tld; +}; +#endif #endif // Py_INTERNAL_MIMALLOC_H diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index c031a38cd6bfa3..37b45faf8a74a0 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -187,6 +187,7 @@ extern PyThreadState * _PyThreadState_New( int whence); extern void _PyThreadState_Bind(PyThreadState *tstate); extern void _PyThreadState_DeleteExcept(PyThreadState *tstate); +extern void _PyThreadState_ClearMimallocHeaps(PyThreadState *tstate); // Export for '_testinternalcapi' shared extension PyAPI_FUNC(PyObject*) _PyThreadState_GetDict(PyThreadState *tstate); diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 17f3e865641773..856ddd5e7e5ff0 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -8,6 +8,8 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_mimalloc.h" // struct _mimalloc_thread_state + // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The // PyThreadState fields are exposed as part of the C API, although most fields @@ -16,7 +18,10 @@ typedef struct _PyThreadStateImpl { // semi-public fields are in PyThreadState. PyThreadState base; - // TODO: add private fields here +#ifdef Py_GIL_DISABLED + struct _mimalloc_thread_state mimalloc; +#endif + } _PyThreadStateImpl; diff --git a/Objects/mimalloc/heap.c b/Objects/mimalloc/heap.c index 4eb622ed4bad76..c50e3b05590b6f 100644 --- a/Objects/mimalloc/heap.c +++ b/Objects/mimalloc/heap.c @@ -123,6 +123,9 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) const bool force = collect >= MI_FORCE; _mi_deferred_free(heap, force); + // gh-112532: we may be called from a thread that is not the owner of the heap + bool is_main_thread = _mi_is_main_thread() && heap->thread_id == _mi_thread_id(); + // note: never reclaim on collect but leave it to threads that need storage to reclaim const bool force_main = #ifdef NDEBUG @@ -130,7 +133,7 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) #else collect >= MI_FORCE #endif - && _mi_is_main_thread() && mi_heap_is_backing(heap) && !heap->no_reclaim; + && is_main_thread && mi_heap_is_backing(heap) && !heap->no_reclaim; if (force_main) { // the main thread is abandoned (end-of-program), try to reclaim all abandoned segments. @@ -164,7 +167,7 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) } // collect regions on program-exit (or shared library unload) - if (force && _mi_is_main_thread() && mi_heap_is_backing(heap)) { + if (force && is_main_thread && mi_heap_is_backing(heap)) { _mi_thread_data_collect(); // collect thread data cache _mi_arena_collect(true /* force purge */, &heap->tld->stats); } @@ -206,18 +209,28 @@ mi_heap_t* mi_heap_get_backing(void) { return bheap; } -mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) { - mi_heap_t* bheap = mi_heap_get_backing(); - mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode? - if (heap == NULL) return NULL; +void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id) +{ _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t)); - heap->tld = bheap->tld; + heap->tld = tld; heap->thread_id = _mi_thread_id(); heap->arena_id = arena_id; - _mi_random_split(&bheap->random, &heap->random); + if (heap == tld->heap_backing) { + _mi_random_init(&heap->random); + } + else { + _mi_random_split(&tld->heap_backing->random, &heap->random); + } heap->cookie = _mi_heap_random_next(heap) | 1; heap->keys[0] = _mi_heap_random_next(heap); heap->keys[1] = _mi_heap_random_next(heap); +} + +mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) { + mi_heap_t* bheap = mi_heap_get_backing(); + mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode? + if (heap == NULL) return NULL; + _mi_heap_init_ex(heap, bheap->tld, arena_id); heap->no_reclaim = true; // don't reclaim abandoned pages or otherwise destroy is unsafe // push on the thread local heaps list heap->next = heap->tld->heaps; diff --git a/Objects/mimalloc/init.c b/Objects/mimalloc/init.c index 7dfa7657737117..376e14b49b7c7b 100644 --- a/Objects/mimalloc/init.c +++ b/Objects/mimalloc/init.c @@ -297,24 +297,20 @@ static bool _mi_heap_init(void) { mi_thread_data_t* td = mi_thread_data_zalloc(); if (td == NULL) return false; - mi_tld_t* tld = &td->tld; - mi_heap_t* heap = &td->heap; + _mi_tld_init(&td->tld, &td->heap); + _mi_heap_init_ex(&td->heap, &td->tld, _mi_arena_id_none()); + _mi_heap_set_default_direct(&td->heap); + } + return false; +} + +void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) { _mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld)); - _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(*heap)); - heap->thread_id = _mi_thread_id(); - _mi_random_init(&heap->random); - heap->cookie = _mi_heap_random_next(heap) | 1; - heap->keys[0] = _mi_heap_random_next(heap); - heap->keys[1] = _mi_heap_random_next(heap); - heap->tld = tld; - tld->heap_backing = heap; - tld->heaps = heap; tld->segments.stats = &tld->stats; tld->segments.os = &tld->os; tld->os.stats = &tld->stats; - _mi_heap_set_default_direct(heap); - } - return false; + tld->heap_backing = bheap; + tld->heaps = bheap; } // Free the thread local default heap (called from `mi_thread_done`) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 99c95d90658b08..883adcb1c19b6e 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -88,19 +88,37 @@ _PyMem_RawFree(void *Py_UNUSED(ctx), void *ptr) void * _PyMem_MiMalloc(void *ctx, size_t size) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM]; + return mi_heap_malloc(heap, size); +#else return mi_malloc(size); +#endif } void * _PyMem_MiCalloc(void *ctx, size_t nelem, size_t elsize) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM]; + return mi_heap_calloc(heap, nelem, elsize); +#else return mi_calloc(nelem, elsize); +#endif } void * _PyMem_MiRealloc(void *ctx, void *ptr, size_t size) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = &tstate->mimalloc.heaps[_Py_MIMALLOC_HEAP_MEM]; + return mi_heap_realloc(heap, ptr, size); +#else return mi_realloc(ptr, size); +#endif } void @@ -112,20 +130,38 @@ _PyMem_MiFree(void *ctx, void *ptr) void * _PyObject_MiMalloc(void *ctx, size_t nbytes) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = tstate->mimalloc.current_object_heap; + return mi_heap_malloc(heap, nbytes); +#else return mi_malloc(nbytes); +#endif } void * _PyObject_MiCalloc(void *ctx, size_t nelem, size_t elsize) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = tstate->mimalloc.current_object_heap; + return mi_heap_calloc(heap, nelem, elsize); +#else return mi_calloc(nelem, elsize); +#endif } void * _PyObject_MiRealloc(void *ctx, void *ptr, size_t nbytes) { +#ifdef Py_GIL_DISABLED + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + mi_heap_t *heap = tstate->mimalloc.current_object_heap; + return mi_heap_realloc(heap, ptr, nbytes); +#else return mi_realloc(ptr, nbytes); +#endif } void diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 0ec29846b0850b..1d8af26e4a1cb7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1794,6 +1794,10 @@ finalize_interp_clear(PyThreadState *tstate) } finalize_interp_types(tstate->interp); + + /* finalize_interp_types may allocate Python objects so we may need to + abandon mimalloc segments again */ + _PyThreadState_ClearMimallocHeaps(tstate); } diff --git a/Python/pystate.c b/Python/pystate.c index 632a119ea6d4f8..84e2d6ea172f2b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -236,6 +236,8 @@ tstate_is_bound(PyThreadState *tstate) static void bind_gilstate_tstate(PyThreadState *); static void unbind_gilstate_tstate(PyThreadState *); +static void tstate_mimalloc_bind(PyThreadState *); + static void bind_tstate(PyThreadState *tstate) { @@ -256,6 +258,9 @@ bind_tstate(PyThreadState *tstate) tstate->native_thread_id = PyThread_get_thread_native_id(); #endif + // mimalloc state needs to be initialized from the active thread. + tstate_mimalloc_bind(tstate); + tstate->_status.bound = 1; } @@ -1533,6 +1538,8 @@ PyThreadState_Clear(PyThreadState *tstate) tstate->on_delete(tstate->on_delete_data); } + _PyThreadState_ClearMimallocHeaps(tstate); + tstate->_status.cleared = 1; // XXX Call _PyThreadStateSwap(runtime, NULL) here if "current". @@ -2509,3 +2516,51 @@ _PyThreadState_MustExit(PyThreadState *tstate) } return 1; } + +/********************/ +/* mimalloc support */ +/********************/ + +static void +tstate_mimalloc_bind(PyThreadState *tstate) +{ +#ifdef Py_GIL_DISABLED + struct _mimalloc_thread_state *mts = &((_PyThreadStateImpl*)tstate)->mimalloc; + + // Initialize the mimalloc thread state. This must be called from the + // same thread that will use the thread state. The "mem" heap doubles as + // the "backing" heap. + mi_tld_t *tld = &mts->tld; + _mi_tld_init(tld, &mts->heaps[_Py_MIMALLOC_HEAP_MEM]); + + // Initialize each heap + for (Py_ssize_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) { + _mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none()); + } + + // By default, object allocations use _Py_MIMALLOC_HEAP_OBJECT. + // _PyObject_GC_New() and similar functions temporarily override this to + // use one of the GC heaps. + mts->current_object_heap = &mts->heaps[_Py_MIMALLOC_HEAP_OBJECT]; +#endif +} + +void +_PyThreadState_ClearMimallocHeaps(PyThreadState *tstate) +{ +#ifdef Py_GIL_DISABLED + if (!tstate->_status.bound) { + // The mimalloc heaps are only initialized when the thread is bound. + return; + } + + _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; + for (Py_ssize_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) { + // Abandon all segments in use by this thread. This pushes them to + // a shared pool to later be reclaimed by other threads. It's important + // to do this before the thread state is destroyed so that objects + // remain visible to the GC. + _mi_heap_collect_abandon(&tstate_impl->mimalloc.heaps[i]); + } +#endif +} From e5cce70df71b5d89c045d4712203f54198188b65 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 26 Dec 2023 17:54:16 +0000 Subject: [PATCH 376/442] gh-112532: Fix peg generator build.py for mimalloc build (gh-113492) gh-112532: Fix peg generator for mimalloc build --- Tools/peg_generator/pegen/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/peg_generator/pegen/build.py b/Tools/peg_generator/pegen/build.py index 7df39a3b0ae48e..00295c984d1bb6 100644 --- a/Tools/peg_generator/pegen/build.py +++ b/Tools/peg_generator/pegen/build.py @@ -139,6 +139,7 @@ def compile_c_extension( ] include_dirs = [ str(MOD_DIR.parent.parent.parent / "Include" / "internal"), + str(MOD_DIR.parent.parent.parent / "Include" / "internal" / "mimalloc"), str(MOD_DIR.parent.parent.parent / "Parser"), str(MOD_DIR.parent.parent.parent / "Parser" / "lexer"), str(MOD_DIR.parent.parent.parent / "Parser" / "tokenizer"), From 4b2c3e8e436b5191039cbe8cd9932654a60803e6 Mon Sep 17 00:00:00 2001 From: "Gordon P. Hemsley" Date: Tue, 26 Dec 2023 14:26:17 -0500 Subject: [PATCH 377/442] bpo-36959: Fix error messages for invalid ISO format string in _strptime() (GH-13408) Previously some error messages complained about incompatible combinations of directives that are not contained in the format string. Co-authored-by: Serhiy Storchaka --- Lib/_strptime.py | 27 ++++--- Lib/test/test_strptime.py | 72 ++++++++++++------- .../2019-05-18-15-50-14.bpo-36959.ew6WZ4.rst | 2 + 3 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-05-18-15-50-14.bpo-36959.ew6WZ4.rst diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 77ccdc9e1d789b..798cf9f9d3fffe 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -342,8 +342,6 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): tz = -1 gmtoff = None gmtoff_fraction = 0 - # Default to -1 to signify that values not known; not critical to have, - # though iso_week = week_of_year = None week_of_year_start = None # weekday and julian defaulted to None so as to signal need to calculate @@ -470,17 +468,17 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): # Deal with the cases where ambiguities arise # don't assume default values for ISO week/year - if year is None and iso_year is not None: - if iso_week is None or weekday is None: - raise ValueError("ISO year directive '%G' must be used with " - "the ISO week directive '%V' and a weekday " - "directive ('%A', '%a', '%w', or '%u').") + if iso_year is not None: if julian is not None: raise ValueError("Day of the year directive '%j' is not " "compatible with ISO year directive '%G'. " "Use '%Y' instead.") - elif week_of_year is None and iso_week is not None: - if weekday is None: + elif iso_week is None or weekday is None: + raise ValueError("ISO year directive '%G' must be used with " + "the ISO week directive '%V' and a weekday " + "directive ('%A', '%a', '%w', or '%u').") + elif iso_week is not None: + if year is None or weekday is None: raise ValueError("ISO week directive '%V' must be used with " "the ISO year directive '%G' and a weekday " "directive ('%A', '%a', '%w', or '%u').") @@ -490,11 +488,12 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): "instead.") leap_year_fix = False - if year is None and month == 2 and day == 29: - year = 1904 # 1904 is first leap year of 20th century - leap_year_fix = True - elif year is None: - year = 1900 + if year is None: + if month == 2 and day == 29: + year = 1904 # 1904 is first leap year of 20th century + leap_year_fix = True + else: + year = 1900 # If we know the week of the year and what day of that week, we can figure # out the Julian day of the year. diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 810c5a36e02f41..05c8afc907ad3c 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -224,35 +224,55 @@ def test_ValueError(self): else: self.fail("'%s' did not raise ValueError" % bad_format) + msg_week_no_year_or_weekday = r"ISO week directive '%V' must be used with " \ + r"the ISO year directive '%G' and a weekday directive " \ + r"\('%A', '%a', '%w', or '%u'\)." + msg_week_not_compatible = r"ISO week directive '%V' is incompatible with " \ + r"the year directive '%Y'. Use the ISO year '%G' instead." + msg_julian_not_compatible = r"Day of the year directive '%j' is not " \ + r"compatible with ISO year directive '%G'. Use '%Y' instead." + msg_year_no_week_or_weekday = r"ISO year directive '%G' must be used with " \ + r"the ISO week directive '%V' and a weekday directive " \ + r"\('%A', '%a', '%w', or '%u'\)." + + locale_time = _strptime.LocaleTime() + # Ambiguous or incomplete cases using ISO year/week/weekday directives - # 1. ISO week (%V) is specified, but the year is specified with %Y - # instead of %G - with self.assertRaises(ValueError): - _strptime._strptime("1999 50", "%Y %V") - # 2. ISO year (%G) and ISO week (%V) are specified, but weekday is not - with self.assertRaises(ValueError): - _strptime._strptime("1999 51", "%G %V") - # 3. ISO year (%G) and weekday are specified, but ISO week (%V) is not - for w in ('A', 'a', 'w', 'u'): - with self.assertRaises(ValueError): - _strptime._strptime("1999 51","%G %{}".format(w)) - # 4. ISO year is specified alone (e.g. time.strptime('2015', '%G')) - with self.assertRaises(ValueError): - _strptime._strptime("2015", "%G") - # 5. Julian/ordinal day (%j) is specified with %G, but not %Y - with self.assertRaises(ValueError): - _strptime._strptime("1999 256", "%G %j") - # 6. Invalid ISO weeks - invalid_iso_weeks = [ - "2019-00-1", - "2019-54-1", - "2021-53-1", + subtests = [ + # 1. ISO week (%V) is specified, but the year is specified with %Y + # instead of %G + ("1999 50", "%Y %V", msg_week_no_year_or_weekday), + ("1999 50 5", "%Y %V %u", msg_week_not_compatible), + # 2. ISO year (%G) and ISO week (%V) are specified, but weekday is not + ("1999 51", "%G %V", msg_year_no_week_or_weekday), + # 3. ISO year (%G) and weekday are specified, but ISO week (%V) is not + ("1999 {}".format(locale_time.f_weekday[5]), "%G %A", + msg_year_no_week_or_weekday), + ("1999 {}".format(locale_time.a_weekday[5]), "%G %a", + msg_year_no_week_or_weekday), + ("1999 5", "%G %w", msg_year_no_week_or_weekday), + ("1999 5", "%G %u", msg_year_no_week_or_weekday), + # 4. ISO year is specified alone (e.g. time.strptime('2015', '%G')) + ("2015", "%G", msg_year_no_week_or_weekday), + # 5. Julian/ordinal day (%j) is specified with %G, but not %Y + ("1999 256", "%G %j", msg_julian_not_compatible), + ("1999 50 5 256", "%G %V %u %j", msg_julian_not_compatible), + # ISO week specified alone + ("50", "%V", msg_week_no_year_or_weekday), + # ISO year is unspecified, falling back to year + ("50 5", "%V %u", msg_week_no_year_or_weekday), + # 6. Invalid ISO weeks + ("2019-00-1", "%G-%V-%u", + "time data '2019-00-1' does not match format '%G-%V-%u'"), + ("2019-54-1", "%G-%V-%u", + "time data '2019-54-1' does not match format '%G-%V-%u'"), + ("2021-53-1", "%G-%V-%u", "Invalid week: 53"), ] - for invalid_iso_dtstr in invalid_iso_weeks: - with self.subTest(invalid_iso_dtstr): - with self.assertRaises(ValueError): - _strptime._strptime(invalid_iso_dtstr, "%G-%V-%u") + for (data_string, format, message) in subtests: + with self.subTest(data_string=data_string, format=format): + with self.assertRaisesRegex(ValueError, message): + _strptime._strptime(data_string, format) def test_strptime_exception_context(self): # check that this doesn't chain exceptions needlessly (see #17572) diff --git a/Misc/NEWS.d/next/Library/2019-05-18-15-50-14.bpo-36959.ew6WZ4.rst b/Misc/NEWS.d/next/Library/2019-05-18-15-50-14.bpo-36959.ew6WZ4.rst new file mode 100644 index 00000000000000..1ac05a730a2086 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-18-15-50-14.bpo-36959.ew6WZ4.rst @@ -0,0 +1,2 @@ +Fix some error messages for invalid ISO format string combinations in ``strptime()`` that referred to directives not contained in the format string. +Patch by Gordon P. Hemsley. From 4e67644d367673bbacc40a0d8efb777410437b93 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 26 Dec 2023 21:40:48 +0200 Subject: [PATCH 378/442] gh-101100: Fix Sphinx warnings in `howto/isolating-extensions.rst` (#113493) --- Doc/howto/isolating-extensions.rst | 4 ++-- Doc/tools/.nitignore | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/howto/isolating-extensions.rst b/Doc/howto/isolating-extensions.rst index 835c0afad7c6c8..e35855deedbe5f 100644 --- a/Doc/howto/isolating-extensions.rst +++ b/Doc/howto/isolating-extensions.rst @@ -337,7 +337,7 @@ That is, heap types should: - Have the :c:macro:`Py_TPFLAGS_HAVE_GC` flag. - Define a traverse function using ``Py_tp_traverse``, which - visits the type (e.g. using :c:expr:`Py_VISIT(Py_TYPE(self))`). + visits the type (e.g. using ``Py_VISIT(Py_TYPE(self))``). Please refer to the the documentation of :c:macro:`Py_TPFLAGS_HAVE_GC` and :c:member:`~PyTypeObject.tp_traverse` @@ -482,7 +482,7 @@ The largest roadblock is getting *the class a method was defined in*, or that method's "defining class" for short. The defining class can have a reference to the module it is part of. -Do not confuse the defining class with :c:expr:`Py_TYPE(self)`. If the method +Do not confuse the defining class with ``Py_TYPE(self)``. If the method is called on a *subclass* of your type, ``Py_TYPE(self)`` will refer to that subclass, which may be defined in different module than yours. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8b0fc13832d51a..4eb007577e1aaa 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -23,7 +23,6 @@ Doc/extending/extending.rst Doc/glossary.rst Doc/howto/descriptor.rst Doc/howto/enum.rst -Doc/howto/isolating-extensions.rst Doc/howto/logging.rst Doc/howto/urllib2.rst Doc/library/ast.rst From 2b53c767de0a7afd29598a87da084d0e125e1c34 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 26 Dec 2023 23:12:15 +0200 Subject: [PATCH 379/442] gh-101100: Fix Sphinx warnings in `library/bisect.rst` (#113496) --- Doc/library/bisect.rst | 8 ++++---- Doc/tools/.nitignore | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 8022c596f0af97..c0923093c1cb06 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -19,9 +19,9 @@ linear searches or frequent resorting. The module is called :mod:`bisect` because it uses a basic bisection algorithm to do its work. Unlike other bisection tools that search for a specific value, the functions in this module are designed to locate an -insertion point. Accordingly, the functions never call an :meth:`__eq__` +insertion point. Accordingly, the functions never call an :meth:`~object.__eq__` method to determine whether a value has been found. Instead, the -functions only call the :meth:`__lt__` method and will return an insertion +functions only call the :meth:`~object.__lt__` method and will return an insertion point between values in an array. .. _bisect functions: @@ -73,7 +73,7 @@ The following functions are provided: Insert *x* in *a* in sorted order. This function first runs :py:func:`~bisect.bisect_left` to locate an insertion point. - Next, it runs the :meth:`insert` method on *a* to insert *x* at the + Next, it runs the :meth:`!insert` method on *a* to insert *x* at the appropriate position to maintain sort order. To support inserting records in a table, the *key* function (if any) is @@ -93,7 +93,7 @@ The following functions are provided: entries of *x*. This function first runs :py:func:`~bisect.bisect_right` to locate an insertion point. - Next, it runs the :meth:`insert` method on *a* to insert *x* at the + Next, it runs the :meth:`!insert` method on *a* to insert *x* at the appropriate position to maintain sort order. To support inserting records in a table, the *key* function (if any) is diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 4eb007577e1aaa..f2ffe85917c659 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -31,7 +31,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/asyncio-task.rst Doc/library/bdb.rst -Doc/library/bisect.rst Doc/library/calendar.rst Doc/library/cmd.rst Doc/library/collections.rst From af2b8f6845e31dd6ab3bb0bac41b19a0e023fd61 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Tue, 26 Dec 2023 16:35:41 -0500 Subject: [PATCH 380/442] gh-113332: Simplify calls to SSL_(CTX_)set_verify in _ssl.c (#113333) _ssl.c currently tries to preserve the verification callback, but at no point does it ever set one. Just pass in NULL. --- Modules/_ssl.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 90b600f4b776a3..04c9f7daadf573 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -893,10 +893,8 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, * only in combination with SSL_VERIFY_PEER flag. */ int mode = SSL_get_verify_mode(self->ssl); if (mode & SSL_VERIFY_PEER) { - int (*verify_cb)(int, X509_STORE_CTX *) = NULL; - verify_cb = SSL_get_verify_callback(self->ssl); mode |= SSL_VERIFY_POST_HANDSHAKE; - SSL_set_verify(self->ssl, mode, verify_cb); + SSL_set_verify(self->ssl, mode, NULL); } } else { /* client socket */ @@ -2997,7 +2995,6 @@ static int _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n) { int mode; - int (*verify_cb)(int, X509_STORE_CTX *) = NULL; switch(n) { case PY_SSL_CERT_NONE: @@ -3018,9 +3015,7 @@ _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n) /* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for * server sockets and SSL_set_post_handshake_auth() for client. */ - /* keep current verify cb */ - verify_cb = SSL_CTX_get_verify_callback(self->ctx); - SSL_CTX_set_verify(self->ctx, mode, verify_cb); + SSL_CTX_set_verify(self->ctx, mode, NULL); return 0; } From 00cdd416fc60876ff21d9eafdc5d5d7a91737db5 Mon Sep 17 00:00:00 2001 From: Vaishnavi Maheshwari Date: Wed, 27 Dec 2023 13:21:45 +0530 Subject: [PATCH 381/442] gh-113350: Improve the wording of python logging docs to remove an ambiguity around use of the word "higher". (GH-113491) Co-authored-by: Wei-Hsiang (Matt) Wang --- Doc/howto/logging-cookbook.rst | 8 ++++---- Doc/howto/logging.rst | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Doc/howto/logging-cookbook.rst b/Doc/howto/logging-cookbook.rst index 588f5a0a53ded0..ea494f2fdbbce4 100644 --- a/Doc/howto/logging-cookbook.rst +++ b/Doc/howto/logging-cookbook.rst @@ -332,10 +332,10 @@ Suppose you configure logging with the following JSON: } } -This configuration does *almost* what we want, except that ``sys.stdout`` would -show messages of severity ``ERROR`` and above as well as ``INFO`` and -``WARNING`` messages. To prevent this, we can set up a filter which excludes -those messages and add it to the relevant handler. This can be configured by +This configuration does *almost* what we want, except that ``sys.stdout`` would show messages +of severity ``ERROR`` and only events of this severity and higher will be tracked +as well as ``INFO`` and ``WARNING`` messages. To prevent this, we can set up a filter which +excludes those messages and add it to the relevant handler. This can be configured by adding a ``filters`` section parallel to ``formatters`` and ``handlers``: .. code-block:: json diff --git a/Doc/howto/logging.rst b/Doc/howto/logging.rst index 7330cf675baa36..f164b461c93b9c 100644 --- a/Doc/howto/logging.rst +++ b/Doc/howto/logging.rst @@ -89,9 +89,8 @@ described below (in increasing order of severity): | | itself may be unable to continue running. | +--------------+---------------------------------------------+ -The default level is ``WARNING``, which means that only events of this level -and above will be tracked, unless the logging package is configured to do -otherwise. +The default level is ``WARNING``, which means that only events of this severity and higher +will be tracked, unless the logging package is configured to do otherwise. Events that are tracked can be handled in different ways. The simplest way of handling tracked events is to print them to the console. Another common way From 67655d8ad5de8666c97b0a377c6141a6abf66350 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Wed, 27 Dec 2023 09:35:15 +0000 Subject: [PATCH 382/442] gh-111615: Fix regression in QueueHandler configuration. (GH-111638) --- Lib/logging/config.py | 36 ++++++++++--------- Lib/test/test_logging.py | 19 ++++++++++ ...-11-02-10-13-31.gh-issue-111615.3SMixi.rst | 2 ++ 3 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-02-10-13-31.gh-issue-111615.3SMixi.rst diff --git a/Lib/logging/config.py b/Lib/logging/config.py index 4b520e3b1e0da6..de06090942d965 100644 --- a/Lib/logging/config.py +++ b/Lib/logging/config.py @@ -1,4 +1,4 @@ -# Copyright 2001-2022 by Vinay Sajip. All Rights Reserved. +# Copyright 2001-2023 by Vinay Sajip. All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, @@ -734,7 +734,7 @@ def _configure_queue_handler(self, klass, **kwargs): lklass = kwargs['listener'] else: lklass = logging.handlers.QueueListener - listener = lklass(q, *kwargs['handlers'], respect_handler_level=rhl) + listener = lklass(q, *kwargs.get('handlers', []), respect_handler_level=rhl) handler = klass(q) handler.listener = listener return handler @@ -776,11 +776,12 @@ def configure_handler(self, config): raise ValueError('Unable to set target handler %r' % tn) from e elif issubclass(klass, logging.handlers.QueueHandler): # Another special case for handler which refers to other handlers - if 'handlers' not in config: - raise ValueError('No handlers specified for a QueueHandler') + # if 'handlers' not in config: + # raise ValueError('No handlers specified for a QueueHandler') if 'queue' in config: + from multiprocessing.queues import Queue as MPQueue qspec = config['queue'] - if not isinstance(qspec, queue.Queue): + if not isinstance(qspec, (queue.Queue, MPQueue)): if isinstance(qspec, str): q = self.resolve(qspec) if not callable(q): @@ -813,18 +814,19 @@ def configure_handler(self, config): if not callable(listener): raise TypeError('Invalid listener specifier %r' % lspec) config['listener'] = listener - hlist = [] - try: - for hn in config['handlers']: - h = self.config['handlers'][hn] - if not isinstance(h, logging.Handler): - config.update(config_copy) # restore for deferred cfg - raise TypeError('Required handler %r ' - 'is not configured yet' % hn) - hlist.append(h) - except Exception as e: - raise ValueError('Unable to set required handler %r' % hn) from e - config['handlers'] = hlist + if 'handlers' in config: + hlist = [] + try: + for hn in config['handlers']: + h = self.config['handlers'][hn] + if not isinstance(h, logging.Handler): + config.update(config_copy) # restore for deferred cfg + raise TypeError('Required handler %r ' + 'is not configured yet' % hn) + hlist.append(h) + except Exception as e: + raise ValueError('Unable to set required handler %r' % hn) from e + config['handlers'] = hlist elif issubclass(klass, logging.handlers.SMTPHandler) and\ 'mailhost' in config: config['mailhost'] = self.as_tuple(config['mailhost']) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 89be432e4b78c0..33db5d6443f6fd 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -3922,6 +3922,25 @@ def test_90195(self): # Logger should be enabled, since explicitly mentioned self.assertFalse(logger.disabled) + def test_111615(self): + # See gh-111615 + import multiprocessing as mp + + config = { + 'version': 1, + 'handlers': { + 'sink': { + 'class': 'logging.handlers.QueueHandler', + 'queue': mp.get_context('spawn').Queue(), + }, + }, + 'root': { + 'handlers': ['sink'], + 'level': 'DEBUG', + }, + } + logging.config.dictConfig(config) + class ManagerTest(BaseTest): def test_manager_loggerclass(self): logged = [] diff --git a/Misc/NEWS.d/next/Library/2023-11-02-10-13-31.gh-issue-111615.3SMixi.rst b/Misc/NEWS.d/next/Library/2023-11-02-10-13-31.gh-issue-111615.3SMixi.rst new file mode 100644 index 00000000000000..f80ab00a3adbff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-02-10-13-31.gh-issue-111615.3SMixi.rst @@ -0,0 +1,2 @@ +Fix a regression caused by a fix to gh-93162 whereby you couldn't configure +a :class:`QueueHandler` without specifying handlers. From fb02b6696002fc3dedbcf7c58aa40301650936ba Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 27 Dec 2023 11:36:17 +0200 Subject: [PATCH 383/442] gh-101100: Fix Sphinx warnings in `library/calendar.rst` (#113500) --- Doc/library/calendar.rst | 13 ++++++++++--- Doc/tools/.nitignore | 1 - 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 157a7537f97dc6..6586f539a8da4f 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -196,6 +196,13 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is output (defaulting to the system default encoding). + .. method:: formatmonthname(theyear, themonth, withyear=True) + + Return a month name as an HTML table row. If *withyear* is true the year + will be included in the row, otherwise just the month name will be + used. + + :class:`!HTMLCalendar` has the following attributes you can override to customize the CSS classes used by the calendar: @@ -289,7 +296,7 @@ interpreted as prescribed by the ISO 8601 standard. Year 0 is 1 BC, year -1 is .. note:: - The constructor, :meth:`formatweekday` and :meth:`formatmonthname` methods + The constructor, :meth:`!formatweekday` and :meth:`!formatmonthname` methods of these two classes temporarily change the ``LC_TIME`` locale to the given *locale*. Because the current locale is a process-wide setting, they are not thread-safe. @@ -358,7 +365,7 @@ For simple text calendars this module provides the following functions. .. function:: month(theyear, themonth, w=0, l=0) - Returns a month's calendar in a multi-line string using the :meth:`formatmonth` + Returns a month's calendar in a multi-line string using the :meth:`~TextCalendar.formatmonth` of the :class:`TextCalendar` class. @@ -370,7 +377,7 @@ For simple text calendars this module provides the following functions. .. function:: calendar(year, w=2, l=1, c=6, m=3) Returns a 3-column calendar for an entire year as a multi-line string using - the :meth:`formatyear` of the :class:`TextCalendar` class. + the :meth:`~TextCalendar.formatyear` of the :class:`TextCalendar` class. .. function:: timegm(tuple) diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index f2ffe85917c659..88d479ad3cbeac 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -31,7 +31,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/asyncio-task.rst Doc/library/bdb.rst -Doc/library/calendar.rst Doc/library/cmd.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst From 4acf825058a7785ed3d66d4f5a4991298c011f64 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 27 Dec 2023 12:17:30 +0200 Subject: [PATCH 384/442] gh-101100: Fix Sphinx warnings in `library/cmd.rst` (#113502) Co-authored-by: Serhiy Storchaka Co-authored-by: Alex Waygood --- Doc/library/cmd.rst | 25 ++++++++++++++----------- Doc/tools/.nitignore | 1 - 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Doc/library/cmd.rst b/Doc/library/cmd.rst index a79882ed1cca4f..39ef4b481478d1 100644 --- a/Doc/library/cmd.rst +++ b/Doc/library/cmd.rst @@ -76,27 +76,30 @@ A :class:`Cmd` instance has the following methods: single: ! (exclamation); in a command interpreter An interpreter instance will recognize a command name ``foo`` if and only if it - has a method :meth:`do_foo`. As a special case, a line beginning with the + has a method :meth:`!do_foo`. As a special case, a line beginning with the character ``'?'`` is dispatched to the method :meth:`do_help`. As another special case, a line beginning with the character ``'!'`` is dispatched to the - method :meth:`do_shell` (if such a method is defined). + method :meth:`!do_shell` (if such a method is defined). This method will return when the :meth:`postcmd` method returns a true value. The *stop* argument to :meth:`postcmd` is the return value from the command's corresponding :meth:`!do_\*` method. If completion is enabled, completing commands will be done automatically, and - completing of commands args is done by calling :meth:`complete_foo` with + completing of commands args is done by calling :meth:`!complete_foo` with arguments *text*, *line*, *begidx*, and *endidx*. *text* is the string prefix we are attempting to match: all returned matches must begin with it. *line* is the current input line with leading whitespace removed, *begidx* and *endidx* are the beginning and ending indexes of the prefix text, which could be used to provide different completion depending upon which position the argument is in. - All subclasses of :class:`Cmd` inherit a predefined :meth:`do_help`. This + +.. method:: Cmd.do_help(arg) + + All subclasses of :class:`Cmd` inherit a predefined :meth:`!do_help`. This method, called with an argument ``'bar'``, invokes the corresponding method - :meth:`help_bar`, and if that is not present, prints the docstring of - :meth:`do_bar`, if available. With no argument, :meth:`do_help` lists all + :meth:`!help_bar`, and if that is not present, prints the docstring of + :meth:`!do_bar`, if available. With no argument, :meth:`!do_help` lists all available help topics (that is, all commands with corresponding :meth:`!help_\*` methods or commands that have docstrings), and also lists any undocumented commands. @@ -229,8 +232,8 @@ Instances of :class:`Cmd` subclasses have some public instance variables: .. attribute:: Cmd.use_rawinput A flag, defaulting to true. If true, :meth:`cmdloop` uses :func:`input` to - display a prompt and read the next command; if false, :meth:`sys.stdout.write` - and :meth:`sys.stdin.readline` are used. (This means that by importing + display a prompt and read the next command; if false, :data:`sys.stdout.write() ` + and :data:`sys.stdin.readline() ` are used. (This means that by importing :mod:`readline`, on systems that support it, the interpreter will automatically support :program:`Emacs`\ -like line editing and command-history keystrokes.) @@ -249,14 +252,14 @@ This section presents a simple example of how to build a shell around a few of the commands in the :mod:`turtle` module. Basic turtle commands such as :meth:`~turtle.forward` are added to a -:class:`Cmd` subclass with method named :meth:`do_forward`. The argument is +:class:`Cmd` subclass with method named :meth:`!do_forward`. The argument is converted to a number and dispatched to the turtle module. The docstring is used in the help utility provided by the shell. The example also includes a basic record and playback facility implemented with the :meth:`~Cmd.precmd` method which is responsible for converting the input to -lowercase and writing the commands to a file. The :meth:`do_playback` method -reads the file and adds the recorded commands to the :attr:`cmdqueue` for +lowercase and writing the commands to a file. The :meth:`!do_playback` method +reads the file and adds the recorded commands to the :attr:`~Cmd.cmdqueue` for immediate playback:: import cmd, sys diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 88d479ad3cbeac..9953f2ea9ed4d5 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -31,7 +31,6 @@ Doc/library/asyncio-policy.rst Doc/library/asyncio-subprocess.rst Doc/library/asyncio-task.rst Doc/library/bdb.rst -Doc/library/cmd.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst Doc/library/configparser.rst From 1ddd773293bd692b9dbeba9be54403a7b1e95dbf Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Wed, 27 Dec 2023 06:04:31 -0800 Subject: [PATCH 385/442] gh-64020: Deprecate pydoc.ispackage() (GH-20908) Co-authored-by: Serhiy Storchaka --- Doc/whatsnew/3.13.rst | 3 +++ Lib/pydoc.py | 2 ++ Lib/test/test_pydoc.py | 8 ++++++-- .../next/Library/2020-06-15-23-44-53.bpo-19821.ihBk39.rst | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-06-15-23-44-53.bpo-19821.ihBk39.rst diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index c905cddc6cba62..4b02ecddd63b27 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -556,6 +556,9 @@ 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 ------------------------------ diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 83c74a75cd1c00..96aa1dfc1aacf6 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -345,6 +345,8 @@ def sort_attributes(attrs, object): def ispackage(path): """Guess whether a path refers to a package directory.""" + warnings.warn('The pydoc.ispackage() function is deprecated', + DeprecationWarning, stacklevel=2) if os.path.isdir(path): for ext in ('.py', '.pyc'): if os.path.isfile(os.path.join(path, '__init__' + ext)): diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 982ee60c0be4f7..99b19d01783a10 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -745,14 +745,18 @@ def test_splitdoc_with_description(self): def test_is_package_when_not_package(self): with os_helper.temp_cwd() as test_dir: - self.assertFalse(pydoc.ispackage(test_dir)) + with self.assertWarns(DeprecationWarning) as cm: + self.assertFalse(pydoc.ispackage(test_dir)) + self.assertEqual(cm.filename, __file__) def test_is_package_when_is_package(self): with os_helper.temp_cwd() as test_dir: init_path = os.path.join(test_dir, '__init__.py') open(init_path, 'w').close() - self.assertTrue(pydoc.ispackage(test_dir)) + with self.assertWarns(DeprecationWarning) as cm: + self.assertTrue(pydoc.ispackage(test_dir)) os.remove(init_path) + self.assertEqual(cm.filename, __file__) def test_allmethods(self): # issue 17476: allmethods was no longer returning unbound methods. diff --git a/Misc/NEWS.d/next/Library/2020-06-15-23-44-53.bpo-19821.ihBk39.rst b/Misc/NEWS.d/next/Library/2020-06-15-23-44-53.bpo-19821.ihBk39.rst new file mode 100644 index 00000000000000..ede68106b56ff8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-06-15-23-44-53.bpo-19821.ihBk39.rst @@ -0,0 +1 @@ +The :func:`!pydoc.ispackage` function has been deprecated. From 712afab5acbe27ceb1eddde5aa559078ae7eaa3b Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Wed, 27 Dec 2023 06:27:40 -0800 Subject: [PATCH 386/442] gh-57795: IDLE: Enter the selected text when opening the "Replace" dialog (GH-17593) Co-authored-by: Roger Serwy Co-authored-by: Serhiy Storchaka --- Lib/idlelib/replace.py | 23 ++++++------------- .../2019-12-13-12-26-56.bpo-13586.1grqsR.rst | 1 + 2 files changed, 8 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2019-12-13-12-26-56.bpo-13586.1grqsR.rst diff --git a/Lib/idlelib/replace.py b/Lib/idlelib/replace.py index 7997f24f1b0fa6..3716d841568d30 100644 --- a/Lib/idlelib/replace.py +++ b/Lib/idlelib/replace.py @@ -25,7 +25,8 @@ def replace(text, insert_tags=None): if not hasattr(engine, "_replacedialog"): engine._replacedialog = ReplaceDialog(root, engine) dialog = engine._replacedialog - dialog.open(text, insert_tags=insert_tags) + searchphrase = text.get("sel.first", "sel.last") + dialog.open(text, searchphrase, insert_tags=insert_tags) class ReplaceDialog(SearchDialogBase): @@ -51,27 +52,17 @@ def __init__(self, root, engine): self.replvar = StringVar(root) self.insert_tags = None - def open(self, text, insert_tags=None): + def open(self, text, searchphrase=None, *, insert_tags=None): """Make dialog visible on top of others and ready to use. - Also, highlight the currently selected text and set the - search to include the current selection (self.ok). + Also, set the search to include the current selection + (self.ok). Args: text: Text widget being searched. + searchphrase: String phrase to search. """ - SearchDialogBase.open(self, text) - try: - first = text.index("sel.first") - except TclError: - first = None - try: - last = text.index("sel.last") - except TclError: - last = None - first = first or text.index("insert") - last = last or first - self.show_hit(first, last) + SearchDialogBase.open(self, text, searchphrase) self.ok = True self.insert_tags = insert_tags diff --git a/Misc/NEWS.d/next/IDLE/2019-12-13-12-26-56.bpo-13586.1grqsR.rst b/Misc/NEWS.d/next/IDLE/2019-12-13-12-26-56.bpo-13586.1grqsR.rst new file mode 100644 index 00000000000000..1a73cad175c888 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2019-12-13-12-26-56.bpo-13586.1grqsR.rst @@ -0,0 +1 @@ +Enter the selected text when opening the "Replace" dialog. From f8b6e171ad79bf3e57e39ebc0d96ad097a310cbf Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Wed, 27 Dec 2023 15:32:35 +0000 Subject: [PATCH 387/442] GH-110109: pathlib ABCs: drop use of `io.text_encoding()` (#113417) Do not use the locale-specific default encoding in `PathBase.read_text()` and `write_text()`. Locale settings shouldn't influence the operation of these base classes, which are intended mostly for implementing rich paths on *nonlocal* filesystems. --- Lib/pathlib/__init__.py | 18 ++++++++++++++++++ Lib/pathlib/_abc.py | 3 --- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index bfd2a924979746..ab87b49d0277f3 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -270,6 +270,24 @@ def open(self, mode='r', buffering=-1, encoding=None, encoding = io.text_encoding(encoding) return io.open(self, mode, buffering, encoding, errors, newline) + def read_text(self, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, read it, and close the file. + """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = io.text_encoding(encoding) + return _abc.PathBase.read_text(self, encoding, errors, newline) + + def write_text(self, data, encoding=None, errors=None, newline=None): + """ + Open the file in text mode, write to it, and close the file. + """ + # Call io.text_encoding() here to ensure any warning is raised at an + # appropriate stack level. + encoding = io.text_encoding(encoding) + return _abc.PathBase.write_text(self, data, encoding, errors, newline) + def iterdir(self): """Yield path objects of the directory contents. diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 43e2670c4d0258..cfd59ece24673c 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -1,5 +1,4 @@ import functools -import io import ntpath import posixpath import sys @@ -755,7 +754,6 @@ def read_text(self, encoding=None, errors=None, newline=None): """ Open the file in text mode, read it, and close the file. """ - encoding = io.text_encoding(encoding) with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f: return f.read() @@ -775,7 +773,6 @@ def write_text(self, data, encoding=None, errors=None, newline=None): if not isinstance(data, str): raise TypeError('data must be str, not %s' % data.__class__.__name__) - encoding = io.text_encoding(encoding) with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f: return f.write(data) From 1b19d7376818d14ab865fa22cb66baeacdb88277 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Wed, 27 Dec 2023 15:40:03 +0000 Subject: [PATCH 388/442] GH-110109: pathlib ABCs: drop use of `warnings._deprecated()` (#113419) The `pathlib._abc` module will be made available as a PyPI backport supporting Python 3.8+. The `warnings._deprecated()` function was only added last year, and it's private from an external package perspective, so here we switch to `warnings.warn()` instead. --- Lib/pathlib/_abc.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index cfd59ece24673c..efe56ec565c162 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -395,9 +395,8 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): if _deprecated: msg = ("support for supplying more than one positional argument " "to pathlib.PurePath.relative_to() is deprecated and " - "scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, - remove=(3, 14)) + "scheduled for removal in Python 3.14") + warnings.warn(msg, DeprecationWarning, stacklevel=2) other = self.with_segments(other, *_deprecated) elif not isinstance(other, PurePathBase): other = self.with_segments(other) @@ -419,9 +418,8 @@ def is_relative_to(self, other, /, *_deprecated): if _deprecated: msg = ("support for supplying more than one argument to " "pathlib.PurePath.is_relative_to() is deprecated and " - "scheduled for removal in Python {remove}") - warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", - msg, remove=(3, 14)) + "scheduled for removal in Python 3.14") + warnings.warn(msg, DeprecationWarning, stacklevel=2) other = self.with_segments(other, *_deprecated) elif not isinstance(other, PurePathBase): other = self.with_segments(other) From c66b577d9f7a11ffab57985fd6fb22e9dfd4f245 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher <49998481+websurfer5@users.noreply.github.com> Date: Wed, 27 Dec 2023 08:23:42 -0800 Subject: [PATCH 389/442] bpo-26791: Update shutil.move() to provide the same symlink move behavior as the mv shell when moving a symlink into a directory that is the target of the symlink (GH-21759) --- Lib/shutil.py | 2 +- Lib/test/test_shutil.py | 29 +++++++++++++++++++ .../2020-08-06-14-43-55.bpo-26791.KxoEfO.rst | 4 +++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index c40f6ddae39a17..acc9419be4dfca 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -885,7 +885,7 @@ def move(src, dst, copy_function=copy2): sys.audit("shutil.move", src, dst) real_dst = dst if os.path.isdir(dst): - if _samefile(src, dst): + if _samefile(src, dst) and not os.path.islink(src): # We might be on a case insensitive filesystem, # perform the rename anyway. os.rename(src, dst) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index cc5459aa08fe33..8edd75e9907ec0 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2688,6 +2688,35 @@ def test_move_dir_caseinsensitive(self): finally: os.rmdir(dst_dir) + # bpo-26791: Check that a symlink to a directory can + # be moved into that directory. + @mock_rename + def _test_move_symlink_to_dir_into_dir(self, dst): + src = os.path.join(self.src_dir, 'linktodir') + dst_link = os.path.join(self.dst_dir, 'linktodir') + os.symlink(self.dst_dir, src, target_is_directory=True) + shutil.move(src, dst) + self.assertTrue(os.path.islink(dst_link)) + self.assertTrue(os.path.samefile(self.dst_dir, dst_link)) + self.assertFalse(os.path.exists(src)) + + # Repeat the move operation with the destination + # symlink already in place (should raise shutil.Error). + os.symlink(self.dst_dir, src, target_is_directory=True) + with self.assertRaises(shutil.Error): + shutil.move(src, dst) + self.assertTrue(os.path.samefile(self.dst_dir, dst_link)) + self.assertTrue(os.path.exists(src)) + + @os_helper.skip_unless_symlink + def test_move_symlink_to_dir_into_dir(self): + self._test_move_symlink_to_dir_into_dir(self.dst_dir) + + @os_helper.skip_unless_symlink + def test_move_symlink_to_dir_into_symlink_to_dir(self): + dst = os.path.join(self.src_dir, 'otherlinktodir') + os.symlink(self.dst_dir, dst, target_is_directory=True) + self._test_move_symlink_to_dir_into_dir(dst) @os_helper.skip_unless_dac_override @unittest.skipUnless(hasattr(os, 'lchflags') diff --git a/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst b/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst new file mode 100644 index 00000000000000..c6f8dcb6f9269c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-08-06-14-43-55.bpo-26791.KxoEfO.rst @@ -0,0 +1,4 @@ +:func:`shutil.move` now moves a symlink into a directory when that +directory is the target of the symlink. This provides the same behavior as +the mv shell command. The previous behavior raised an exception. Patch by +Jeffrey Kintscher. From 0651936ae2bc6999f488f8c519b8d07a06a11557 Mon Sep 17 00:00:00 2001 From: Stanley <46876382+slateny@users.noreply.github.com> Date: Wed, 27 Dec 2023 09:16:36 -0800 Subject: [PATCH 390/442] gh-67641: Clarify documentation on bytes vs text with non-seeking tarfile stream (GH-31610) --- Doc/library/tarfile.rst | 10 ++++++---- Lib/tarfile.py | 9 +++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index f4e83d64bb1580..7ba29d4a40dedb 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -116,10 +116,12 @@ Some facts and figures: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will be done on the file. If given, *fileobj* may be any object that has a - :meth:`~io.TextIOBase.read` or :meth:`~io.TextIOBase.write` method (depending on the *mode*). *bufsize* - specifies the blocksize and defaults to ``20 * 512`` bytes. Use this variant - in combination with e.g. ``sys.stdin``, a socket :term:`file object` or a tape - device. However, such a :class:`TarFile` object is limited in that it does + :meth:`~io.RawIOBase.read` or :meth:`~io.RawIOBase.write` method + (depending on the *mode*) that works with bytes. + *bufsize* specifies the blocksize and defaults to ``20 * 512`` bytes. + Use this variant in combination with e.g. ``sys.stdin.buffer``, a socket + :term:`file object` or a tape device. + However, such a :class:`TarFile` object is limited in that it does not allow random access, see :ref:`tar-examples`. The currently possible modes: diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 5ada0ad626bda8..20e0394507f5db 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -330,10 +330,11 @@ def write(self, s): class _Stream: """Class that serves as an adapter between TarFile and a stream-like object. The stream-like object only - needs to have a read() or write() method and is accessed - blockwise. Use of gzip or bzip2 compression is possible. - A stream-like object could be for example: sys.stdin, - sys.stdout, a socket, a tape device etc. + needs to have a read() or write() method that works with bytes, + and the method is accessed blockwise. + Use of gzip or bzip2 compression is possible. + A stream-like object could be for example: sys.stdin.buffer, + sys.stdout.buffer, a socket, a tape device etc. _Stream is intended to be used only internally. """ From 6c98fce33a4c2d6671978f6286377af0d6e22182 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 27 Dec 2023 15:51:49 -0500 Subject: [PATCH 391/442] gh-57795: Add news to idlelib/News3.txt (#113522) --- Lib/idlelib/News3.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/idlelib/News3.txt b/Lib/idlelib/News3.txt index 308865d968814c..f38cc96eceb766 100644 --- a/Lib/idlelib/News3.txt +++ b/Lib/idlelib/News3.txt @@ -4,7 +4,11 @@ Released on 2024-10-xx ========================= +gh-57795: Enter selected text into the Find box when opening +a Replace dialog. Patch by Roger Serwy and Zackery Spytz. + gh-113269: Fix test_editor hang on macOS Catalina. +Patch by Terry Reedy. gh-112939: Fix processing unsaved files when quitting IDLE on macOS. Patch by Ronald Oussoren and Christopher Chavez. From 87295b4068762f9cbdfcae5fed5ff54aadd3cb62 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Wed, 27 Dec 2023 22:43:19 +0100 Subject: [PATCH 392/442] gh-113317: Rework Argument Clinic cpp.py error handling (#113525) Rework error handling in the C preprocessor helper. Instead of monkey- patching the cpp.Monitor.fail() method from within clinic.py, rewrite cpp.py to use a subclass of the ClinicError exception. As a side-effect, ClinicError is moved into Tools/clinic/libclinic/errors.py. Yak-shaving in preparation for putting cpp.py into libclinic. --- Lib/test/test_clinic.py | 4 ++-- Tools/clinic/clinic.py | 23 +---------------------- Tools/clinic/cpp.py | 21 +++++++++------------ Tools/clinic/libclinic/__init__.py | 6 ++++++ Tools/clinic/libclinic/errors.py | 26 ++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 Tools/clinic/libclinic/errors.py diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 21f56fe0195e69..3d6816d73d45bc 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -22,7 +22,7 @@ def _make_clinic(*, filename='clinic_tests'): - clang = clinic.CLanguage(None) + clang = clinic.CLanguage(filename) c = clinic.Clinic(clang, filename=filename, limited_capi=False) c.block_parser = clinic.BlockParser('', clang) return c @@ -3920,7 +3920,7 @@ def test_Function_and_Parameter_reprs(self): self.assertEqual(repr(parameter), "") def test_Monitor_repr(self): - monitor = clinic.cpp.Monitor() + monitor = clinic.cpp.Monitor("test.c") self.assertRegex(repr(monitor), r"") monitor.line_number = 42 diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index f004bec3cce8f6..82efff56eda756 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -53,6 +53,7 @@ # Local imports. import libclinic +from libclinic import ClinicError # TODO: @@ -94,27 +95,6 @@ def __repr__(self) -> str: TemplateDict = dict[str, str] -@dc.dataclass -class ClinicError(Exception): - message: str - _: dc.KW_ONLY - lineno: int | None = None - filename: str | None = None - - def __post_init__(self) -> None: - super().__init__(self.message) - - def report(self, *, warn_only: bool = False) -> str: - msg = "Warning" if warn_only else "Error" - if self.filename is not None: - msg += f" in file {self.filename!r}" - if self.lineno is not None: - msg += f" on line {self.lineno}" - msg += ":\n" - msg += f"{self.message}\n" - return msg - - @overload def warn_or_fail( *args: object, @@ -669,7 +649,6 @@ class CLanguage(Language): def __init__(self, filename: str) -> None: super().__init__(filename) self.cpp = cpp.Monitor(filename) - self.cpp.fail = fail # type: ignore[method-assign] def parse_line(self, line: str) -> None: self.cpp.writeline(line) diff --git a/Tools/clinic/cpp.py b/Tools/clinic/cpp.py index 16eee6fc399491..659099056cd46c 100644 --- a/Tools/clinic/cpp.py +++ b/Tools/clinic/cpp.py @@ -3,6 +3,8 @@ import sys from typing import NoReturn +from libclinic.errors import ParseError + TokenAndCondition = tuple[str, str] TokenStack = list[TokenAndCondition] @@ -32,7 +34,7 @@ class Monitor: Anyway this implementation seems to work well enough for the CPython sources. """ - filename: str | None = None + filename: str _: dc.KW_ONLY verbose: bool = False @@ -59,14 +61,8 @@ def condition(self) -> str: """ return " && ".join(condition for token, condition in self.stack) - def fail(self, *a: object) -> NoReturn: - if self.filename: - filename = " " + self.filename - else: - filename = '' - print("Error at" + filename, "line", self.line_number, ":") - print(" ", ' '.join(str(x) for x in a)) - sys.exit(-1) + def fail(self, msg: str) -> NoReturn: + raise ParseError(msg, filename=self.filename, lineno=self.line_number) def writeline(self, line: str) -> None: self.line_number += 1 @@ -74,7 +70,7 @@ def writeline(self, line: str) -> None: def pop_stack() -> TokenAndCondition: if not self.stack: - self.fail("#" + token + " without matching #if / #ifdef / #ifndef!") + self.fail(f"#{token} without matching #if / #ifdef / #ifndef!") return self.stack.pop() if self.continuation: @@ -145,7 +141,7 @@ def pop_stack() -> TokenAndCondition: if token in {'if', 'ifdef', 'ifndef', 'elif'}: if not condition: - self.fail("Invalid format for #" + token + " line: no argument!") + self.fail(f"Invalid format for #{token} line: no argument!") if token in {'if', 'elif'}: if not is_a_simple_defined(condition): condition = "(" + condition + ")" @@ -155,7 +151,8 @@ def pop_stack() -> TokenAndCondition: else: fields = condition.split() if len(fields) != 1: - self.fail("Invalid format for #" + token + " line: should be exactly one argument!") + self.fail(f"Invalid format for #{token} line: " + "should be exactly one argument!") symbol = fields[0] condition = 'defined(' + symbol + ')' if token == 'ifndef': diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 0c3c6840901a42..d4e7a0c5cf7b76 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -1,5 +1,8 @@ from typing import Final +from .errors import ( + ClinicError, +) from .formatting import ( SIG_END_MARKER, c_repr, @@ -15,6 +18,9 @@ __all__ = [ + # Error handling + "ClinicError", + # Formatting helpers "SIG_END_MARKER", "c_repr", diff --git a/Tools/clinic/libclinic/errors.py b/Tools/clinic/libclinic/errors.py new file mode 100644 index 00000000000000..afb21b02386fe7 --- /dev/null +++ b/Tools/clinic/libclinic/errors.py @@ -0,0 +1,26 @@ +import dataclasses as dc + + +@dc.dataclass +class ClinicError(Exception): + message: str + _: dc.KW_ONLY + lineno: int | None = None + filename: str | None = None + + def __post_init__(self) -> None: + super().__init__(self.message) + + def report(self, *, warn_only: bool = False) -> str: + msg = "Warning" if warn_only else "Error" + if self.filename is not None: + msg += f" in file {self.filename!r}" + if self.lineno is not None: + msg += f" on line {self.lineno}" + msg += ":\n" + msg += f"{self.message}\n" + return msg + + +class ParseError(ClinicError): + pass From 7ab9efdd6a2fb21cddca1ccd70175f1ac6bd9168 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Thu, 28 Dec 2023 00:20:57 +0100 Subject: [PATCH 393/442] gh-113299: Move cpp.py into libclinic (#113526) --- Lib/test/test_clinic.py | 2 +- Tools/clinic/clinic.py | 4 ++-- Tools/clinic/{ => libclinic}/cpp.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) rename Tools/clinic/{ => libclinic}/cpp.py (99%) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 3d6816d73d45bc..7323bdd801f4be 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -3920,7 +3920,7 @@ def test_Function_and_Parameter_reprs(self): self.assertEqual(repr(parameter), "") def test_Monitor_repr(self): - monitor = clinic.cpp.Monitor("test.c") + monitor = libclinic.cpp.Monitor("test.c") self.assertRegex(repr(monitor), r"") monitor.line_number = 42 diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 82efff56eda756..f6f95580f1a177 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -13,7 +13,6 @@ import collections import contextlib import copy -import cpp import dataclasses as dc import enum import functools @@ -53,6 +52,7 @@ # Local imports. import libclinic +import libclinic.cpp from libclinic import ClinicError @@ -648,7 +648,7 @@ class CLanguage(Language): def __init__(self, filename: str) -> None: super().__init__(filename) - self.cpp = cpp.Monitor(filename) + self.cpp = libclinic.cpp.Monitor(filename) def parse_line(self, line: str) -> None: self.cpp.writeline(line) diff --git a/Tools/clinic/cpp.py b/Tools/clinic/libclinic/cpp.py similarity index 99% rename from Tools/clinic/cpp.py rename to Tools/clinic/libclinic/cpp.py index 659099056cd46c..e115d65a88e1b6 100644 --- a/Tools/clinic/cpp.py +++ b/Tools/clinic/libclinic/cpp.py @@ -3,7 +3,10 @@ import sys from typing import NoReturn -from libclinic.errors import ParseError +from .errors import ParseError + + +__all__ = ["Monitor"] TokenAndCondition = tuple[str, str] From bfee2f77e16f01a718c1044564ee624f1f2bc328 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 28 Dec 2023 17:31:19 +0900 Subject: [PATCH 394/442] gh-73427: deprecate `_enablelegacywindowsfsencoding` (#107729) --- Doc/library/sys.rst | 8 ++++++++ Doc/whatsnew/3.13.rst | 4 ++++ .../Windows/2023-08-08-01-42-14.gh-issue-73427.WOpiNt.rst | 2 ++ Python/sysmodule.c | 7 +++++++ 4 files changed, 21 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2023-08-08-01-42-14.gh-issue-73427.WOpiNt.rst diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index aaf79205d44282..2426c37ccb1e0f 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1744,9 +1744,17 @@ always available. .. availability:: Windows. + .. note:: + Changing the filesystem encoding after Python startup is risky because + the old fsencoding or paths encoded by the old fsencoding may be cached + somewhere. Use :envvar:`PYTHONLEGACYWINDOWSFSENCODING` instead. + .. versionadded:: 3.6 See :pep:`529` for more details. + .. deprecated-removed:: 3.13 3.16 + Use :envvar:`PYTHONLEGACYWINDOWSFSENCODING` instead. + .. data:: stdin stdout stderr diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4b02ecddd63b27..888ebd0402d0e7 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -474,6 +474,10 @@ Deprecated security and functionality bugs. This includes removal of the ``--cgi`` flag to the ``python -m http.server`` command line in 3.15. +* :mod:`sys`: :func:`sys._enablelegacywindowsfsencoding` function. + Replace it with :envvar:`PYTHONLEGACYWINDOWSFSENCODING` environment variable. + (Contributed by Inada Naoki in :gh:`73427`.) + * :mod:`traceback`: * The field *exc_type* of :class:`traceback.TracebackException` is diff --git a/Misc/NEWS.d/next/Windows/2023-08-08-01-42-14.gh-issue-73427.WOpiNt.rst b/Misc/NEWS.d/next/Windows/2023-08-08-01-42-14.gh-issue-73427.WOpiNt.rst new file mode 100644 index 00000000000000..830c4c54838e80 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-08-08-01-42-14.gh-issue-73427.WOpiNt.rst @@ -0,0 +1,2 @@ +Deprecate :func:`sys._enablelegacywindowsfsencoding`. Use +:envvar:`PYTHONLEGACYWINDOWSFSENCODING` instead. Patch by Inada Naoki. diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 57dc4a1226ce75..c2de4ecdc8ce0f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1715,6 +1715,13 @@ static PyObject * sys__enablelegacywindowsfsencoding_impl(PyObject *module) /*[clinic end generated code: output=f5c3855b45e24fe9 input=2bfa931a20704492]*/ { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "sys._enablelegacywindowsfsencoding() is deprecated and will be " + "removed in Python 3.16. Use PYTHONLEGACYWINDOWSFSENCODING " + "instead.", 1)) + { + return NULL; + } if (_PyUnicode_EnableLegacyWindowsFSEncoding() < 0) { return NULL; } From cc13eabc7ce08accf49656e258ba500f74a1dae8 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Thu, 28 Dec 2023 09:42:05 +0100 Subject: [PATCH 395/442] gh-110459: Make sure --with-openssl-rpath works on macOS (#113441) * gh-110459: Make sure --with-openssl-rpath works on macOS On macOS the `-rpath` linker flag is spelled differently than on on platforms. --- .../macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst | 2 ++ configure | 7 ++++++- configure.ac | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst diff --git a/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst b/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst new file mode 100644 index 00000000000000..44ffd857785f0d --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-23-22-41-07.gh-issue-110459.NaMBJy.rst @@ -0,0 +1,2 @@ +Running ``configure ... --with-openssl-rpath=X/Y/Z`` no longer fails to detect +OpenSSL on macOS. diff --git a/configure b/configure index 7e50abc29d0c1a..6d65d3abc1811b 100755 --- a/configure +++ b/configure @@ -27478,7 +27478,12 @@ then : else $as_nop - rpath_arg="-Wl,-rpath=" + if test "$ac_sys_system" = "Darwin" + then + rpath_arg="-Wl,-rpath," + else + rpath_arg="-Wl,-rpath=" + fi fi diff --git a/configure.ac b/configure.ac index e064848af9ed1b..bfdabc4474e5eb 100644 --- a/configure.ac +++ b/configure.ac @@ -6808,7 +6808,12 @@ AX_CHECK_OPENSSL([have_openssl=yes],[have_openssl=no]) AS_VAR_IF([GNULD], [yes], [ rpath_arg="-Wl,--enable-new-dtags,-rpath=" ], [ - rpath_arg="-Wl,-rpath=" + if test "$ac_sys_system" = "Darwin" + then + rpath_arg="-Wl,-rpath," + else + rpath_arg="-Wl,-rpath=" + fi ]) AC_MSG_CHECKING([for --with-openssl-rpath]) From f1676867b52f8b6c7f70bf32e2a53f7edd6700a7 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Thu, 28 Dec 2023 12:12:21 +0300 Subject: [PATCH 396/442] gh-103092: Make `_elementtree` module importable in sub-interpreters (#113434) Enable imports of _elementtree module in sub-interpreters --- Modules/_elementtree.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 5bf67870767698..b574c96d3f9625 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -4430,9 +4430,7 @@ module_exec(PyObject *m) static struct PyModuleDef_Slot elementtree_slots[] = { {Py_mod_exec, module_exec}, - // XXX gh-103092: fix isolation. - {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, - //{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {0, NULL}, }; From f108468970bf4e70910862476900f924fb701399 Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Thu, 28 Dec 2023 02:47:44 -0800 Subject: [PATCH 397/442] bpo-11102: Make configure enable major(), makedev(), and minor() on HP-UX (GH-19856) Always include before . Co-authored-by: Serhiy Storchaka --- .../next/Build/2020-05-01-23-44-31.bpo-11102.Fw9zeS.rst | 2 ++ Modules/posixmodule.c | 9 +++++---- configure | 1 + configure.ac | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2020-05-01-23-44-31.bpo-11102.Fw9zeS.rst diff --git a/Misc/NEWS.d/next/Build/2020-05-01-23-44-31.bpo-11102.Fw9zeS.rst b/Misc/NEWS.d/next/Build/2020-05-01-23-44-31.bpo-11102.Fw9zeS.rst new file mode 100644 index 00000000000000..6477538edf5550 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2020-05-01-23-44-31.bpo-11102.Fw9zeS.rst @@ -0,0 +1,2 @@ +The :func:`os.major`, :func:`os.makedev`, and :func:`os.minor` functions are +now available on HP-UX v3. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c635fd4d993d57..f4a18536e8f1e1 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -236,15 +236,16 @@ corresponding Unix manual entries for more information on calls."); # include #endif +#ifdef HAVE_SYS_TYPES_H +/* Should be included before on HP-UX v3 */ +# include +#endif /* HAVE_SYS_TYPES_H */ + #ifdef HAVE_SYS_SYSMACROS_H /* GNU C Library: major(), minor(), makedev() */ # include #endif -#ifdef HAVE_SYS_TYPES_H -# include -#endif /* HAVE_SYS_TYPES_H */ - #ifdef HAVE_SYS_STAT_H # include #endif /* HAVE_SYS_STAT_H */ diff --git a/configure b/configure index 6d65d3abc1811b..3322b7a682dd25 100755 --- a/configure +++ b/configure @@ -21805,6 +21805,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext #if defined(MAJOR_IN_MKDEV) #include #elif defined(MAJOR_IN_SYSMACROS) +#include #include #else #include diff --git a/configure.ac b/configure.ac index bfdabc4474e5eb..13a6d746763d62 100644 --- a/configure.ac +++ b/configure.ac @@ -5102,6 +5102,7 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #if defined(MAJOR_IN_MKDEV) #include #elif defined(MAJOR_IN_SYSMACROS) +#include #include #else #include From fba324154e65b752e42aa59dea287d639935565f Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 28 Dec 2023 21:58:06 +0300 Subject: [PATCH 398/442] gh-113543: Make sure that `MacOSXOSAScript` sends `webbrowser.open` audit event (#113544) --- Lib/webbrowser.py | 1 + .../next/Library/2023-12-28-14-36-20.gh-issue-113543.2iWkOR.rst | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-12-28-14-36-20.gh-issue-113543.2iWkOR.rst diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 6f9c6a6de177e6..636e8ca459d109 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -574,6 +574,7 @@ def __init__(self, name='default'): super().__init__(name) def open(self, url, new=0, autoraise=True): + sys.audit("webbrowser.open", url) if self.name == 'default': script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser else: diff --git a/Misc/NEWS.d/next/Library/2023-12-28-14-36-20.gh-issue-113543.2iWkOR.rst b/Misc/NEWS.d/next/Library/2023-12-28-14-36-20.gh-issue-113543.2iWkOR.rst new file mode 100644 index 00000000000000..5bf557bedd0204 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-28-14-36-20.gh-issue-113543.2iWkOR.rst @@ -0,0 +1,2 @@ +Make sure that ``webbrowser.MacOSXOSAScript`` sends ``webbrowser.open`` +audit event. From 8e5d70f4b6bc1d0321f4290f8a2d350706bce8b7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 28 Dec 2023 21:29:12 +0200 Subject: [PATCH 399/442] gh-101100: Fix Sphinx warnings in library/random.rst (#112981) Co-authored-by: Alex Waygood --- Doc/library/random.rst | 59 +++++++++++++++++++++++++++++++----------- Doc/tools/.nitignore | 1 - 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Doc/library/random.rst b/Doc/library/random.rst index feaf260caf3568..d0ced2416c9578 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -34,10 +34,8 @@ instance of the :class:`random.Random` class. You can instantiate your own instances of :class:`Random` to get generators that don't share state. Class :class:`Random` can also be subclassed if you want to use a different -basic generator of your own devising: in that case, override the :meth:`~Random.random`, -:meth:`~Random.seed`, :meth:`~Random.getstate`, and :meth:`~Random.setstate` methods. -Optionally, a new generator can supply a :meth:`~Random.getrandbits` method --- this -allows :meth:`randrange` to produce selections over an arbitrarily large range. +basic generator of your own devising: see the documentation on that class for +more details. The :mod:`random` module also provides the :class:`SystemRandom` class which uses the system function :func:`os.urandom` to generate random numbers @@ -88,7 +86,7 @@ Bookkeeping functions .. versionchanged:: 3.11 The *seed* must be one of the following types: - *NoneType*, :class:`int`, :class:`float`, :class:`str`, + ``None``, :class:`int`, :class:`float`, :class:`str`, :class:`bytes`, or :class:`bytearray`. .. function:: getstate() @@ -412,6 +410,37 @@ Alternative Generator ``None``, :class:`int`, :class:`float`, :class:`str`, :class:`bytes`, or :class:`bytearray`. + Subclasses of :class:`!Random` should override the following methods if they + wish to make use of a different basic generator: + + .. method:: Random.seed(a=None, version=2) + + Override this method in subclasses to customise the :meth:`~random.seed` + behaviour of :class:`!Random` instances. + + .. method:: Random.getstate() + + Override this method in subclasses to customise the :meth:`~random.getstate` + behaviour of :class:`!Random` instances. + + .. method:: Random.setstate(state) + + Override this method in subclasses to customise the :meth:`~random.setstate` + behaviour of :class:`!Random` instances. + + .. method:: Random.random() + + Override this method in subclasses to customise the :meth:`~random.random` + behaviour of :class:`!Random` instances. + + Optionally, a custom generator subclass can also supply the following method: + + .. method:: Random.getrandbits(k) + + Override this method in subclasses to customise the + :meth:`~random.getrandbits` behaviour of :class:`!Random` instances. + + .. class:: SystemRandom([seed]) Class that uses the :func:`os.urandom` function for generating random numbers @@ -445,30 +474,30 @@ Examples Basic examples:: - >>> random() # Random float: 0.0 <= x < 1.0 + >>> random() # Random float: 0.0 <= x < 1.0 0.37444887175646646 - >>> uniform(2.5, 10.0) # Random float: 2.5 <= x <= 10.0 + >>> uniform(2.5, 10.0) # Random float: 2.5 <= x <= 10.0 3.1800146073117523 - >>> expovariate(1 / 5) # Interval between arrivals averaging 5 seconds + >>> expovariate(1 / 5) # Interval between arrivals averaging 5 seconds 5.148957571865031 - >>> randrange(10) # Integer from 0 to 9 inclusive + >>> randrange(10) # Integer from 0 to 9 inclusive 7 - >>> randrange(0, 101, 2) # Even integer from 0 to 100 inclusive + >>> randrange(0, 101, 2) # Even integer from 0 to 100 inclusive 26 - >>> choice(['win', 'lose', 'draw']) # Single random element from a sequence + >>> choice(['win', 'lose', 'draw']) # Single random element from a sequence 'draw' >>> deck = 'ace two three four'.split() - >>> shuffle(deck) # Shuffle a list + >>> shuffle(deck) # Shuffle a list >>> deck ['four', 'two', 'ace', 'three'] - >>> sample([10, 20, 30, 40, 50], k=4) # Four samples without replacement + >>> sample([10, 20, 30, 40, 50], k=4) # Four samples without replacement [40, 10, 50, 30] Simulations:: @@ -572,14 +601,14 @@ Simulation of arrival times and service deliveries for a multiserver queue:: including simulation, sampling, shuffling, and cross-validation. `Economics Simulation - `_ + `_ a simulation of a marketplace by `Peter Norvig `_ that shows effective use of many of the tools and distributions provided by this module (gauss, uniform, sample, betavariate, choice, triangular, and randrange). `A Concrete Introduction to Probability (using Python) - `_ + `_ a tutorial by `Peter Norvig `_ covering the basics of probability theory, how to write simulations, and how to perform data analysis using Python. diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 9953f2ea9ed4d5..ab6baf819de97a 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -71,7 +71,6 @@ Doc/library/profile.rst Doc/library/pyclbr.rst Doc/library/pydoc.rst Doc/library/pyexpat.rst -Doc/library/random.rst Doc/library/readline.rst Doc/library/resource.rst Doc/library/select.rst From db1c88223986efe3076eb3b229a8b6db59bae284 Mon Sep 17 00:00:00 2001 From: John Hawkinson Date: Thu, 28 Dec 2023 14:36:20 -0500 Subject: [PATCH 400/442] Doc/library/os.rst: `os.waitid` absent on MacOS (#104558) * Doc/library/os.rst: `os.waitid` absent on MacOS Co-authored-by: AN Long --- Doc/library/os.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 6b6e62a683ab18..2af61f2960cc63 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4986,6 +4986,9 @@ written in Python, such as a mail server's external command delivery program. .. availability:: Unix, not Emscripten, not WASI. + .. note:: + This function is not available on macOS. + .. versionadded:: 3.3 From b664d9159964f0609d50dabd02f71af0227d8718 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Thu, 28 Dec 2023 22:23:01 +0000 Subject: [PATCH 401/442] GH-113225: Speed up `pathlib._abc.PathBase.glob()` (#113556) `PathBase._scandir()` is implemented using `iterdir()`, so we can use its results directly, rather than passing them through `_make_child_relpath()`. --- Lib/pathlib/__init__.py | 4 ++++ Lib/pathlib/_abc.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index ab87b49d0277f3..2b4193c400a099 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -299,6 +299,10 @@ def iterdir(self): def _scandir(self): return os.scandir(self) + def _make_child_entry(self, entry): + # Transform an entry yielded from _scandir() into a path object. + return self._make_child_relpath(entry.name) + def absolute(self): """Return an absolute version of this path No normalization or symlink resolution is performed. diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index efe56ec565c162..f75b20a1d5f1e5 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -87,9 +87,8 @@ def _select_children(parent_paths, dir_only, follow_symlinks, match): continue except OSError: continue - name = entry.name - if match(name): - yield parent_path._make_child_relpath(name) + if match(entry.name): + yield parent_path._make_child_entry(entry) def _select_recursive(parent_paths, dir_only, follow_symlinks): @@ -112,12 +111,12 @@ def _select_recursive(parent_paths, dir_only, follow_symlinks): for entry in entries: try: if entry.is_dir(follow_symlinks=follow_symlinks): - paths.append(path._make_child_relpath(entry.name)) + paths.append(path._make_child_entry(entry)) continue except OSError: pass if not dir_only: - yield path._make_child_relpath(entry.name) + yield path._make_child_entry(entry) def _select_unique(paths): @@ -788,6 +787,10 @@ def _scandir(self): from contextlib import nullcontext return nullcontext(self.iterdir()) + def _make_child_entry(self, entry): + # Transform an entry yielded from _scandir() into a path object. + return entry + def _make_child_relpath(self, name): path_str = str(self) tail = self._tail From 6ca0e6754eedf4c9cf48794fa6c27281668b8d7c Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Thu, 28 Dec 2023 22:44:29 +0000 Subject: [PATCH 402/442] GH-113528: Remove a couple of expensive pathlib ABC tests (#113534) Run expensive tests for walking and globbing from `test_pathlib` but not `test_pathlib_abc`. The ABCs are not as tightly optimised as the classes in top-level `pathlib`, and so these tests are taking rather a long time on some buildbots. Coverage of the main `pathlib` classes should suffice. --- Lib/test/test_pathlib/test_pathlib.py | 43 +++++++++++++++++++++++ Lib/test/test_pathlib/test_pathlib_abc.py | 42 ---------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index db5f3b2634be97..8f95c804f80e69 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -15,6 +15,7 @@ from test.support import import_helper from test.support import is_emscripten, is_wasi +from test.support import set_recursion_limit from test.support import os_helper from test.support.os_helper import TESTFN, FakePath from test.test_pathlib import test_pathlib_abc @@ -1660,6 +1661,48 @@ def test_walk_many_open_files(self): self.assertEqual(next(it), expected) path = path / 'd' + def test_walk_above_recursion_limit(self): + recursion_limit = 40 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(self.base, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.walk()) + list(base.walk(top_down=False)) + + def test_glob_many_open_files(self): + depth = 30 + P = self.cls + p = base = P(self.base) / 'deep' + p.mkdir() + for _ in range(depth): + p /= 'd' + p.mkdir() + pattern = '/'.join(['*'] * depth) + iters = [base.glob(pattern) for j in range(100)] + for it in iters: + self.assertEqual(next(it), p) + iters = [base.rglob('d') for j in range(100)] + p = base + for i in range(depth): + p = p / 'd' + for it in iters: + self.assertEqual(next(it), p) + + def test_glob_above_recursion_limit(self): + recursion_limit = 50 + # directory_depth > recursion_limit + directory_depth = recursion_limit + 10 + base = self.cls(self.base, 'deep') + path = base.joinpath(*(['d'] * directory_depth)) + path.mkdir(parents=True) + + with set_recursion_limit(recursion_limit): + list(base.glob('**/')) + @only_posix class PosixPathTest(PathTest, PurePosixPathTest): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 568a3183b40b8d..e4a4e81e547cd1 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -8,7 +8,6 @@ from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase import posixpath -from test.support import set_recursion_limit from test.support.os_helper import TESTFN @@ -1224,25 +1223,6 @@ def test_rglob_symlink_loop(self): } self.assertEqual(given, {p / x for x in expect}) - def test_glob_many_open_files(self): - depth = 30 - P = self.cls - p = base = P(self.base) / 'deep' - p.mkdir() - for _ in range(depth): - p /= 'd' - p.mkdir() - pattern = '/'.join(['*'] * depth) - iters = [base.glob(pattern) for j in range(100)] - for it in iters: - self.assertEqual(next(it), p) - iters = [base.rglob('d') for j in range(100)] - p = base - for i in range(depth): - p = p / 'd' - for it in iters: - self.assertEqual(next(it), p) - def test_glob_dotdot(self): # ".." is not special in globs. P = self.cls @@ -1286,17 +1266,6 @@ def test_glob_long_symlink(self): bad_link.symlink_to("bad" * 200) self.assertEqual(sorted(base.glob('**/*')), [bad_link]) - def test_glob_above_recursion_limit(self): - recursion_limit = 50 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(self.base, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.glob('**/')) - def test_glob_recursive_no_trailing_slash(self): P = self.cls p = P(self.base) @@ -1825,17 +1794,6 @@ def test_walk_symlink_location(self): else: self.fail("symlink not found") - def test_walk_above_recursion_limit(self): - recursion_limit = 40 - # directory_depth > recursion_limit - directory_depth = recursion_limit + 10 - base = self.cls(self.base, 'deep') - path = base.joinpath(*(['d'] * directory_depth)) - path.mkdir(parents=True) - - with set_recursion_limit(recursion_limit): - list(base.walk()) - list(base.walk(top_down=False)) class DummyPathWithSymlinks(DummyPath): def readlink(self): From cf34b7704be4c97d0479c04df0d9cd8fe210e5f4 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Fri, 29 Dec 2023 16:13:46 +0300 Subject: [PATCH 403/442] gh-103092: Make ``pyexpat`` module importable in sub-interpreters (#113555) --- Modules/pyexpat.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 9d95309dbb7aa6..ec44892d101e44 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -2062,9 +2062,7 @@ pyexpat_free(void *module) static PyModuleDef_Slot pyexpat_slots[] = { {Py_mod_exec, pyexpat_exec}, - // XXX gh-103092: fix isolation. - {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, - //{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {0, NULL} }; From f46987b8281148503568516c29a4a04a75aaba8d Mon Sep 17 00:00:00 2001 From: Ankit Kumar Pandey <93041495+itsankitkp@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:25:17 +0530 Subject: [PATCH 404/442] gh-103708: Make directory layout in sysconfig implementation configurable (#103709) --- Lib/site.py | 16 +++- Lib/sysconfig/__init__.py | 74 ++++++++++--------- ...-04-23-11-08-02.gh-issue-103708.Y17C7p.rst | 1 + 3 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-23-11-08-02.gh-issue-103708.Y17C7p.rst diff --git a/Lib/site.py b/Lib/site.py index 2517b7e5f1d22a..6f5738b02cb23b 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -260,6 +260,10 @@ def check_enableusersite(): # # See https://bugs.python.org/issue29585 +# Copy of sysconfig._get_implementation() +def _get_implementation(): + return 'Python' + # Copy of sysconfig._getuserbase() def _getuserbase(): env_base = os.environ.get("PYTHONUSERBASE", None) @@ -275,7 +279,7 @@ def joinuser(*args): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") + return joinuser(base, _get_implementation()) if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -288,12 +292,14 @@ def joinuser(*args): def _get_path(userbase): version = sys.version_info + implementation = _get_implementation() + implementation_lower = implementation.lower() if os.name == 'nt': ver_nodot = sys.winver.replace('.', '') - return f'{userbase}\\Python{ver_nodot}\\site-packages' + return f'{userbase}\\{implementation}{ver_nodot}\\site-packages' if sys.platform == 'darwin' and sys._framework: - return f'{userbase}/lib/python/site-packages' + return f'{userbase}/lib/{implementation_lower}/site-packages' return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages' @@ -361,6 +367,8 @@ def getsitepackages(prefixes=None): continue seen.add(prefix) + implementation = _get_implementation().lower() + ver = sys.version_info if os.sep == '/': libdirs = [sys.platlibdir] if sys.platlibdir != "lib": @@ -368,7 +376,7 @@ def getsitepackages(prefixes=None): for libdir in libdirs: path = os.path.join(prefix, libdir, - "python%d.%d" % sys.version_info[:2], + f"{implementation}{ver[0]}.{ver[1]}", "site-packages") sitepackages.append(path) else: diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index deb438c705f3a0..07ab27c7fb0c35 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -26,24 +26,24 @@ _INSTALL_SCHEMES = { 'posix_prefix': { - 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', 'include': - '{installed_base}/include/python{py_version_short}{abiflags}', + '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': - '{installed_platbase}/include/python{py_version_short}{abiflags}', + '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}', 'scripts': '{base}/bin', 'data': '{base}', }, 'posix_home': { - 'stdlib': '{installed_base}/lib/python', - 'platstdlib': '{base}/lib/python', - 'purelib': '{base}/lib/python', - 'platlib': '{base}/lib/python', - 'include': '{installed_base}/include/python', - 'platinclude': '{installed_base}/include/python', + 'stdlib': '{installed_base}/lib/{implementation_lower}', + 'platstdlib': '{base}/lib/{implementation_lower}', + 'purelib': '{base}/lib/{implementation_lower}', + 'platlib': '{base}/lib/{implementation_lower}', + 'include': '{installed_base}/include/{implementation_lower}', + 'platinclude': '{installed_base}/include/{implementation_lower}', 'scripts': '{base}/bin', 'data': '{base}', }, @@ -75,14 +75,14 @@ # Downstream distributors who patch posix_prefix/nt scheme are encouraged to # leave the following schemes unchanged 'posix_venv': { - 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', + 'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}', + 'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', 'include': - '{installed_base}/include/python{py_version_short}{abiflags}', + '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}', 'platinclude': - '{installed_platbase}/include/python{py_version_short}{abiflags}', + '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}', 'scripts': '{base}/bin', 'data': '{base}', }, @@ -104,6 +104,8 @@ else: _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] +def _get_implementation(): + return 'Python' # NOTE: site.py has copy of this function. # Sync it when modify this function. @@ -121,7 +123,7 @@ def joinuser(*args): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") + return joinuser(base, _get_implementation()) if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -135,29 +137,29 @@ def joinuser(*args): _INSTALL_SCHEMES |= { # NOTE: When modifying "purelib" scheme, update site._get_path() too. 'nt_user': { - 'stdlib': '{userbase}/Python{py_version_nodot_plat}', - 'platstdlib': '{userbase}/Python{py_version_nodot_plat}', - 'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'include': '{userbase}/Python{py_version_nodot_plat}/Include', - 'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts', + 'stdlib': '{userbase}/{implementation}{py_version_nodot_plat}', + 'platstdlib': '{userbase}/{implementation}{py_version_nodot_plat}', + 'purelib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages', + 'platlib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages', + 'include': '{userbase}/{implementation}{py_version_nodot_plat}/Include', + 'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts', 'data': '{userbase}', }, 'posix_user': { - 'stdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', - 'platlib': '{userbase}/lib/python{py_version_short}/site-packages', - 'include': '{userbase}/include/python{py_version_short}', + 'stdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}', + 'platstdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}', + 'purelib': '{userbase}/lib/{implementation_lower}{py_version_short}/site-packages', + 'platlib': '{userbase}/lib/{implementation_lower}{py_version_short}/site-packages', + 'include': '{userbase}/include/{implementation_lower}{py_version_short}', 'scripts': '{userbase}/bin', 'data': '{userbase}', }, 'osx_framework_user': { - 'stdlib': '{userbase}/lib/python', - 'platstdlib': '{userbase}/lib/python', - 'purelib': '{userbase}/lib/python/site-packages', - 'platlib': '{userbase}/lib/python/site-packages', - 'include': '{userbase}/include/python{py_version_short}', + 'stdlib': '{userbase}/lib/{implementation_lower}', + 'platstdlib': '{userbase}/lib/{implementation_lower}', + 'purelib': '{userbase}/lib/{implementation_lower}/site-packages', + 'platlib': '{userbase}/lib/{implementation_lower}/site-packages', + 'include': '{userbase}/include/{implementation_lower}{py_version_short}', 'scripts': '{userbase}/bin', 'data': '{userbase}', }, @@ -459,6 +461,8 @@ def _init_config_vars(): _CONFIG_VARS['platbase'] = _EXEC_PREFIX _CONFIG_VARS['projectbase'] = _PROJECT_BASE _CONFIG_VARS['platlibdir'] = sys.platlibdir + _CONFIG_VARS['implementation'] = _get_implementation() + _CONFIG_VARS['implementation_lower'] = _get_implementation().lower() try: _CONFIG_VARS['abiflags'] = sys.abiflags except AttributeError: diff --git a/Misc/NEWS.d/next/Library/2023-04-23-11-08-02.gh-issue-103708.Y17C7p.rst b/Misc/NEWS.d/next/Library/2023-04-23-11-08-02.gh-issue-103708.Y17C7p.rst new file mode 100644 index 00000000000000..4b7d747175df03 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-23-11-08-02.gh-issue-103708.Y17C7p.rst @@ -0,0 +1 @@ +Make hardcoded python name, a configurable parameter so that different implementations of python can override it instead of making huge diffs in sysconfig.py From 88cb9720001295f82c7771ab4ebf20f3cd0b31fb Mon Sep 17 00:00:00 2001 From: Samet YASLAN Date: Sat, 30 Dec 2023 09:17:02 +0100 Subject: [PATCH 405/442] gh-112536: Add support for thread sanitizer (TSAN) (gh-112648) --- Doc/using/configure.rst | 7 ++++++ Include/pyport.h | 5 ++++ Lib/test/libregrtest/utils.py | 7 ++++++ Lib/test/support/__init__.py | 19 ++++++++------ Lib/test/test_io.py | 9 ++++--- ...-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst | 1 + configure | 25 +++++++++++++++++++ configure.ac | 18 +++++++++++++ 8 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index cb7eda42fe3fad..aab9469b44828a 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -745,6 +745,13 @@ Debug options .. versionadded:: 3.6 +.. option:: --with-thread-sanitizer + + Enable ThreadSanitizer data race detector, ``tsan`` + (default is no). + + .. versionadded:: 3.13 + Linker options -------------- diff --git a/Include/pyport.h b/Include/pyport.h index 328471085f959d..9d7ef0061806ad 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -563,6 +563,11 @@ extern "C" { # define _Py_ADDRESS_SANITIZER # endif # endif +# if __has_feature(thread_sanitizer) +# if !defined(_Py_THREAD_SANITIZER) +# define _Py_THREAD_SANITIZER +# endif +# endif #elif defined(__GNUC__) # if defined(__SANITIZE_ADDRESS__) # define _Py_ADDRESS_SANITIZER diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index 26481e71221ade..b30025d962413c 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -340,6 +340,9 @@ def get_build_info(): # --with-undefined-behavior-sanitizer if support.check_sanitizer(ub=True): sanitizers.append("UBSAN") + # --with-thread-sanitizer + if support.check_sanitizer(thread=True): + sanitizers.append("TSAN") if sanitizers: build.append('+'.join(sanitizers)) @@ -634,6 +637,7 @@ def display_header(use_resources: tuple[str, ...], asan = support.check_sanitizer(address=True) msan = support.check_sanitizer(memory=True) ubsan = support.check_sanitizer(ub=True) + tsan = support.check_sanitizer(thread=True) sanitizers = [] if asan: sanitizers.append("address") @@ -641,12 +645,15 @@ def display_header(use_resources: tuple[str, ...], sanitizers.append("memory") if ubsan: sanitizers.append("undefined behavior") + if tsan: + sanitizers.append("thread") if sanitizers: print(f"== sanitizers: {', '.join(sanitizers)}") for sanitizer, env_var in ( (asan, "ASAN_OPTIONS"), (msan, "MSAN_OPTIONS"), (ubsan, "UBSAN_OPTIONS"), + (tsan, "TSAN_OPTIONS"), ): options= os.environ.get(env_var) if sanitizer and options is not None: diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index c8f73cede230d8..e5fb725a30b5b8 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -392,10 +392,10 @@ def skip_if_buildbot(reason=None): isbuildbot = False return unittest.skipIf(isbuildbot, reason) -def check_sanitizer(*, address=False, memory=False, ub=False): +def check_sanitizer(*, address=False, memory=False, ub=False, thread=False): """Returns True if Python is compiled with sanitizer support""" - if not (address or memory or ub): - raise ValueError('At least one of address, memory, or ub must be True') + if not (address or memory or ub or thread): + raise ValueError('At least one of address, memory, ub or thread must be True') cflags = sysconfig.get_config_var('CFLAGS') or '' @@ -412,18 +412,23 @@ def check_sanitizer(*, address=False, memory=False, ub=False): '-fsanitize=undefined' in cflags or '--with-undefined-behavior-sanitizer' in config_args ) + thread_sanitizer = ( + '-fsanitize=thread' in cflags or + '--with-thread-sanitizer' in config_args + ) return ( (memory and memory_sanitizer) or (address and address_sanitizer) or - (ub and ub_sanitizer) + (ub and ub_sanitizer) or + (thread and thread_sanitizer) ) -def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False): +def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False, thread=False): """Decorator raising SkipTest if running with a sanitizer active.""" if not reason: reason = 'not working with sanitizers active' - skip = check_sanitizer(address=address, memory=memory, ub=ub) + skip = check_sanitizer(address=address, memory=memory, ub=ub, thread=thread) return unittest.skipIf(skip, reason) # gh-89363: True if fork() can hang if Python is built with Address Sanitizer @@ -432,7 +437,7 @@ def skip_if_sanitizer(reason=None, *, address=False, memory=False, ub=False): def set_sanitizer_env_var(env, option): - for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS'): + for name in ('ASAN_OPTIONS', 'MSAN_OPTIONS', 'UBSAN_OPTIONS', 'TSAN_OPTIONS'): if name in env: env[name] += f':{option}' else: diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 09cced9baef99b..1d78876f2a1c84 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1654,7 +1654,8 @@ def test_truncate_on_read_only(self): class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedReaderTest.test_constructor(self) @@ -2021,7 +2022,8 @@ def test_slow_close_from_thread(self): class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedWriterTest.test_constructor(self) @@ -2520,7 +2522,8 @@ def test_interleaved_readline_write(self): class CBufferedRandomTest(BufferedRandomTest, SizeofTest): tp = io.BufferedRandom - @skip_if_sanitizer(memory=True, address=True, reason= "sanitizer defaults to crashing " + @skip_if_sanitizer(memory=True, address=True, thread=True, + reason="sanitizer defaults to crashing " "instead of returning NULL for malloc failure.") def test_constructor(self): BufferedRandomTest.test_constructor(self) diff --git a/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst b/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst new file mode 100644 index 00000000000000..a136eb47584993 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-12-17-18-23-02.gh-issue-112536.8lr3Ep.rst @@ -0,0 +1 @@ +Add support for thread sanitizer (TSAN) diff --git a/configure b/configure index 3322b7a682dd25..3cc9aecafad13e 100755 --- a/configure +++ b/configure @@ -1082,6 +1082,7 @@ with_dsymutil with_address_sanitizer with_memory_sanitizer with_undefined_behavior_sanitizer +with_thread_sanitizer with_hash_algorithm with_tzpath with_libs @@ -1860,6 +1861,8 @@ Optional Packages: --with-undefined-behavior-sanitizer enable UndefinedBehaviorSanitizer undefined behaviour detector, 'ubsan' (default is no) + --with-thread-sanitizer enable ThreadSanitizer data race detector, 'tsan' + (default is no) --with-hash-algorithm=[fnv|siphash13|siphash24] select hash algorithm for use in Python/pyhash.c (default is SipHash13) @@ -12506,6 +12509,28 @@ with_ubsan="no" fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --with-thread-sanitizer" >&5 +printf %s "checking for --with-thread-sanitizer... " >&6; } + +# Check whether --with-thread_sanitizer was given. +if test ${with_thread_sanitizer+y} +then : + withval=$with_thread_sanitizer; +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $withval" >&5 +printf "%s\n" "$withval" >&6; } +BASECFLAGS="-fsanitize=thread $BASECFLAGS" +LDFLAGS="-fsanitize=thread $LDFLAGS" +with_tsan="yes" + +else $as_nop + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +with_tsan="no" + +fi + + # Set info about shared libraries. diff --git a/configure.ac b/configure.ac index 13a6d746763d62..6a80a5d29a04ef 100644 --- a/configure.ac +++ b/configure.ac @@ -3067,6 +3067,24 @@ AC_MSG_RESULT([no]) with_ubsan="no" ]) +AC_MSG_CHECKING([for --with-thread-sanitizer]) +AC_ARG_WITH( + [thread_sanitizer], + [AS_HELP_STRING( + [--with-thread-sanitizer], + [enable ThreadSanitizer data race detector, 'tsan' (default is no)] + )], +[ +AC_MSG_RESULT([$withval]) +BASECFLAGS="-fsanitize=thread $BASECFLAGS" +LDFLAGS="-fsanitize=thread $LDFLAGS" +with_tsan="yes" +], +[ +AC_MSG_RESULT([no]) +with_tsan="no" +]) + # Set info about shared libraries. AC_SUBST([SHLIB_SUFFIX]) AC_SUBST([LDSHARED]) From f48a1bcb2914addee971814fd014e4d8075ea6a9 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Sat, 30 Dec 2023 16:19:47 +0100 Subject: [PATCH 406/442] gh-89414: Document that SIGCLD is not available on macOS (#113580) Document that SIGCLD is not available on macOS --- Doc/library/signal.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index 7ee5ece8859825..85a073aad233ac 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -157,6 +157,8 @@ The variables defined in the :mod:`signal` module are: Alias to :data:`SIGCHLD`. + .. availability:: not macOS. + .. data:: SIGCONT Continue the process if it is currently stopped From 471aa752415029c508693fa7971076f5148022a6 Mon Sep 17 00:00:00 2001 From: Delgan <4193924+Delgan@users.noreply.github.com> Date: Sun, 31 Dec 2023 00:18:06 +0100 Subject: [PATCH 407/442] Update ConfigParser docs defining valid section name (#110506) --- Doc/library/configparser.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 12eee47613d186..4d0dad20287a90 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -271,7 +271,7 @@ out. Values can also span multiple lines, as long as they are indented deeper than the first line of the value. Depending on the parser's mode, blank lines may be treated as parts of multiline values or ignored. -By default, a valid section name can be any string that does not contain '\\n' or ']'. +By default, a valid section name can be any string that does not contain '\\n'. To change this, see :attr:`ConfigParser.SECTCRE`. Configuration files may include comments, prefixed by specific From 30a6d79fb8bc1ef96600c290c016720103b74b2d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 31 Dec 2023 19:57:33 +0200 Subject: [PATCH 408/442] gh-101100: Fix Sphinx warnings in `library/configparser.rst` (#113598) Co-authored-by: Alex Waygood --- Doc/library/configparser.rst | 30 +++++++++++++++--------------- Doc/library/logging.config.rst | 6 +++--- Doc/tools/.nitignore | 1 - Doc/whatsnew/2.0.rst | 2 +- Doc/whatsnew/2.4.rst | 4 ++-- Doc/whatsnew/2.7.rst | 4 ++-- Doc/whatsnew/3.11.rst | 2 +- Doc/whatsnew/3.2.rst | 10 +++++----- Misc/NEWS.d/3.11.0a1.rst | 6 +++--- Misc/NEWS.d/3.11.0a6.rst | 2 +- Misc/NEWS.d/3.8.0a1.rst | 4 ++-- 11 files changed, 35 insertions(+), 36 deletions(-) diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 4d0dad20287a90..0031737853e7b4 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -208,7 +208,7 @@ converters and customize the provided ones. [1]_ Fallback Values --------------- -As with a dictionary, you can use a section's :meth:`get` method to +As with a dictionary, you can use a section's :meth:`~ConfigParser.get` method to provide fallback values: .. doctest:: @@ -232,7 +232,7 @@ even if we specify a fallback: >>> topsecret.get('CompressionLevel', '3') '9' -One more thing to be aware of is that the parser-level :meth:`get` method +One more thing to be aware of is that the parser-level :meth:`~ConfigParser.get` method provides a custom, more complex interface, maintained for backwards compatibility. When using this method, a fallback value can be provided via the ``fallback`` keyword-only argument: @@ -481,7 +481,7 @@ historical background and it's very likely that you will want to customize some of the features. The most common way to change the way a specific config parser works is to use -the :meth:`__init__` options: +the :meth:`!__init__` options: * *defaults*, default value: ``None`` @@ -491,7 +491,7 @@ the :meth:`__init__` options: the documented default. Hint: if you want to specify default values for a specific section, use - :meth:`read_dict` before you read the actual file. + :meth:`~ConfigParser.read_dict` before you read the actual file. * *dict_type*, default value: :class:`dict` @@ -635,8 +635,8 @@ the :meth:`__init__` options: * *strict*, default value: ``True`` When set to ``True``, the parser will not allow for any section or option - duplicates while reading from a single source (using :meth:`read_file`, - :meth:`read_string` or :meth:`read_dict`). It is recommended to use strict + duplicates while reading from a single source (using :meth:`~ConfigParser.read_file`, + :meth:`~ConfigParser.read_string` or :meth:`~ConfigParser.read_dict`). It is recommended to use strict parsers in new applications. .. versionchanged:: 3.2 @@ -697,7 +697,7 @@ the :meth:`__init__` options: desirable, users may define them in a subclass or pass a dictionary where each key is a name of the converter and each value is a callable implementing said conversion. For instance, passing ``{'decimal': decimal.Decimal}`` would add - :meth:`getdecimal` on both the parser object and all section proxies. In + :meth:`!getdecimal` on both the parser object and all section proxies. In other words, it will be possible to write both ``parser_instance.getdecimal('section', 'key', fallback=0)`` and ``parser_instance['section'].getdecimal('key', 0)``. @@ -1062,11 +1062,11 @@ ConfigParser Objects yielding Unicode strings (for example files opened in text mode). Optional argument *source* specifies the name of the file being read. If - not given and *f* has a :attr:`name` attribute, that is used for + not given and *f* has a :attr:`!name` attribute, that is used for *source*; the default is ``''``. .. versionadded:: 3.2 - Replaces :meth:`readfp`. + Replaces :meth:`!readfp`. .. method:: read_string(string, source='') @@ -1214,7 +1214,7 @@ ConfigParser Objects .. data:: MAX_INTERPOLATION_DEPTH - The maximum depth for recursive interpolation for :meth:`get` when the *raw* + The maximum depth for recursive interpolation for :meth:`~configparser.ConfigParser.get` when the *raw* parameter is false. This is relevant only when the default *interpolation* is used. @@ -1287,13 +1287,13 @@ Exceptions .. exception:: DuplicateSectionError - Exception raised if :meth:`add_section` is called with the name of a section + Exception raised if :meth:`~ConfigParser.add_section` is called with the name of a section that is already present or in strict parsers when a section if found more than once in a single input file, string or dictionary. .. versionadded:: 3.2 Optional ``source`` and ``lineno`` attributes and arguments to - :meth:`__init__` were added. + :meth:`!__init__` were added. .. exception:: DuplicateOptionError @@ -1345,9 +1345,9 @@ Exceptions Exception raised when errors occur attempting to parse a file. -.. versionchanged:: 3.12 - The ``filename`` attribute and :meth:`__init__` constructor argument were - removed. They have been available using the name ``source`` since 3.2. + .. versionchanged:: 3.12 + The ``filename`` attribute and :meth:`!__init__` constructor argument were + removed. They have been available using the name ``source`` since 3.2. .. rubric:: Footnotes diff --git a/Doc/library/logging.config.rst b/Doc/library/logging.config.rst index 85a53e6aa7a78b..85a68cb11ee22c 100644 --- a/Doc/library/logging.config.rst +++ b/Doc/library/logging.config.rst @@ -93,8 +93,8 @@ in :mod:`logging` itself) and defining handlers which are declared either in :param fname: A filename, or a file-like object, or an instance derived from :class:`~configparser.RawConfigParser`. If a - ``RawConfigParser``-derived instance is passed, it is used as - is. Otherwise, a :class:`~configparser.Configparser` is + :class:`!RawConfigParser`-derived instance is passed, it is used as + is. Otherwise, a :class:`~configparser.ConfigParser` is instantiated, and the configuration read by it from the object passed in ``fname``. If that has a :meth:`readline` method, it is assumed to be a file-like object and read using @@ -103,7 +103,7 @@ in :mod:`logging` itself) and defining handlers which are declared either in :meth:`~configparser.ConfigParser.read`. - :param defaults: Defaults to be passed to the ConfigParser can be specified + :param defaults: Defaults to be passed to the :class:`!ConfigParser` can be specified in this argument. :param disable_existing_loggers: If specified as ``False``, loggers which diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index ab6baf819de97a..05df332fa7c9a8 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -33,7 +33,6 @@ Doc/library/asyncio-task.rst Doc/library/bdb.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst -Doc/library/configparser.rst Doc/library/csv.rst Doc/library/datetime.rst Doc/library/dbm.rst diff --git a/Doc/whatsnew/2.0.rst b/Doc/whatsnew/2.0.rst index 6d6e51006d5bd8..b0e495b0651789 100644 --- a/Doc/whatsnew/2.0.rst +++ b/Doc/whatsnew/2.0.rst @@ -1030,7 +1030,7 @@ Module changes Lots of improvements and bugfixes were made to Python's extensive standard library; some of the affected modules include :mod:`readline`, -:mod:`ConfigParser`, :mod:`!cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`, +:mod:`ConfigParser `, :mod:`!cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`, :mod:`xmllib`, :mod:`!aifc`, :mod:`!chunk`, :mod:`wave`, :mod:`random`, :mod:`shelve`, and :mod:`!nntplib`. Consult the CVS logs for the exact patch-by-patch details. diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index bc748dd44f5f8e..6df59dd245ff55 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -1052,9 +1052,9 @@ complete list of changes, or look through the CVS logs for all the details. advantage of :class:`collections.deque` for improved performance. (Contributed by Raymond Hettinger.) -* The :mod:`ConfigParser` classes have been enhanced slightly. The :meth:`read` +* The :mod:`ConfigParser ` classes have been enhanced slightly. The :meth:`~configparser.ConfigParser.read` method now returns a list of the files that were successfully parsed, and the - :meth:`set` method raises :exc:`TypeError` if passed a *value* argument that + :meth:`~configparser.ConfigParser.set` method raises :exc:`TypeError` if passed a *value* argument that isn't a string. (Contributed by John Belmonte and David Goodger.) * The :mod:`curses` module now supports the ncurses extension diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 5af700bd5d3506..81fe132d50e1f1 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -287,7 +287,7 @@ remains O(1). The standard library now supports use of ordered dictionaries in several modules. -* The :mod:`ConfigParser` module uses them by default, meaning that +* The :mod:`ConfigParser ` module uses them by default, meaning that configuration files can now be read, modified, and then written back in their original order. @@ -1134,7 +1134,7 @@ changes, or look through the Subversion logs for all the details. another type that isn't a :class:`Mapping`. (Fixed by Daniel Stutzbach; :issue:`8729`.) -* Constructors for the parsing classes in the :mod:`ConfigParser` module now +* Constructors for the parsing classes in the :mod:`ConfigParser ` module now take an *allow_no_value* parameter, defaulting to false; if true, options without values will be allowed. For example:: diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 8db133b90a7a4b..cae5a26bae1148 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1773,7 +1773,7 @@ Standard Library * the :class:`!configparser.SafeConfigParser` class * the :attr:`!configparser.ParsingError.filename` property - * the :meth:`configparser.RawConfigParser.readfp` method + * the :meth:`!configparser.RawConfigParser.readfp` method (Contributed by Hugo van Kemenade in :issue:`45173`.) diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 5ef76968e9d86b..aad196478dd38b 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -183,7 +183,7 @@ PEP 391: Dictionary Based Configuration for Logging The :mod:`logging` module provided two kinds of configuration, one style with function calls for each option or another style driven by an external file saved -in a :mod:`ConfigParser` format. Those options did not provide the flexibility +in a :mod:`configparser` format. Those options did not provide the flexibility to create configurations from JSON or YAML files, nor did they support incremental configuration, which is needed for specifying logger options from a command line. @@ -2134,7 +2134,7 @@ configparser The :mod:`configparser` module was modified to improve usability and predictability of the default parser and its supported INI syntax. The old -:class:`ConfigParser` class was removed in favor of :class:`SafeConfigParser` +:class:`!ConfigParser` class was removed in favor of :class:`!SafeConfigParser` which has in turn been renamed to :class:`~configparser.ConfigParser`. Support for inline comments is now turned off by default and section or option duplicates are not allowed in a single configuration source. @@ -2414,7 +2414,7 @@ when one operand is much larger than the other (patch by Andress Bennetts in (:issue:`1569291` by Alexander Belopolsky). The :class:`BaseHTTPRequestHandler` has more efficient buffering (:issue:`3709` by Andrew Schaaf). The :func:`operator.attrgetter` function has been sped-up (:issue:`10160` by -Christos Georgiou). And :class:`ConfigParser` loads multi-line arguments a bit +Christos Georgiou). And :class:`~configparser.ConfigParser` loads multi-line arguments a bit faster (:issue:`7113` by Łukasz Langa). @@ -2614,8 +2614,8 @@ This section lists previously described changes and other bugfixes that may require changes to your code: * The :mod:`configparser` module has a number of clean-ups. The major change is - to replace the old :class:`ConfigParser` class with long-standing preferred - alternative :class:`SafeConfigParser`. In addition there are a number of + to replace the old :class:`!ConfigParser` class with long-standing preferred + alternative :class:`!SafeConfigParser`. In addition there are a number of smaller incompatibilities: * The interpolation syntax is now validated on diff --git a/Misc/NEWS.d/3.11.0a1.rst b/Misc/NEWS.d/3.11.0a1.rst index 26c44b6c1af0ed..1c96c0760a57b2 100644 --- a/Misc/NEWS.d/3.11.0a1.rst +++ b/Misc/NEWS.d/3.11.0a1.rst @@ -1642,9 +1642,9 @@ interval specified with nanosecond precision. .. nonce: UptGAn .. section: Library -Remove from the :mod:`configparser` module: the :class:`SafeConfigParser` -class, the :attr:`filename` property of the :class:`ParsingError` class, the -:meth:`readfp` method of the :class:`ConfigParser` class, deprecated since +Remove from the :mod:`configparser` module: the :class:`!SafeConfigParser` +class, the :attr:`!filename` property of the :class:`~configparser.ParsingError` class, the +:meth:`!readfp` method of the :class:`~configparser.ConfigParser` class, deprecated since Python 3.2. Patch by Hugo van Kemenade. diff --git a/Misc/NEWS.d/3.11.0a6.rst b/Misc/NEWS.d/3.11.0a6.rst index 52055b3fafd485..974d025c631a45 100644 --- a/Misc/NEWS.d/3.11.0a6.rst +++ b/Misc/NEWS.d/3.11.0a6.rst @@ -941,7 +941,7 @@ uvloop library. Make the :class:`configparser.ConfigParser` constructor raise :exc:`TypeError` if the ``interpolation`` parameter is not of type -:class:`configparser.Interpolation` +:class:`!configparser.Interpolation` .. diff --git a/Misc/NEWS.d/3.8.0a1.rst b/Misc/NEWS.d/3.8.0a1.rst index 2b9dbd5d63a87e..99f408661d9f69 100644 --- a/Misc/NEWS.d/3.8.0a1.rst +++ b/Misc/NEWS.d/3.8.0a1.rst @@ -5044,8 +5044,8 @@ functionality. .. nonce: C_K-J9 .. section: Library -`ConfigParser.items()` was fixed so that key-value pairs passed in via -`vars` are not included in the resulting output. +``ConfigParser.items()`` was fixed so that key-value pairs passed in via +:func:`vars` are not included in the resulting output. .. From 2849cbb53afc8c6a4465f1b3490c67c2455caf6f Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:16:33 +0000 Subject: [PATCH 409/442] gh-101578: [doc] mention that PyErr_GetRaisedException returns NULL when the error indicator is not set (#113369) --- Doc/c-api/exceptions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 284a9c71e420da..c7e3cd9463e5d7 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -440,7 +440,7 @@ Querying the error indicator .. c:function:: PyObject *PyErr_GetRaisedException(void) Return the exception currently being raised, clearing the error indicator at - the same time. + the same time. Return ``NULL`` if the error indicator is not set. This function is used by code that needs to catch exceptions, or code that needs to save and restore the error indicator temporarily. From 9ce6c01e38a2fc7a5ce832f1f8c8d9097132556d Mon Sep 17 00:00:00 2001 From: Parth Doshi Date: Mon, 1 Jan 2024 00:08:05 -0800 Subject: [PATCH 410/442] # gh-111700: Fix syntax highlighting for C code in the "What's New In Python 3.12" documentation (#113609) Fix PEP 684 syntax highlighting in what's new Python 3.12 --- Doc/whatsnew/3.12.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 8551b35438e2c3..9a2ccf7ebc6a68 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -343,7 +343,9 @@ cores. This is currently only available through the C-API, though a Python API is :pep:`anticipated for 3.13 <554>`. Use the new :c:func:`Py_NewInterpreterFromConfig` function to -create an interpreter with its own GIL:: +create an interpreter with its own GIL: + +.. code-block:: c PyInterpreterConfig config = { .check_multi_interp_extensions = 1, From 686d65aec1fa47ccc0e20f60d17c1b961183f8ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 09:25:58 +0000 Subject: [PATCH 411/442] build(deps): bump actions/setup-python from 4 to 5 (#113612) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/mypy.yml | 2 +- .github/workflows/reusable-docs.yml | 4 ++-- .github/workflows/verify-ensurepip-wheels.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cfb36c8c32e18d..9f62c48b371902 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,7 +128,7 @@ jobs: if: needs.check_source.outputs.run_tests == 'true' steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Restore config.cache diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c1c29a58cf4fc..4a70ec6205a05b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.x" - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 792903a90a4880..11928e72b9b43a 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -46,7 +46,7 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 1c4fa4239c1e34..e534751ee1011d 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -41,7 +41,7 @@ jobs: git fetch origin ${{ env.refspec_base }} --shallow-since="${DATE}" \ --no-tags --prune --no-recurse-submodules - name: 'Set up Python' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3' cache: 'pip' @@ -72,7 +72,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: 'Set up Python' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' # known to work with Sphinx 4.2 cache: 'pip' diff --git a/.github/workflows/verify-ensurepip-wheels.yml b/.github/workflows/verify-ensurepip-wheels.yml index 4a545037bf6e2b..83b007f1c9c2ef 100644 --- a/.github/workflows/verify-ensurepip-wheels.yml +++ b/.github/workflows/verify-ensurepip-wheels.yml @@ -26,7 +26,7 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3' - name: Compare checksum of bundled wheels to the ones published on PyPI From 9132f4287bf022a2fa79b2cc5f130df5188801ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 09:30:30 +0000 Subject: [PATCH 412/442] build(deps): bump github/codeql-action from 2 to 3 (#113613) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f62c48b371902..e8b44a7c6952a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -490,7 +490,7 @@ jobs: path: ./out/artifacts - name: Upload SARIF if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif From 4036e48d59b0f9e8057e01458ab7df3dfd323a10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 09:42:44 +0000 Subject: [PATCH 413/442] build(deps): bump hypothesis from 6.91.0 to 6.92.2 in /Tools (#113615) Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.91.0 to 6.92.2. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.91.0...hypothesis-python-6.92.2) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 1bca5d2367f4b2..0e6e16ae198162 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.91.0 +hypothesis==6.92.2 From 5f3cc90a12d6df404fd6f48a0df1334902e271f2 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher <49998481+websurfer5@users.noreply.github.com> Date: Mon, 1 Jan 2024 08:24:24 -0800 Subject: [PATCH 414/442] gh-62260: Fix ctypes.Structure subclassing with multiple layers (GH-13374) The length field of StgDictObject for Structure class contains now the total number of items in ffi_type_pointer.elements (excluding the trailing null). The old behavior of using the number of elements in the parent class can cause the array to be truncated when it is copied, especially when there are multiple layers of subclassing. Co-authored-by: Serhiy Storchaka --- Lib/test/test_ctypes/test_structures.py | 63 ++++++++++++++++++- .../2019-05-17-07-22-33.bpo-18060.5mqTQM.rst | 2 + Modules/_ctypes/_ctypes.c | 10 +-- Modules/_ctypes/stgdict.c | 2 +- 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst diff --git a/Lib/test/test_ctypes/test_structures.py b/Lib/test/test_ctypes/test_structures.py index 21039f04947507..3eafc77ca70aea 100644 --- a/Lib/test/test_ctypes/test_structures.py +++ b/Lib/test/test_ctypes/test_structures.py @@ -1,5 +1,5 @@ import _ctypes_test -import platform +from platform import architecture as _architecture import struct import sys import unittest @@ -8,6 +8,7 @@ c_uint8, c_uint16, c_uint32, c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong, c_float, c_double) +from ctypes.util import find_library from struct import calcsize from collections import namedtuple from test import support @@ -472,6 +473,66 @@ class X(Structure): self.assertEqual(s.first, got.first) self.assertEqual(s.second, got.second) + def _test_issue18060(self, Vector): + # The call to atan2() should succeed if the + # class fields were correctly cloned in the + # subclasses. Otherwise, it will segfault. + if sys.platform == 'win32': + libm = CDLL(find_library('msvcrt.dll')) + else: + libm = CDLL(find_library('m')) + + libm.atan2.argtypes = [Vector] + libm.atan2.restype = c_double + + arg = Vector(y=0.0, x=-1.0) + self.assertAlmostEqual(libm.atan2(arg), 3.141592653589793) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_a(self): + # This test case calls + # PyCStructUnionType_update_stgdict() for each + # _fields_ assignment, and PyCStgDict_clone() + # for the Mid and Vector class definitions. + class Base(Structure): + _fields_ = [('y', c_double), + ('x', c_double)] + class Mid(Base): + pass + Mid._fields_ = [] + class Vector(Mid): pass + self._test_issue18060(Vector) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_b(self): + # This test case calls + # PyCStructUnionType_update_stgdict() for each + # _fields_ assignment. + class Base(Structure): + _fields_ = [('y', c_double), + ('x', c_double)] + class Mid(Base): + _fields_ = [] + class Vector(Mid): + _fields_ = [] + self._test_issue18060(Vector) + + @unittest.skipIf(_architecture() == ('64bit', 'WindowsPE'), "can't test Windows x64 build") + @unittest.skipUnless(sys.byteorder == 'little', "can't test on this platform") + def test_issue18060_c(self): + # This test case calls + # PyCStructUnionType_update_stgdict() for each + # _fields_ assignment. + class Base(Structure): + _fields_ = [('y', c_double)] + class Mid(Base): + _fields_ = [] + class Vector(Mid): + _fields_ = [('x', c_double)] + self._test_issue18060(Vector) + def test_array_in_struct(self): # See bpo-22273 diff --git a/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst b/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst new file mode 100644 index 00000000000000..3fefbc3efb63c0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-17-07-22-33.bpo-18060.5mqTQM.rst @@ -0,0 +1,2 @@ +Fixed a class inheritance issue that can cause segfaults when deriving two or more levels of subclasses from a base class of Structure or Union. + diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index f909a9496b6526..fc16b9176fd1c0 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -4354,10 +4354,10 @@ _init_pos_args(PyObject *self, PyTypeObject *type, return index; } - for (i = 0; - i < dict->length && (i+index) < PyTuple_GET_SIZE(args); + for (i = index; + i < dict->length && i < PyTuple_GET_SIZE(args); ++i) { - PyObject *pair = PySequence_GetItem(fields, i); + PyObject *pair = PySequence_GetItem(fields, i - index); PyObject *name, *val; int res; if (!pair) @@ -4367,7 +4367,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type, Py_DECREF(pair); return -1; } - val = PyTuple_GET_ITEM(args, i + index); + val = PyTuple_GET_ITEM(args, i); if (kwds) { res = PyDict_Contains(kwds, name); if (res != 0) { @@ -4388,7 +4388,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type, if (res == -1) return -1; } - return index + dict->length; + return dict->length; } static int diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index dfdb96b0e7258a..fb3e20e8db3e27 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -695,7 +695,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct stgdict->size = aligned_size; stgdict->align = total_align; - stgdict->length = len; /* ADD ffi_ofs? */ + stgdict->length = ffi_ofs + len; /* * The value of MAX_STRUCT_SIZE depends on the platform Python is running on. From d0b0e3d2eff30f699c620bc87c4dadd8cd4a77d5 Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Mon, 1 Jan 2024 19:38:29 +0100 Subject: [PATCH 415/442] gh-113536: Expose `os.waitid` on macOS (#113542) * gh-113536: Expose `os.waitid` on macOS This API has been available on macOS for a long time, but was explicitly excluded due to unspecified problems with the API in ancient versions of macOS. * Document that the API is available on macOS starting in Python 3.13 --- Doc/library/os.rst | 6 +++--- .../2023-12-28-12-18-39.gh-issue-113536.0ythg7.rst | 1 + Modules/clinic/posixmodule.c.h | 6 +++--- Modules/posixmodule.c | 14 +++++++------- 4 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/macOS/2023-12-28-12-18-39.gh-issue-113536.0ythg7.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 2af61f2960cc63..637191f2980a05 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4986,11 +4986,11 @@ written in Python, such as a mail server's external command delivery program. .. availability:: Unix, not Emscripten, not WASI. - .. note:: - This function is not available on macOS. - .. versionadded:: 3.3 + .. versionchanged:: 3.13 + This function is now available on macOS as well. + .. function:: waitpid(pid, options, /) diff --git a/Misc/NEWS.d/next/macOS/2023-12-28-12-18-39.gh-issue-113536.0ythg7.rst b/Misc/NEWS.d/next/macOS/2023-12-28-12-18-39.gh-issue-113536.0ythg7.rst new file mode 100644 index 00000000000000..828b872d283627 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-28-12-18-39.gh-issue-113536.0ythg7.rst @@ -0,0 +1 @@ +:func:`os.waitid` is now available on macOS diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index b7639af4b78a9d..ba3e1cfa8dbc21 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -5467,7 +5467,7 @@ os_wait4(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw #endif /* defined(HAVE_WAIT4) */ -#if (defined(HAVE_WAITID) && !defined(__APPLE__)) +#if defined(HAVE_WAITID) PyDoc_STRVAR(os_waitid__doc__, "waitid($module, idtype, id, options, /)\n" @@ -5510,7 +5510,7 @@ os_waitid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -#endif /* (defined(HAVE_WAITID) && !defined(__APPLE__)) */ +#endif /* defined(HAVE_WAITID) */ #if defined(HAVE_WAITPID) @@ -12422,4 +12422,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=b82391c4f58231b6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=18c128534c355d84 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index f4a18536e8f1e1..39b1f3cb7b2b9b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1024,7 +1024,7 @@ typedef struct { PyObject *TerminalSizeType; PyObject *TimesResultType; PyObject *UnameResultType; -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) PyObject *WaitidResultType; #endif #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) @@ -2292,7 +2292,7 @@ static PyStructSequence_Desc statvfs_result_desc = { 10 }; -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) PyDoc_STRVAR(waitid_result__doc__, "waitid_result: Result from waitid.\n\n\ This object may be accessed either as a tuple of\n\ @@ -2367,7 +2367,7 @@ _posix_clear(PyObject *module) Py_CLEAR(state->TerminalSizeType); Py_CLEAR(state->TimesResultType); Py_CLEAR(state->UnameResultType); -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) Py_CLEAR(state->WaitidResultType); #endif #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) @@ -2392,7 +2392,7 @@ _posix_traverse(PyObject *module, visitproc visit, void *arg) Py_VISIT(state->TerminalSizeType); Py_VISIT(state->TimesResultType); Py_VISIT(state->UnameResultType); -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) Py_VISIT(state->WaitidResultType); #endif #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) @@ -9518,7 +9518,7 @@ os_wait4_impl(PyObject *module, pid_t pid, int options) #endif /* HAVE_WAIT4 */ -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) /*[clinic input] os.waitid @@ -9575,7 +9575,7 @@ os_waitid_impl(PyObject *module, idtype_t idtype, id_t id, int options) return result; } -#endif /* defined(HAVE_WAITID) && !defined(__APPLE__) */ +#endif /* defined(HAVE_WAITID) */ #if defined(HAVE_WAITPID) @@ -17309,7 +17309,7 @@ posixmodule_exec(PyObject *m) return -1; } -#if defined(HAVE_WAITID) && !defined(__APPLE__) +#if defined(HAVE_WAITID) waitid_result_desc.name = MODNAME ".waitid_result"; state->WaitidResultType = (PyObject *)PyStructSequence_NewType(&waitid_result_desc); if (PyModule_AddObjectRef(m, "waitid_result", state->WaitidResultType) < 0) { From b4b2cc101216ae1017898dfbe43c90da2fd0a308 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 2 Jan 2024 02:51:24 +0800 Subject: [PATCH 416/442] gh-53502: add a new option aware_datetime in plistlib to loads or dumps aware datetime. (#113363) * add options to loads and dumps aware datetime in plistlib --- Doc/library/plistlib.rst | 22 +++++- Lib/plistlib.py | 60 ++++++++++----- Lib/test/test_plistlib.py | 73 +++++++++++++++++++ ...3-12-21-23-47-42.gh-issue-53502.dercJI.rst | 2 + 4 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-21-23-47-42.gh-issue-53502.dercJI.rst diff --git a/Doc/library/plistlib.rst b/Doc/library/plistlib.rst index 732ef3536863cc..10f1a48fc70a72 100644 --- a/Doc/library/plistlib.rst +++ b/Doc/library/plistlib.rst @@ -52,7 +52,7 @@ or :class:`datetime.datetime` objects. This module defines the following functions: -.. function:: load(fp, *, fmt=None, dict_type=dict) +.. function:: load(fp, *, fmt=None, dict_type=dict, aware_datetime=False) Read a plist file. *fp* should be a readable and binary file object. Return the unpacked root object (which usually is a @@ -69,6 +69,10 @@ This module defines the following functions: The *dict_type* is the type used for dictionaries that are read from the plist file. + When *aware_datetime* is true, fields with type ``datetime.datetime`` will + be created as :ref:`aware object `, with + :attr:`!tzinfo` as :attr:`datetime.UTC`. + XML data for the :data:`FMT_XML` format is parsed using the Expat parser from :mod:`xml.parsers.expat` -- see its documentation for possible exceptions on ill-formed XML. Unknown elements will simply be ignored @@ -79,8 +83,11 @@ This module defines the following functions: .. versionadded:: 3.4 + .. versionchanged:: 3.13 + The keyword-only parameter *aware_datetime* has been added. + -.. function:: loads(data, *, fmt=None, dict_type=dict) +.. function:: loads(data, *, fmt=None, dict_type=dict, aware_datetime=False) Load a plist from a bytes object. See :func:`load` for an explanation of the keyword arguments. @@ -88,7 +95,7 @@ This module defines the following functions: .. versionadded:: 3.4 -.. function:: dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False) +.. function:: dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False, aware_datetime=False) Write *value* to a plist file. *Fp* should be a writable, binary file object. @@ -107,6 +114,10 @@ This module defines the following functions: When *skipkeys* is false (the default) the function raises :exc:`TypeError` when a key of a dictionary is not a string, otherwise such keys are skipped. + When *aware_datetime* is true and any field with type ``datetime.datetime`` + is set as a :ref:`aware object `, it will convert to + UTC timezone before writing it. + A :exc:`TypeError` will be raised if the object is of an unsupported type or a container that contains objects of unsupported types. @@ -115,8 +126,11 @@ This module defines the following functions: .. versionadded:: 3.4 + .. versionchanged:: 3.13 + The keyword-only parameter *aware_datetime* has been added. + -.. function:: dumps(value, *, fmt=FMT_XML, sort_keys=True, skipkeys=False) +.. function:: dumps(value, *, fmt=FMT_XML, sort_keys=True, skipkeys=False, aware_datetime=False) Return *value* as a plist-formatted bytes object. See the documentation for :func:`dump` for an explanation of the keyword diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 3292c30d5fb29b..0fc1b5cbfa8c49 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -140,7 +140,7 @@ def _decode_base64(s): _dateParser = re.compile(r"(?P\d\d\d\d)(?:-(?P\d\d)(?:-(?P\d\d)(?:T(?P\d\d)(?::(?P\d\d)(?::(?P\d\d))?)?)?)?)?Z", re.ASCII) -def _date_from_string(s): +def _date_from_string(s, aware_datetime): order = ('year', 'month', 'day', 'hour', 'minute', 'second') gd = _dateParser.match(s).groupdict() lst = [] @@ -149,10 +149,14 @@ def _date_from_string(s): if val is None: break lst.append(int(val)) + if aware_datetime: + return datetime.datetime(*lst, tzinfo=datetime.UTC) return datetime.datetime(*lst) -def _date_to_string(d): +def _date_to_string(d, aware_datetime): + if aware_datetime: + d = d.astimezone(datetime.UTC) return '%04d-%02d-%02dT%02d:%02d:%02dZ' % ( d.year, d.month, d.day, d.hour, d.minute, d.second @@ -171,11 +175,12 @@ def _escape(text): return text class _PlistParser: - def __init__(self, dict_type): + def __init__(self, dict_type, aware_datetime=False): self.stack = [] self.current_key = None self.root = None self._dict_type = dict_type + self._aware_datetime = aware_datetime def parse(self, fileobj): self.parser = ParserCreate() @@ -277,7 +282,8 @@ def end_data(self): self.add_object(_decode_base64(self.get_data())) def end_date(self): - self.add_object(_date_from_string(self.get_data())) + self.add_object(_date_from_string(self.get_data(), + aware_datetime=self._aware_datetime)) class _DumbXMLWriter: @@ -321,13 +327,14 @@ def writeln(self, line): class _PlistWriter(_DumbXMLWriter): def __init__( self, file, indent_level=0, indent=b"\t", writeHeader=1, - sort_keys=True, skipkeys=False): + sort_keys=True, skipkeys=False, aware_datetime=False): if writeHeader: file.write(PLISTHEADER) _DumbXMLWriter.__init__(self, file, indent_level, indent) self._sort_keys = sort_keys self._skipkeys = skipkeys + self._aware_datetime = aware_datetime def write(self, value): self.writeln("") @@ -360,7 +367,8 @@ def write_value(self, value): self.write_bytes(value) elif isinstance(value, datetime.datetime): - self.simple_element("date", _date_to_string(value)) + self.simple_element("date", + _date_to_string(value, self._aware_datetime)) elif isinstance(value, (tuple, list)): self.write_array(value) @@ -461,8 +469,9 @@ class _BinaryPlistParser: see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c """ - def __init__(self, dict_type): + def __init__(self, dict_type, aware_datetime=False): self._dict_type = dict_type + self._aware_datime = aware_datetime def parse(self, fp): try: @@ -556,8 +565,11 @@ def _read_object(self, ref): f = struct.unpack('>d', self._fp.read(8))[0] # timestamp 0 of binary plists corresponds to 1/1/2001 # (year of Mac OS X 10.0), instead of 1/1/1970. - result = (datetime.datetime(2001, 1, 1) + - datetime.timedelta(seconds=f)) + if self._aware_datime: + epoch = datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC) + else: + epoch = datetime.datetime(2001, 1, 1) + result = epoch + datetime.timedelta(seconds=f) elif tokenH == 0x40: # data s = self._get_size(tokenL) @@ -629,10 +641,11 @@ def _count_to_size(count): _scalars = (str, int, float, datetime.datetime, bytes) class _BinaryPlistWriter (object): - def __init__(self, fp, sort_keys, skipkeys): + def __init__(self, fp, sort_keys, skipkeys, aware_datetime=False): self._fp = fp self._sort_keys = sort_keys self._skipkeys = skipkeys + self._aware_datetime = aware_datetime def write(self, value): @@ -778,7 +791,12 @@ def _write_object(self, value): self._fp.write(struct.pack('>Bd', 0x23, value)) elif isinstance(value, datetime.datetime): - f = (value - datetime.datetime(2001, 1, 1)).total_seconds() + if self._aware_datetime: + dt = value.astimezone(datetime.UTC) + offset = dt - datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC) + f = offset.total_seconds() + else: + f = (value - datetime.datetime(2001, 1, 1)).total_seconds() self._fp.write(struct.pack('>Bd', 0x33, f)) elif isinstance(value, (bytes, bytearray)): @@ -862,7 +880,7 @@ def _is_fmt_binary(header): } -def load(fp, *, fmt=None, dict_type=dict): +def load(fp, *, fmt=None, dict_type=dict, aware_datetime=False): """Read a .plist file. 'fp' should be a readable and binary file object. Return the unpacked root object (which usually is a dictionary). """ @@ -880,32 +898,36 @@ def load(fp, *, fmt=None, dict_type=dict): else: P = _FORMATS[fmt]['parser'] - p = P(dict_type=dict_type) + p = P(dict_type=dict_type, aware_datetime=aware_datetime) return p.parse(fp) -def loads(value, *, fmt=None, dict_type=dict): +def loads(value, *, fmt=None, dict_type=dict, aware_datetime=False): """Read a .plist file from a bytes object. Return the unpacked root object (which usually is a dictionary). """ fp = BytesIO(value) - return load(fp, fmt=fmt, dict_type=dict_type) + return load(fp, fmt=fmt, dict_type=dict_type, aware_datetime=aware_datetime) -def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False): +def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False, + aware_datetime=False): """Write 'value' to a .plist file. 'fp' should be a writable, binary file object. """ if fmt not in _FORMATS: raise ValueError("Unsupported format: %r"%(fmt,)) - writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys) + writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys, + aware_datetime=aware_datetime) writer.write(value) -def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True): +def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True, + aware_datetime=False): """Return a bytes object with the contents for a .plist file. """ fp = BytesIO() - dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys) + dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys, + aware_datetime=aware_datetime) return fp.getvalue() diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index b08ababa341cfe..d41975f1b17184 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -13,6 +13,8 @@ import subprocess import binascii import collections +import time +import zoneinfo from test import support from test.support import os_helper from io import BytesIO @@ -838,6 +840,54 @@ def test_xml_plist_with_entity_decl(self): "XML entity declarations are not supported"): plistlib.loads(XML_PLIST_WITH_ENTITY, fmt=plistlib.FMT_XML) + def test_load_aware_datetime(self): + dt = plistlib.loads(b"2023-12-10T08:03:30Z", + aware_datetime=True) + self.assertEqual(dt.tzinfo, datetime.UTC) + + @unittest.skipUnless("America/Los_Angeles" in zoneinfo.available_timezones(), + "Can't find timezone datebase") + def test_dump_aware_datetime(self): + dt = datetime.datetime(2345, 6, 7, 8, 9, 10, + tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles")) + for fmt in ALL_FORMATS: + s = plistlib.dumps(dt, fmt=fmt, aware_datetime=True) + loaded_dt = plistlib.loads(s, fmt=fmt, aware_datetime=True) + self.assertEqual(loaded_dt.tzinfo, datetime.UTC) + self.assertEqual(loaded_dt, dt) + + def test_dump_utc_aware_datetime(self): + dt = datetime.datetime(2345, 6, 7, 8, 9, 10, tzinfo=datetime.UTC) + for fmt in ALL_FORMATS: + s = plistlib.dumps(dt, fmt=fmt, aware_datetime=True) + loaded_dt = plistlib.loads(s, fmt=fmt, aware_datetime=True) + self.assertEqual(loaded_dt.tzinfo, datetime.UTC) + self.assertEqual(loaded_dt, dt) + + @unittest.skipUnless("America/Los_Angeles" in zoneinfo.available_timezones(), + "Can't find timezone datebase") + def test_dump_aware_datetime_without_aware_datetime_option(self): + dt = datetime.datetime(2345, 6, 7, 8, + tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles")) + s = plistlib.dumps(dt, fmt=plistlib.FMT_XML, aware_datetime=False) + self.assertIn(b"2345-06-07T08:00:00Z", s) + + def test_dump_utc_aware_datetime_without_aware_datetime_option(self): + dt = datetime.datetime(2345, 6, 7, 8, tzinfo=datetime.UTC) + s = plistlib.dumps(dt, fmt=plistlib.FMT_XML, aware_datetime=False) + self.assertIn(b"2345-06-07T08:00:00Z", s) + + def test_dump_naive_datetime_with_aware_datetime_option(self): + # Save a naive datetime with aware_datetime set to true. This will lead + # to having different time as compared to the current machine's + # timezone, which is UTC. + dt = datetime.datetime(2345, 6, 7, 8, tzinfo=None) + for fmt in ALL_FORMATS: + s = plistlib.dumps(dt, fmt=fmt, aware_datetime=True) + parsed = plistlib.loads(s, aware_datetime=False) + expected = dt + datetime.timedelta(seconds=time.timezone) + self.assertEqual(parsed, expected) + class TestBinaryPlistlib(unittest.TestCase): @@ -962,6 +1012,28 @@ def test_invalid_binary(self): with self.assertRaises(plistlib.InvalidFileException): plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) + def test_load_aware_datetime(self): + data = (b'bplist003B\x04>\xd0d\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00' + b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11') + self.assertEqual(plistlib.loads(data, aware_datetime=True), + datetime.datetime(2345, 6, 7, 8, tzinfo=datetime.UTC)) + + @unittest.skipUnless("America/Los_Angeles" in zoneinfo.available_timezones(), + "Can't find timezone datebase") + def test_dump_aware_datetime_without_aware_datetime_option(self): + dt = datetime.datetime(2345, 6, 7, 8, + tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles")) + msg = "can't subtract offset-naive and offset-aware datetimes" + with self.assertRaisesRegex(TypeError, msg): + plistlib.dumps(dt, fmt=plistlib.FMT_BINARY, aware_datetime=False) + + def test_dump_utc_aware_datetime_without_aware_datetime_option(self): + dt = datetime.datetime(2345, 6, 7, 8, tzinfo=datetime.UTC) + msg = "can't subtract offset-naive and offset-aware datetimes" + with self.assertRaisesRegex(TypeError, msg): + plistlib.dumps(dt, fmt=plistlib.FMT_BINARY, aware_datetime=False) + class TestKeyedArchive(unittest.TestCase): def test_keyed_archive_data(self): @@ -1072,5 +1144,6 @@ def test_octal_and_hex(self): self.assertEqual(p.get("HexType"), 16777228) self.assertEqual(p.get("IntType"), 83) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2023-12-21-23-47-42.gh-issue-53502.dercJI.rst b/Misc/NEWS.d/next/Library/2023-12-21-23-47-42.gh-issue-53502.dercJI.rst new file mode 100644 index 00000000000000..aa7274161d4166 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-21-23-47-42.gh-issue-53502.dercJI.rst @@ -0,0 +1,2 @@ +Add a new option ``aware_datetime`` in :mod:`plistlib` to loads or dumps +aware datetime. From 8e4ff5c7885abb04a66d079499335c4d46106aff Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Mon, 1 Jan 2024 21:31:43 +0100 Subject: [PATCH 417/442] gh-53502: Fixes for tests in gh-113363 (#113627) * gh-53502: Fixes for tests in gh-113363 * Use 32-bit compatible date in test_dump_naive_datetime_with_aware_datetime_option * Saving non-aware datetimes will use the old behaviour regardless of the aware_datimetime setting --- Lib/plistlib.py | 4 ++-- Lib/test/test_plistlib.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 0fc1b5cbfa8c49..6eb70cedd7aec6 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -155,7 +155,7 @@ def _date_from_string(s, aware_datetime): def _date_to_string(d, aware_datetime): - if aware_datetime: + if aware_datetime and d.tzinfo is not None: d = d.astimezone(datetime.UTC) return '%04d-%02d-%02dT%02d:%02d:%02dZ' % ( d.year, d.month, d.day, @@ -791,7 +791,7 @@ def _write_object(self, value): self._fp.write(struct.pack('>Bd', 0x23, value)) elif isinstance(value, datetime.datetime): - if self._aware_datetime: + if self._aware_datetime and value.tzinfo is not None: dt = value.astimezone(datetime.UTC) offset = dt - datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC) f = offset.total_seconds() diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index d41975f1b17184..010393a417b946 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -881,12 +881,11 @@ def test_dump_naive_datetime_with_aware_datetime_option(self): # Save a naive datetime with aware_datetime set to true. This will lead # to having different time as compared to the current machine's # timezone, which is UTC. - dt = datetime.datetime(2345, 6, 7, 8, tzinfo=None) + dt = datetime.datetime(2003, 6, 7, 8, tzinfo=None) for fmt in ALL_FORMATS: s = plistlib.dumps(dt, fmt=fmt, aware_datetime=True) parsed = plistlib.loads(s, aware_datetime=False) - expected = dt + datetime.timedelta(seconds=time.timezone) - self.assertEqual(parsed, expected) + self.assertEqual(parsed, dt) class TestBinaryPlistlib(unittest.TestCase): From b2566d89ce50e9924bb2fccb87dcfa3ceb6cc0d6 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Mon, 1 Jan 2024 15:04:09 -0800 Subject: [PATCH 418/442] GH-113633: Use module state structure for _testcapi. (GH-113634) Use module state structure for _testcapi. --- ...-01-01-14-40-02.gh-issue-113633.VOY5ai.rst | 1 + Modules/_testcapimodule.c | 115 ++++++++++-------- 2 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-01-01-14-40-02.gh-issue-113633.VOY5ai.rst diff --git a/Misc/NEWS.d/next/Tests/2024-01-01-14-40-02.gh-issue-113633.VOY5ai.rst b/Misc/NEWS.d/next/Tests/2024-01-01-14-40-02.gh-issue-113633.VOY5ai.rst new file mode 100644 index 00000000000000..150c0d91852cdf --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-01-01-14-40-02.gh-issue-113633.VOY5ai.rst @@ -0,0 +1 @@ +Use module state for the _testcapi extension module. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3527dfa77279ac..6762c611fb12a2 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -33,15 +33,32 @@ // Forward declarations static struct PyModuleDef _testcapimodule; -static PyObject *TestError; /* set to exception object in init */ +// Module state +typedef struct { + PyObject *error; // _testcapi.error object +} testcapistate_t; + +static testcapistate_t* +get_testcapi_state(PyObject *module) +{ + void *state = PyModule_GetState(module); + assert(state != NULL); + return (testcapistate_t *)state; +} -/* Raise TestError with test_name + ": " + msg, and return NULL. */ +static PyObject * +get_testerror(PyObject *self) { + testcapistate_t *state = get_testcapi_state((PyObject *)Py_TYPE(self)); + return state->error; +} + +/* Raise _testcapi.error with test_name + ": " + msg, and return NULL. */ static PyObject * -raiseTestError(const char* test_name, const char* msg) +raiseTestError(PyObject *self, const char* test_name, const char* msg) { - PyErr_Format(TestError, "%s: %s", test_name, msg); + PyErr_Format(get_testerror(self), "%s: %s", test_name, msg); return NULL; } @@ -52,10 +69,10 @@ raiseTestError(const char* test_name, const char* msg) platforms have these hardcoded. Better safe than sorry. */ static PyObject* -sizeof_error(const char* fatname, const char* typname, +sizeof_error(PyObject *self, const char* fatname, const char* typname, int expected, int got) { - PyErr_Format(TestError, + PyErr_Format(get_testerror(self), "%s #define == %d but sizeof(%s) == %d", fatname, expected, typname, got); return (PyObject*)NULL; @@ -66,7 +83,7 @@ test_config(PyObject *self, PyObject *Py_UNUSED(ignored)) { #define CHECK_SIZEOF(FATNAME, TYPE) \ if (FATNAME != sizeof(TYPE)) \ - return sizeof_error(#FATNAME, #TYPE, FATNAME, sizeof(TYPE)) + return sizeof_error(self, #FATNAME, #TYPE, FATNAME, sizeof(TYPE)) CHECK_SIZEOF(SIZEOF_SHORT, short); CHECK_SIZEOF(SIZEOF_INT, int); @@ -89,7 +106,7 @@ test_sizeof_c_types(PyObject *self, PyObject *Py_UNUSED(ignored)) #endif #define CHECK_SIZEOF(TYPE, EXPECTED) \ if (EXPECTED != sizeof(TYPE)) { \ - PyErr_Format(TestError, \ + PyErr_Format(get_testerror(self), \ "sizeof(%s) = %u instead of %u", \ #TYPE, sizeof(TYPE), EXPECTED); \ return (PyObject*)NULL; \ @@ -97,7 +114,7 @@ test_sizeof_c_types(PyObject *self, PyObject *Py_UNUSED(ignored)) #define IS_SIGNED(TYPE) (((TYPE)-1) < (TYPE)0) #define CHECK_SIGNNESS(TYPE, SIGNED) \ if (IS_SIGNED(TYPE) != SIGNED) { \ - PyErr_Format(TestError, \ + PyErr_Format(get_testerror(self), \ "%s signness is, instead of %i", \ #TYPE, IS_SIGNED(TYPE), SIGNED); \ return (PyObject*)NULL; \ @@ -170,7 +187,7 @@ test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored)) for (i = 0; i < NLIST; ++i) { PyObject* anint = PyList_GET_ITEM(list, i); if (PyLong_AS_LONG(anint) != NLIST-1-i) { - PyErr_SetString(TestError, + PyErr_SetString(get_testerror(self), "test_list_api: reverse screwed up"); Py_DECREF(list); return (PyObject*)NULL; @@ -183,7 +200,7 @@ test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored)) } static int -test_dict_inner(int count) +test_dict_inner(PyObject *self, int count) { Py_ssize_t pos = 0, iterations = 0; int i; @@ -231,7 +248,7 @@ test_dict_inner(int count) if (iterations != count) { PyErr_SetString( - TestError, + get_testerror(self), "test_dict_iteration: dict iteration went wrong "); return -1; } else { @@ -250,7 +267,7 @@ test_dict_iteration(PyObject* self, PyObject *Py_UNUSED(ignored)) int i; for (i = 0; i < 200; i++) { - if (test_dict_inner(i) < 0) { + if (test_dict_inner(self, i) < 0) { return NULL; } } @@ -334,14 +351,14 @@ test_lazy_hash_inheritance(PyObject* self, PyObject *Py_UNUSED(ignored)) if (obj == NULL) { PyErr_Clear(); PyErr_SetString( - TestError, + get_testerror(self), "test_lazy_hash_inheritance: failed to create object"); return NULL; } if (type->tp_dict != NULL) { PyErr_SetString( - TestError, + get_testerror(self), "test_lazy_hash_inheritance: type initialised too soon"); Py_DECREF(obj); return NULL; @@ -351,7 +368,7 @@ test_lazy_hash_inheritance(PyObject* self, PyObject *Py_UNUSED(ignored)) if ((hash == -1) && PyErr_Occurred()) { PyErr_Clear(); PyErr_SetString( - TestError, + get_testerror(self), "test_lazy_hash_inheritance: could not hash object"); Py_DECREF(obj); return NULL; @@ -359,7 +376,7 @@ test_lazy_hash_inheritance(PyObject* self, PyObject *Py_UNUSED(ignored)) if (type->tp_dict == NULL) { PyErr_SetString( - TestError, + get_testerror(self), "test_lazy_hash_inheritance: type not initialised by hash()"); Py_DECREF(obj); return NULL; @@ -367,7 +384,7 @@ test_lazy_hash_inheritance(PyObject* self, PyObject *Py_UNUSED(ignored)) if (type->tp_hash != PyType_Type.tp_hash) { PyErr_SetString( - TestError, + get_testerror(self), "test_lazy_hash_inheritance: unexpected hash function"); Py_DECREF(obj); return NULL; @@ -427,7 +444,7 @@ py_buildvalue_ints(PyObject *self, PyObject *args) } static int -test_buildvalue_N_error(const char *fmt) +test_buildvalue_N_error(PyObject *self, const char *fmt) { PyObject *arg, *res; @@ -443,7 +460,7 @@ test_buildvalue_N_error(const char *fmt) } Py_DECREF(res); if (Py_REFCNT(arg) != 1) { - PyErr_Format(TestError, "test_buildvalue_N: " + PyErr_Format(get_testerror(self), "test_buildvalue_N: " "arg was not decrefed in successful " "Py_BuildValue(\"%s\")", fmt); return -1; @@ -452,13 +469,13 @@ test_buildvalue_N_error(const char *fmt) Py_INCREF(arg); res = Py_BuildValue(fmt, raise_error, NULL, arg); if (res != NULL || !PyErr_Occurred()) { - PyErr_Format(TestError, "test_buildvalue_N: " + PyErr_Format(get_testerror(self), "test_buildvalue_N: " "Py_BuildValue(\"%s\") didn't complain", fmt); return -1; } PyErr_Clear(); if (Py_REFCNT(arg) != 1) { - PyErr_Format(TestError, "test_buildvalue_N: " + PyErr_Format(get_testerror(self), "test_buildvalue_N: " "arg was not decrefed in failed " "Py_BuildValue(\"%s\")", fmt); return -1; @@ -482,25 +499,25 @@ test_buildvalue_N(PyObject *self, PyObject *Py_UNUSED(ignored)) return NULL; } if (res != arg) { - return raiseTestError("test_buildvalue_N", + return raiseTestError(self, "test_buildvalue_N", "Py_BuildValue(\"N\") returned wrong result"); } if (Py_REFCNT(arg) != 2) { - return raiseTestError("test_buildvalue_N", + return raiseTestError(self, "test_buildvalue_N", "arg was not decrefed in Py_BuildValue(\"N\")"); } Py_DECREF(res); Py_DECREF(arg); - if (test_buildvalue_N_error("O&N") < 0) + if (test_buildvalue_N_error(self, "O&N") < 0) return NULL; - if (test_buildvalue_N_error("(O&N)") < 0) + if (test_buildvalue_N_error(self, "(O&N)") < 0) return NULL; - if (test_buildvalue_N_error("[O&N]") < 0) + if (test_buildvalue_N_error(self, "[O&N]") < 0) return NULL; - if (test_buildvalue_N_error("{O&N}") < 0) + if (test_buildvalue_N_error(self, "{O&N}") < 0) return NULL; - if (test_buildvalue_N_error("{()O&(())N}") < 0) + if (test_buildvalue_N_error(self, "{()O&(())N}") < 0) return NULL; Py_RETURN_NONE; @@ -910,7 +927,7 @@ test_string_to_double(PyObject *self, PyObject *Py_UNUSED(ignored)) { Py_RETURN_NONE; fail: - return raiseTestError("test_string_to_double", msg); + return raiseTestError(self, "test_string_to_double", msg); #undef CHECK_STRING #undef CHECK_INVALID } @@ -1061,7 +1078,7 @@ test_capsule(PyObject *self, PyObject *Py_UNUSED(ignored)) exit: if (error) { - return raiseTestError("test_capsule", error); + return raiseTestError(self, "test_capsule", error); } Py_RETURN_NONE; #undef FAIL @@ -1272,7 +1289,7 @@ test_from_contiguous(PyObject* self, PyObject *Py_UNUSED(ignored)) ptr = view.buf; for (i = 0; i < 5; i++) { if (ptr[2*i] != i) { - PyErr_SetString(TestError, + PyErr_SetString(get_testerror(self), "test_from_contiguous: incorrect result"); return NULL; } @@ -1285,7 +1302,7 @@ test_from_contiguous(PyObject* self, PyObject *Py_UNUSED(ignored)) ptr = view.buf; for (i = 0; i < 5; i++) { if (*(ptr-2*i) != i) { - PyErr_SetString(TestError, + PyErr_SetString(get_testerror(self), "test_from_contiguous: incorrect result"); return NULL; } @@ -1338,7 +1355,7 @@ test_pep3118_obsolete_write_locks(PyObject* self, PyObject *Py_UNUSED(ignored)) Py_RETURN_NONE; error: - PyErr_SetString(TestError, + PyErr_SetString(get_testerror(self), "test_pep3118_obsolete_write_locks: failure"); return NULL; } @@ -1959,7 +1976,7 @@ test_pythread_tss_key_state(PyObject *self, PyObject *args) { Py_tss_t tss_key = Py_tss_NEEDS_INIT; if (PyThread_tss_is_created(&tss_key)) { - return raiseTestError("test_pythread_tss_key_state", + return raiseTestError(self, "test_pythread_tss_key_state", "TSS key not in an uninitialized state at " "creation time"); } @@ -1968,19 +1985,19 @@ test_pythread_tss_key_state(PyObject *self, PyObject *args) return NULL; } if (!PyThread_tss_is_created(&tss_key)) { - return raiseTestError("test_pythread_tss_key_state", + return raiseTestError(self, "test_pythread_tss_key_state", "PyThread_tss_create succeeded, " "but with TSS key in an uninitialized state"); } if (PyThread_tss_create(&tss_key) != 0) { - return raiseTestError("test_pythread_tss_key_state", + return raiseTestError(self, "test_pythread_tss_key_state", "PyThread_tss_create unsuccessful with " "an already initialized key"); } #define CHECK_TSS_API(expr) \ (void)(expr); \ if (!PyThread_tss_is_created(&tss_key)) { \ - return raiseTestError("test_pythread_tss_key_state", \ + return raiseTestError(self, "test_pythread_tss_key_state", \ "TSS key initialization state was not " \ "preserved after calling " #expr); } CHECK_TSS_API(PyThread_tss_set(&tss_key, NULL)); @@ -1988,7 +2005,7 @@ test_pythread_tss_key_state(PyObject *self, PyObject *args) #undef CHECK_TSS_API PyThread_tss_delete(&tss_key); if (PyThread_tss_is_created(&tss_key)) { - return raiseTestError("test_pythread_tss_key_state", + return raiseTestError(self, "test_pythread_tss_key_state", "PyThread_tss_delete called, but did not " "set the key state to uninitialized"); } @@ -1999,7 +2016,7 @@ test_pythread_tss_key_state(PyObject *self, PyObject *args) return NULL; } if (PyThread_tss_is_created(ptr_key)) { - return raiseTestError("test_pythread_tss_key_state", + return raiseTestError(self, "test_pythread_tss_key_state", "TSS key not in an uninitialized state at " "allocation time"); } @@ -3831,14 +3848,9 @@ static PyTypeObject ContainerNoGC_type = { static struct PyModuleDef _testcapimodule = { PyModuleDef_HEAD_INIT, - "_testcapi", - NULL, - -1, - TestMethods, - NULL, - NULL, - NULL, - NULL + .m_name = "_testcapi", + .m_size = sizeof(testcapistate_t), + .m_methods = TestMethods, }; /* Per PEP 489, this module will not be converted to multi-phase initialization @@ -3933,9 +3945,10 @@ PyInit__testcapi(void) PyModule_AddIntConstant(m, "the_number_three", 3); PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT); - TestError = PyErr_NewException("_testcapi.error", NULL, NULL); - Py_INCREF(TestError); - PyModule_AddObject(m, "error", TestError); + testcapistate_t *state = get_testcapi_state(m); + state->error = PyErr_NewException("_testcapi.error", NULL, NULL); + Py_INCREF(state->error); + PyModule_AddObject(m, "error", state->error); if (PyType_Ready(&ContainerNoGC_type) < 0) { return NULL; From 3aadb9508592877c429083f213fa03bda1045ca1 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 2 Jan 2024 09:16:53 +0900 Subject: [PATCH 419/442] no-issue: Use the official term "free-threading" for GitHub Action (gh-113622) --- .github/workflows/build.yml | 28 +++++++++++++------------- .github/workflows/reusable-macos.yml | 4 ++-- .github/workflows/reusable-windows.yml | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8b44a7c6952a4..9f67f30ed07d74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,13 +187,13 @@ jobs: if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-windows.yml - build_windows_free_threaded: - name: 'Windows (free-threaded)' + build_windows_free_threading: + name: 'Windows (free-threading)' needs: check_source if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-windows.yml with: - free-threaded: true + free-threading: true build_macos: name: 'macOS' @@ -203,14 +203,14 @@ jobs: with: config_hash: ${{ needs.check_source.outputs.config_hash }} - build_macos_free_threaded: - name: 'macOS (free-threaded)' + build_macos_free_threading: + name: 'macOS (free-threading)' needs: check_source if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-macos.yml with: config_hash: ${{ needs.check_source.outputs.config_hash }} - free-threaded: true + free-threading: true build_ubuntu: name: 'Ubuntu' @@ -225,8 +225,8 @@ jobs: --with-pydebug \ --with-openssl=$OPENSSL_DIR - build_ubuntu_free_threaded: - name: 'Ubuntu (free-threaded)' + build_ubuntu_free_threading: + name: 'Ubuntu (free-threading)' needs: check_source if: needs.check_source.outputs.run_tests == 'true' uses: ./.github/workflows/reusable-ubuntu.yml @@ -504,12 +504,12 @@ jobs: - check-docs - check_generated_files - build_macos - - build_macos_free_threaded + - build_macos_free_threading - build_ubuntu - - build_ubuntu_free_threaded + - build_ubuntu_free_threading - build_ubuntu_ssltests - build_windows - - build_windows_free_threaded + - build_windows_free_threading - test_hypothesis - build_asan - cifuzz @@ -537,12 +537,12 @@ jobs: && ' check_generated_files, build_macos, - build_macos_free_threaded, + build_macos_free_threading, build_ubuntu, - build_ubuntu_free_threaded, + build_ubuntu_free_threading, build_ubuntu_ssltests, build_windows, - build_windows_free_threaded, + build_windows_free_threading, build_asan, ' || '' diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 22f46d18e1b43a..c24b6e963ddfd6 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -4,7 +4,7 @@ on: config_hash: required: true type: string - free-threaded: + free-threading: required: false type: boolean default: false @@ -35,7 +35,7 @@ jobs: ./configure \ --config-cache \ --with-pydebug \ - ${{ inputs.free-threaded && '--disable-gil' || '' }} \ + ${{ inputs.free-threading && '--disable-gil' || '' }} \ --prefix=/opt/python-dev \ --with-openssl="$(brew --prefix openssl@3.0)" - name: Build CPython diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 47a3c10d2ca4c1..ae27c108d8368c 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -1,7 +1,7 @@ on: workflow_call: inputs: - free-threaded: + free-threading: required: false type: boolean default: false @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build CPython - run: .\PCbuild\build.bat -e -d -v -p Win32 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p Win32 ${{ inputs.free-threading && '--disable-gil' || '' }} - name: Display build info run: .\python.bat -m test.pythoninfo - name: Tests @@ -33,7 +33,7 @@ jobs: - name: Register MSVC problem matcher run: echo "::add-matcher::.github/problem-matchers/msvc.json" - name: Build CPython - run: .\PCbuild\build.bat -e -d -v -p x64 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p x64 ${{ inputs.free-threading && '--disable-gil' || '' }} - name: Display build info run: .\python.bat -m test.pythoninfo - name: Tests @@ -50,4 +50,4 @@ jobs: - name: Register MSVC problem matcher run: echo "::add-matcher::.github/problem-matchers/msvc.json" - name: Build CPython - run: .\PCbuild\build.bat -e -d -v -p arm64 ${{ inputs.free-threaded && '--disable-gil' || '' }} + run: .\PCbuild\build.bat -e -d -v -p arm64 ${{ inputs.free-threading && '--disable-gil' || '' }} From 7595380347610598a3f5529214a449660892537b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 2 Jan 2024 09:37:37 +0200 Subject: [PATCH 420/442] gh-101100: Fix Sphinx warnings from removed `~!` references (#113629) Co-authored-by: Alex Waygood --- Doc/whatsnew/3.11.rst | 2 +- Doc/whatsnew/3.3.rst | 2 +- Doc/whatsnew/3.4.rst | 14 +++++++------- Doc/whatsnew/3.5.rst | 2 +- Doc/whatsnew/3.7.rst | 8 ++++---- Doc/whatsnew/3.9.rst | 2 +- Misc/NEWS.d/3.11.0a1.rst | 6 +++--- Misc/NEWS.d/3.11.0a7.rst | 4 ++-- Misc/NEWS.d/3.9.0a3.rst | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index cae5a26bae1148..ce4c98eba71443 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1860,7 +1860,7 @@ Standard Library (Contributed by Erlend E. Aasland in :issue:`5846`.) -* :meth:`~!unittest.TestProgram.usageExit` is marked deprecated, to be removed +* :meth:`!unittest.TestProgram.usageExit` is marked deprecated, to be removed in 3.13. (Contributed by Carlos Damázio in :gh:`67048`.) diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 5674bc7f359b72..79e2dd9dcee361 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -1052,7 +1052,7 @@ their ``__init__`` method (for example, file objects) or in their crypt ----- -Addition of salt and modular crypt format (hashing method) and the :func:`~!crypt.mksalt` +Addition of salt and modular crypt format (hashing method) and the :func:`!mksalt` function to the :mod:`!crypt` module. (:issue:`10924`) diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index 72d12461d8f730..b26e3d36c4bfbc 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -605,15 +605,15 @@ Using ``ABC`` as a base class has essentially the same effect as specifying aifc ---- -The :meth:`~!aifc.aifc.getparams` method now returns a namedtuple rather than a +The :meth:`!getparams` method now returns a namedtuple rather than a plain tuple. (Contributed by Claudiu Popa in :issue:`17818`.) :func:`!aifc.open` now supports the context management protocol: when used in a -:keyword:`with` block, the :meth:`~!aifc.aifc.close` method of the returned +:keyword:`with` block, the :meth:`!close` method of the returned object will be called automatically at the end of the block. (Contributed by Serhiy Storchacha in :issue:`16486`.) -The :meth:`~!aifc.aifc.writeframesraw` and :meth:`~!aifc.aifc.writeframes` +The :meth:`!writeframesraw` and :meth:`!writeframes` methods now accept any :term:`bytes-like object`. (Contributed by Serhiy Storchaka in :issue:`8311`.) @@ -632,7 +632,7 @@ audioop :mod:`!audioop` now supports 24-bit samples. (Contributed by Serhiy Storchaka in :issue:`12866`.) -New :func:`~!audioop.byteswap` function converts big-endian samples to +New :func:`!byteswap` function converts big-endian samples to little-endian and vice versa. (Contributed by Serhiy Storchaka in :issue:`19641`.) @@ -1528,7 +1528,7 @@ work on Windows. This change was actually inadvertently made in 3.3.4. sunau ----- -The :meth:`~!sunau.getparams` method now returns a namedtuple rather than a +The :meth:`!getparams` method now returns a namedtuple rather than a plain tuple. (Contributed by Claudiu Popa in :issue:`18901`.) :meth:`!sunau.open` now supports the context management protocol: when used in a @@ -1540,8 +1540,8 @@ in :issue:`18878`.) support for writing 24 sample using the module. (Contributed by Serhiy Storchaka in :issue:`19261`.) -The :meth:`~!sunau.AU_write.writeframesraw` and -:meth:`~!sunau.AU_write.writeframes` methods now accept any :term:`bytes-like +The :meth:`!writeframesraw` and +:meth:`!writeframes` methods now accept any :term:`bytes-like object`. (Contributed by Serhiy Storchaka in :issue:`8311`.) diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index a32866094ffeb5..bbf2dc59a9f60a 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -1252,7 +1252,7 @@ Oberkirch in :issue:`21800`.) imghdr ------ -The :func:`~!imghdr.what` function now recognizes the +The :func:`!what` function now recognizes the `OpenEXR `_ format (contributed by Martin Vignali and Claudiu Popa in :issue:`20295`), and the `WebP `_ format diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 616e51571388a8..775a45a1b3ff06 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -851,7 +851,7 @@ crypt The :mod:`!crypt` module now supports the Blowfish hashing method. (Contributed by Serhiy Storchaka in :issue:`31664`.) -The :func:`~!crypt.mksalt` function now allows specifying the number of rounds +The :func:`!mksalt` function now allows specifying the number of rounds for hashing. (Contributed by Serhiy Storchaka in :issue:`31702`.) @@ -2004,15 +2004,15 @@ importlib --------- Methods -:meth:`MetaPathFinder.find_module() ` +:meth:`!MetaPathFinder.find_module()` (replaced by :meth:`MetaPathFinder.find_spec() `) and -:meth:`PathEntryFinder.find_loader() ` +:meth:`!PathEntryFinder.find_loader()` (replaced by :meth:`PathEntryFinder.find_spec() `) both deprecated in Python 3.4 now emit :exc:`DeprecationWarning`. -(Contributed by Matthias Bussonnier in :issue:`29576`) +(Contributed by Matthias Bussonnier in :issue:`29576`.) The :class:`importlib.abc.ResourceLoader` ABC has been deprecated in favour of :class:`importlib.abc.ResourceReader`. diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index cb2482ee48d7fa..0c85fe15915518 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -585,7 +585,7 @@ queue. nntplib ------- -:class:`~!nntplib.NNTP` and :class:`~!nntplib.NNTP_SSL` now raise a :class:`ValueError` +:class:`!NNTP` and :class:`!NNTP_SSL` now raise a :class:`ValueError` if the given timeout for their constructor is zero to prevent the creation of a non-blocking socket. (Contributed by Donghee Na in :issue:`39259`.) diff --git a/Misc/NEWS.d/3.11.0a1.rst b/Misc/NEWS.d/3.11.0a1.rst index 1c96c0760a57b2..ba7fb515305ff5 100644 --- a/Misc/NEWS.d/3.11.0a1.rst +++ b/Misc/NEWS.d/3.11.0a1.rst @@ -3483,9 +3483,9 @@ Improved reprs of :mod:`threading` synchronization objects: Deprecated the following :mod:`unittest` functions, scheduled for removal in Python 3.13: -* :func:`~!unittest.findTestCases` -* :func:`~!unittest.makeSuite` -* :func:`~!unittest.getTestCaseNames` +* :func:`!findTestCases` +* :func:`!makeSuite` +* :func:`!getTestCaseNames` Use :class:`~unittest.TestLoader` methods instead: diff --git a/Misc/NEWS.d/3.11.0a7.rst b/Misc/NEWS.d/3.11.0a7.rst index 6e41f9cbd933b5..79557d5c436593 100644 --- a/Misc/NEWS.d/3.11.0a7.rst +++ b/Misc/NEWS.d/3.11.0a7.rst @@ -1038,8 +1038,8 @@ Add optional parameter *dir_fd* in :func:`shutil.rmtree`. .. nonce: AixHW7 .. section: Library -:meth:`~!unittest.TestProgram.usageExit` is marked deprecated, to be removed -in 3.13. +:meth:`!unittest.TestProgram.usageExit` is marked as deprecated, +to be removed in Python 3.13. .. diff --git a/Misc/NEWS.d/3.9.0a3.rst b/Misc/NEWS.d/3.9.0a3.rst index 8a94848427382b..bc7f4f9c5d39c1 100644 --- a/Misc/NEWS.d/3.9.0a3.rst +++ b/Misc/NEWS.d/3.9.0a3.rst @@ -454,7 +454,7 @@ resilients to inaccessible sys.path entries (importlib_metadata v1.4.0). .. nonce: _S5VjC .. section: Library -:class:`~!nntplib.NNTP` and :class:`~!nntplib.NNTP_SSL` now raise a +:class:`!NNTP` and :class:`!NNTP_SSL` now raise a :class:`ValueError` if the given timeout for their constructor is zero to prevent the creation of a non-blocking socket. Patch by Donghee Na. @@ -498,7 +498,7 @@ prevent the creation of a non-blocking socket. Patch by Donghee Na. .. section: Library Updated the Gmane domain from news.gmane.org to news.gmane.io which is used -for examples of :class:`~!nntplib.NNTP` news reader server and nntplib tests. +for examples of :class:`!NNTP` news reader server and nntplib tests. .. From 8ff44f855450244d965dbf82c7f0a31de666007c Mon Sep 17 00:00:00 2001 From: "John D. McDonald" <43117960+Rasputin2@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:40:14 -0600 Subject: [PATCH 421/442] gh-81094: Refer to PEP 318 in compound_statements.rst (#113588) Co-authored-by: Hugo van Kemenade --- Doc/reference/compound_stmts.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 7a735095bdecb2..374404bf33abbe 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1362,12 +1362,15 @@ access the local variables of the function containing the def. See section :pep:`526` - Syntax for Variable Annotations Ability to type hint variable declarations, including class - variables and instance variables + variables and instance variables. :pep:`563` - Postponed Evaluation of Annotations Support for forward references within annotations by preserving annotations in a string form at runtime instead of eager evaluation. + :pep:`318` - Decorators for Functions and Methods + Function and method decorators were introduced. + Class decorators were introduced in :pep:`3129`. .. _class: From 9ed36d533ab8b256f0a589b5be6d7a2fdcf4aff2 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Tue, 2 Jan 2024 13:00:52 +0000 Subject: [PATCH 422/442] gh-113602: Bail out when the parser tries to override existing errors (#113607) Signed-off-by: Pablo Galindo --- Lib/test/test_syntax.py | 2 ++ .../2024-01-01-00-07-02.gh-issue-113602.cWuTzk.rst | 2 ++ Parser/pegen_errors.c | 4 ++++ 3 files changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-01-00-07-02.gh-issue-113602.cWuTzk.rst diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 8b3ca69c9fe155..83cbf5ec865dbb 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -2360,6 +2360,8 @@ def test_error_parenthesis(self): """ self._check_error(code, "parenthesis '\\)' does not match opening parenthesis '\\['") + self._check_error("match y:\n case e(e=v,v,", " was never closed") + # Examples with dencodings s = b'# coding=latin\n(aaaaaaaaaaaaaaaaa\naaaaaaaaaaa\xb5' self._check_error(s, r"'\(' was never closed") diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-01-00-07-02.gh-issue-113602.cWuTzk.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-01-00-07-02.gh-issue-113602.cWuTzk.rst new file mode 100644 index 00000000000000..5e064657348720 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-01-00-07-02.gh-issue-113602.cWuTzk.rst @@ -0,0 +1,2 @@ +Fix an error that was causing the parser to try to overwrite existing errors +and crashing in the process. Patch by Pablo Galindo diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c index 8a02aab1f4e504..e15673d02dd3b0 100644 --- a/Parser/pegen_errors.c +++ b/Parser/pegen_errors.c @@ -311,6 +311,10 @@ _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype, Py_ssize_t end_lineno, Py_ssize_t end_col_offset, const char *errmsg, va_list va) { + // Bail out if we already have an error set. + if (p->error_indicator && PyErr_Occurred()) { + return NULL; + } PyObject *value = NULL; PyObject *errstr = NULL; PyObject *error_line = NULL; From 5d36a95e64e30606e8f8e332edf6bde91ac344cf Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Tue, 2 Jan 2024 07:41:32 -0600 Subject: [PATCH 423/442] gh-111178: Avoid calling functions from incompatible pointer types in listobject.c (GH-112820) Fix undefined behavior warnings (UBSan -fsanitize=function), for example: Objects/object.c:674:11: runtime error: call to function list_repr through pointer to incorrect function type 'struct _object *(*)(struct _object *)' listobject.c:382: note: list_repr defined here SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Objects/object.c:674:11 in --- Objects/listobject.c | 164 ++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 72 deletions(-) diff --git a/Objects/listobject.c b/Objects/listobject.c index 2d04218439bd20..dfb8cd2b106511 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -343,8 +343,9 @@ PyList_Append(PyObject *op, PyObject *newitem) /* Methods */ static void -list_dealloc(PyListObject *op) +list_dealloc(PyObject *self) { + PyListObject *op = (PyListObject *)self; Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_BEGIN(op, list_dealloc) @@ -378,8 +379,9 @@ list_dealloc(PyListObject *op) } static PyObject * -list_repr(PyListObject *v) +list_repr(PyObject *self) { + PyListObject *v = (PyListObject *)self; Py_ssize_t i; PyObject *s; _PyUnicodeWriter writer; @@ -434,14 +436,15 @@ list_repr(PyListObject *v) } static Py_ssize_t -list_length(PyListObject *a) +list_length(PyObject *a) { return Py_SIZE(a); } static int -list_contains(PyListObject *a, PyObject *el) +list_contains(PyObject *aa, PyObject *el) { + PyListObject *a = (PyListObject *)aa; PyObject *item; Py_ssize_t i; int cmp; @@ -456,8 +459,9 @@ list_contains(PyListObject *a, PyObject *el) } static PyObject * -list_item(PyListObject *a, Py_ssize_t i) +list_item(PyObject *aa, Py_ssize_t i) { + PyListObject *a = (PyListObject *)aa; if (!valid_index(i, Py_SIZE(a))) { PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); return NULL; @@ -512,8 +516,9 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) } static PyObject * -list_concat(PyListObject *a, PyObject *bb) +list_concat(PyObject *aa, PyObject *bb) { + PyListObject *a = (PyListObject *)aa; Py_ssize_t size; Py_ssize_t i; PyObject **src, **dest; @@ -552,8 +557,9 @@ list_concat(PyListObject *a, PyObject *bb) } static PyObject * -list_repeat(PyListObject *a, Py_ssize_t n) +list_repeat(PyObject *aa, Py_ssize_t n) { + PyListObject *a = (PyListObject *)aa; const Py_ssize_t input_size = Py_SIZE(a); if (input_size == 0 || n <= 0) return PyList_New(0); @@ -616,9 +622,9 @@ list_clear(PyListObject *a) } static int -list_clear_slot(PyListObject *self) +list_clear_slot(PyObject *self) { - list_clear(self); + list_clear((PyListObject *)self); return 0; } @@ -745,8 +751,9 @@ PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) } static PyObject * -list_inplace_repeat(PyListObject *self, Py_ssize_t n) +list_inplace_repeat(PyObject *_self, Py_ssize_t n) { + PyListObject *self = (PyListObject *)_self; Py_ssize_t input_size = PyList_GET_SIZE(self); if (input_size == 0 || n == 1) { return Py_NewRef(self); @@ -776,8 +783,9 @@ list_inplace_repeat(PyListObject *self, Py_ssize_t n) } static int -list_ass_item(PyListObject *a, Py_ssize_t i, PyObject *v) +list_ass_item(PyObject *aa, Py_ssize_t i, PyObject *v) { + PyListObject *a = (PyListObject *)aa; if (!valid_index(i, Py_SIZE(a))) { PyErr_SetString(PyExc_IndexError, "list assignment index out of range"); @@ -1044,8 +1052,9 @@ PyList_Clear(PyObject *self) static PyObject * -list_inplace_concat(PyListObject *self, PyObject *other) +list_inplace_concat(PyObject *_self, PyObject *other) { + PyListObject *self = (PyListObject *)_self; if (list_extend(self, other) < 0) { return NULL; } @@ -2756,8 +2765,9 @@ list_remove(PyListObject *self, PyObject *value) } static int -list_traverse(PyListObject *o, visitproc visit, void *arg) +list_traverse(PyObject *self, visitproc visit, void *arg) { + PyListObject *o = (PyListObject *)self; Py_ssize_t i; for (i = Py_SIZE(o); --i >= 0; ) @@ -2897,10 +2907,10 @@ list___sizeof___impl(PyListObject *self) } static PyObject *list_iter(PyObject *seq); -static PyObject *list_subscript(PyListObject*, PyObject*); +static PyObject *list_subscript(PyObject*, PyObject*); static PyMethodDef list_methods[] = { - {"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, + {"__getitem__", list_subscript, METH_O|METH_COEXIST, PyDoc_STR("__getitem__($self, index, /)\n--\n\nReturn self[index].")}, LIST___REVERSED___METHODDEF LIST___SIZEOF___METHODDEF @@ -2920,21 +2930,22 @@ static PyMethodDef list_methods[] = { }; static PySequenceMethods list_as_sequence = { - (lenfunc)list_length, /* sq_length */ - (binaryfunc)list_concat, /* sq_concat */ - (ssizeargfunc)list_repeat, /* sq_repeat */ - (ssizeargfunc)list_item, /* sq_item */ + list_length, /* sq_length */ + list_concat, /* sq_concat */ + list_repeat, /* sq_repeat */ + list_item, /* sq_item */ 0, /* sq_slice */ - (ssizeobjargproc)list_ass_item, /* sq_ass_item */ + list_ass_item, /* sq_ass_item */ 0, /* sq_ass_slice */ - (objobjproc)list_contains, /* sq_contains */ - (binaryfunc)list_inplace_concat, /* sq_inplace_concat */ - (ssizeargfunc)list_inplace_repeat, /* sq_inplace_repeat */ + list_contains, /* sq_contains */ + list_inplace_concat, /* sq_inplace_concat */ + list_inplace_repeat, /* sq_inplace_repeat */ }; static PyObject * -list_subscript(PyListObject* self, PyObject* item) +list_subscript(PyObject* _self, PyObject* item) { + PyListObject* self = (PyListObject*)_self; if (_PyIndex_Check(item)) { Py_ssize_t i; i = PyNumber_AsSsize_t(item, PyExc_IndexError); @@ -2942,7 +2953,7 @@ list_subscript(PyListObject* self, PyObject* item) return NULL; if (i < 0) i += PyList_GET_SIZE(self); - return list_item(self, i); + return list_item((PyObject *)self, i); } else if (PySlice_Check(item)) { Py_ssize_t start, stop, step, slicelength, i; @@ -2987,15 +2998,16 @@ list_subscript(PyListObject* self, PyObject* item) } static int -list_ass_subscript(PyListObject* self, PyObject* item, PyObject* value) +list_ass_subscript(PyObject* _self, PyObject* item, PyObject* value) { + PyListObject *self = (PyListObject *)_self; if (_PyIndex_Check(item)) { Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); if (i == -1 && PyErr_Occurred()) return -1; if (i < 0) i += PyList_GET_SIZE(self); - return list_ass_item(self, i, value); + return list_ass_item((PyObject *)self, i, value); } else if (PySlice_Check(item)) { Py_ssize_t start, stop, step, slicelength; @@ -3149,9 +3161,9 @@ list_ass_subscript(PyListObject* self, PyObject* item, PyObject* value) } static PyMappingMethods list_as_mapping = { - (lenfunc)list_length, - (binaryfunc)list_subscript, - (objobjargproc)list_ass_subscript + list_length, + list_subscript, + list_ass_subscript }; PyTypeObject PyList_Type = { @@ -3159,12 +3171,12 @@ PyTypeObject PyList_Type = { "list", sizeof(PyListObject), 0, - (destructor)list_dealloc, /* tp_dealloc */ + list_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)list_repr, /* tp_repr */ + list_repr, /* tp_repr */ 0, /* tp_as_number */ &list_as_sequence, /* tp_as_sequence */ &list_as_mapping, /* tp_as_mapping */ @@ -3178,8 +3190,8 @@ PyTypeObject PyList_Type = { Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS | _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */ list___init____doc__, /* tp_doc */ - (traverseproc)list_traverse, /* tp_traverse */ - (inquiry)list_clear_slot, /* tp_clear */ + list_traverse, /* tp_traverse */ + list_clear_slot, /* tp_clear */ list_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ list_iter, /* tp_iter */ @@ -3201,22 +3213,22 @@ PyTypeObject PyList_Type = { /*********************** List Iterator **************************/ -static void listiter_dealloc(_PyListIterObject *); -static int listiter_traverse(_PyListIterObject *, visitproc, void *); -static PyObject *listiter_next(_PyListIterObject *); -static PyObject *listiter_len(_PyListIterObject *, PyObject *); +static void listiter_dealloc(PyObject *); +static int listiter_traverse(PyObject *, visitproc, void *); +static PyObject *listiter_next(PyObject *); +static PyObject *listiter_len(PyObject *, PyObject *); static PyObject *listiter_reduce_general(void *_it, int forward); -static PyObject *listiter_reduce(_PyListIterObject *, PyObject *); -static PyObject *listiter_setstate(_PyListIterObject *, PyObject *state); +static PyObject *listiter_reduce(PyObject *, PyObject *); +static PyObject *listiter_setstate(PyObject *, PyObject *state); PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it))."); PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); PyDoc_STRVAR(setstate_doc, "Set state information for unpickling."); static PyMethodDef listiter_methods[] = { - {"__length_hint__", (PyCFunction)listiter_len, METH_NOARGS, length_hint_doc}, - {"__reduce__", (PyCFunction)listiter_reduce, METH_NOARGS, reduce_doc}, - {"__setstate__", (PyCFunction)listiter_setstate, METH_O, setstate_doc}, + {"__length_hint__", listiter_len, METH_NOARGS, length_hint_doc}, + {"__reduce__", listiter_reduce, METH_NOARGS, reduce_doc}, + {"__setstate__", listiter_setstate, METH_O, setstate_doc}, {NULL, NULL} /* sentinel */ }; @@ -3226,7 +3238,7 @@ PyTypeObject PyListIter_Type = { sizeof(_PyListIterObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)listiter_dealloc, /* tp_dealloc */ + listiter_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -3243,12 +3255,12 @@ PyTypeObject PyListIter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)listiter_traverse, /* tp_traverse */ + listiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc)listiter_next, /* tp_iternext */ + listiter_next, /* tp_iternext */ listiter_methods, /* tp_methods */ 0, /* tp_members */ }; @@ -3273,23 +3285,25 @@ list_iter(PyObject *seq) } static void -listiter_dealloc(_PyListIterObject *it) +listiter_dealloc(PyObject *self) { + _PyListIterObject *it = (_PyListIterObject *)self; _PyObject_GC_UNTRACK(it); Py_XDECREF(it->it_seq); PyObject_GC_Del(it); } static int -listiter_traverse(_PyListIterObject *it, visitproc visit, void *arg) +listiter_traverse(PyObject *it, visitproc visit, void *arg) { - Py_VISIT(it->it_seq); + Py_VISIT(((_PyListIterObject *)it)->it_seq); return 0; } static PyObject * -listiter_next(_PyListIterObject *it) +listiter_next(PyObject *self) { + _PyListIterObject *it = (_PyListIterObject *)self; PyListObject *seq; PyObject *item; @@ -3311,8 +3325,9 @@ listiter_next(_PyListIterObject *it) } static PyObject * -listiter_len(_PyListIterObject *it, PyObject *Py_UNUSED(ignored)) +listiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { + _PyListIterObject *it = (_PyListIterObject *)self; Py_ssize_t len; if (it->it_seq) { len = PyList_GET_SIZE(it->it_seq) - it->it_index; @@ -3323,14 +3338,15 @@ listiter_len(_PyListIterObject *it, PyObject *Py_UNUSED(ignored)) } static PyObject * -listiter_reduce(_PyListIterObject *it, PyObject *Py_UNUSED(ignored)) +listiter_reduce(PyObject *it, PyObject *Py_UNUSED(ignored)) { return listiter_reduce_general(it, 1); } static PyObject * -listiter_setstate(_PyListIterObject *it, PyObject *state) +listiter_setstate(PyObject *self, PyObject *state) { + _PyListIterObject *it = (_PyListIterObject *)self; Py_ssize_t index = PyLong_AsSsize_t(state); if (index == -1 && PyErr_Occurred()) return NULL; @@ -3352,17 +3368,17 @@ typedef struct { PyListObject *it_seq; /* Set to NULL when iterator is exhausted */ } listreviterobject; -static void listreviter_dealloc(listreviterobject *); -static int listreviter_traverse(listreviterobject *, visitproc, void *); -static PyObject *listreviter_next(listreviterobject *); -static PyObject *listreviter_len(listreviterobject *, PyObject *); -static PyObject *listreviter_reduce(listreviterobject *, PyObject *); -static PyObject *listreviter_setstate(listreviterobject *, PyObject *); +static void listreviter_dealloc(PyObject *); +static int listreviter_traverse(PyObject *, visitproc, void *); +static PyObject *listreviter_next(PyObject *); +static PyObject *listreviter_len(PyObject *, PyObject *); +static PyObject *listreviter_reduce(PyObject *, PyObject *); +static PyObject *listreviter_setstate(PyObject *, PyObject *); static PyMethodDef listreviter_methods[] = { - {"__length_hint__", (PyCFunction)listreviter_len, METH_NOARGS, length_hint_doc}, - {"__reduce__", (PyCFunction)listreviter_reduce, METH_NOARGS, reduce_doc}, - {"__setstate__", (PyCFunction)listreviter_setstate, METH_O, setstate_doc}, + {"__length_hint__", listreviter_len, METH_NOARGS, length_hint_doc}, + {"__reduce__", listreviter_reduce, METH_NOARGS, reduce_doc}, + {"__setstate__", listreviter_setstate, METH_O, setstate_doc}, {NULL, NULL} /* sentinel */ }; @@ -3372,7 +3388,7 @@ PyTypeObject PyListRevIter_Type = { sizeof(listreviterobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)listreviter_dealloc, /* tp_dealloc */ + listreviter_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -3389,12 +3405,12 @@ PyTypeObject PyListRevIter_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)listreviter_traverse, /* tp_traverse */ + listreviter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc)listreviter_next, /* tp_iternext */ + listreviter_next, /* tp_iternext */ listreviter_methods, /* tp_methods */ 0, }; @@ -3422,23 +3438,25 @@ list___reversed___impl(PyListObject *self) } static void -listreviter_dealloc(listreviterobject *it) +listreviter_dealloc(PyObject *self) { + listreviterobject *it = (listreviterobject *)self; PyObject_GC_UnTrack(it); Py_XDECREF(it->it_seq); PyObject_GC_Del(it); } static int -listreviter_traverse(listreviterobject *it, visitproc visit, void *arg) +listreviter_traverse(PyObject *it, visitproc visit, void *arg) { - Py_VISIT(it->it_seq); + Py_VISIT(((listreviterobject *)it)->it_seq); return 0; } static PyObject * -listreviter_next(listreviterobject *it) +listreviter_next(PyObject *self) { + listreviterobject *it = (listreviterobject *)self; PyObject *item; Py_ssize_t index; PyListObject *seq; @@ -3463,8 +3481,9 @@ listreviter_next(listreviterobject *it) } static PyObject * -listreviter_len(listreviterobject *it, PyObject *Py_UNUSED(ignored)) +listreviter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { + listreviterobject *it = (listreviterobject *)self; Py_ssize_t len = it->it_index + 1; if (it->it_seq == NULL || PyList_GET_SIZE(it->it_seq) < len) len = 0; @@ -3472,14 +3491,15 @@ listreviter_len(listreviterobject *it, PyObject *Py_UNUSED(ignored)) } static PyObject * -listreviter_reduce(listreviterobject *it, PyObject *Py_UNUSED(ignored)) +listreviter_reduce(PyObject *it, PyObject *Py_UNUSED(ignored)) { return listiter_reduce_general(it, 0); } static PyObject * -listreviter_setstate(listreviterobject *it, PyObject *state) +listreviter_setstate(PyObject *self, PyObject *state) { + listreviterobject *it = (listreviterobject *)self; Py_ssize_t index = PyLong_AsSsize_t(state); if (index == -1 && PyErr_Occurred()) return NULL; From acf4cf5ca5ef62407e35609fb365e7dfaa362648 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Tue, 2 Jan 2024 08:03:39 -0600 Subject: [PATCH 424/442] gh-111178: Avoid calling functions from incompatible pointer types in descrobject.c (GH-112861) Fix undefined behavior warnings (UBSan -fsanitize=function), for example: Python/generated_cases.c.h:3315:13: runtime error: call to function mappingproxy_dealloc through pointer to incorrect function type 'void (*)(struct _object *)' descrobject.c:1160: note: mappingproxy_dealloc defined here SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:3315:13 in --- Objects/descrobject.c | 253 ++++++++++++++++++++++++------------------ 1 file changed, 147 insertions(+), 106 deletions(-) diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 57921b110591e5..8d771adf307dc4 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -19,8 +19,9 @@ class property "propertyobject *" "&PyProperty_Type" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=556352653fd4c02e]*/ static void -descr_dealloc(PyDescrObject *descr) +descr_dealloc(PyObject *self) { + PyDescrObject *descr = (PyDescrObject *)self; _PyObject_GC_UNTRACK(descr); Py_XDECREF(descr->d_type); Py_XDECREF(descr->d_name); @@ -47,28 +48,28 @@ descr_repr(PyDescrObject *descr, const char *format) } static PyObject * -method_repr(PyMethodDescrObject *descr) +method_repr(PyObject *descr) { return descr_repr((PyDescrObject *)descr, ""); } static PyObject * -member_repr(PyMemberDescrObject *descr) +member_repr(PyObject *descr) { return descr_repr((PyDescrObject *)descr, ""); } static PyObject * -getset_repr(PyGetSetDescrObject *descr) +getset_repr(PyObject *descr) { return descr_repr((PyDescrObject *)descr, ""); } static PyObject * -wrapperdescr_repr(PyWrapperDescrObject *descr) +wrapperdescr_repr(PyObject *descr) { return descr_repr((PyDescrObject *)descr, ""); @@ -90,8 +91,9 @@ descr_check(PyDescrObject *descr, PyObject *obj) } static PyObject * -classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type) +classmethod_get(PyObject *self, PyObject *obj, PyObject *type) { + PyMethodDescrObject *descr = (PyMethodDescrObject *)self; /* Ensure a valid type. Class methods ignore obj. */ if (type == NULL) { if (obj != NULL) @@ -132,8 +134,9 @@ classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type) } static PyObject * -method_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type) +method_get(PyObject *self, PyObject *obj, PyObject *type) { + PyMethodDescrObject *descr = (PyMethodDescrObject *)self; if (obj == NULL) { return Py_NewRef(descr); } @@ -156,8 +159,9 @@ method_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type) } static PyObject * -member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type) +member_get(PyObject *self, PyObject *obj, PyObject *type) { + PyMemberDescrObject *descr = (PyMemberDescrObject *)self; if (obj == NULL) { return Py_NewRef(descr); } @@ -176,8 +180,9 @@ member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type) } static PyObject * -getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) +getset_get(PyObject *self, PyObject *obj, PyObject *type) { + PyGetSetDescrObject *descr = (PyGetSetDescrObject *)self; if (obj == NULL) { return Py_NewRef(descr); } @@ -195,8 +200,9 @@ getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type) } static PyObject * -wrapperdescr_get(PyWrapperDescrObject *descr, PyObject *obj, PyObject *type) +wrapperdescr_get(PyObject *self, PyObject *obj, PyObject *type) { + PyWrapperDescrObject *descr = (PyWrapperDescrObject *)self; if (obj == NULL) { return Py_NewRef(descr); } @@ -223,8 +229,9 @@ descr_setcheck(PyDescrObject *descr, PyObject *obj, PyObject *value) } static int -member_set(PyMemberDescrObject *descr, PyObject *obj, PyObject *value) +member_set(PyObject *self, PyObject *obj, PyObject *value) { + PyMemberDescrObject *descr = (PyMemberDescrObject *)self; if (descr_setcheck((PyDescrObject *)descr, obj, value) < 0) { return -1; } @@ -232,8 +239,9 @@ member_set(PyMemberDescrObject *descr, PyObject *obj, PyObject *value) } static int -getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value) +getset_set(PyObject *self, PyObject *obj, PyObject *value) { + PyGetSetDescrObject *descr = (PyGetSetDescrObject *)self; if (descr_setcheck((PyDescrObject *)descr, obj, value) < 0) { return -1; } @@ -479,9 +487,10 @@ method_vectorcall_O( we implement this simply by calling __get__ and then calling the result. */ static PyObject * -classmethoddescr_call(PyMethodDescrObject *descr, PyObject *args, +classmethoddescr_call(PyObject *_descr, PyObject *args, PyObject *kwds) { + PyMethodDescrObject *descr = (PyMethodDescrObject *)_descr; Py_ssize_t argc = PyTuple_GET_SIZE(args); if (argc < 1) { PyErr_Format(PyExc_TypeError, @@ -492,7 +501,7 @@ classmethoddescr_call(PyMethodDescrObject *descr, PyObject *args, return NULL; } PyObject *self = PyTuple_GET_ITEM(args, 0); - PyObject *bound = classmethod_get(descr, NULL, self); + PyObject *bound = classmethod_get((PyObject *)descr, NULL, self); if (bound == NULL) { return NULL; } @@ -523,8 +532,9 @@ wrapperdescr_raw_call(PyWrapperDescrObject *descr, PyObject *self, } static PyObject * -wrapperdescr_call(PyWrapperDescrObject *descr, PyObject *args, PyObject *kwds) +wrapperdescr_call(PyObject *_descr, PyObject *args, PyObject *kwds) { + PyWrapperDescrObject *descr = (PyWrapperDescrObject *)_descr; Py_ssize_t argc; PyObject *self, *result; @@ -563,14 +573,16 @@ wrapperdescr_call(PyWrapperDescrObject *descr, PyObject *args, PyObject *kwds) static PyObject * -method_get_doc(PyMethodDescrObject *descr, void *closure) +method_get_doc(PyObject *_descr, void *closure) { + PyMethodDescrObject *descr = (PyMethodDescrObject *)_descr; return _PyType_GetDocFromInternalDoc(descr->d_method->ml_name, descr->d_method->ml_doc); } static PyObject * -method_get_text_signature(PyMethodDescrObject *descr, void *closure) +method_get_text_signature(PyObject *_descr, void *closure) { + PyMethodDescrObject *descr = (PyMethodDescrObject *)_descr; return _PyType_GetTextSignatureFromInternalDoc(descr->d_method->ml_name, descr->d_method->ml_doc, descr->d_method->ml_flags); @@ -605,22 +617,24 @@ calculate_qualname(PyDescrObject *descr) } static PyObject * -descr_get_qualname(PyDescrObject *descr, void *Py_UNUSED(ignored)) +descr_get_qualname(PyObject *self, void *Py_UNUSED(ignored)) { + PyDescrObject *descr = (PyDescrObject *)self; if (descr->d_qualname == NULL) descr->d_qualname = calculate_qualname(descr); return Py_XNewRef(descr->d_qualname); } static PyObject * -descr_reduce(PyDescrObject *descr, PyObject *Py_UNUSED(ignored)) +descr_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { + PyDescrObject *descr = (PyDescrObject *)self; return Py_BuildValue("N(OO)", _PyEval_GetBuiltin(&_Py_ID(getattr)), PyDescr_TYPE(descr), PyDescr_NAME(descr)); } static PyMethodDef descr_methods[] = { - {"__reduce__", (PyCFunction)descr_reduce, METH_NOARGS, NULL}, + {"__reduce__", descr_reduce, METH_NOARGS, NULL}, {NULL, NULL} }; @@ -631,15 +645,16 @@ static PyMemberDef descr_members[] = { }; static PyGetSetDef method_getset[] = { - {"__doc__", (getter)method_get_doc}, - {"__qualname__", (getter)descr_get_qualname}, - {"__text_signature__", (getter)method_get_text_signature}, + {"__doc__", method_get_doc}, + {"__qualname__", descr_get_qualname}, + {"__text_signature__", method_get_text_signature}, {0} }; static PyObject * -member_get_doc(PyMemberDescrObject *descr, void *closure) +member_get_doc(PyObject *_descr, void *closure) { + PyMemberDescrObject *descr = (PyMemberDescrObject *)_descr; if (descr->d_member->doc == NULL) { Py_RETURN_NONE; } @@ -647,14 +662,15 @@ member_get_doc(PyMemberDescrObject *descr, void *closure) } static PyGetSetDef member_getset[] = { - {"__doc__", (getter)member_get_doc}, - {"__qualname__", (getter)descr_get_qualname}, + {"__doc__", member_get_doc}, + {"__qualname__", descr_get_qualname}, {0} }; static PyObject * -getset_get_doc(PyGetSetDescrObject *descr, void *closure) +getset_get_doc(PyObject *self, void *closure) { + PyGetSetDescrObject *descr = (PyGetSetDescrObject *)self; if (descr->d_getset->doc == NULL) { Py_RETURN_NONE; } @@ -662,28 +678,30 @@ getset_get_doc(PyGetSetDescrObject *descr, void *closure) } static PyGetSetDef getset_getset[] = { - {"__doc__", (getter)getset_get_doc}, - {"__qualname__", (getter)descr_get_qualname}, + {"__doc__", getset_get_doc}, + {"__qualname__", descr_get_qualname}, {0} }; static PyObject * -wrapperdescr_get_doc(PyWrapperDescrObject *descr, void *closure) +wrapperdescr_get_doc(PyObject *self, void *closure) { + PyWrapperDescrObject *descr = (PyWrapperDescrObject *)self; return _PyType_GetDocFromInternalDoc(descr->d_base->name, descr->d_base->doc); } static PyObject * -wrapperdescr_get_text_signature(PyWrapperDescrObject *descr, void *closure) +wrapperdescr_get_text_signature(PyObject *self, void *closure) { + PyWrapperDescrObject *descr = (PyWrapperDescrObject *)self; return _PyType_GetTextSignatureFromInternalDoc(descr->d_base->name, descr->d_base->doc, 0); } static PyGetSetDef wrapperdescr_getset[] = { - {"__doc__", (getter)wrapperdescr_get_doc}, - {"__qualname__", (getter)descr_get_qualname}, - {"__text_signature__", (getter)wrapperdescr_get_text_signature}, + {"__doc__", wrapperdescr_get_doc}, + {"__qualname__", descr_get_qualname}, + {"__text_signature__", wrapperdescr_get_text_signature}, {0} }; @@ -700,12 +718,12 @@ PyTypeObject PyMethodDescr_Type = { "method_descriptor", sizeof(PyMethodDescrObject), 0, - (destructor)descr_dealloc, /* tp_dealloc */ + descr_dealloc, /* tp_dealloc */ offsetof(PyMethodDescrObject, vectorcall), /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)method_repr, /* tp_repr */ + method_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -730,7 +748,7 @@ PyTypeObject PyMethodDescr_Type = { method_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc)method_get, /* tp_descr_get */ + method_get, /* tp_descr_get */ 0, /* tp_descr_set */ }; @@ -740,17 +758,17 @@ PyTypeObject PyClassMethodDescr_Type = { "classmethod_descriptor", sizeof(PyMethodDescrObject), 0, - (destructor)descr_dealloc, /* tp_dealloc */ + descr_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)method_repr, /* tp_repr */ + method_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - (ternaryfunc)classmethoddescr_call, /* tp_call */ + classmethoddescr_call, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ @@ -768,7 +786,7 @@ PyTypeObject PyClassMethodDescr_Type = { method_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc)classmethod_get, /* tp_descr_get */ + classmethod_get, /* tp_descr_get */ 0, /* tp_descr_set */ }; @@ -777,12 +795,12 @@ PyTypeObject PyMemberDescr_Type = { "member_descriptor", sizeof(PyMemberDescrObject), 0, - (destructor)descr_dealloc, /* tp_dealloc */ + descr_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)member_repr, /* tp_repr */ + member_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -805,8 +823,8 @@ PyTypeObject PyMemberDescr_Type = { member_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc)member_get, /* tp_descr_get */ - (descrsetfunc)member_set, /* tp_descr_set */ + member_get, /* tp_descr_get */ + member_set, /* tp_descr_set */ }; PyTypeObject PyGetSetDescr_Type = { @@ -814,12 +832,12 @@ PyTypeObject PyGetSetDescr_Type = { "getset_descriptor", sizeof(PyGetSetDescrObject), 0, - (destructor)descr_dealloc, /* tp_dealloc */ + descr_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)getset_repr, /* tp_repr */ + getset_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -842,8 +860,8 @@ PyTypeObject PyGetSetDescr_Type = { getset_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc)getset_get, /* tp_descr_get */ - (descrsetfunc)getset_set, /* tp_descr_set */ + getset_get, /* tp_descr_get */ + getset_set, /* tp_descr_set */ }; PyTypeObject PyWrapperDescr_Type = { @@ -851,17 +869,17 @@ PyTypeObject PyWrapperDescr_Type = { "wrapper_descriptor", sizeof(PyWrapperDescrObject), 0, - (destructor)descr_dealloc, /* tp_dealloc */ + descr_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)wrapperdescr_repr, /* tp_repr */ + wrapperdescr_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ - (ternaryfunc)wrapperdescr_call, /* tp_call */ + wrapperdescr_call, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ @@ -880,7 +898,7 @@ PyTypeObject PyWrapperDescr_Type = { wrapperdescr_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc)wrapperdescr_get, /* tp_descr_get */ + wrapperdescr_get, /* tp_descr_get */ 0, /* tp_descr_set */ }; @@ -1022,20 +1040,22 @@ typedef struct { } mappingproxyobject; static Py_ssize_t -mappingproxy_len(mappingproxyobject *pp) +mappingproxy_len(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_Size(pp->mapping); } static PyObject * -mappingproxy_getitem(mappingproxyobject *pp, PyObject *key) +mappingproxy_getitem(PyObject *self, PyObject *key) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_GetItem(pp->mapping, key); } static PyMappingMethods mappingproxy_as_mapping = { - (lenfunc)mappingproxy_len, /* mp_length */ - (binaryfunc)mappingproxy_getitem, /* mp_subscript */ + mappingproxy_len, /* mp_length */ + mappingproxy_getitem, /* mp_subscript */ 0, /* mp_ass_subscript */ }; @@ -1064,8 +1084,9 @@ static PyNumberMethods mappingproxy_as_number = { }; static int -mappingproxy_contains(mappingproxyobject *pp, PyObject *key) +mappingproxy_contains(PyObject *self, PyObject *key) { + mappingproxyobject *pp = (mappingproxyobject *)self; if (PyDict_CheckExact(pp->mapping)) return PyDict_Contains(pp->mapping, key); else @@ -1080,14 +1101,15 @@ static PySequenceMethods mappingproxy_as_sequence = { 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ - (objobjproc)mappingproxy_contains, /* sq_contains */ + mappingproxy_contains, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; static PyObject * -mappingproxy_get(mappingproxyobject *pp, PyObject *const *args, Py_ssize_t nargs) +mappingproxy_get(PyObject *self, PyObject *const *args, Py_ssize_t nargs) { + mappingproxyobject *pp = (mappingproxyobject *)self; /* newargs: mapping, key, default=None */ PyObject *newargs[3]; newargs[0] = pp->mapping; @@ -1104,32 +1126,37 @@ mappingproxy_get(mappingproxyobject *pp, PyObject *const *args, Py_ssize_t nargs } static PyObject * -mappingproxy_keys(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) +mappingproxy_keys(PyObject *self, PyObject *Py_UNUSED(ignored)) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_CallMethodNoArgs(pp->mapping, &_Py_ID(keys)); } static PyObject * -mappingproxy_values(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) +mappingproxy_values(PyObject *self, PyObject *Py_UNUSED(ignored)) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_CallMethodNoArgs(pp->mapping, &_Py_ID(values)); } static PyObject * -mappingproxy_items(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) +mappingproxy_items(PyObject *self, PyObject *Py_UNUSED(ignored)) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_CallMethodNoArgs(pp->mapping, &_Py_ID(items)); } static PyObject * -mappingproxy_copy(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) +mappingproxy_copy(PyObject *self, PyObject *Py_UNUSED(ignored)) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_CallMethodNoArgs(pp->mapping, &_Py_ID(copy)); } static PyObject * -mappingproxy_reversed(mappingproxyobject *pp, PyObject *Py_UNUSED(ignored)) +mappingproxy_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_CallMethodNoArgs(pp->mapping, &_Py_ID(__reversed__)); } @@ -1140,50 +1167,55 @@ static PyMethodDef mappingproxy_methods[] = { {"get", _PyCFunction_CAST(mappingproxy_get), METH_FASTCALL, PyDoc_STR("D.get(k[,d]) -> D[k] if k in D, else d." " d defaults to None.")}, - {"keys", (PyCFunction)mappingproxy_keys, METH_NOARGS, + {"keys", mappingproxy_keys, METH_NOARGS, PyDoc_STR("D.keys() -> a set-like object providing a view on D's keys")}, - {"values", (PyCFunction)mappingproxy_values, METH_NOARGS, + {"values", mappingproxy_values, METH_NOARGS, PyDoc_STR("D.values() -> an object providing a view on D's values")}, - {"items", (PyCFunction)mappingproxy_items, METH_NOARGS, + {"items", mappingproxy_items, METH_NOARGS, PyDoc_STR("D.items() -> a set-like object providing a view on D's items")}, - {"copy", (PyCFunction)mappingproxy_copy, METH_NOARGS, + {"copy", mappingproxy_copy, METH_NOARGS, PyDoc_STR("D.copy() -> a shallow copy of D")}, {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, - {"__reversed__", (PyCFunction)mappingproxy_reversed, METH_NOARGS, + {"__reversed__", mappingproxy_reversed, METH_NOARGS, PyDoc_STR("D.__reversed__() -> reverse iterator")}, {0} }; static void -mappingproxy_dealloc(mappingproxyobject *pp) +mappingproxy_dealloc(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; _PyObject_GC_UNTRACK(pp); Py_DECREF(pp->mapping); PyObject_GC_Del(pp); } static PyObject * -mappingproxy_getiter(mappingproxyobject *pp) +mappingproxy_getiter(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_GetIter(pp->mapping); } static Py_hash_t -mappingproxy_hash(mappingproxyobject *pp) +mappingproxy_hash(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_Hash(pp->mapping); } static PyObject * -mappingproxy_str(mappingproxyobject *pp) +mappingproxy_str(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyObject_Str(pp->mapping); } static PyObject * -mappingproxy_repr(mappingproxyobject *pp) +mappingproxy_repr(PyObject *self) { + mappingproxyobject *pp = (mappingproxyobject *)self; return PyUnicode_FromFormat("mappingproxy(%R)", pp->mapping); } @@ -1196,8 +1228,9 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg) } static PyObject * -mappingproxy_richcompare(mappingproxyobject *v, PyObject *w, int op) +mappingproxy_richcompare(PyObject *self, PyObject *w, int op) { + mappingproxyobject *v = (mappingproxyobject *)self; return PyObject_RichCompare(v->mapping, w, op); } @@ -1271,8 +1304,9 @@ typedef struct { #define Wrapper_Check(v) Py_IS_TYPE(v, &_PyMethodWrapper_Type) static void -wrapper_dealloc(wrapperobject *wp) +wrapper_dealloc(PyObject *self) { + wrapperobject *wp = (wrapperobject *)self; PyObject_GC_UnTrack(wp); Py_TRASHCAN_BEGIN(wp, wrapper_dealloc) Py_XDECREF(wp->descr); @@ -1308,8 +1342,9 @@ wrapper_richcompare(PyObject *a, PyObject *b, int op) } static Py_hash_t -wrapper_hash(wrapperobject *wp) +wrapper_hash(PyObject *self) { + wrapperobject *wp = (wrapperobject *)self; Py_hash_t x, y; x = _Py_HashPointer(wp->self); y = _Py_HashPointer(wp->descr); @@ -1320,8 +1355,9 @@ wrapper_hash(wrapperobject *wp) } static PyObject * -wrapper_repr(wrapperobject *wp) +wrapper_repr(PyObject *self) { + wrapperobject *wp = (wrapperobject *)self; return PyUnicode_FromFormat("", wp->descr->d_base->name, Py_TYPE(wp->self)->tp_name, @@ -1329,14 +1365,15 @@ wrapper_repr(wrapperobject *wp) } static PyObject * -wrapper_reduce(wrapperobject *wp, PyObject *Py_UNUSED(ignored)) +wrapper_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { + wrapperobject *wp = (wrapperobject *)self; return Py_BuildValue("N(OO)", _PyEval_GetBuiltin(&_Py_ID(getattr)), wp->self, PyDescr_NAME(wp->descr)); } static PyMethodDef wrapper_methods[] = { - {"__reduce__", (PyCFunction)wrapper_reduce, METH_NOARGS, NULL}, + {"__reduce__", wrapper_reduce, METH_NOARGS, NULL}, {NULL, NULL} }; @@ -1346,52 +1383,56 @@ static PyMemberDef wrapper_members[] = { }; static PyObject * -wrapper_objclass(wrapperobject *wp, void *Py_UNUSED(ignored)) +wrapper_objclass(PyObject *wp, void *Py_UNUSED(ignored)) { - PyObject *c = (PyObject *)PyDescr_TYPE(wp->descr); + PyObject *c = (PyObject *)PyDescr_TYPE(((wrapperobject *)wp)->descr); return Py_NewRef(c); } static PyObject * -wrapper_name(wrapperobject *wp, void *Py_UNUSED(ignored)) +wrapper_name(PyObject *wp, void *Py_UNUSED(ignored)) { - const char *s = wp->descr->d_base->name; + const char *s = ((wrapperobject *)wp)->descr->d_base->name; return PyUnicode_FromString(s); } static PyObject * -wrapper_doc(wrapperobject *wp, void *Py_UNUSED(ignored)) +wrapper_doc(PyObject *self, void *Py_UNUSED(ignored)) { + wrapperobject *wp = (wrapperobject *)self; return _PyType_GetDocFromInternalDoc(wp->descr->d_base->name, wp->descr->d_base->doc); } static PyObject * -wrapper_text_signature(wrapperobject *wp, void *Py_UNUSED(ignored)) +wrapper_text_signature(PyObject *self, void *Py_UNUSED(ignored)) { + wrapperobject *wp = (wrapperobject *)self; return _PyType_GetTextSignatureFromInternalDoc(wp->descr->d_base->name, wp->descr->d_base->doc, 0); } static PyObject * -wrapper_qualname(wrapperobject *wp, void *Py_UNUSED(ignored)) +wrapper_qualname(PyObject *self, void *Py_UNUSED(ignored)) { - return descr_get_qualname((PyDescrObject *)wp->descr, NULL); + wrapperobject *wp = (wrapperobject *)self; + return descr_get_qualname((PyObject *)wp->descr, NULL); } static PyGetSetDef wrapper_getsets[] = { - {"__objclass__", (getter)wrapper_objclass}, - {"__name__", (getter)wrapper_name}, - {"__qualname__", (getter)wrapper_qualname}, - {"__doc__", (getter)wrapper_doc}, - {"__text_signature__", (getter)wrapper_text_signature}, + {"__objclass__", wrapper_objclass}, + {"__name__", wrapper_name}, + {"__qualname__", wrapper_qualname}, + {"__doc__", wrapper_doc}, + {"__text_signature__", wrapper_text_signature}, {0} }; static PyObject * -wrapper_call(wrapperobject *wp, PyObject *args, PyObject *kwds) +wrapper_call(PyObject *self, PyObject *args, PyObject *kwds) { + wrapperobject *wp = (wrapperobject *)self; return wrapperdescr_raw_call(wp->descr, wp->self, args, kwds); } @@ -1410,17 +1451,17 @@ PyTypeObject _PyMethodWrapper_Type = { sizeof(wrapperobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)wrapper_dealloc, /* tp_dealloc */ + wrapper_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)wrapper_repr, /* tp_repr */ + wrapper_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ - (hashfunc)wrapper_hash, /* tp_hash */ - (ternaryfunc)wrapper_call, /* tp_call */ + wrapper_hash, /* tp_hash */ + wrapper_call, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ @@ -1910,18 +1951,18 @@ PyTypeObject PyDictProxy_Type = { sizeof(mappingproxyobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)mappingproxy_dealloc, /* tp_dealloc */ + mappingproxy_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)mappingproxy_repr, /* tp_repr */ + mappingproxy_repr, /* tp_repr */ &mappingproxy_as_number, /* tp_as_number */ &mappingproxy_as_sequence, /* tp_as_sequence */ &mappingproxy_as_mapping, /* tp_as_mapping */ - (hashfunc)mappingproxy_hash, /* tp_hash */ + mappingproxy_hash, /* tp_hash */ 0, /* tp_call */ - (reprfunc)mappingproxy_str, /* tp_str */ + mappingproxy_str, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ @@ -1930,9 +1971,9 @@ PyTypeObject PyDictProxy_Type = { 0, /* tp_doc */ mappingproxy_traverse, /* tp_traverse */ 0, /* tp_clear */ - (richcmpfunc)mappingproxy_richcompare, /* tp_richcompare */ + mappingproxy_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - (getiterfunc)mappingproxy_getiter, /* tp_iter */ + mappingproxy_getiter, /* tp_iter */ 0, /* tp_iternext */ mappingproxy_methods, /* tp_methods */ 0, /* tp_members */ @@ -1972,7 +2013,7 @@ PyTypeObject PyProperty_Type = { Py_TPFLAGS_BASETYPE, /* tp_flags */ property_init__doc__, /* tp_doc */ property_traverse, /* tp_traverse */ - (inquiry)property_clear, /* tp_clear */ + property_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ From a1eea1d032d4436131716aec3d8936042bcbfa9d Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Tue, 2 Jan 2024 08:32:37 -0600 Subject: [PATCH 425/442] gh-111178: Avoid calling functions from incompatible pointer types in dictobject.c (#112892) Fix undefined behavior warnings (UBSan -fsanitize=function). --- Objects/dictobject.c | 226 ++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 100 deletions(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 70f424e07ece9a..2482a918ba983b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -237,7 +237,7 @@ equally good collision statistics, needed less code & used less memory. static int dictresize(PyInterpreterState *interp, PyDictObject *mp, uint8_t log_newsize, int unicode); -static PyObject* dict_iter(PyDictObject *dict); +static PyObject* dict_iter(PyObject *dict); #include "clinic/dictobject.c.h" @@ -792,7 +792,7 @@ static PyDictKeysObject * clone_combined_dict_keys(PyDictObject *orig) { assert(PyDict_Check(orig)); - assert(Py_TYPE(orig)->tp_iter == (getiterfunc)dict_iter); + assert(Py_TYPE(orig)->tp_iter == dict_iter); assert(orig->ma_values == NULL); assert(orig->ma_keys != Py_EMPTY_KEYS); assert(orig->ma_keys->dk_refcnt == 1); @@ -2450,8 +2450,9 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) /* Methods */ static void -dict_dealloc(PyDictObject *mp) +dict_dealloc(PyObject *self) { + PyDictObject *mp = (PyDictObject *)self; PyInterpreterState *interp = _PyInterpreterState_GET(); assert(Py_REFCNT(mp) == 0); Py_SET_REFCNT(mp, 1); @@ -2499,8 +2500,9 @@ dict_dealloc(PyDictObject *mp) static PyObject * -dict_repr(PyDictObject *mp) +dict_repr(PyObject *self) { + PyDictObject *mp = (PyDictObject *)self; Py_ssize_t i; PyObject *key = NULL, *value = NULL; _PyUnicodeWriter writer; @@ -2582,14 +2584,16 @@ dict_repr(PyDictObject *mp) } static Py_ssize_t -dict_length(PyDictObject *mp) +dict_length(PyObject *self) { + PyDictObject *mp = (PyDictObject *)self; return mp->ma_used; } static PyObject * -dict_subscript(PyDictObject *mp, PyObject *key) +dict_subscript(PyObject *self, PyObject *key) { + PyDictObject *mp = (PyDictObject *)self; Py_ssize_t ix; Py_hash_t hash; PyObject *value; @@ -2623,18 +2627,18 @@ dict_subscript(PyDictObject *mp, PyObject *key) } static int -dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w) +dict_ass_sub(PyObject *mp, PyObject *v, PyObject *w) { if (w == NULL) - return PyDict_DelItem((PyObject *)mp, v); + return PyDict_DelItem(mp, v); else - return PyDict_SetItem((PyObject *)mp, v, w); + return PyDict_SetItem(mp, v, w); } static PyMappingMethods dict_as_mapping = { - (lenfunc)dict_length, /*mp_length*/ - (binaryfunc)dict_subscript, /*mp_subscript*/ - (objobjargproc)dict_ass_sub, /*mp_ass_subscript*/ + dict_length, /*mp_length*/ + dict_subscript, /*mp_subscript*/ + dict_ass_sub, /*mp_ass_subscript*/ }; static PyObject * @@ -2925,7 +2929,7 @@ dict_merge(PyInterpreterState *interp, PyObject *a, PyObject *b, int override) return -1; } mp = (PyDictObject*)a; - if (PyDict_Check(b) && (Py_TYPE(b)->tp_iter == (getiterfunc)dict_iter)) { + 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 */ @@ -3105,9 +3109,9 @@ _PyDict_MergeEx(PyObject *a, PyObject *b, int override) } static PyObject * -dict_copy(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) +dict_copy(PyObject *mp, PyObject *Py_UNUSED(ignored)) { - return PyDict_Copy((PyObject*)mp); + return PyDict_Copy(mp); } PyObject * @@ -3155,7 +3159,7 @@ PyDict_Copy(PyObject *o) return (PyObject *)split_copy; } - if (Py_TYPE(mp)->tp_iter == (getiterfunc)dict_iter && + if (Py_TYPE(mp)->tp_iter == dict_iter && mp->ma_values == NULL && (mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3)) { @@ -3509,9 +3513,9 @@ dict_setdefault_impl(PyDictObject *self, PyObject *key, } static PyObject * -dict_clear(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) +dict_clear(PyObject *mp, PyObject *Py_UNUSED(ignored)) { - PyDict_Clear((PyObject *)mp); + PyDict_Clear(mp); Py_RETURN_NONE; } @@ -3700,8 +3704,9 @@ _PyDict_KeysSize(PyDictKeysObject *keys) } static PyObject * -dict_sizeof(PyDictObject *mp, PyObject *Py_UNUSED(ignored)) +dict_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) { + PyDictObject *mp = (PyDictObject *)self; return PyLong_FromSsize_t(_PyDict_SizeOf(mp)); } @@ -3763,9 +3768,9 @@ PyDoc_STRVAR(values__doc__, static PyMethodDef mapp_methods[] = { DICT___CONTAINS___METHODDEF - {"__getitem__", _PyCFunction_CAST(dict_subscript), METH_O | METH_COEXIST, + {"__getitem__", dict_subscript, METH_O | METH_COEXIST, getitem__doc__}, - {"__sizeof__", _PyCFunction_CAST(dict_sizeof), METH_NOARGS, + {"__sizeof__", dict_sizeof, METH_NOARGS, sizeof__doc__}, DICT_GET_METHODDEF DICT_SETDEFAULT_METHODDEF @@ -3780,9 +3785,9 @@ static PyMethodDef mapp_methods[] = { {"update", _PyCFunction_CAST(dict_update), METH_VARARGS | METH_KEYWORDS, update__doc__}, DICT_FROMKEYS_METHODDEF - {"clear", (PyCFunction)dict_clear, METH_NOARGS, + {"clear", dict_clear, METH_NOARGS, clear__doc__}, - {"copy", (PyCFunction)dict_copy, METH_NOARGS, + {"copy", dict_copy, METH_NOARGS, copy__doc__}, DICT___REVERSED___METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, @@ -3937,8 +3942,9 @@ dict_vectorcall(PyObject *type, PyObject * const*args, } static PyObject * -dict_iter(PyDictObject *dict) +dict_iter(PyObject *self) { + PyDictObject *dict = (PyDictObject *)self; return dictiter_new(dict, &PyDictIterKey_Type); } @@ -3958,12 +3964,12 @@ PyTypeObject PyDict_Type = { "dict", sizeof(PyDictObject), 0, - (destructor)dict_dealloc, /* tp_dealloc */ + dict_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)dict_repr, /* tp_repr */ + dict_repr, /* tp_repr */ &dict_as_number, /* tp_as_number */ &dict_as_sequence, /* tp_as_sequence */ &dict_as_mapping, /* tp_as_mapping */ @@ -3981,7 +3987,7 @@ PyTypeObject PyDict_Type = { dict_tp_clear, /* tp_clear */ dict_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - (getiterfunc)dict_iter, /* tp_iter */ + dict_iter, /* tp_iter */ 0, /* tp_iternext */ mapp_methods, /* tp_methods */ 0, /* tp_members */ @@ -4128,8 +4134,9 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) } static void -dictiter_dealloc(dictiterobject *di) +dictiter_dealloc(PyObject *self) { + dictiterobject *di = (dictiterobject *)self; /* bpo-31095: UnTrack is needed before calling any callbacks */ _PyObject_GC_UNTRACK(di); Py_XDECREF(di->di_dict); @@ -4138,16 +4145,18 @@ dictiter_dealloc(dictiterobject *di) } static int -dictiter_traverse(dictiterobject *di, visitproc visit, void *arg) +dictiter_traverse(PyObject *self, visitproc visit, void *arg) { + dictiterobject *di = (dictiterobject *)self; Py_VISIT(di->di_dict); Py_VISIT(di->di_result); return 0; } static PyObject * -dictiter_len(dictiterobject *di, PyObject *Py_UNUSED(ignored)) +dictiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { + dictiterobject *di = (dictiterobject *)self; Py_ssize_t len = 0; if (di->di_dict != NULL && di->di_used == di->di_dict->ma_used) len = di->len; @@ -4158,21 +4167,22 @@ PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it))."); static PyObject * -dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored)); +dictiter_reduce(PyObject *di, PyObject *Py_UNUSED(ignored)); PyDoc_STRVAR(reduce_doc, "Return state information for pickling."); static PyMethodDef dictiter_methods[] = { - {"__length_hint__", _PyCFunction_CAST(dictiter_len), METH_NOARGS, + {"__length_hint__", dictiter_len, METH_NOARGS, length_hint_doc}, - {"__reduce__", _PyCFunction_CAST(dictiter_reduce), METH_NOARGS, + {"__reduce__", dictiter_reduce, METH_NOARGS, reduce_doc}, {NULL, NULL} /* sentinel */ }; static PyObject* -dictiter_iternextkey(dictiterobject *di) +dictiter_iternextkey(PyObject *self) { + dictiterobject *di = (dictiterobject *)self; PyObject *key; Py_ssize_t i; PyDictKeysObject *k; @@ -4244,7 +4254,7 @@ PyTypeObject PyDictIterKey_Type = { sizeof(dictiterobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictiter_dealloc, /* tp_dealloc */ + dictiter_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -4261,19 +4271,20 @@ PyTypeObject PyDictIterKey_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictiter_traverse, /* tp_traverse */ + dictiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc)dictiter_iternextkey, /* tp_iternext */ + dictiter_iternextkey, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, }; static PyObject * -dictiter_iternextvalue(dictiterobject *di) +dictiter_iternextvalue(PyObject *self) { + dictiterobject *di = (dictiterobject *)self; PyObject *value; Py_ssize_t i; PyDictObject *d = di->di_dict; @@ -4343,7 +4354,7 @@ PyTypeObject PyDictIterValue_Type = { sizeof(dictiterobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictiter_dealloc, /* tp_dealloc */ + dictiter_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -4360,19 +4371,20 @@ PyTypeObject PyDictIterValue_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictiter_traverse, /* tp_traverse */ + dictiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc)dictiter_iternextvalue, /* tp_iternext */ + dictiter_iternextvalue, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, }; static PyObject * -dictiter_iternextitem(dictiterobject *di) +dictiter_iternextitem(PyObject *self) { + dictiterobject *di = (dictiterobject *)self; PyObject *key, *value, *result; Py_ssize_t i; PyDictObject *d = di->di_dict; @@ -4467,7 +4479,7 @@ PyTypeObject PyDictIterItem_Type = { sizeof(dictiterobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictiter_dealloc, /* tp_dealloc */ + dictiter_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ @@ -4484,12 +4496,12 @@ PyTypeObject PyDictIterItem_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictiter_traverse, /* tp_traverse */ + dictiter_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc)dictiter_iternextitem, /* tp_iternext */ + dictiter_iternextitem, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, }; @@ -4498,8 +4510,9 @@ PyTypeObject PyDictIterItem_Type = { /* dictreviter */ static PyObject * -dictreviter_iternext(dictiterobject *di) +dictreviter_iternext(PyObject *self) { + dictiterobject *di = (dictiterobject *)self; PyDictObject *d = di->di_dict; if (d == NULL) { @@ -4600,11 +4613,11 @@ PyTypeObject PyDictRevIterKey_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_reversekeyiterator", sizeof(dictiterobject), - .tp_dealloc = (destructor)dictiter_dealloc, + .tp_dealloc = dictiter_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)dictiter_traverse, + .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)dictreviter_iternext, + .tp_iternext = dictreviter_iternext, .tp_methods = dictiter_methods }; @@ -4624,8 +4637,9 @@ dict___reversed___impl(PyDictObject *self) } static PyObject * -dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored)) +dictiter_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) { + dictiterobject *di = (dictiterobject *)self; /* copy the iterator state */ dictiterobject tmp = *di; Py_XINCREF(tmp.di_dict); @@ -4641,11 +4655,11 @@ PyTypeObject PyDictRevIterItem_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_reverseitemiterator", sizeof(dictiterobject), - .tp_dealloc = (destructor)dictiter_dealloc, + .tp_dealloc = dictiter_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)dictiter_traverse, + .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)dictreviter_iternext, + .tp_iternext = dictreviter_iternext, .tp_methods = dictiter_methods }; @@ -4653,11 +4667,11 @@ PyTypeObject PyDictRevIterValue_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "dict_reversevalueiterator", sizeof(dictiterobject), - .tp_dealloc = (destructor)dictiter_dealloc, + .tp_dealloc = dictiter_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)dictiter_traverse, + .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)dictreviter_iternext, + .tp_iternext = dictreviter_iternext, .tp_methods = dictiter_methods }; @@ -4668,8 +4682,9 @@ PyTypeObject PyDictRevIterValue_Type = { /* The instance lay-out is the same for all three; but the type differs. */ static void -dictview_dealloc(_PyDictViewObject *dv) +dictview_dealloc(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; /* bpo-31095: UnTrack is needed before calling any callbacks */ _PyObject_GC_UNTRACK(dv); Py_XDECREF(dv->dv_dict); @@ -4677,15 +4692,17 @@ dictview_dealloc(_PyDictViewObject *dv) } static int -dictview_traverse(_PyDictViewObject *dv, visitproc visit, void *arg) +dictview_traverse(PyObject *self, visitproc visit, void *arg) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; Py_VISIT(dv->dv_dict); return 0; } static Py_ssize_t -dictview_len(_PyDictViewObject *dv) +dictview_len(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; Py_ssize_t len = 0; if (dv->dv_dict != NULL) len = dv->dv_dict->ma_used; @@ -4825,8 +4842,9 @@ dictview_richcompare(PyObject *self, PyObject *other, int op) } static PyObject * -dictview_repr(_PyDictViewObject *dv) +dictview_repr(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; PyObject *seq; PyObject *result = NULL; Py_ssize_t rc; @@ -4850,8 +4868,9 @@ dictview_repr(_PyDictViewObject *dv) /*** dict_keys ***/ static PyObject * -dictkeys_iter(_PyDictViewObject *dv) +dictkeys_iter(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } @@ -4859,22 +4878,23 @@ dictkeys_iter(_PyDictViewObject *dv) } static int -dictkeys_contains(_PyDictViewObject *dv, PyObject *obj) +dictkeys_contains(PyObject *self, PyObject *obj) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) return 0; return PyDict_Contains((PyObject *)dv->dv_dict, obj); } static PySequenceMethods dictkeys_as_sequence = { - (lenfunc)dictview_len, /* sq_length */ + dictview_len, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ - (objobjproc)dictkeys_contains, /* sq_contains */ + dictkeys_contains, /* sq_contains */ }; // Create a set object from dictviews object. @@ -4914,7 +4934,7 @@ dictviews_sub(PyObject *self, PyObject *other) } static int -dictitems_contains(_PyDictViewObject *dv, PyObject *obj); +dictitems_contains(PyObject *dv, PyObject *obj); PyObject * _PyDictView_Intersect(PyObject* self, PyObject *other) @@ -4924,7 +4944,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) PyObject *key; Py_ssize_t len_self; int rv; - int (*dict_contains)(_PyDictViewObject *, PyObject *); + objobjproc dict_contains; /* Python interpreter swaps parameters when dict view is on right side of & */ @@ -4934,7 +4954,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) self = tmp; } - len_self = dictview_len((_PyDictViewObject *)self); + len_self = dictview_len(self); /* if other is a set and self is smaller than other, reuse set intersection logic */ @@ -4946,7 +4966,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) /* if other is another dict view, and it is bigger than self, swap them */ if (PyDictViewSet_Check(other)) { - Py_ssize_t len_other = dictview_len((_PyDictViewObject *)other); + Py_ssize_t len_other = dictview_len(other); if (len_other > len_self) { PyObject *tmp = other; other = self; @@ -4976,7 +4996,7 @@ _PyDictView_Intersect(PyObject* self, PyObject *other) } while ((key = PyIter_Next(it)) != NULL) { - rv = dict_contains((_PyDictViewObject *)self, key); + rv = dict_contains(self, key); if (rv < 0) { goto error; } @@ -5150,7 +5170,7 @@ dictviews_isdisjoint(PyObject *self, PyObject *other) PyObject *item = NULL; if (self == other) { - if (dictview_len((_PyDictViewObject *)self) == 0) + if (dictview_len(self) == 0) Py_RETURN_TRUE; else Py_RETURN_FALSE; @@ -5159,7 +5179,7 @@ dictviews_isdisjoint(PyObject *self, PyObject *other) /* Iterate over the shorter object (only if other is a set, * because PySequence_Contains may be expensive otherwise): */ if (PyAnySet_Check(other) || PyDictViewSet_Check(other)) { - Py_ssize_t len_self = dictview_len((_PyDictViewObject *)self); + Py_ssize_t len_self = dictview_len(self); Py_ssize_t len_other = PyObject_Size(other); if (len_other == -1) return NULL; @@ -5197,15 +5217,15 @@ dictviews_isdisjoint(PyObject *self, PyObject *other) PyDoc_STRVAR(isdisjoint_doc, "Return True if the view and the given iterable have a null intersection."); -static PyObject* dictkeys_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)); +static PyObject* dictkeys_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); PyDoc_STRVAR(reversed_keys_doc, "Return a reverse iterator over the dict keys."); static PyMethodDef dictkeys_methods[] = { - {"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O, + {"isdisjoint", dictviews_isdisjoint, METH_O, isdisjoint_doc}, - {"__reversed__", _PyCFunction_CAST(dictkeys_reversed), METH_NOARGS, + {"__reversed__", dictkeys_reversed, METH_NOARGS, reversed_keys_doc}, {NULL, NULL} /* sentinel */ }; @@ -5216,12 +5236,12 @@ PyTypeObject PyDictKeys_Type = { sizeof(_PyDictViewObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictview_dealloc, /* tp_dealloc */ + dictview_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)dictview_repr, /* tp_repr */ + dictview_repr, /* tp_repr */ &dictviews_as_number, /* tp_as_number */ &dictkeys_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -5233,11 +5253,11 @@ PyTypeObject PyDictKeys_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictview_traverse, /* tp_traverse */ + dictview_traverse, /* tp_traverse */ 0, /* tp_clear */ dictview_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - (getiterfunc)dictkeys_iter, /* tp_iter */ + dictkeys_iter, /* tp_iter */ 0, /* tp_iternext */ dictkeys_methods, /* tp_methods */ .tp_getset = dictview_getset, @@ -5250,8 +5270,9 @@ dictkeys_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) } static PyObject * -dictkeys_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)) +dictkeys_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } @@ -5261,8 +5282,9 @@ dictkeys_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)) /*** dict_items ***/ static PyObject * -dictitems_iter(_PyDictViewObject *dv) +dictitems_iter(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } @@ -5270,8 +5292,9 @@ dictitems_iter(_PyDictViewObject *dv) } static int -dictitems_contains(_PyDictViewObject *dv, PyObject *obj) +dictitems_contains(PyObject *self, PyObject *obj) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; int result; PyObject *key, *value, *found; if (dv->dv_dict == NULL) @@ -5289,25 +5312,25 @@ dictitems_contains(_PyDictViewObject *dv, PyObject *obj) } static PySequenceMethods dictitems_as_sequence = { - (lenfunc)dictview_len, /* sq_length */ + dictview_len, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ - (objobjproc)dictitems_contains, /* sq_contains */ + dictitems_contains, /* sq_contains */ }; -static PyObject* dictitems_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)); +static PyObject* dictitems_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); PyDoc_STRVAR(reversed_items_doc, "Return a reverse iterator over the dict items."); static PyMethodDef dictitems_methods[] = { - {"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O, + {"isdisjoint", dictviews_isdisjoint, METH_O, isdisjoint_doc}, - {"__reversed__", (PyCFunction)dictitems_reversed, METH_NOARGS, + {"__reversed__", dictitems_reversed, METH_NOARGS, reversed_items_doc}, {NULL, NULL} /* sentinel */ }; @@ -5318,12 +5341,12 @@ PyTypeObject PyDictItems_Type = { sizeof(_PyDictViewObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictview_dealloc, /* tp_dealloc */ + dictview_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)dictview_repr, /* tp_repr */ + dictview_repr, /* tp_repr */ &dictviews_as_number, /* tp_as_number */ &dictitems_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -5335,11 +5358,11 @@ PyTypeObject PyDictItems_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictview_traverse, /* tp_traverse */ + dictview_traverse, /* tp_traverse */ 0, /* tp_clear */ dictview_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - (getiterfunc)dictitems_iter, /* tp_iter */ + dictitems_iter, /* tp_iter */ 0, /* tp_iternext */ dictitems_methods, /* tp_methods */ .tp_getset = dictview_getset, @@ -5352,8 +5375,9 @@ dictitems_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) } static PyObject * -dictitems_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)) +dictitems_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } @@ -5363,8 +5387,9 @@ dictitems_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)) /*** dict_values ***/ static PyObject * -dictvalues_iter(_PyDictViewObject *dv) +dictvalues_iter(PyObject *self) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } @@ -5372,7 +5397,7 @@ dictvalues_iter(_PyDictViewObject *dv) } static PySequenceMethods dictvalues_as_sequence = { - (lenfunc)dictview_len, /* sq_length */ + dictview_len, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ @@ -5382,13 +5407,13 @@ static PySequenceMethods dictvalues_as_sequence = { (objobjproc)0, /* sq_contains */ }; -static PyObject* dictvalues_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)); +static PyObject* dictvalues_reversed(PyObject *dv, PyObject *Py_UNUSED(ignored)); PyDoc_STRVAR(reversed_values_doc, "Return a reverse iterator over the dict values."); static PyMethodDef dictvalues_methods[] = { - {"__reversed__", (PyCFunction)dictvalues_reversed, METH_NOARGS, + {"__reversed__", dictvalues_reversed, METH_NOARGS, reversed_values_doc}, {NULL, NULL} /* sentinel */ }; @@ -5399,12 +5424,12 @@ PyTypeObject PyDictValues_Type = { sizeof(_PyDictViewObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ - (destructor)dictview_dealloc, /* tp_dealloc */ + dictview_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ - (reprfunc)dictview_repr, /* tp_repr */ + dictview_repr, /* tp_repr */ 0, /* tp_as_number */ &dictvalues_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ @@ -5416,11 +5441,11 @@ PyTypeObject PyDictValues_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ 0, /* tp_doc */ - (traverseproc)dictview_traverse, /* tp_traverse */ + dictview_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - (getiterfunc)dictvalues_iter, /* tp_iter */ + dictvalues_iter, /* tp_iter */ 0, /* tp_iternext */ dictvalues_methods, /* tp_methods */ .tp_getset = dictview_getset, @@ -5433,8 +5458,9 @@ dictvalues_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) } static PyObject * -dictvalues_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored)) +dictvalues_reversed(PyObject *self, PyObject *Py_UNUSED(ignored)) { + _PyDictViewObject *dv = (_PyDictViewObject *)self; if (dv->dv_dict == NULL) { Py_RETURN_NONE; } From f637b44dd279a7e42d34dc3a00959315b1778072 Mon Sep 17 00:00:00 2001 From: Christopher Chavez Date: Tue, 2 Jan 2024 08:51:32 -0600 Subject: [PATCH 426/442] gh-111178: Avoid calling functions from incompatible pointer types in _tkinter.c (GH-112893) Fix undefined behavior warnings (UBSan -fsanitize=function). --- Modules/_tkinter.c | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 64e752c305aae1..f6181168a85ae1 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -735,8 +735,9 @@ newPyTclObject(Tcl_Obj *arg) } static void -PyTclObject_dealloc(PyTclObject *self) +PyTclObject_dealloc(PyObject *_self) { + PyTclObject *self = (PyTclObject *)_self; PyObject *tp = (PyObject *) Py_TYPE(self); Tcl_DecrRefCount(self->value); Py_XDECREF(self->string); @@ -749,8 +750,9 @@ PyDoc_STRVAR(PyTclObject_string__doc__, "the string representation of this object, either as str or bytes"); static PyObject * -PyTclObject_string(PyTclObject *self, void *ignored) +PyTclObject_string(PyObject *_self, void *ignored) { + PyTclObject *self = (PyTclObject *)_self; if (!self->string) { self->string = unicodeFromTclObj(self->value); if (!self->string) @@ -760,8 +762,9 @@ PyTclObject_string(PyTclObject *self, void *ignored) } static PyObject * -PyTclObject_str(PyTclObject *self) +PyTclObject_str(PyObject *_self) { + PyTclObject *self = (PyTclObject *)_self; if (self->string) { return Py_NewRef(self->string); } @@ -770,9 +773,10 @@ PyTclObject_str(PyTclObject *self) } static PyObject * -PyTclObject_repr(PyTclObject *self) +PyTclObject_repr(PyObject *_self) { - PyObject *repr, *str = PyTclObject_str(self); + PyTclObject *self = (PyTclObject *)_self; + PyObject *repr, *str = PyTclObject_str(_self); if (str == NULL) return NULL; repr = PyUnicode_FromFormat("<%s object: %R>", @@ -809,23 +813,24 @@ PyTclObject_richcompare(PyObject *self, PyObject *other, int op) PyDoc_STRVAR(get_typename__doc__, "name of the Tcl type"); static PyObject* -get_typename(PyTclObject* obj, void* ignored) +get_typename(PyObject *self, void* ignored) { + PyTclObject *obj = (PyTclObject *)self; return unicodeFromTclString(obj->value->typePtr->name); } static PyGetSetDef PyTclObject_getsetlist[] = { - {"typename", (getter)get_typename, NULL, get_typename__doc__}, - {"string", (getter)PyTclObject_string, NULL, + {"typename", get_typename, NULL, get_typename__doc__}, + {"string", PyTclObject_string, NULL, PyTclObject_string__doc__}, {0}, }; static PyType_Slot PyTclObject_Type_slots[] = { - {Py_tp_dealloc, (destructor)PyTclObject_dealloc}, - {Py_tp_repr, (reprfunc)PyTclObject_repr}, - {Py_tp_str, (reprfunc)PyTclObject_str}, + {Py_tp_dealloc, PyTclObject_dealloc}, + {Py_tp_repr, PyTclObject_repr}, + {Py_tp_str, PyTclObject_str}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_richcompare, PyTclObject_richcompare}, {Py_tp_getset, PyTclObject_getsetlist}, @@ -1306,8 +1311,9 @@ Tkapp_ObjectResult(TkappObject *self) hold the Python lock. */ static int -Tkapp_CallProc(Tkapp_CallEvent *e, int flags) +Tkapp_CallProc(Tcl_Event *evPtr, int flags) { + Tkapp_CallEvent *e = (Tkapp_CallEvent *)evPtr; Tcl_Obj *objStore[ARGSZ]; Tcl_Obj **objv; int objc; @@ -1385,7 +1391,7 @@ Tkapp_Call(PyObject *selfptr, PyObject *args) PyErr_NoMemory(); return NULL; } - ev->ev.proc = (Tcl_EventProc*)Tkapp_CallProc; + ev->ev.proc = Tkapp_CallProc; ev->self = self; ev->args = args; ev->res = &res; @@ -1624,8 +1630,9 @@ var_perform(VarEvent *ev) } static int -var_proc(VarEvent* ev, int flags) +var_proc(Tcl_Event *evPtr, int flags) { + VarEvent *ev = (VarEvent *)evPtr; ENTER_PYTHON var_perform(ev); Tcl_MutexLock(&var_mutex); @@ -1663,7 +1670,7 @@ var_invoke(EventFunc func, PyObject *selfptr, PyObject *args, int flags) ev->res = &res; ev->exc = &exc; ev->cond = &cond; - ev->ev.proc = (Tcl_EventProc*)var_proc; + ev->ev.proc = var_proc; Tkapp_ThreadSend(self, (Tcl_Event*)ev, &cond, &var_mutex); Tcl_ConditionFinalize(&cond); if (!res) { @@ -2236,8 +2243,9 @@ typedef struct CommandEvent{ } CommandEvent; static int -Tkapp_CommandProc(CommandEvent *ev, int flags) +Tkapp_CommandProc(Tcl_Event *evPtr, int flags) { + CommandEvent *ev = (CommandEvent *)evPtr; if (ev->create) *ev->status = Tcl_CreateObjCommand( ev->interp, ev->name, PythonCmd, @@ -2290,7 +2298,7 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name, PyMem_Free(data); return NULL; } - ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc; + ev->ev.proc = Tkapp_CommandProc; ev->interp = self->interp; ev->create = 1; ev->name = name; @@ -2343,7 +2351,7 @@ _tkinter_tkapp_deletecommand_impl(TkappObject *self, const char *name) PyErr_NoMemory(); return NULL; } - ev->ev.proc = (Tcl_EventProc*)Tkapp_CommandProc; + ev->ev.proc = Tkapp_CommandProc; ev->interp = self->interp; ev->create = 0; ev->name = name; From ce7a8eef79c1f81358e00aa84b906540edd91458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 18:19:01 +0200 Subject: [PATCH 427/442] build(deps): bump actions/stale from 8 to 9 (#113611) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 94676f5ee5fffc..07608fe91b4dbe 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: steps: - name: "Check PRs" - uses: actions/stale@v8 + uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity.' From fff1e8a50b4eeea83090f4c11e21b4577e8d09e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 18:20:17 +0200 Subject: [PATCH 428/442] build(deps): bump actions/upload-artifact from 3 to 4 (#113614) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f67f30ed07d74..2168ec101cf3d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -395,7 +395,7 @@ jobs: -x test_subprocess \ -x test_signal \ -x test_sysconfig - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: hypothesis-example-db @@ -483,7 +483,7 @@ jobs: output-sarif: true sanitizer: ${{ matrix.sanitizer }} - name: Upload crash - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: ${{ matrix.sanitizer }}-artifacts From 50b093f5c7060c0b44c264808411346cee7becf0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 2 Jan 2024 21:45:36 +0200 Subject: [PATCH 429/442] gh-53502: Fix plistlib.dump() for naive datetime with aware_datetime option (GH-113645) --- Lib/plistlib.py | 4 ++-- Lib/test/test_plistlib.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 6eb70cedd7aec6..0fc1b5cbfa8c49 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -155,7 +155,7 @@ def _date_from_string(s, aware_datetime): def _date_to_string(d, aware_datetime): - if aware_datetime and d.tzinfo is not None: + if aware_datetime: d = d.astimezone(datetime.UTC) return '%04d-%02d-%02dT%02d:%02d:%02dZ' % ( d.year, d.month, d.day, @@ -791,7 +791,7 @@ def _write_object(self, value): self._fp.write(struct.pack('>Bd', 0x23, value)) elif isinstance(value, datetime.datetime): - if self._aware_datetime and value.tzinfo is not None: + if self._aware_datetime: dt = value.astimezone(datetime.UTC) offset = dt - datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC) f = offset.total_seconds() diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py index 010393a417b946..1d2e14a30c4e13 100644 --- a/Lib/test/test_plistlib.py +++ b/Lib/test/test_plistlib.py @@ -885,7 +885,8 @@ def test_dump_naive_datetime_with_aware_datetime_option(self): for fmt in ALL_FORMATS: s = plistlib.dumps(dt, fmt=fmt, aware_datetime=True) parsed = plistlib.loads(s, aware_datetime=False) - self.assertEqual(parsed, dt) + expected = dt.astimezone(datetime.UTC).replace(tzinfo=None) + self.assertEqual(parsed, expected) class TestBinaryPlistlib(unittest.TestCase): From bab0758ea4a1d4666a973ae2d65f21a09e4478ba Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Tue, 2 Jan 2024 15:29:08 -0600 Subject: [PATCH 430/442] gh-110824 Temporarily skip test_sysconfig.test_library on macOS framework builds. (GH-113298) Co-authored-by: Ned Deily --- Lib/test/test_sysconfig.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index a19c04b1b2cde5..be609a0abd29c8 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -43,6 +43,7 @@ def setUp(self): self.name = os.name self.platform = sys.platform self.version = sys.version + self._framework = sys._framework self.sep = os.sep self.join = os.path.join self.isabs = os.path.isabs @@ -66,6 +67,7 @@ def tearDown(self): os.name = self.name sys.platform = self.platform sys.version = self.version + sys._framework = self._framework os.sep = self.sep os.path.join = self.join os.path.isabs = self.isabs @@ -139,7 +141,7 @@ def test_get_preferred_schemes(self): # Mac, framework build. os.name = 'posix' sys.platform = 'darwin' - sys._framework = True + sys._framework = "MyPython" self.assertIsInstance(schemes, dict) self.assertEqual(set(schemes), expected_schemes) @@ -413,7 +415,10 @@ def test_library(self): else: self.assertTrue(library.startswith(f'libpython{major}.{minor}')) self.assertTrue(library.endswith('.a')) - self.assertTrue(ldlibrary.startswith(f'libpython{major}.{minor}')) + if sys.platform == 'darwin' and sys._framework: + self.skipTest('gh-110824: skip LDLIBRARY test for framework build') + else: + self.assertTrue(ldlibrary.startswith(f'libpython{major}.{minor}')) @unittest.skipUnless(sys.platform == "darwin", "test only relevant on MacOSX") @requires_subprocess() From b0fb074d5983f07517cec76a37268f13c986d314 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Tue, 2 Jan 2024 14:09:57 -0800 Subject: [PATCH 431/442] GH-113657: Add back missing _SET_IP uops in tier two (GH-113662) --- .../2024-01-02-11-14-29.gh-issue-113657.CQo9vF.rst | 2 ++ Python/optimizer_analysis.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-02-11-14-29.gh-issue-113657.CQo9vF.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-02-11-14-29.gh-issue-113657.CQo9vF.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-02-11-14-29.gh-issue-113657.CQo9vF.rst new file mode 100644 index 00000000000000..b520b5c2529425 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-02-11-14-29.gh-issue-113657.CQo9vF.rst @@ -0,0 +1,2 @@ +Fix an issue that caused important instruction pointer updates to be +optimized out of tier two traces. diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 8b471d70a10d7d..4eb2d9711f5e56 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -4,6 +4,7 @@ #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_uop_metadata.h" #include "pycore_uops.h" #include "pycore_long.h" #include "cpython/optimizer.h" @@ -35,13 +36,13 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) break; } else { - if (OPCODE_HAS_ESCAPES(opcode)) { + if (_PyUop_Flags[opcode] & HAS_ESCAPES_FLAG) { maybe_invalid = true; if (last_set_ip >= 0) { buffer[last_set_ip].opcode = _SET_IP; } } - if (OPCODE_HAS_ERROR(opcode) || opcode == _PUSH_FRAME) { + if ((_PyUop_Flags[opcode] & HAS_ERROR_FLAG) || opcode == _PUSH_FRAME) { if (last_set_ip >= 0) { buffer[last_set_ip].opcode = _SET_IP; } From 5dc79e3d7f26a6a871a89ce3efc9f1bcee7bb447 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Tue, 2 Jan 2024 16:30:53 -0800 Subject: [PATCH 432/442] gh-113628: Fix test_site test with long stdlib paths (#113640) --- Lib/test/test_site.py | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 33d0975bda8eaa..9f199d9069d207 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -641,10 +641,24 @@ def _calc_sys_path_for_underpth_nosite(self, sys_prefix, lines): sys_path.append(abs_path) return sys_path + def _get_pth_lines(self, libpath: str, *, import_site: bool): + pth_lines = ['fake-path-name'] + # include 200 lines of `libpath` in _pth lines (or fewer + # if the `libpath` is long enough to get close to 32KB + # see https://github.com/python/cpython/issues/113628) + encoded_libpath_length = len(libpath.encode("utf-8")) + repetitions = min(200, 30000 // encoded_libpath_length) + if repetitions <= 2: + self.skipTest( + f"Python stdlib path is too long ({encoded_libpath_length:,} bytes)") + pth_lines.extend(libpath for _ in range(repetitions)) + pth_lines.extend(['', '# comment']) + if import_site: + pth_lines.append('import site') + return pth_lines + @support.requires_subprocess() def test_underpth_basic(self): - libpath = test.support.STDLIB_DIR - exe_prefix = os.path.dirname(sys.executable) pth_lines = ['#.', '# ..', *sys.path, '.', '..'] exe_file = self._create_underpth_exe(pth_lines) sys_path = self._calc_sys_path_for_underpth_nosite( @@ -666,12 +680,7 @@ def test_underpth_basic(self): def test_underpth_nosite_file(self): libpath = test.support.STDLIB_DIR exe_prefix = os.path.dirname(sys.executable) - pth_lines = [ - 'fake-path-name', - *[libpath for _ in range(200)], - '', - '# comment', - ] + pth_lines = self._get_pth_lines(libpath, import_site=False) exe_file = self._create_underpth_exe(pth_lines) sys_path = self._calc_sys_path_for_underpth_nosite( os.path.dirname(exe_file), @@ -695,13 +704,8 @@ def test_underpth_nosite_file(self): def test_underpth_file(self): libpath = test.support.STDLIB_DIR exe_prefix = os.path.dirname(sys.executable) - exe_file = self._create_underpth_exe([ - 'fake-path-name', - *[libpath for _ in range(200)], - '', - '# comment', - 'import site' - ]) + exe_file = self._create_underpth_exe( + self._get_pth_lines(libpath, import_site=True)) sys_prefix = os.path.dirname(exe_file) env = os.environ.copy() env['PYTHONPATH'] = 'from-env' @@ -720,13 +724,8 @@ def test_underpth_file(self): def test_underpth_dll_file(self): libpath = test.support.STDLIB_DIR exe_prefix = os.path.dirname(sys.executable) - exe_file = self._create_underpth_exe([ - 'fake-path-name', - *[libpath for _ in range(200)], - '', - '# comment', - 'import site' - ], exe_pth=False) + exe_file = self._create_underpth_exe( + self._get_pth_lines(libpath, import_site=True), exe_pth=False) sys_prefix = os.path.dirname(exe_file) env = os.environ.copy() env['PYTHONPATH'] = 'from-env' From dc8df6e84024b79aa96e85a64f354bf8e827bcba Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 3 Jan 2024 11:01:13 +0000 Subject: [PATCH 433/442] GH-113595: Don't enter invalid executor (GH-113596) --- Python/bytecodes.c | 28 +++++++++++++++++++--------- Python/generated_cases.c.h | 30 ++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 29e1dab184ef4e..2eeeac53e1dd7e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2364,17 +2364,27 @@ dummy_func( PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; - Py_INCREF(executor); - if (executor->execute == _PyUOpExecute) { - current_executor = (_PyUOpExecutorObject *)executor; - GOTO_TIER_TWO(); + if (executor->vm_data.valid) { + Py_INCREF(executor); + if (executor->execute == _PyUOpExecute) { + current_executor = (_PyUOpExecutorObject *)executor; + GOTO_TIER_TWO(); + } + next_instr = executor->execute(executor, frame, stack_pointer); + frame = tstate->current_frame; + if (next_instr == NULL) { + goto resume_with_error; + } + stack_pointer = _PyFrame_GetStackPointer(frame); } - next_instr = executor->execute(executor, frame, stack_pointer); - frame = tstate->current_frame; - if (next_instr == NULL) { - goto resume_with_error; + else { + opcode = this_instr->op.code = executor->vm_data.opcode; + this_instr->op.arg = executor->vm_data.oparg; + oparg = (oparg & (~255)) | executor->vm_data.oparg; + code->co_executors->executors[oparg&255] = NULL; + Py_DECREF(executor); + DISPATCH_GOTO(); } - stack_pointer = _PyFrame_GetStackPointer(frame); } replaced op(_POP_JUMP_IF_FALSE, (cond -- )) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index ce31967b7912d7..99fd169ca4fec3 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2371,24 +2371,34 @@ } TARGET(ENTER_EXECUTOR) { - frame->instr_ptr = next_instr; + _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(ENTER_EXECUTOR); TIER_ONE_ONLY CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; - Py_INCREF(executor); - if (executor->execute == _PyUOpExecute) { - current_executor = (_PyUOpExecutorObject *)executor; - GOTO_TIER_TWO(); + if (executor->vm_data.valid) { + Py_INCREF(executor); + if (executor->execute == _PyUOpExecute) { + current_executor = (_PyUOpExecutorObject *)executor; + GOTO_TIER_TWO(); + } + next_instr = executor->execute(executor, frame, stack_pointer); + frame = tstate->current_frame; + if (next_instr == NULL) { + goto resume_with_error; + } + stack_pointer = _PyFrame_GetStackPointer(frame); } - next_instr = executor->execute(executor, frame, stack_pointer); - frame = tstate->current_frame; - if (next_instr == NULL) { - goto resume_with_error; + else { + opcode = this_instr->op.code = executor->vm_data.opcode; + this_instr->op.arg = executor->vm_data.oparg; + oparg = (oparg & (~255)) | executor->vm_data.oparg; + code->co_executors->executors[oparg&255] = NULL; + Py_DECREF(executor); + DISPATCH_GOTO(); } - stack_pointer = _PyFrame_GetStackPointer(frame); DISPATCH(); } From ea978c645edd7bc29d811c61477dff766d7318b6 Mon Sep 17 00:00:00 2001 From: Ege Akman Date: Wed, 3 Jan 2024 14:22:38 +0300 Subject: [PATCH 434/442] gh-113637: Let c_annotations.py to handle the spacing of Limited/Unstable API & Stable ABI translation strings (#113638) --- Doc/tools/extensions/c_annotations.py | 13 +++++++------ Doc/tools/templates/dummy.html | 12 ++++++------ Misc/ACKS | 1 + 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py index 42c2f10e0be260..ba37634545c2cf 100644 --- a/Doc/tools/extensions/c_annotations.py +++ b/Doc/tools/extensions/c_annotations.py @@ -126,7 +126,8 @@ def add_annotations(self, app, doctree): f"Object type mismatch in limited API annotation " f"for {name}: {record['role']!r} != {objtype!r}") stable_added = record['added'] - message = sphinx_gettext(' Part of the ') + message = sphinx_gettext('Part of the') + message = message.center(len(message) + 2) emph_node = nodes.emphasis(message, message, classes=['stableabi']) ref_node = addnodes.pending_xref( @@ -139,27 +140,27 @@ def add_annotations(self, app, doctree): ref_node += nodes.Text(sphinx_gettext('Stable ABI')) emph_node += ref_node if struct_abi_kind == 'opaque': - emph_node += nodes.Text(sphinx_gettext(' (as an opaque struct)')) + emph_node += nodes.Text(' ' + sphinx_gettext('(as an opaque struct)')) elif struct_abi_kind == 'full-abi': - emph_node += nodes.Text(sphinx_gettext(' (including all members)')) + emph_node += nodes.Text(' ' + sphinx_gettext('(including all members)')) if record['ifdef_note']: emph_node += nodes.Text(' ' + record['ifdef_note']) if stable_added == '3.2': # Stable ABI was introduced in 3.2. pass else: - emph_node += nodes.Text(sphinx_gettext(' since version %s') % stable_added) + emph_node += nodes.Text(' ' + sphinx_gettext('since version %s') % stable_added) emph_node += nodes.Text('.') if struct_abi_kind == 'members': emph_node += nodes.Text( - sphinx_gettext(' (Only some members are part of the stable ABI.)')) + ' ' + sphinx_gettext('(Only some members are part of the stable ABI.)')) node.insert(0, emph_node) # Unstable API annotation. if name.startswith('PyUnstable'): warn_node = nodes.admonition( classes=['unstable-c-api', 'warning']) - message = sphinx_gettext('This is ') + message = sphinx_gettext('This is') + ' ' emph_node = nodes.emphasis(message, message) ref_node = addnodes.pending_xref( 'Unstable API', refdomain="std", diff --git a/Doc/tools/templates/dummy.html b/Doc/tools/templates/dummy.html index 3a0acab8836b11..49c2a71a5e40cf 100644 --- a/Doc/tools/templates/dummy.html +++ b/Doc/tools/templates/dummy.html @@ -9,14 +9,14 @@ In extensions/c_annotations.py: -{% trans %} Part of the {% endtrans %} +{% trans %}Part of the{% endtrans %} {% trans %}Limited API{% endtrans %} {% trans %}Stable ABI{% endtrans %} -{% trans %} (as an opaque struct){% endtrans %} -{% trans %} (including all members){% endtrans %} -{% trans %} since version %s{% endtrans %} -{% trans %} (Only some members are part of the stable ABI.){% endtrans %} -{% trans %}This is {% endtrans %} +{% trans %}(as an opaque struct){% endtrans %} +{% trans %}(including all members){% endtrans %} +{% trans %}since version %s{% endtrans %} +{% trans %}(Only some members are part of the stable ABI.){% endtrans %} +{% trans %}This is{% endtrans %} {% trans %}Unstable API{% endtrans %} {% trans %}. It may change without warning in minor releases.{% endtrans %} {% trans %}Return value: Always NULL.{% endtrans %} diff --git a/Misc/ACKS b/Misc/ACKS index 6b98be32905391..ab1255be2d58fa 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -31,6 +31,7 @@ Farhan Ahmad Matthew Ahrens Nir Aides Akira +Ege Akman Yaniv Aknin Jyrki Alakuijala Tatiana Al-Chueyr From 4de468cce106221968d7ac08ddd94571b903c194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:50:44 +0000 Subject: [PATCH 435/442] `functools.partial` docs: Use the more common spelling for "referenceable" (#113675) --- Doc/library/functools.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 69ec1eb3ecd89d..6749a5137b446f 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -742,7 +742,7 @@ have three read-only attributes: called. :class:`partial` objects are like :class:`function` objects in that they are -callable, weak referencable, and can have attributes. There are some important +callable, weak referenceable, and can have attributes. There are some important differences. For instance, the :attr:`~definition.__name__` and :attr:`__doc__` attributes are not created automatically. Also, :class:`partial` objects defined in classes behave like static methods and do not transform into bound methods From fab7ad62ceca1f88767bca4e1f06f8e4b1faef2f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 3 Jan 2024 15:04:26 +0200 Subject: [PATCH 436/442] gh-101100: Fix Sphinx warnings for removed dead batteries (#113669) Co-authored-by: Alex Waygood --- Doc/whatsnew/2.4.rst | 2 +- Doc/whatsnew/2.6.rst | 2 +- Doc/whatsnew/3.10.rst | 6 +++--- Doc/whatsnew/3.11.rst | 12 ++++++------ Doc/whatsnew/3.2.rst | 12 ++++++------ Doc/whatsnew/3.3.rst | 2 +- Doc/whatsnew/3.4.rst | 6 +++--- Doc/whatsnew/3.5.rst | 22 +++++++++++----------- Doc/whatsnew/3.6.rst | 14 +++++++------- Doc/whatsnew/3.7.rst | 4 ++-- Doc/whatsnew/3.8.rst | 2 +- Doc/whatsnew/3.9.rst | 2 +- Misc/NEWS.d/3.10.0a1.rst | 2 +- Misc/NEWS.d/3.11.0a1.rst | 4 ++-- Misc/NEWS.d/3.11.0a7.rst | 4 ++-- Misc/NEWS.d/3.12.0a1.rst | 2 +- Misc/NEWS.d/3.12.0a2.rst | 2 +- Misc/NEWS.d/3.8.0a1.rst | 4 ++-- Misc/NEWS.d/3.8.0a4.rst | 2 +- 19 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Doc/whatsnew/2.4.rst b/Doc/whatsnew/2.4.rst index 6df59dd245ff55..e9a59f4a62551a 100644 --- a/Doc/whatsnew/2.4.rst +++ b/Doc/whatsnew/2.4.rst @@ -995,7 +995,7 @@ fixes. Here's a partial list of the most notable changes, sorted alphabetically by module name. Consult the :file:`Misc/NEWS` file in the source tree for a more complete list of changes, or look through the CVS logs for all the details. -* The :mod:`asyncore` module's :func:`loop` function now has a *count* parameter +* The :mod:`!asyncore` module's :func:`!loop` function now has a *count* parameter that lets you perform a limited number of passes through the polling loop. The default is still to loop forever. diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index e8c1709c42abac..d947f61b50cfe0 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1789,7 +1789,7 @@ changes, sorted alphabetically by module name. Consult the :file:`Misc/NEWS` file in the source tree for a more complete list of changes, or look through the Subversion logs for all the details. -* The :mod:`asyncore` and :mod:`asynchat` modules are +* The :mod:`!asyncore` and :mod:`!asynchat` modules are being actively maintained again, and a number of patches and bugfixes were applied. (Maintained by Josiah Carlson; see :issue:`1736190` for one patch.) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 2da90b7ed55744..a8a27bfd3dc1bc 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1278,7 +1278,7 @@ Add negative indexing support to :attr:`PurePath.parents (Contributed by Yaroslav Pankovych in :issue:`21041`.) Add :meth:`Path.hardlink_to ` method that -supersedes :meth:`~pathlib.Path.link_to`. The new method has the same argument +supersedes :meth:`!link_to`. The new method has the same argument order as :meth:`~pathlib.Path.symlink_to`. (Contributed by Barney Gale in :issue:`39950`.) @@ -1740,7 +1740,7 @@ Deprecated (Contributed by Jelle Zijlstra in :gh:`87889`.) -* :meth:`pathlib.Path.link_to` is deprecated and slated for removal in +* :meth:`!pathlib.Path.link_to` is deprecated and slated for removal in Python 3.12. Use :meth:`pathlib.Path.hardlink_to` instead. (Contributed by Barney Gale in :issue:`39950`.) @@ -1771,7 +1771,7 @@ Deprecated * NPN features like :meth:`ssl.SSLSocket.selected_npn_protocol` and :meth:`ssl.SSLContext.set_npn_protocols` are replaced by ALPN. -* The threading debug (:envvar:`PYTHONTHREADDEBUG` environment variable) is +* The threading debug (:envvar:`!PYTHONTHREADDEBUG` environment variable) is deprecated in Python 3.10 and will be removed in Python 3.12. This feature requires a :ref:`debug build of Python `. (Contributed by Victor Stinner in :issue:`44584`.) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index ce4c98eba71443..cb646a54df3607 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1747,7 +1747,7 @@ Modules (Contributed by Brett Cannon in :issue:`47061` and Victor Stinner in :gh:`68966`.) -* The :mod:`asynchat`, :mod:`asyncore` and :mod:`smtpd` modules have been +* The :mod:`!asynchat`, :mod:`!asyncore` and :mod:`!smtpd` modules have been deprecated since at least Python 3.6. Their documentation and deprecation warnings have now been updated to note they will be removed in Python 3.12. (Contributed by Hugo van Kemenade in :issue:`47022`.) @@ -1877,8 +1877,8 @@ and will be removed in Python 3.12. C APIs pending removal are :ref:`listed separately `. -* The :mod:`asynchat` module -* The :mod:`asyncore` module +* The :mod:`!asynchat` module +* The :mod:`!asyncore` module * The :ref:`entire distutils package ` * The :mod:`!imp` module * The :class:`typing.io ` namespace @@ -1902,10 +1902,10 @@ C APIs pending removal are * :func:`!importlib.util.set_package_wrapper` * :class:`!pkgutil.ImpImporter` * :class:`!pkgutil.ImpLoader` -* :meth:`pathlib.Path.link_to` +* :meth:`!pathlib.Path.link_to` * :func:`!sqlite3.enable_shared_cache` * :func:`!sqlite3.OptimizedUnicode` -* :envvar:`PYTHONTHREADDEBUG` environment variable +* :envvar:`!PYTHONTHREADDEBUG` environment variable * The following deprecated aliases in :mod:`unittest`: ============================ =============================== =============== @@ -2007,7 +2007,7 @@ Removed C APIs are :ref:`listed separately `. because it was not used and added by mistake in previous versions. (Contributed by Nikita Sobolev in :issue:`46483`.) -* Removed the :class:`!MailmanProxy` class in the :mod:`smtpd` module, +* Removed the :class:`!MailmanProxy` class in the :mod:`!smtpd` module, as it is unusable without the external :mod:`!mailman` package. (Contributed by Donghee Na in :issue:`35800`.) diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index aad196478dd38b..9834bc03dc4b74 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -1858,12 +1858,12 @@ structure. asyncore -------- -:class:`asyncore.dispatcher` now provides a -:meth:`~asyncore.dispatcher.handle_accepted()` method +:class:`!asyncore.dispatcher` now provides a +:meth:`!handle_accepted()` method returning a ``(sock, addr)`` pair which is called when a connection has actually been established with a new remote endpoint. This is supposed to be used as a -replacement for old :meth:`~asyncore.dispatcher.handle_accept()` and avoids -the user to call :meth:`~asyncore.dispatcher.accept()` directly. +replacement for old :meth:`!handle_accept()` and avoids +the user to call :meth:`!accept()` directly. (Contributed by Giampaolo Rodolà; :issue:`6706`.) @@ -2737,8 +2737,8 @@ require changes to your code: thread-state aware APIs (such as :c:func:`PyEval_SaveThread` and :c:func:`PyEval_RestoreThread`) should be used instead. -* Due to security risks, :func:`asyncore.handle_accept` has been deprecated, and - a new function, :func:`asyncore.handle_accepted`, was added to replace it. +* Due to security risks, :func:`!asyncore.handle_accept` has been deprecated, and + a new function, :func:`!asyncore.handle_accepted`, was added to replace it. (Contributed by Giampaolo Rodola in :issue:`6706`.) diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 79e2dd9dcee361..760324ae66a3af 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -1845,7 +1845,7 @@ signal smtpd ----- -The :mod:`smtpd` module now supports :rfc:`5321` (extended SMTP) and :rfc:`1870` +The :mod:`!smtpd` module now supports :rfc:`5321` (extended SMTP) and :rfc:`1870` (size extension). Per the standard, these extensions are enabled if and only if the client initiates the session with an ``EHLO`` command. diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index b26e3d36c4bfbc..e07eda985d9bad 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -1369,9 +1369,9 @@ error. (Contributed by Atsuo Ishimoto and Hynek Schlawack in smtpd ----- -The :class:`~smtpd.SMTPServer` and :class:`~smtpd.SMTPChannel` classes now +The :class:`!SMTPServer` and :class:`!SMTPChannel` classes now accept a *map* keyword argument which, if specified, is passed in to -:class:`asynchat.async_chat` as its *map* argument. This allows an application +:class:`!asynchat.async_chat` as its *map* argument. This allows an application to avoid affecting the global socket map. (Contributed by Vinay Sajip in :issue:`11959`.) @@ -2370,7 +2370,7 @@ Changes in the Python API :issue:`18011`.) Note: this change was also inadvertently applied in Python 3.3.3. -* The :attr:`~cgi.FieldStorage.file` attribute is now automatically closed when +* The :attr:`!file` attribute is now automatically closed when the creating :class:`!cgi.FieldStorage` instance is garbage collected. If you were pulling the file object out separately from the :class:`!cgi.FieldStorage` instance and not keeping the instance alive, then you should either store the diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index bbf2dc59a9f60a..1c7a9270af0aab 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -878,7 +878,7 @@ size of decompressed data. (Contributed by Nikolaus Rath in :issue:`15955`.) cgi --- -The :class:`~cgi.FieldStorage` class now supports the :term:`context manager` +The :class:`!FieldStorage` class now supports the :term:`context manager` protocol. (Contributed by Berker Peksag in :issue:`20289`.) @@ -1663,34 +1663,34 @@ during debugging, instead of integer "magic numbers". smtpd ----- -Both the :class:`~smtpd.SMTPServer` and :class:`~smtpd.SMTPChannel` classes now +Both the :class:`!SMTPServer` and :class:`!SMTPChannel` classes now accept a *decode_data* keyword argument to determine if the ``DATA`` portion of the SMTP transaction is decoded using the ``"utf-8"`` codec or is instead provided to the -:meth:`SMTPServer.process_message() ` +:meth:`!SMTPServer.process_message()` method as a byte string. The default is ``True`` for backward compatibility reasons, but will change to ``False`` in Python 3.6. If *decode_data* is set to ``False``, the ``process_message`` method must be prepared to accept keyword arguments. (Contributed by Maciej Szulik in :issue:`19662`.) -The :class:`~smtpd.SMTPServer` class now advertises the ``8BITMIME`` extension +The :class:`!SMTPServer` class now advertises the ``8BITMIME`` extension (:rfc:`6152`) if *decode_data* has been set ``True``. If the client specifies ``BODY=8BITMIME`` on the ``MAIL`` command, it is passed to -:meth:`SMTPServer.process_message() ` +:meth:`!SMTPServer.process_message()` via the *mail_options* keyword. (Contributed by Milan Oberkirch and R. David Murray in :issue:`21795`.) -The :class:`~smtpd.SMTPServer` class now also supports the ``SMTPUTF8`` +The :class:`!SMTPServer` class now also supports the ``SMTPUTF8`` extension (:rfc:`6531`: Internationalized Email). If the client specified ``SMTPUTF8 BODY=8BITMIME`` on the ``MAIL`` command, they are passed to -:meth:`SMTPServer.process_message() ` +:meth:`!SMTPServer.process_message()` via the *mail_options* keyword. It is the responsibility of the ``process_message`` method to correctly handle the ``SMTPUTF8`` data. (Contributed by Milan Oberkirch in :issue:`21725`.) It is now possible to provide, directly or via name resolution, IPv6 -addresses in the :class:`~smtpd.SMTPServer` constructor, and have it +addresses in the :class:`!SMTPServer` constructor, and have it successfully connect. (Contributed by Milan Oberkirch in :issue:`14758`.) @@ -1714,7 +1714,7 @@ support :rfc:`6531` (SMTPUTF8). sndhdr ------ -The :func:`~sndhdr.what` and :func:`~sndhdr.whathdr` functions now return +The :func:`!what` and :func:`!whathdr` functions now return a :func:`~collections.namedtuple`. (Contributed by Claudiu Popa in :issue:`18615`.) @@ -2296,9 +2296,9 @@ slated for removal in Python 3.6. The :func:`asyncio.async` function is deprecated in favor of :func:`~asyncio.ensure_future`. -The :mod:`smtpd` module has in the past always decoded the DATA portion of +The :mod:`!smtpd` module has in the past always decoded the DATA portion of email messages using the ``utf-8`` codec. This can now be controlled by the -new *decode_data* keyword to :class:`~smtpd.SMTPServer`. The default value is +new *decode_data* keyword to :class:`!SMTPServer`. The default value is ``True``, but this default is deprecated. Specify the *decode_data* keyword with an appropriate value to avoid the deprecation warning. diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 5a3cea0ec87cb2..11e1d73232a96d 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -1961,14 +1961,14 @@ Deprecated Python modules, functions and methods asynchat ~~~~~~~~ -The :mod:`asynchat` has been deprecated in favor of :mod:`asyncio`. +The :mod:`!asynchat` has been deprecated in favor of :mod:`asyncio`. (Contributed by Mariatta in :issue:`25002`.) asyncore ~~~~~~~~ -The :mod:`asyncore` has been deprecated in favor of :mod:`asyncio`. +The :mod:`!asyncore` has been deprecated in favor of :mod:`asyncio`. (Contributed by Mariatta in :issue:`25002`.) @@ -2189,7 +2189,7 @@ Changes in the Python API :mod:`calendar`, :mod:`!cgi`, :mod:`csv`, :mod:`~xml.etree.ElementTree`, :mod:`enum`, :mod:`fileinput`, :mod:`ftplib`, :mod:`logging`, :mod:`mailbox`, - :mod:`mimetypes`, :mod:`optparse`, :mod:`plistlib`, :mod:`smtpd`, + :mod:`mimetypes`, :mod:`optparse`, :mod:`plistlib`, :mod:`!smtpd`, :mod:`subprocess`, :mod:`tarfile`, :mod:`threading` and :mod:`wave`. This means they will export new symbols when ``import *`` is used. @@ -2219,11 +2219,11 @@ Changes in the Python API an error (e.g. ``EBADF``) was reported by the underlying system call. (Contributed by Martin Panter in :issue:`26685`.) -* The *decode_data* argument for the :class:`smtpd.SMTPChannel` and - :class:`smtpd.SMTPServer` constructors is now ``False`` by default. +* The *decode_data* argument for the :class:`!smtpd.SMTPChannel` and + :class:`!smtpd.SMTPServer` constructors is now ``False`` by default. This means that the argument passed to - :meth:`~smtpd.SMTPServer.process_message` is now a bytes object by - default, and ``process_message()`` will be passed keyword arguments. + :meth:`!process_message` is now a bytes object by + default, and :meth:`!process_message` will be passed keyword arguments. Code that has already been updated in accordance with the deprecation warning generated by 3.5 will not be affected. diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 775a45a1b3ff06..402b15a277e53d 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -2304,9 +2304,9 @@ Changes in the Python API * The :attr:`struct.Struct.format` type is now :class:`str` instead of :class:`bytes`. (Contributed by Victor Stinner in :issue:`21071`.) -* :func:`~cgi.parse_multipart` now accepts the *encoding* and *errors* +* :func:`!cgi.parse_multipart` now accepts the *encoding* and *errors* arguments and returns the same results as - :class:`~FieldStorage`: for non-file fields, the value associated to a key + :class:`!FieldStorage`: for non-file fields, the value associated to a key is a list of strings, not bytes. (Contributed by Pierre Quentel in :issue:`29979`.) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index e4dcb9bf872e28..d373fa163ff737 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -1086,7 +1086,7 @@ pathlib contain characters unrepresentable at the OS level. (Contributed by Serhiy Storchaka in :issue:`33721`.) -Added :meth:`pathlib.Path.link_to()` which creates a hard link pointing +Added :meth:`!pathlib.Path.link_to()` which creates a hard link pointing to a path. (Contributed by Joannah Nanjekye in :issue:`26978`) Note that ``link_to`` was deprecated in 3.10 and removed in 3.12 in diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 0c85fe15915518..f7ad4372325ccb 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -931,7 +931,7 @@ Deprecated * Passing ``None`` as the first argument to the :func:`shlex.split` function has been deprecated. (Contributed by Zackery Spytz in :issue:`33262`.) -* :func:`smtpd.MailmanProxy` is now deprecated as it is unusable without +* :func:`!smtpd.MailmanProxy` is now deprecated as it is unusable without an external module, ``mailman``. (Contributed by Samuel Colvin in :issue:`35800`.) * The :mod:`!lib2to3` module now emits a :exc:`PendingDeprecationWarning`. diff --git a/Misc/NEWS.d/3.10.0a1.rst b/Misc/NEWS.d/3.10.0a1.rst index 731eed3447d2bc..3186de75efd9c5 100644 --- a/Misc/NEWS.d/3.10.0a1.rst +++ b/Misc/NEWS.d/3.10.0a1.rst @@ -2527,7 +2527,7 @@ in Python 3.4 and removed in Python 3.5. .. nonce: BE7zbu .. section: Library -Fix `cgi.parse_multipart` without content_length. Patch by Roger Duran +Fix ``cgi.parse_multipart`` without content_length. Patch by Roger Duran .. diff --git a/Misc/NEWS.d/3.11.0a1.rst b/Misc/NEWS.d/3.11.0a1.rst index ba7fb515305ff5..63abcbd5a6499e 100644 --- a/Misc/NEWS.d/3.11.0a1.rst +++ b/Misc/NEWS.d/3.11.0a1.rst @@ -819,7 +819,7 @@ always available when needed. Patch by Mark Shannon. .. nonce: qKnSqV .. section: Core and Builtins -The threading debug (:envvar:`PYTHONTHREADDEBUG` environment variable) is +The threading debug (:envvar:`!PYTHONTHREADDEBUG` environment variable) is deprecated in Python 3.10 and will be removed in Python 3.12. This feature requires a debug build of Python. Patch by Victor Stinner. @@ -2808,7 +2808,7 @@ behaves differently than the similar implementation in :mod:`sysconfig`. .. nonce: 3hmkWw .. section: Library -:class:`smtpd.MailmanProxy` is now removed as it is unusable without an +:class:`!smtpd.MailmanProxy` is now removed as it is unusable without an external module, ``mailman``. Patch by Donghee Na. .. diff --git a/Misc/NEWS.d/3.11.0a7.rst b/Misc/NEWS.d/3.11.0a7.rst index 79557d5c436593..76699632db223a 100644 --- a/Misc/NEWS.d/3.11.0a7.rst +++ b/Misc/NEWS.d/3.11.0a7.rst @@ -717,7 +717,7 @@ Fix :class:`asyncio.Semaphore` re-aquiring FIFO order. .. nonce: uaEDcI .. section: Library -The :mod:`asynchat`, :mod:`asyncore` and :mod:`smtpd` modules have been +The :mod:`!asynchat`, :mod:`!asyncore` and :mod:`!smtpd` modules have been deprecated since at least Python 3.6. Their documentation and deprecation warnings and have now been updated to note they will removed in Python 3.12 (:pep:`594`). @@ -1324,7 +1324,7 @@ extensions. .. section: Tests A test case for :func:`os.sendfile` is converted from deprecated -:mod:`asyncore` (see :pep:`594`) to :mod:`asyncio`. Patch by Oleg Iarygin. +:mod:`!asyncore` (see :pep:`594`) to :mod:`asyncio`. Patch by Oleg Iarygin. .. diff --git a/Misc/NEWS.d/3.12.0a1.rst b/Misc/NEWS.d/3.12.0a1.rst index 29d04fa0e175bf..81ef69093005e8 100644 --- a/Misc/NEWS.d/3.12.0a1.rst +++ b/Misc/NEWS.d/3.12.0a1.rst @@ -3617,7 +3617,7 @@ allow access to handlers by name. .. nonce: uw6x5z .. section: Library -The :mod:`smtpd` module was removed per the schedule in :pep:`594`. +The :mod:`!smtpd` module was removed per the schedule in :pep:`594`. .. diff --git a/Misc/NEWS.d/3.12.0a2.rst b/Misc/NEWS.d/3.12.0a2.rst index 1a04ed473f329d..dbc743abe8a767 100644 --- a/Misc/NEWS.d/3.12.0a2.rst +++ b/Misc/NEWS.d/3.12.0a2.rst @@ -695,7 +695,7 @@ Make sure ``patch.dict()`` can be applied on async functions. .. nonce: jUpzF3 .. section: Library -Remove modules :mod:`asyncore` and :mod:`asynchat`, which were deprecated by +Remove modules :mod:`!asyncore` and :mod:`!asynchat`, which were deprecated by :pep:`594`. .. diff --git a/Misc/NEWS.d/3.8.0a1.rst b/Misc/NEWS.d/3.8.0a1.rst index 99f408661d9f69..b56cda86f11faa 100644 --- a/Misc/NEWS.d/3.8.0a1.rst +++ b/Misc/NEWS.d/3.8.0a1.rst @@ -2006,8 +2006,8 @@ Improved support of custom data descriptors in :func:`help` and .. nonce: V4kNN3 .. section: Library -The `crypt` module now internally uses the `crypt_r()` library function -instead of `crypt()` when available. +The ``crypt`` module now internally uses the ``crypt_r()`` library function +instead of ``crypt()`` when available. .. diff --git a/Misc/NEWS.d/3.8.0a4.rst b/Misc/NEWS.d/3.8.0a4.rst index 7e8bfa5c4364a9..3097245b74a511 100644 --- a/Misc/NEWS.d/3.8.0a4.rst +++ b/Misc/NEWS.d/3.8.0a4.rst @@ -255,7 +255,7 @@ all tags in a namespace. Patch by Stefan Behnel. .. nonce: Lpm-SI .. section: Library -`pathlib.path.link_to()` is now implemented. It creates a hard link pointing +``pathlib.path.link_to()`` is now implemented. It creates a hard link pointing to a path. .. From 0c3455a9693cfabcd991c4c33db7cccb1387de58 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 3 Jan 2024 22:25:27 +0900 Subject: [PATCH 437/442] gh-111926: Set up basic sementics of weakref API for freethreading (gh-113621) --------- Co-authored-by: Sam Gross --- Include/internal/pycore_weakref.h | 56 ++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/Include/internal/pycore_weakref.h b/Include/internal/pycore_weakref.h index eacbe14c903289..dea267b49039e7 100644 --- a/Include/internal/pycore_weakref.h +++ b/Include/internal/pycore_weakref.h @@ -9,48 +9,66 @@ extern "C" { #endif #include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() +#include "pycore_object.h" // _Py_REF_IS_MERGED() -static inline PyObject* _PyWeakref_GET_REF(PyObject *ref_obj) { +static inline int _is_dead(PyObject *obj) +{ + // Explanation for the Py_REFCNT() check: when a weakref's target is part + // of a long chain of deallocations which triggers the trashcan mechanism, + // clearing the weakrefs can be delayed long after the target's refcount + // has dropped to zero. In the meantime, code accessing the weakref will + // be able to "see" the target object even though it is supposed to be + // unreachable. See issue gh-60806. +#if defined(Py_GIL_DISABLED) + Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared); + return shared == _Py_REF_SHARED(0, _Py_REF_MERGED); +#else + return (Py_REFCNT(obj) == 0); +#endif +} + +static inline PyObject* _PyWeakref_GET_REF(PyObject *ref_obj) +{ assert(PyWeakref_Check(ref_obj)); + PyObject *ret = NULL; + Py_BEGIN_CRITICAL_SECTION(ref_obj); PyWeakReference *ref = _Py_CAST(PyWeakReference*, ref_obj); PyObject *obj = ref->wr_object; if (obj == Py_None) { // clear_weakref() was called - return NULL; + goto end; } - // Explanation for the Py_REFCNT() check: when a weakref's target is part - // of a long chain of deallocations which triggers the trashcan mechanism, - // clearing the weakrefs can be delayed long after the target's refcount - // has dropped to zero. In the meantime, code accessing the weakref will - // be able to "see" the target object even though it is supposed to be - // unreachable. See issue gh-60806. - Py_ssize_t refcnt = Py_REFCNT(obj); - if (refcnt == 0) { - return NULL; + if (_is_dead(obj)) { + goto end; } - - assert(refcnt > 0); - return Py_NewRef(obj); +#if !defined(Py_GIL_DISABLED) + assert(Py_REFCNT(obj) > 0); +#endif + ret = Py_NewRef(obj); +end: + Py_END_CRITICAL_SECTION(); + return ret; } -static inline int _PyWeakref_IS_DEAD(PyObject *ref_obj) { +static inline int _PyWeakref_IS_DEAD(PyObject *ref_obj) +{ assert(PyWeakref_Check(ref_obj)); - int is_dead; + int ret = 0; Py_BEGIN_CRITICAL_SECTION(ref_obj); PyWeakReference *ref = _Py_CAST(PyWeakReference*, ref_obj); PyObject *obj = ref->wr_object; if (obj == Py_None) { // clear_weakref() was called - is_dead = 1; + ret = 1; } else { // See _PyWeakref_GET_REF() for the rationale of this test - is_dead = (Py_REFCNT(obj) == 0); + ret = _is_dead(obj); } Py_END_CRITICAL_SECTION(); - return is_dead; + return ret; } extern Py_ssize_t _PyWeakref_GetWeakrefCount(PyWeakReference *head); From 7d01fb48089872155e1721ba0a8cc27ee5c4fecd Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:57:48 +0000 Subject: [PATCH 438/442] gh-113603: Compiler no longer tries to maintain the no-empty-block invariant (#113636) --- Lib/test/test_compile.py | 13 ++ ...-01-01-23-57-24.gh-issue-113603.ySwovr.rst | 1 + Python/flowgraph.c | 116 ++++++------------ 3 files changed, 52 insertions(+), 78 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-01-23-57-24.gh-issue-113603.ySwovr.rst diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 906e16cc9437fb..7850977428985f 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -448,6 +448,19 @@ def test_condition_expression_with_dead_blocks_compiles(self): # See gh-113054 compile('if (5 if 5 else T): 0', '', 'exec') + def test_condition_expression_with_redundant_comparisons_compiles(self): + # See gh-113054 + compile('if 9<9<9and 9or 9:9', '', 'exec') + + def test_dead_code_with_except_handler_compiles(self): + compile(textwrap.dedent(""" + if None: + with CM: + x = 1 + else: + x = 2 + """), '', 'exec') + def test_compile_invalid_namedexpr(self): # gh-109351 m = ast.Module( diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-01-23-57-24.gh-issue-113603.ySwovr.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-01-23-57-24.gh-issue-113603.ySwovr.rst new file mode 100644 index 00000000000000..5fe6d80dedd19d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-01-23-57-24.gh-issue-113603.ySwovr.rst @@ -0,0 +1 @@ +Fixed bug where a redundant NOP is not removed, causing an assertion to fail in the compiler in debug mode. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 0e6ffbc32e1526..5bb11980b8ca37 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -449,6 +449,15 @@ _PyCfgBuilder_Addop(cfg_builder *g, int opcode, int oparg, location loc) } +static basicblock * +next_nonempty_block(basicblock *b) +{ + while (b && b->b_iused == 0) { + b = b->b_next; + } + return b; +} + /***** debugging helpers *****/ #ifndef NDEBUG @@ -464,24 +473,16 @@ no_redundant_nops(cfg_builder *g) { return true; } -static bool -no_empty_basic_blocks(cfg_builder *g) { - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - if (b->b_iused == 0) { - return false; - } - } - return true; -} - static bool no_redundant_jumps(cfg_builder *g) { for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { cfg_instr *last = basicblock_last_instr(b); if (last != NULL) { if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)) { - assert(last->i_target != b->b_next); - if (last->i_target == b->b_next) { + basicblock *next = next_nonempty_block(b->b_next); + basicblock *jump_target = next_nonempty_block(last->i_target); + assert(jump_target != next); + if (jump_target == next) { return false; } } @@ -961,42 +962,6 @@ mark_reachable(basicblock *entryblock) { return SUCCESS; } -static void -eliminate_empty_basic_blocks(cfg_builder *g) { - /* Eliminate empty blocks */ - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - basicblock *next = b->b_next; - while (next && next->b_iused == 0) { - next = next->b_next; - } - b->b_next = next; - } - while(g->g_entryblock && g->g_entryblock->b_iused == 0) { - g->g_entryblock = g->g_entryblock->b_next; - } - int next_lbl = get_max_label(g->g_entryblock) + 1; - for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - assert(b->b_iused > 0); - for (int i = 0; i < b->b_iused; i++) { - cfg_instr *instr = &b->b_instr[i]; - if (HAS_TARGET(instr->i_opcode)) { - basicblock *target = instr->i_target; - while (target->b_iused == 0) { - target = target->b_next; - } - if (instr->i_target != target) { - if (!IS_LABEL(target->b_label)) { - target->b_label.id = next_lbl++; - } - instr->i_target = target; - instr->i_oparg = target->b_label.id; - } - assert(instr->i_target && instr->i_target->b_iused > 0); - } - } - } -} - static int remove_redundant_nops(basicblock *bb) { /* Remove NOPs when legal to do so. */ @@ -1025,10 +990,7 @@ remove_redundant_nops(basicblock *bb) { } } else { - basicblock* next = bb->b_next; - while (next && next->b_iused == 0) { - next = next->b_next; - } + basicblock *next = next_nonempty_block(bb->b_next); /* or if last instruction in BB and next BB has same line number */ if (next) { location next_loc = NO_LOCATION; @@ -1112,25 +1074,22 @@ remove_redundant_jumps(cfg_builder *g) { * can be deleted. */ - assert(no_empty_basic_blocks(g)); - - bool remove_empty_blocks = false; for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { cfg_instr *last = basicblock_last_instr(b); - assert(last != NULL); + if (last == NULL) { + continue; + } assert(!IS_ASSEMBLER_OPCODE(last->i_opcode)); if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)) { - if (last->i_target == NULL) { + basicblock* jump_target = next_nonempty_block(last->i_target); + if (jump_target == NULL) { PyErr_SetString(PyExc_SystemError, "jump with NULL target"); return ERROR; } - if (last->i_target == b->b_next) { - assert(b->b_next->b_iused); + basicblock *next = next_nonempty_block(b->b_next); + if (jump_target == next) { if (last->i_loc.lineno == NO_LOCATION.lineno) { b->b_iused--; - if (b->b_iused == 0) { - remove_empty_blocks = true; - } } else { INSTR_SET_OP0(last, NOP); @@ -1138,10 +1097,6 @@ remove_redundant_jumps(cfg_builder *g) { } } } - if (remove_empty_blocks) { - eliminate_empty_basic_blocks(g); - } - assert(no_empty_basic_blocks(g)); return SUCCESS; } @@ -1749,11 +1704,9 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache) { assert(PyDict_CheckExact(const_cache)); RETURN_IF_ERROR(check_cfg(g)); - eliminate_empty_basic_blocks(g); for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { RETURN_IF_ERROR(inline_small_exit_blocks(b)); } - assert(no_empty_basic_blocks(g)); for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts)); assert(b->b_predecessors == 0); @@ -1768,14 +1721,21 @@ optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache) for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { if (b->b_predecessors == 0) { b->b_iused = 0; + b->b_except_handler = 0; } } for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { remove_redundant_nops(b); } - eliminate_empty_basic_blocks(g); - assert(no_redundant_nops(g)); RETURN_IF_ERROR(remove_redundant_jumps(g)); + + for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { + remove_redundant_nops(b); + } + + RETURN_IF_ERROR(remove_redundant_jumps(g)); + + assert(no_redundant_jumps(g)); return SUCCESS; } @@ -1825,7 +1785,6 @@ insert_superinstructions(cfg_builder *g) for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { remove_redundant_nops(b); } - eliminate_empty_basic_blocks(g); assert(no_redundant_nops(g)); } @@ -2299,8 +2258,6 @@ is_exit_without_lineno(basicblock *b) { static int duplicate_exits_without_lineno(cfg_builder *g) { - assert(no_empty_basic_blocks(g)); - int next_lbl = get_max_label(g->g_entryblock) + 1; /* Copy all exit blocks without line number that are targets of a jump. @@ -2308,9 +2265,11 @@ duplicate_exits_without_lineno(cfg_builder *g) basicblock *entryblock = g->g_entryblock; for (basicblock *b = entryblock; b != NULL; b = b->b_next) { cfg_instr *last = basicblock_last_instr(b); - assert(last != NULL); + if (last == NULL) { + continue; + } if (is_jump(last)) { - basicblock *target = last->i_target; + basicblock *target = next_nonempty_block(last->i_target); if (is_exit_without_lineno(target) && target->b_predecessors > 1) { basicblock *new_target = copy_basicblock(g, target); if (new_target == NULL) { @@ -2367,9 +2326,10 @@ propagate_line_numbers(basicblock *entryblock) { } } if (BB_HAS_FALLTHROUGH(b) && b->b_next->b_predecessors == 1) { - assert(b->b_next->b_iused); - if (b->b_next->b_instr[0].i_loc.lineno < 0) { - b->b_next->b_instr[0].i_loc = prev_location; + 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; + } } } if (is_jump(last)) { From 178919cf2132a67bc03ae5994769d93cfb7e2cd3 Mon Sep 17 00:00:00 2001 From: Itamar Oren Date: Wed, 3 Jan 2024 09:30:20 -0800 Subject: [PATCH 439/442] gh-113258: Write frozen modules to the build tree on Windows (GH-113303) This ensures the source directory is not modified at build time, and different builds (e.g. different versions or GIL vs no-GIL) do not have conflicts. --- ...-12-23-09-35-48.gh-issue-113258.GlsAyH.rst | 2 + PCbuild/_freeze_module.vcxproj | 98 +++++++++---------- PCbuild/pyproject.props | 1 + PCbuild/pythoncore.vcxproj | 8 +- Tools/build/freeze_modules.py | 8 +- 5 files changed, 61 insertions(+), 56 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2023-12-23-09-35-48.gh-issue-113258.GlsAyH.rst diff --git a/Misc/NEWS.d/next/Build/2023-12-23-09-35-48.gh-issue-113258.GlsAyH.rst b/Misc/NEWS.d/next/Build/2023-12-23-09-35-48.gh-issue-113258.GlsAyH.rst new file mode 100644 index 00000000000000..e7256ea423b3e0 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2023-12-23-09-35-48.gh-issue-113258.GlsAyH.rst @@ -0,0 +1,2 @@ +Changed the Windows build to write out generated frozen modules into the +build tree instead of the source tree. diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index f8c5fafa561efa..292bfa76519507 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -266,117 +266,117 @@ importlib._bootstrap $(IntDir)importlib._bootstrap.g.h - $(PySourcePath)Python\frozen_modules\importlib._bootstrap.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\importlib._bootstrap.h importlib._bootstrap_external $(IntDir)importlib._bootstrap_external.g.h - $(PySourcePath)Python\frozen_modules\importlib._bootstrap_external.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\importlib._bootstrap_external.h zipimport $(IntDir)zipimport.g.h - $(PySourcePath)Python\frozen_modules\zipimport.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\zipimport.h abc $(IntDir)abc.g.h - $(PySourcePath)Python\frozen_modules\abc.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\abc.h codecs $(IntDir)codecs.g.h - $(PySourcePath)Python\frozen_modules\codecs.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\codecs.h io $(IntDir)io.g.h - $(PySourcePath)Python\frozen_modules\io.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\io.h _collections_abc $(IntDir)_collections_abc.g.h - $(PySourcePath)Python\frozen_modules\_collections_abc.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\_collections_abc.h _sitebuiltins $(IntDir)_sitebuiltins.g.h - $(PySourcePath)Python\frozen_modules\_sitebuiltins.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\_sitebuiltins.h genericpath $(IntDir)genericpath.g.h - $(PySourcePath)Python\frozen_modules\genericpath.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\genericpath.h ntpath $(IntDir)ntpath.g.h - $(PySourcePath)Python\frozen_modules\ntpath.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\ntpath.h posixpath $(IntDir)posixpath.g.h - $(PySourcePath)Python\frozen_modules\posixpath.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\posixpath.h os $(IntDir)os.g.h - $(PySourcePath)Python\frozen_modules\os.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\os.h site $(IntDir)site.g.h - $(PySourcePath)Python\frozen_modules\site.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\site.h stat $(IntDir)stat.g.h - $(PySourcePath)Python\frozen_modules\stat.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\stat.h importlib.util $(IntDir)importlib.util.g.h - $(PySourcePath)Python\frozen_modules\importlib.util.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\importlib.util.h importlib.machinery $(IntDir)importlib.machinery.g.h - $(PySourcePath)Python\frozen_modules\importlib.machinery.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\importlib.machinery.h runpy $(IntDir)runpy.g.h - $(PySourcePath)Python\frozen_modules\runpy.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\runpy.h __hello__ $(IntDir)__hello__.g.h - $(PySourcePath)Python\frozen_modules\__hello__.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\__hello__.h __phello__ $(IntDir)__phello__.g.h - $(PySourcePath)Python\frozen_modules\__phello__.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\__phello__.h __phello__.ham $(IntDir)__phello__.ham.g.h - $(PySourcePath)Python\frozen_modules\__phello__.ham.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\__phello__.ham.h __phello__.ham.eggs $(IntDir)__phello__.ham.eggs.g.h - $(PySourcePath)Python\frozen_modules\__phello__.ham.eggs.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\__phello__.ham.eggs.h __phello__.spam $(IntDir)__phello__.spam.g.h - $(PySourcePath)Python\frozen_modules\__phello__.spam.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\__phello__.spam.h frozen_only $(IntDir)frozen_only.g.h - $(PySourcePath)Python\frozen_modules\frozen_only.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\frozen_only.h @@ -385,34 +385,34 @@ getpath $(IntDir)getpath.g.h - $(PySourcePath)Python\frozen_modules\getpath.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\getpath.h - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -484,7 +484,7 @@ $(IntDir)\deepfreeze_mappings.txt Overwrite="true" Lines="@(FrozenModule->'%(FullPath):%(FrozenId)')" /> - + @@ -493,7 +493,7 @@ $(IntDir)\deepfreeze_mappings.txt - + diff --git a/PCbuild/pyproject.props b/PCbuild/pyproject.props index 68c0550f7603b7..d69b43b0406ce0 100644 --- a/PCbuild/pyproject.props +++ b/PCbuild/pyproject.props @@ -12,6 +12,7 @@ $(IntDir.Replace(`\\`, `\`)) $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)$(ArchName)_$(Configuration)\pythoncore\ + $(Py_IntDir)\$(MajorVersionNumber)$(MinorVersionNumber)_frozen_$(Configuration)\ $(ProjectName) $(TargetName)$(PyDebugExt) false diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index c90ad1a3592f67..be5b34220aa0bc 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -111,6 +111,7 @@ + $(GeneratedFrozenModulesDir);%(AdditionalIncludeDirectories) PREFIX=NULL; EXEC_PREFIX=NULL; @@ -120,7 +121,6 @@ PLATLIBDIR="DLLs"; %(PreprocessorDefinitions) - $(PySourcePath);%(AdditionalIncludeDirectories) @@ -562,7 +562,9 @@ - + + $(GeneratedFrozenModulesDir)Python;%(AdditionalIncludeDirectories) + @@ -617,7 +619,7 @@ - + diff --git a/Tools/build/freeze_modules.py b/Tools/build/freeze_modules.py index 6a54f45bac3a86..a541b4b33c519b 100644 --- a/Tools/build/freeze_modules.py +++ b/Tools/build/freeze_modules.py @@ -658,7 +658,7 @@ def regen_pcbuild(modules): filterlines = [] corelines = [] deepfreezemappingsfile = f'$(IntDir)\\{DEEPFREEZE_MAPPING_FNAME}' - deepfreezerules = [f' '] + deepfreezerules = [f' '] deepfreezemappings = [] for src in _iter_sources(modules): pyfile = relpath_for_windows_display(src.pyfile, ROOT_DIR) @@ -667,15 +667,15 @@ def regen_pcbuild(modules): projlines.append(f' ') projlines.append(f' {src.frozenid}') projlines.append(f' $(IntDir){intfile}') - projlines.append(f' $(PySourcePath){header}') + projlines.append(f' $(GeneratedFrozenModulesDir){header}') projlines.append(f' ') filterlines.append(f' ') filterlines.append(' Python Files') filterlines.append(' ') - deepfreezemappings.append(f' \n') + deepfreezemappings.append(f' \n') - corelines.append(f' ') + corelines.append(f' ') print(f'# Updating {os.path.relpath(PCBUILD_PROJECT)}') with updating_file_with_tmpfile(PCBUILD_PROJECT) as (infile, outfile): From f1f839243251fef7422c31d6a7c3c747e0b5e27c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 3 Jan 2024 19:29:24 +0000 Subject: [PATCH 440/442] Document the `co_lines` method on code objects (#113682) Co-authored-by: Hugo van Kemenade --- Doc/library/dis.rst | 9 +++++---- Doc/reference/datamodel.rst | 39 +++++++++++++++++++++++++++++++++++-- Doc/whatsnew/3.10.rst | 6 ++++-- Doc/whatsnew/3.13.rst | 3 ++- Misc/NEWS.d/3.12.0a4.rst | 4 ++-- Objects/lnotab_notes.txt | 2 +- 6 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5823142cc75998..7492ae85c4ea46 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -342,17 +342,18 @@ operation is being performed, so the intermediate analysis object isn't useful: .. function:: findlinestarts(code) - This generator function uses the ``co_lines`` method - of the code object *code* to find the offsets which are starts of + This generator function uses the :meth:`~codeobject.co_lines` method + of the :ref:`code object ` *code* to find the offsets which + are starts of lines in the source code. They are generated as ``(offset, lineno)`` pairs. .. versionchanged:: 3.6 Line numbers can be decreasing. Before, they were always increasing. .. versionchanged:: 3.10 - The :pep:`626` ``co_lines`` method is used instead of the + The :pep:`626` :meth:`~codeobject.co_lines` method is used instead of the :attr:`~codeobject.co_firstlineno` and :attr:`~codeobject.co_lnotab` - attributes of the code object. + attributes of the :ref:`code object `. .. versionchanged:: 3.13 Line numbers can be ``None`` for bytecode that does not map to source lines. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index b3af5c6298d02d..d611bda298b509 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1219,8 +1219,8 @@ If a code object represents a function, the first item in :attr:`~codeobject.co_consts` is the documentation string of the function, or ``None`` if undefined. -The :meth:`!co_positions` method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Methods on code objects +~~~~~~~~~~~~~~~~~~~~~~~ .. method:: codeobject.co_positions() @@ -1255,6 +1255,41 @@ The :meth:`!co_positions` method :option:`-X` ``no_debug_ranges`` command line flag or the :envvar:`PYTHONNODEBUGRANGES` environment variable can be used. +.. method:: codeobject.co_lines() + + Returns an iterator that yields information about successive ranges of + :term:`bytecode`\s. Each item yielded is a ``(start, end, lineno)`` + :class:`tuple`: + + * ``start`` (an :class:`int`) represents the offset (inclusive) of the start + of the :term:`bytecode` range + * ``end`` (an :class:`int`) represents the offset (inclusive) of the end of + the :term:`bytecode` range + * ``lineno`` is an :class:`int` representing the line number of the + :term:`bytecode` range, or ``None`` if the bytecodes in the given range + have no line number + + The items yielded generated will have the following properties: + + * The first range yielded will have a ``start`` of 0. + * The ``(start, end)`` ranges will be non-decreasing and consecutive. That + is, for any pair of :class:`tuple`\s, the ``start`` of the second will be + equal to the ``end`` of the first. + * No range will be backwards: ``end >= start`` for all triples. + * The :class:`tuple` yielded will have ``end`` equal to the size of the + :term:`bytecode`. + + Zero-width ranges, where ``start == end``, are allowed. Zero-width ranges + are used for lines that are present in the source code, but have been + eliminated by the :term:`bytecode` compiler. + + .. versionadded:: 3.10 + + .. seealso:: + + :pep:`626` - Precise line numbers for debugging and other tools. + The PEP that introduced the :meth:`!co_lines` method. + .. _frame-objects: diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index a8a27bfd3dc1bc..cd86c82caffc56 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -402,9 +402,11 @@ Tracing events, with the correct line number, are generated for all lines of cod The :attr:`~frame.f_lineno` attribute of frame objects will always contain the expected line number. -The :attr:`~codeobject.co_lnotab` attribute of code objects is deprecated and +The :attr:`~codeobject.co_lnotab` attribute of +:ref:`code objects ` is deprecated and will be removed in 3.12. -Code that needs to convert from offset to line number should use the new ``co_lines()`` method instead. +Code that needs to convert from offset to line number should use the new +:meth:`~codeobject.co_lines` method instead. PEP 634: Structural Pattern Matching ------------------------------------ diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 888ebd0402d0e7..3ab6d1ddc6ef21 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -816,7 +816,8 @@ although there is currently no date scheduled for their removal. * :mod:`!sre_compile`, :mod:`!sre_constants` and :mod:`!sre_parse` modules. -* :attr:`~codeobject.co_lnotab`: use the ``co_lines`` attribute instead. +* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method + instead. * :class:`typing.Text` (:gh:`92332`). diff --git a/Misc/NEWS.d/3.12.0a4.rst b/Misc/NEWS.d/3.12.0a4.rst index 75246f3f13503e..ce2814bbe2e5ab 100644 --- a/Misc/NEWS.d/3.12.0a4.rst +++ b/Misc/NEWS.d/3.12.0a4.rst @@ -147,8 +147,8 @@ clinic. .. nonce: yRWQ1y .. section: Core and Builtins -Improve the output of ``co_lines`` by emitting only one entry for each line -range. +Improve the output of :meth:`codeobject.co_lines` by emitting only one entry +for each line range. .. diff --git a/Objects/lnotab_notes.txt b/Objects/lnotab_notes.txt index d45d09d4ab9a50..0f3599340318f0 100644 --- a/Objects/lnotab_notes.txt +++ b/Objects/lnotab_notes.txt @@ -60,7 +60,7 @@ Final form: Iterating over the table. ------------------------- -For the `co_lines` attribute we want to emit the full form, omitting the (350, 360, No line number) and empty entries. +For the `co_lines` method we want to emit the full form, omitting the (350, 360, No line number) and empty entries. The code is as follows: From 4c4b08dd2bd5f2cad4e41bf29119a3daa2956f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=81apkiewicz?= <80906036+fipachu@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:37:34 +0100 Subject: [PATCH 441/442] gh-52161: Enhance Cmd support for docstrings (#110987) In `cmd.Cmd.do_help` call `inspect.cleandoc()`, to clean indentation and remove leading/trailing empty lines from a dosctring before printing. --- Lib/cmd.py | 3 ++- .../next/Library/2023-10-17-16-11-03.gh-issue-52161.WBYyCJ.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-10-17-16-11-03.gh-issue-52161.WBYyCJ.rst diff --git a/Lib/cmd.py b/Lib/cmd.py index 2e358d6cd5a02d..a37d16cd7bde16 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -42,7 +42,7 @@ functions respectively. """ -import string, sys +import inspect, string, sys __all__ = ["Cmd"] @@ -305,6 +305,7 @@ def do_help(self, arg): except AttributeError: try: doc=getattr(self, 'do_' + arg).__doc__ + doc = inspect.cleandoc(doc) if doc: self.stdout.write("%s\n"%str(doc)) return diff --git a/Misc/NEWS.d/next/Library/2023-10-17-16-11-03.gh-issue-52161.WBYyCJ.rst b/Misc/NEWS.d/next/Library/2023-10-17-16-11-03.gh-issue-52161.WBYyCJ.rst new file mode 100644 index 00000000000000..3f598d40e4ae93 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-17-16-11-03.gh-issue-52161.WBYyCJ.rst @@ -0,0 +1,2 @@ +:meth:`cmd.Cmd.do_help` now cleans docstrings with :func:`inspect.cleandoc` +before writing them. Patch by Filip Łapkiewicz. From 35ef8cb25917bfd6cbbd7c2bb55dd4f82131c9cf Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 4 Jan 2024 03:14:15 -0800 Subject: [PATCH 442/442] GH-113689: Fix broken handling of invalid executors (GH-113694) --- Python/bytecodes.c | 3 ++- Python/generated_cases.c.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2eeeac53e1dd7e..e1a6a256fbdf96 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2378,11 +2378,12 @@ dummy_func( stack_pointer = _PyFrame_GetStackPointer(frame); } else { + code->co_executors->executors[oparg & 255] = NULL; opcode = this_instr->op.code = executor->vm_data.opcode; this_instr->op.arg = executor->vm_data.oparg; oparg = (oparg & (~255)) | executor->vm_data.oparg; - code->co_executors->executors[oparg&255] = NULL; Py_DECREF(executor); + next_instr = this_instr; DISPATCH_GOTO(); } } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 99fd169ca4fec3..8226d827cde514 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -2392,11 +2392,12 @@ stack_pointer = _PyFrame_GetStackPointer(frame); } else { + code->co_executors->executors[oparg & 255] = NULL; opcode = this_instr->op.code = executor->vm_data.opcode; this_instr->op.arg = executor->vm_data.oparg; oparg = (oparg & (~255)) | executor->vm_data.oparg; - code->co_executors->executors[oparg&255] = NULL; Py_DECREF(executor); + next_instr = this_instr; DISPATCH_GOTO(); } DISPATCH();
  • O3=@lHik4kW(~$h5zRB&Vr@ z19(VIXz)__WX8psmXRhB%dGB;(kr>JnFir_!76j`$`J-^58}fEJM~H! zM%CDG4us)8bcb381)G&ILG~ee9OOP1(;yyU=ePG4DfTz9z9`g^0S7yOXtIw{U8Wn& z-m-smsa?>yF9OId56H}WsFuwey-0co#B$a`z&y2YcD-)e=M41+fkBPS+!4xxzVm+2_F)%ivo0rL=oGuFQaRGZ0 z(K@*CGAyua?l)^Jmu6O2z34=E9ar_s2^@c>TU=gvogGRCQq~YT{+^cENz(?K=gd-R zU$66QV0T$EXYfH|I>QRGJcrkj^JtOaWpxQViWD6J`IdJG{b91D_yy{5O56J>?wP-WTraz+(Ql5K9oGg#@)UWCpTVJ{6oQZK&f)RbT>$Drku>~Zg{05n2u|uEl$jf zk@y&v&Sqg{$$BQ?=Dw)hY8>Lp7YJO zG-RKVO zci)!Y<-gu7?C@JA_CxY_@7yBxl=QaqpGIW5L8*>lJ_5VJDjGvGE2+ z!F_J&PhlIp!#47>{Hp1wcWjxaCOOar1rl3)X^8#bQoZwAGYqJ{QLMK>`6aG&^dbB! z9sZInk-yIumP^MMHdI!%yP!$hd=Re(-WTrb^{_BT9koOpAMeT)A3>C5f|Txn*7;4I z{et`ZNx5qu%I(M({p^3Z)MgkM+Xea3{9*>l_V+WX872#?Yl0|4CS5+lJbH(87_j5H zZ*U&E3x|Eu4g1+60zZ4kpFJY*vuFI-BLd%h#@`t3WdXQg+h95PV?67MjtDDg^_g$pzI2u-KS%Rxv7S1a{wBc$ow;Ubk(Bn*yg(#jD>!R*IuJ zzs{%oNk2~wR;GeF9k5(~9QFE&De?ur==bnmdB8EhGf#1W>!W5g(f!iFuc0d{)aDog zijj(QUP5@Dm$rZ9`=?lNOO3Ruj}@0G2zTe z>su0>AWd9qHDDEDQn%l7zKgA%Io6Gg#K9xrFt`ES&#MRWWaCK_OGN2?c{Soh1q*i| zaS!1;oD#ZRZT#8>AGg*&r_}TIq!j-|%)sq@1Rp9g=*NEt{y3W_h2M#C7q}(yE@H@i-{};hL22nWB;6X`0vScwII8)XuqSiA+eMVd{@} zLXFjF6;h)cBvjKeKMPCoc3k42z5(g|71)z<>XXqto*2!{gGq9rTvgz7JsnmzyY<$= z=f7!&{|xnj9qT=C>OeOLK0JA{lJSXX>X6Vci#C6m-&bn;q8to-RS*6SFnr@e`u>94 zn4fUWdce!#xV%d%y4GvY7MKO+d}V!ue)iO;$~tAU;hE8kSF|Apu+IjT+-Ma`Wt>$i7V9= zO_+bqZD@6P*Poq0p$rdzo19Cl)kzO*ZDAMA5D?D}F0^W`5}}NxM5mY7%h0juN#kml zQ}iO6gnV3Jxu4cP2837rcsjEZpAUSut{zm5917*Ra4swyJj%~7Z6+d8Or#Tn)BS!a zAJywoU2pvCkJc>$*hl4@lx>6NCCtoMdy{{jCz(j&xvquz^*n;?R1>&Gu#p%>=Sn?xwUSpd`)UASu0*wz+i7tQE z^GWb11r^|O0&Fm9jwKD6635I$S0QmUqouu1nHWk%x<$$hM{SwAZA&II);jPaZELN` zjl;6oPXN=Xwg@)Q?0ZjVi>M#bVnObUM!bq*0xu`9P#zjR#@q#i2}>fWy!FQ(KE7a3 zy`W1AfEo)+35q*|T|+Q7vZSgO=WBmSlFkK7LH(vZtjk>>t9*fk%@=UjQ@^H!e2OEh zSR@9}JwsqFL)POkA#Skgmw};cZ?C@$^ny>fEH~8>BUF#D} z{H1li?)XBn4IUCWN{|qUkpxL#6hsoxb{xksg4}-m5l$@fz5i)LcN;KFh#i09A;>Og zPrz^3RlMa|u^q1^*pDRj$YKX@AE?P2P}{&@4F8#dJ08T-og7>cJM}n!E6!%|$G$T1 zM`e+m*xAS_wF6kV*qgNCc;^G;Z!sd|t7UY;S}M-pg1 zf@eFNOp%=%kR}_T{V7gFc1nK$`2{7QfF|v{G)pU9MdEeg8kal$2q)Ixw0L=*_Qg2& z(qS)Hn=OnQ1Jje2sPwK3{g%v;aiTwmC{?lUuzw(^_^q7CZuRP~JJtpu^ z&-p8O5%>rgX})s-LF;zrx6M`z7J=MWj9@fE7o}e-NvO+88$LX4Bmk^i~ z$qhRvDR50B!!nEqx;}rO)&qZJB|&<^7;`Wn!?IqL&|76?EokU^8~DJC6qkK3`g zj5y8ijmSF4=t!J?th5`UUDnCU6|b)kDCIp-4HPeDaXzk!9o;n;L6S>>1KUb%QQ~cM z=ZAt*`4IAz7mY9%$ecyX7(oO?hQpK@-3$f##6Yi8d~+`+gsSPf>>MDx;!re_ZLPul zf90D|4J%sAnP{8W(9E2w`KFXsWNq#Eea){iMFJe#-$ zT6o<)nyrzPhKl!#qogUblDhj0CSp0T+&6gfs{);ibVvVoz1t(@mWC8|B%6_UnTy8p zhjFWIV2OW{Joe>8BP@n@h@ruSM4nBos)+t(P)1X~%|}?Z6M>r{V@mAhBq(ange!0WO^Xhf}OjPMpft%6A7;OiY;W=1ZM1lUB&gm-VX~G zp@Dzf2?97KT~x~bu|UcQ2&7gGS==^NOw*U^xTIOV&{#&TQL;R?&$cOoh;@~EDW%U8 zmjGozn!iH%OhM-xaV8f5>Zl`dvytLqZeOk_k0&RHXE>iL@HVIl+B8opRp4he^2e1q zp!8|7qKcesGYW1LUj#&FF@P5=x^Jo;;;Y$w?;n1DU4fPAClkg=99Hj&BNb(umvCAP*>&>&Ln3&nDZ1N3Av=5FE)* z&&JwDkkImF9&lO83K3|U7Z+D>FX|X|wvt!)?A43fQ2zoi@+|v5h8O?cT7Soj|FF(i znMD|X0U?Bh5dsDwib6?f!-*J9ZU013grW$HfFT6^E|DhQ`Rltx5%m^|EOsPUWII}e zc73HkNo1$iq;HaF3VpvI{N`q+@tYD4?-IznW^?hL&6Moy6*SxNW&!R*&VAK%C&?4r zeZU_eMzq)&_Bgn&g(hzhh`)hmv2V3_x6MI+@J=g5!S~dIw~i(9eieOFRFiB6ncG#{ zIe5GmmNR%)a*p?`=1++<%}%7L{vy%@@*{eAH}{n(FK;6BI^;CvZ$z38C<^#b(Bh7w zfd2$7?kEcQ21W@csQr3oaRZA#%`E;56a)VWTHGHK_!nsLtH%U>^qik~{y(7k$9KVh zif=`G1>{49L$>vF$td2y#{D(&9vF;3LhS0YCWZ z57d1VN6k)zG%}ip2+sHxMhqHyb$Zi(H9sD}2z)z%@wk5Tk)8GH-c3n2G#uf)&+6-? zof$@Q@dN5>46v?`n^_5$s{*@*DOqS0U>VG)v>zTs@i4hbGQrmwZ1`;rg<0Obv zBna(-c@#?QJyhs-Ej0Bu$(tdC-V8w~-(%&y+lbhsVT9Zx=Dk4)eotHZe(h)H`Jj7s z1_AfFPK@r1!@Srn)s318GM#fhMg*;7@M@WN=+rlLU zNKt;BQU+JgXgom6I*m(zfPsPQpYK)%L)>*_rN}W=3AtzZI*H^wL>~02d`(d zhc55W1@JxkB5imBKei8;3}273WzeWV4@Wpgd(d8Th>Y~}iLw2ElQ(rGfI(R1ln<^K zCRmMWMX7~bSjuynsZay0H{&2|A4>n4MKZAnD`gv`p0VncHI&E8?G4~HFXR9V16ia5 zYTr)>`?*E~jr_75l(W45IFahwb@@H5znGQ25AXPV!Z)_>w=Vo-@BaCsuOKM`QzQvP zAc9gTj-W8I89o?)1i^hW2tnZ_LZR??DT)cQm$LV1SIAC6%-^D!8S-cTp3aU^d#!l; z6QkZm>hDWc?Q&Ni#$LtyjE?LrWUxV64EvKrcTyn!mKELA6!N#KRJx4n>i-l%A^>$|G!y(?d=A{+sbY?+b)EQkK^Ar zOAJvvEZ)#{od2#=mC2R@ThiNquG=iU4edYD-|O|}81;J;MSBbTKh<#++qc@JwU#W< zLCsEQ<&)8PVM-)=58%Tz4|79T)Rg$%62k2lTh0KVugP;&hSpDJu-s<>nsF5!z;z-Z}tc&Q~}q<|B@K zCZa}%#LEi#&X^DWFP60 z4A8|WqdIKg_!BZZt322I7zAy>yW|O`hg^q!`Tksg%p$xZ#Y~g~V1q4sR`jxFPh^)} z;YCwxh<&-Vm_kW!9O<^12OVigRqpcfUg|eD5}pAy9S)B($U#8jUHM~*f=lhB8LK6M zv;AILgs1HQ)^v~!qGJiUIW-w$a!n@1&g#OSPiy@!FBikA0S7!XcXTu|CclD#D#!hm zvC{>AwEA8Y2r2FuRPu`fl0&aWBF0~zkNe=W*-Jq%_s*VxXINm(GB#|j!^SaP(ey%- zaF5yesdVnI!)5I@JD%~WL>w6C-j4BViQ2m8xu@7>qXOP|p$;<{7U4+D)|qv}qvFgc zf?J1*>+VHZ6DQq_tlVB@#c9C9<T|TAT z72nLWW5()P-@==#Lp+-Nh+VRPxSw!_SRNiz&Ya``=^=PNZ``EK6Zov@@+D>?NtayS z`g8$CMtdtjsUa$ADizexFDiTvv=%Q8DMN5b^#odCak^)h@&kkBS5^|TY}w<=OsZdh zkB9I`X&ZNN7LPTZIIhy1t{~O2w-*cY#`TQpKrMRr+%dp!1VDdl5c}5*L)VOE{X8?* z=VOo_2lJ73o>m)r|F_x2Ur1Ha@Z-Xr;NdcnmJ~*zh4!ij<(@&dPeYS0=gR=$*yig* zmlm4msm2a2q-s+;O8|bY@h+UqR?pdgQl~1`WSzUYeI-VMA)DOIPfXFDj5;OiGZSg( z8iI%M)DW|Fc#)5J0#4RbBh*_w;zL7r+ zkOtX?xO#`Vq7a~`lj|I(Vc@AAk@lGqVbkH(PuKMdfo}5s6rP=! z4D%GdH1%tA5#TU~=ArkZs1YPJ8{n>5hw?@|yL)RJ?sP8pEFV;8_q!(~vrt|28pTNfpc z_v^(u-GCMgVh{aDwGZMxBC35A?m_u~T-{s%)kAww z@FD^_w>1u`Ps59QmPEa-$IBYQBgxDAY>0auEHHK}MeqbFxgZ?A(XPM+Eugq4b`Mm(+Hz|?pp;?lnWR_903*6D_9(&>8t zO>FgR*Zv(|{ouNvBP@u2BJeGwBvFJykS)Oc!7z6a--E9N+;eGLz(u`fH};fUvNQgm z_Yj*cP|UV-zTZ5zoeoobdmH|qxK5GpRylNUa@*p{0)DG{U^@VU-}7zus%^IGL=`_0 z1Kj}84x^|&Nw{lyyyHq3-(yq?dJ_b(y-=LJ$GvR_w`ehWkB}#S;vItRGVbX@e%AgAJqq&aQy(ZR=NewIi(`sY!i{v|xE~$bqk)IG^3w z=h$ZtXE~!+0?03a*HN=?T9dHDybAZ5l))7n!*^L=IkUNYUQOiBnv4Quxr*@V#ur1_ zhdH04+s4ENu$TnICk~ftDu>BwHDjElPmHR9>~c4F!Y1XZ&+V!^%&}1sGkZFl7;8cd zc1S0!wSgoXrR92n{3FYe;88lofqYLNo~)WLRmejBVe2A)cDwf_sFCG$H8%Uq4(Wr0 zbmZY$0&onSlU}ooZ^Ll{L$WPOs-sg57*qC>w~O0C-Am;3%t9_^nhk-aO&o49_ zEk!Hf_>lIjs#H~}-}I6l>(O-da<+;v97l)S{EhaKj(tKBKDD7uD+dm+x7y%ss- zIRm^qB3IsjnsV}SZ6pbV65rCtbr#a_jJ&k_o^`&RqIA3;nglh@o8< zfLczk3k^FkKJc!eXhjfmIDEURO&PUFsK@9~9g-C$`h-f^XQnzU%2rrnR?Eq&DGv}| zOLe*jlJH2-TeH}kCxq!#gsK5nxOqfd!MWnolAmpVpLCaGg5nXojUMd?_ITH8v19}AMl zULNIA719QhZ-^YwbZl7pazY!8DyVB@ZFn{v&xJfDi#Iu0fO!kyLj?tv9N-@jLA`Ns zx)?lv&x{QAn@9`zQAz?uq_Ust-kka*jf8uvobNM!wgmO-X;{Et-SwL_;tL!xZ!;h9 z=-q`f>fjnJWJcwsmOOd6MM@v~Ph6%`FC3!oB|~NdOz<_6g`%Z>g%K!Cfsx>pw6>WQ zXxyW`I!{PI&AsU(vWGFK5;x#VE@F&T-r??lNglvi+*G(Hx;YSZS2muRMJZPcLvm#* zt_tXfb`)$&vDXudue$j#=pfe%uthYb=>MOZ)*^lz_^{K%YU06oGc`cr|1ysTlGjVvJYAP#(4G!+Kv%UUquiFRquLr$inCDiDjU#I$G?j0OPnQ_ z_$Et!TIK7@)LyzJpt*V~afj6klq&#VC&@>h`{*SQyUh;mJmllTS<#QG(@)HJL(xSoZQ1 z)_`p`bC>COLytAyu%ycsPk?lDs0ikfF<#d`u@sBp!1OYvkx`IX_b?H^rQ!mly_=8R zPw_O7*8`m&(qBeed_R34$GuU`52~jB^+9^)+a`8Ch}c#QcU*S=l8L300p+uJ9k z_{;nHx=f62h>$|C?~da|lbw*4Qg884d>3W|(;Yy8`5TcTyX<=mzNLloKNI-7sE~YL z0TXZap|@@t67Pr&$KR#oY}ZhL-m?fdl$Ip>svD#uHdObc9upMVW#O=YcdcWCt9xd! z*e}N319`w~Ut*t*_$~k!(0gETzQb4)-V=!1hR}QmpBoI^!0Lt>cL|61U5?oG7k-8c z`SXqnm(Scm-fj&|{aY*WhdK1mZ|1ZEds$RyX!nyGJm-3k>;t44Wxjoc3;Nm?>ptlc zNRZw}_Q(F(eg67~$|m-IhywGOh~8%A=P47tB?+{>bbx%iP-Ck`wf7mnG$!+Z-30h} zm-1vYU4qEv7Vd(vNG)+Nv=PfdDeRUBjE(tTq~JavL;9r+)^_*(=0NsWPOVLQYjZDc z69(3hz-VcHUd+N|7W_*MmAlOPN+TN-T>#y2KTfvar`o>{LhoaL&EMa!@4a8Z@4R8( zd%u9+dBeWv7(=!AphmbuGXs*`w0rHD)?I-+ z!SJQi`S^_4JfYUm4AsFd!Vx+;faRiTIz@)|RF=9tBIT<$<10(kyqk|&(RX*!_Lelg z!sU~_=2YB`M$iR+t0Wa3B>Q_iqzI1dsju;qw~M-sNn=|iaar7+H}E1qKo;c@#Egr? zc6S+Pp`dn^($^|IJt373%&F>*M>oY1Z)viM_u!&~Ao$~qr^fyTMq=d(&V2&>)f<4qvVlcx}_h{d6{F4w!S*0dDgeZPG$6?Ax=TE2|>1ab;6CZD31=qCYG3+C)5Hi-+*WnbqF|6B4Fy5#; z2aT`@KLM!F;Vm>GYObt%e{s6eq@JjpM9!h(AozBFH2g(KZ_quB$#cf?xN#NJJEI$p zf<9N&0($y!>7#2!9@&Il85Aa^v;xnEr)b2>7_gGoOHDx%d|S%cJ){WfIy%n>*N&)m z7CUK+l#NSDs?HJO%{$|{bk>keA}ZHlgYMX%K-9~~(LqkoNubK#P!9Y>pUSh)zjyW! z{J#r-ME<|q+;1Y0ZDU_T5e&l6H{*eXC0 zneZN`ISjmK4w+>`DH=-cTyn;KFq_k?0UDNRMmC)#WAVfl2((*G9P)~&lw;*=CAHs5 zDABM*AuaUi7uR!?K;bBgVRUw1%mgrt*-50eCT95apM!*N3EwF}vkeE!r zL4Af3N*}yDi@Vh!#1~Yq*O1yu0J9RBtm>?`IqRRbNq)(Cm(2IM2EZ^OFPyRld)>1p zzsN)DbA!R$TwZ8-S5Y+jDzfZ{XW!mPj)qfO6HQhV1q?Pk7O*HXy|+dr5YbbTkm~so zUC=b-EVuPKw$%(w^2C>(^fMrTrtGL4FU0Y9>$_o=XCLbWpmI`i3P$;uk9`f^+(Rzm z1B!O6QIVIfno_EIDz~|!?{zdS5#ujjd7l_re4yg5%O(gc{Ri*;VHt+AYa3K`AMX|| z?%zy~ADHiNWfT7GBA;awzF6QZC0vleFbpQYuUv)hDgMn3Yz7y8C;f>(caZ=A-;*oQ z_w)rj(&00WtRf*d|6P= z4)jTwO5TO3P>s{}!0`2s2b;?a(3;H_e>&LweTzST+ad6?E&gnWz`n&_7aZV%z<=sI zM;46NdyESrz-SU<$36_fLhuVX24yY0Hd7L~zBLP@;>9j(2tHgq=Q)`lY!gwTcGt-I zexneN9ZrBF502a4Iie==5#if8Z?kj0g?jiIQJYnfU(oIL+8+EUt%SOnD~$)eSjL+8AmYFr4PLM(X3is16TdRa%!ShAkSF#pfA_ zDFosQo897>C8|+VJ;#CHKYZazGNKKQNl#)v9bL*<SNRoq$+> zCDqb*@l+F&y9-U8cD)POp0ktssg?8vgHohnLhLin{h1hsv9i6Z8$+13lBJ}=cy&N zTt8pYgIO9V$z&>ew!H4HOt;E)mo4yr?^3GlDG)qAtEu$LN5Cab`NVjrdIG7xeP}Bd zn_XTZ5V?p}0X<@&(0q~xhP8P^FU32IwKZPkXuo8f=2#F2w_iqMvV@OMw-f)I;~Nt$&SCjmv)SiD2SBrd_~@SZihSmOWz+fR zg5d}F!Kd>%@9pXQsF~was+SeZXAxUh2)R5cw}!#z7}Q4M0C{tq+}GJeYwDJ#BP}mY zCp8-!3Oj!8qqEM%Q$(H((~RX;Q9NMghC=0Zbk7Ipw*AV0##kq}W0!$WZqDNg#;=~j zk8X^z?yZ|v$#fEAkxZfDI?u>|!jplU{h{3K4nv^K28jUPF0b_Q_;lfr9^5M^bP;(p zyUTsRda@-)`9hOo{K9u zxoC_1A?nu`ez3>!SIe1W8D`*XyzM5qx;*4j)iRO$rhYB6<6UC2WO(DapNU!n)MkwBxR+QWqj1>Nf=Lv&&yY<}VJ zE%dxt*y-dF9pR_g`Iyn3J4+#Qct!x$Cr^+y2m^1ik#bozg_SFR7M|@U=$*@n&QefX z58;R->o}Ru8%(Z0HydoyWYpYsaXAw zE&2(b{ba$fr@s&cPGZ~d1Hv(qL?IM~VF;sOf~0Vqz&1cfVEA`QDDhi#G}(f9Xs5vL zvtNmw9lHhbd!8SEdyj4bcdqUhG(+E?i3S!sn%biIT>+HbNx~Zv!{c|$7`X@7F}bfq zGsZCg_$@W%SL=A?EKyG z!xlz;i=J%mFBvDc(j*UToyeWqEsU-;mj2Xe5INg%k9SMJ>L@7btWSMY!uIoX@6?Jo z{;ILzr?|WLbiwBZ?GK91z?YKFX3cP>^I@8S&h6Q9{-y5?W*^4au7>mJ*oN^|A=X6j z1$;feR+lw@>tjIo4zshs_Vm_`Y1**Xa;(7X z$;Ptyc8+91f(R}JD`q<6LIE-LrAFklUFFpj0FVHgM`Gw<)`%psJfakFOrQxF{ps=0 zHrR5IRMQO&HIU4QeI}PsD4b{ZM2G`_5A+N0ATqY;dKi+ztQg?_A>^v-XA4Bx*N5V{ zrlfIy)Z-4~NAzm;>9M!9WW0w|$(WMEVrPJ&TvaGxD!OP$QR`g3`T6SUX=-*ex*Mnu zrY+SBE})0jfug5e&w7&^&r)=U$77-HfCgLP1&4+wN+txlVc<^$42Ykdl|FqWV0<87 zgny^G0^cKF=;l$@J3U8l=kef7oseRd`!KA3t^xN}7)6~!hM`4Pf4L0sf!0C)c;o@k zXa_W3x==KW0TIR%-K}9osH>_c_p$;~uMUbBMFf>RV8=X0djw9SI%3p;%cQzN!L~a0 zhUP?0c?cwaszSp>$^yEn(rK14ptLtP|MElG;SQ=o(~T}w%W=q;GlKGbNL>4hy;vE4 z=$ZH26*E1W#Qu0hqWR=sujt(XqNjT{Nw`;`j0nN#cB?TPK06cY!;=~@sC~LuGJb37 ze1UFQLQnjomZCh*pUT6OqymV~LhGd121F?*io2FPi2)dwlq^n2oWS71K*$FlH zN_TQ${h??+nUd^@1z|Y_^c2!!%X7tlfen$zm;0(nhz+oRAu zlq9DY@tV;m_QV}e@SK-xr++64Q4|z@E&1>`o#Yh4naRNmUg1|5mrf``0Zap4JvSeQg2loc)&B4JR z#uB`p;^yNv=asyN9%Mh#@`>Mr3#e@WbGsOd{{-LJ?9CBk?}-W9|M0t4F(-ECjJ)&q z+x{tZza{qW1l-%h^SwU?M0b&Y?kwN^cDfS_e(E68y&m7*K96iaUx$o2{2%7ttl3R; z+Y)`}SL8eQ7JZiy^*|#K2#`R4fH(TS6$sF;zaZPWZExqv%zaKp-3YH0G7CN$Npp_b zjj=j#sX7yQMcw5*s`0x*=6_dN{z$glKanEZr{%lscBh@;Y4aZaD@IR$+1>36@4ov3 ztbYa%odo;`(T7-^%c3?LcYHlUsIR$IO+fU(uzkfYxVr^2_Q!dARrC4yIICI!{cXIp z_ZL}%ZyUdD)5M=;4LI#Pw{;OXMg^iq96ncYs>wa3IlL`-bY>=vGw(hR+b`ErtJAER z*&=4&k{*ops6^@R$QfXNSIo^hW*e=)S9|_idd6M?VGO624j8I?^^)YVjrSbQmg0FE zkOcGC7+=&khIaXLY3#(vZl0|nby*USF-Qs)zyaP+^JD?o?QS5sei0OwULY%gJ6x~I zF?xn)Xb(<=xj@oPt@xKc#L@!73$1ega*Hu;j&9*~0kDis2Ejak&%`)F+0%Ym!3oXa z>;?7VMph5`0u$a9f5l$$Gj@guvC){##`~R^ znmg>BMf5SX=q9s&`cf2$ko)=`ED0jvSsS%gXB}{*j@^U>FYDsh}Ukb+;v}9pV+7?D3+ZDvo?UmwY+bdzbace{<+gZiI@h_z!IzQ; zY%L2ZRX%ZlO_#0wCQBO-!-UX#d73v#eoF$_l_c~wy})TJ#V$+cDH<>3y}MIHIxQt% zZTFr8Azhc(+1K9p3{YQ>o_|BOo1AaC0u@rEq{ZZA=UIPG|3m}yNSe>hACeDWIYEx_ z?eIxnOb^YuL!*K|v?mbwh~^H>Yx0Ogch7k2A05R82K^Jy7=DEF(oaP= z4vvL?4&jUFIFkG@+oj~8jCTlBd>kLiU!7z4s`F+h8KB7nX zui22{!!uHUct%5xLt{sfwLn*!4sC;*Uv%={Snl92EO(jk?_P0_da`ddVaeIui`&<+ zL}!lVlz~LN_K2?y9?tu4hQQ$smG|zlz7>got4~rM3GbZnUltgxFGKIZ*Vr=r5O|!O zNfy7?_ zeTS}^EU`aD$A%?*7X{~<4>-GX-`yx2Zk+8u^e$|eJtT~K-_y z^ZDUR5hRyg5*y+k`%RILzm>PRj)Q-0W?QZ0(;TAxM9qF3qyEg^ndD^f&lZ5eg4EO} z$eTlZKl1)CTP=Nh?m;qC60qYl-p!qVVQAx}-x3+&z-h7U@v(&ZW4?$Sa8+I=_!glO z)-Ae~edeKI5=cD2PBMR*@|9iPc^w*xg{X0G=ylaP=57{q5#p51&Z~ zNor#JNr4Zjw4zrM-<|pKCR~SqLSV_{;x!JEt0UM~0+O!};OaY6D06|e>f1WS%*E|b zbA72Ulf#)=l|jWNo9gFMhMOYjt{dHov;D#Xid;=KOWuI>q6|Ln7m6rJXZ9zQa@;%f zu2tT4HU*!P6NRJu8<-dGivwu^5f%4h9r(H(g_p?QfgsMVk19~F^Roeei<>fyJol<( zw!fA8EwJ;-C=xS2u5pTOZ?!dYWDK^bR>6%HwJ9UOH)it7*8ZH{ERXX#gU$*0~lLcV9?QI%+CBYxeKqgROC*yCt`Q z2$0^?qs%lXp_6u_JYTG*f9qqUI9FB*@JeebM~Fi{fo*fVh09mrLAtw~Au!w>URBc=K7o>Cf#*^=E{;xTm{X zc2#4C*JS_x&7>Mwfrd|7Us(|FDBsfgVR_ST(ED;F2QgAn5Xq(IYkjvm=8^Yv2(RW< z4=a~W(y2QQgzU(FiIXQ+s0;zNQ`*I9!v!^h%w!XEt$ZjA5>9|uc zPg4_?D(=6=vqa}@3An6!QT6plB_XIKEj_#j@z{r0!v^2o~s!G9M~D20cb+VRS$5(PR>SRH2WlT=XNd{N+s1F*$K`Po@XI3h^0_I0kc* zBb9>&2UiLDk^dk**&{6d9sA3|g~T6>dC>J6`sCO@nUUy!YWX)2c?E>`vGW&pIIh}3 zqU6kf(OW!eC;1yZqwbRP45cHIil6sL{KlnQ%u{{SM7O zXfxWrAW-#3BDi(;I^Z9Sc{-fep|sZF4&ioqT6)K1>|=i|iSrK6b||FRo~x9>(RtVE z@DA&cPnD$o!pQ{0UtybO*|2+OKl-*snz=M|xlJJ(k3Cqc~P(vSguh&+zUyrZQs<^v3 zz!S|t)?K$IVqas`hRelUY5&9m_*Op0Lgd~#6hJO~e=L$9P|e~JZBFMGAv?j+Z33Tv zBe+8!>A8*&ufkmRp7lu#N(#6_n!R%bt;P~DI#eS%@G5E5H4C;r8& zUO8MJZaMne^aRNpYxoHQ1Na?m(+l0|>a$P%8GSFUOvKs+lJnYkjSF_xB3DD$r}?(0 z?ygy)a0_Izdju4(z$6cJr5lSkBZVk`1w*{(H8cdH@M*g~pA9j)pufZ=da`xQ>V4m; zy`;bRQHe&mNXl|pIsn7JY1HNJ7FOUdYZ(jT;W6RO3^Y0u&xGcRP+Nd5qI)M_XCjkV z{UZ3Q=DdmpfzOBh4It?M{S} zb{FB*Skto9d=l^oU%gn|H9rxB~f?V0($J{6fx zyJ@BJa-A56ru_(Mh-vIpIqdUKduG`4g9;r}9dgbyOKbC{=%LrJ47ihj^W5ZwnRr5? z%QK!{dw%k@hZAqPz+E!55kUdTy$Ch870ktQ+IqV1ptWtZr=!V$hD(c+UqPZ4BUE`_ zEO?HbTH{F@LWPGgShnJj=OvWyXG_{qZ46!T>Lzw8R0q(jn*c_I6vnp0cbOTNGPxwd z6~>qPnR|f+;mt$A3a&(d)6#5ByPPULNR$h6LSPmGw-`hnz$en`89l}q+Lfe!xADs8 zyr-$+FTul$dX!oYR3yntU|P@$TQ~2OQE)oJ|_AOi+Kn-Jq}PWT`d)O@FRD3pw+p4Dq3ctefEsaSDX*?)1_SyfEgguz}@JfrYfnjF{!D z0Ev>NCquH79RG5ECWXQIk{*TW{<@IKOO_oC2l{BC&~t=o7v+XNAd{~6$#6S&js1)6 zHJA}QJIrMq%hNBtt7VHYysDcNC^F@j?fC*ER3V|`<$kFo-YF+g3BPtKoZC`{O{(4&jDhKw9C;xZi&cX%7Sk{8I>L3LO6p!CiL*2mGgy*0gou(CP(3 z%>0sZJ!WLS49y^#5be^Qhx`8^?L5Sosg+ zc%6sMF%#pw{fD@DXm$u6zxRb9LvY6xnaCRv(CvR{p~7pXl*FF8yoPh zeN~gyw=eaSzbxMFNnKnY3>`17jZJz=CRoq6Qg?Cco(_z8|HxKVc&0dW(0Sl97xN1y zIOzRrE|YI_F<-`Fi~`{Ci?6V!YpcF0;e|qfAQ5F1Q4K%M#qj@{i%G9yBjeq$7G{w2 zKz+AZsYoKNeC$@X==$8o_2OfWE$KemteaamCr12M) zXzWNHB=NzOg~tc)*FmKU9}OD#uhSE|X$|8~mj-iCIm4f(N(?#D2uS>yl_Zb8%IK2- zVd8`08D%~T(|=;e8S*gv$tUN5et1-mS{e4ybNWIc793SJ^fTEReEKbqO5ASc;luRr zeh3MUS)AR_C&_6Xptl`ocG zc6%*=_5OF7f{Vqr-)z=lo?&HU_04+-r`ocs=vyS8m7UAC}xMA*=ath+0gID$p2YC}dTHsoX zb6(C#>R#GY=HQP625mQU_~m*lWA?>`GMK5wKTOF8eF~Y9X1S2>OIMI zf?NARK`+TdG+4j=9S(?J$M!#RKoFDIHVGBh1S`I?{Y$fPy~oT=7=CRd2L6o$B0GPv zj>&jKxNU`Hxd_<2x`MmDAk>A1>i&N$o@R6{q00r(#mPf&3%5CUV&3D`qRZZey-c~V z@fb)xoaI5P>I+-d^lX(g|0>V2-0JUYI`i4ZAb_jMzGYm|ZVD(p>xXv^@jmaCq9Ldv zcu(%NQx+WR54d6n*{PT$w*% z&V?Fqki_m9qBDev=wbZGpHa+*pwB9ACjz>yz&IRhOY3S7+cgCdK1v>|goK9kB>Xwu zA7i~^H@ko#q<-b#Qu6r?uZDliRY8Y!0B|!Ko?=yp-(lwHN!`QL82jK0LwpFT80O$C z-DB4MH~MEY5ax8+(7uq{MbERp+0x`}|_F zn(+L2p5x$6IDH93j(%1)m#hl0RYwsl>*QH(5ALcpK3lg)@L}1voCM9+wWg z=VeM*t?fXVyWBUE_0Rhk(GV7~q+>XJmTv+!sNt#0h<-i$Z8-{Z_-5b0<19wg%^AbV z;A}Os(q_GMaBIq3JHIrxm!wCFy{H#v42GOUflnw$WjlYh-aLKFR}q*v!3|D((0CF^ z0*l;a_S6z}!kgX%_B10epOD^fY^)5MUzA{r_c!OHcvSToqI3a$0Hg3yx9xHPZC4%mZ>-dD9XiR$sq{%cR*! zd9%4vjOl-j;P0zal^1_dpRR~C%j4-b9c}u)9U&P)_M~8|Koh>I_g#=0xRb7-(?R@& zr0R!ufvJI}&LyMTqH*^qFEZ3;63N-5 zGr3eQgON(;gE!w(5U-yXjiat%vA-+i`i!swS@3^8xdYloQgCW@20ne|6}STCyR1^2tZsh!De?HJtr@^N{gL(*6K7&Kg^GjLUmvEV{AO>L`7S}H{?8^6u z%A4G4`n4IZqU33S>gce8+6fY>v5cRn{^6Ah8Yv|xVncP)edi=oj_5(NckmLQ^NWAs z&~8BWSF*a&W(rW0{I2tpH_`QuD$2Ygtq9CZ0;U$LdDo<_j+T+WNUiIJX`3Y;ZG@{z z=|zF7(FN53rB~?Ry)?a;uRF%oB8T{e(iM6h|3==+If zo7w}&v~C)N#B{+kak-ReQDXadV0=6*2NRhsEd=`QHAp4Jy4QA)HNf$1Y`}lyWpv`i zZ~#*k``tu0+ijkw%gd%%y&;-mnM>(zRNU|?M=P4eK}R0+$f3@J);V2W&G@>TrKPI? zb2!N(69%T)JP=WfAGk@-nt+)gsFt)XjXk1yJqDSXg~N1%o_L3iQ~WVJ>x(CQFJPYO zB?e6!AB$7t6zq^+Rom)s_gjCUPUAlqf?}KC-L*fk%YWJQQ=aO-+V2ls_0v8-WTkKn zJrYthgODUa&;&_S^fBawpcq1-7z`6QfzcSbo2Xw(nc>){u_gUINRC`knjGyiX!>cC zA%Caoqb7iTIO!w$_k{d4#c2E??_`d|{sFdAn#kZo zrH?V`4E%t=Qx;S zKEmozcvLy|d)aUBOH&W|*CS2F2Us-U0G7oyTlyv%sAM|=e{pzG-I}`e!|fNmF2HUy ziC<)aZU-X!ngx=K2a zmyFqRwSFN0R6wi0clod8mF}o~OrG1n!t!4LYd?kY?TWtE7>w+bPd}c+{m%f_=?lOz z4uG}$996-y^ZAnKZV5yIbIwE#P9Kzq;5o`Db;lrC?%irQXHTz@m8X1vx@N{68$Jii z(8rE@by#@Uva|U1HGsL2vP?%H`A(6^Z1MY}Sgs1 zN9>X}L2f<*5^%0_xe#Z6;;z3TILQ{(g{OG`>?3$qF$8sbJV+|SBbP1}>7wQWI7adC z9ALVqE1^JCfWgGbZCZ%zZGL+fm$7E?kaEWT+CO`z z?6iQ_&1ir!+f9Ws%0mY8yPId(;47Wh-z*yd#~AJp7(h0>w`{|Il)7lOV$8hQL@Om5 zR@TYCiWh)m>Wnpbk1pfD@cCSN?q2eqWqP4q$Jy&VNkA@>3YNzhW2_6%2e#%C<7vWDr{;&!dBqoc)uz=5;6|Tv<*~^L)Wm5F5y1 zKLH)gggc4#UcmBy;mh4)=r=ZpPb0!aDYsOo^0+?cbZ`iybK&q-z6& z_LRX*GYv2oF0N8^K^?nUX;&8#M>X7DCeER|5M32rh*3W=U(~iU;>1lPRtT$p1IEEM z)37tz&VNeyKs>D59{1jj*GnS)Q-^~7O^1U14TpmM!k0QK2OWQ3M(l^1AdYHtlpKNe z9#ZcS_Mry?AJq#Y`kji7@f!5wbjV*j6e2hR>Lfj?%rW(~9h3MJD5%e$=u0sQXo(ogk!cVN5YAko9??IAsl z9XAr;M+yC)qhx;$zjN?We1iD^KRPwTzn%p8IF#*2hXVH=lK1pQ03T9v%lB{ke9`|6 zGcW)14rTjuhjNsSf7tdv?NG{pQnNrG?S@A8YP|rNTboU?T{d*eZ1YN64_Ja!RxV@M zKS|?R$rc3FxR)&N;ixKwxp5O7%yc2O60gpo4?JL~w61@dq*s`>V@gI)*s+?7J5?`A8exo|rUebYxY8#Q865Q{vp9;nG zbv3_B)zx^a%##ZnG*Qz5z8d6>Q!MYrTmZVALEU)tr_w5E)P%iaVc1h0TyFSg?&u$T z*e%(2Cc;RLTC$j*EtOy>{z+|h-3hskvVN>2rf|Waj%<=V2Rp1B{83ZSTg_R>@e`F;bAtDewrP= zFT#z}w3{J5SVe}cTWt@FnqTz>u>TEpE`NUn*27S+J8Z!_kS`DoYZyN#uVBZr6z>ZC zm3|B*3aIkBl{oV3c1l1 z&9(=CSqpcH_H^Tm7JClr`}?h84W`}oa&!xU_xvu`MhJ}Prs6OG61&_WBHVI9UF_4Rk#$XG?lZsL#67-tuWhX3niRHcMbMFUC?dI8X%mS zdb38^F!hWRPl_QJ2NZl25g`*v{*uL)BTAQs0mzvVK2K)b#{VQ!0%CVzI0S}4^Oy2RcIJinAm(IRhzDaa)7Gr*Ki-Rs)Lc|nMX znoo+%3dL@$X`)$?>AkeZ=GA|k7dRGE*%Z!TdL?)B-g30NBG1$U@Y_@Iq~$*jtg*j| zgRsAWgRo!Wp#6t|8JHqTl)slq39j5V9{3@xcwhNA7dgUejI=LWyS?fj#ZCY z73?U9kblM%@|k}*hM&_9CsvAlv^YMr*9mrjsE|H7IFe7?zlY8ka!flPOkm+hHzYV( z`w-$IQBE9n4g5=KBR)>LmiU#T z+uz1O2LuAX!$9wUih+Orji39UVxR*80Ur$X=JuVm9~#uYHEivn+?j=Cmg5_@r+qG^ zS-k->PC`P7t}naQDbVdSmv?na$t`ldp@Gu~-@na7K3?7B-AB$f?{>S5=7s6Rj45XE z^PSfIQjY#amcT!uwD^Tr8i8qgxm=H9qrgiXAucQNK1rd7FF$|xVP?9*3Y^3ir#*~( z+`Bcw%Cs5*Zg^NDH+X?0Zs08C@a-8T65kj^E6$q1%LX5@Gql#q_O4&;xE3Qm6>sp` zv$i-T8sKfFeAekDjh*iLPbeLJ`oiOX(|F*2!+7AoG9C;?&@jCl4~(I3?z%6sYvsE*Fr&nQ^@VI$u6!9VT0(XY#%DS2=v(D+gI zjL9Rq*}uc&M;8)~j%MOLF59ik@gh8uW5_4sBKPm}pBazcLJ;xM5QcvUjt;{BAFXEx z6W@m!5TlQ@-4Q!6$8Y=-2kvoK^ck8~ari6aK_AA$ zEq*i}v_zdwOK*VA{`q=7H*VyFpN+@+f7p1y|M!fCF8oDzt9}`aGTuRurbAEZlE8D9 zz`p8HTmdiz9HL9U%CXFn(v@k7l%j=tc9V|jqlOy>J(QJPbj>h?lFSH`YlL5~jEkEE zjd2yAk%E7!@XRuIMSdAL1eB%O%fi!cV~DV5mh-Ed-OHb6seoTrs(#K=>9^ud-P~}u&(p;M0E92D#^dJcYtYmn6qrS6j@A7h~ z7l5y#D=ozS=|xYCZ**4C-*g*(KQeh`uRX$^aB6=Q2K&^XmB%TFWmDgJyoid~)b7*# z0FcY32I!=mYVAG7Y4s{BdNO(xxjg6-`wy&9fRjoj7{*{}-@(-W z;uy6}KNK|ShxIi4ki^6v!8PQ=DgSF;w;d_%SZK#&%qNEweaLQ-gV*!`PS^(r(&;g{ zk>Cfu+85h_U6TAB{aMZm{}Ag(*a5}95aEBvp9u>5Lmao`t=+ui!H0?_{>bfq@>+Cw zx^UHcF!u^n4coRg?(cX$CV-xNDGQ}rGQa8_Mn0`LD4D9#3sunwb;}BB5J*0nQtV61s z;4$qBe5h%4e?P15_WhT)`O9+xtV91;Fupmp$q^m??wH(tn2g#YjoR^fWUXnXyX)1f z55*=%XJJ-Z~7by z5rr1a8@vzi7w0gf3?s5W0y?%t%Zo%#<(|*p-|R71Qd7qt%8Bbk7zw`PZXPOkt)H|x?Y*bU3(Jqqhgp$rINugPWP@3 zXA6mWR#$x~Veu4T6I2iJFnme1H2a8}E;-sPJrATvt zJtyE@%aB2qQbNt1n@eAxx}xd`Hq5cDy5*f=&RV~Ka3lIF$cxv@L&l8zR;EjMO9Ac0> zY3zly4+zth@h6$&b%~k@MzOM^YdMH*JaTtz|K`YptrsuJ5=z1rM#0-`Dgx;8v^jHM zfyschrr#K?`c9eas@XTXmvNVVz~w-g4S2yQVaaEuKp=66lxO#*Y>iKWy2EbM3uarV zvzw9T&6~?DJzamegM90ff6(6`346@*p_5~6>-?KZe3PD$Y*v?k6R!KO@0R%O*z0P_ zmiHTf{a^o&Y6(BHzW$Fa_zfNWda>_e00YB&c*c+fOyMws!pN@`ZuV#>iVi-JL!uNv zdPs@StR#UQam>-2N*}QcPXCUiM;Yi(7}1Z}9Qx>7pg(^@Ge^jT9XJ8|q+jUI;AFxa zXCTRAz5Serw(6e=u!iZ8>Dj@=9)TTlJJ@G_GCrb1Iygcx_>(9?zZJ}w^a#23=x@g} zhZYWcWQ@Mnv#`%(CVB*6$VZZu`O67Ru{eq}*dG#Sa%wa#^y1EB+x>D{$NLT-00%-a zVEhmsUA})2yk6|sF>K;2pUj(8MVSFRcjr_~cU$KUQgg#`?E8ti?~cRYeVTq#i&b+@ z#vZJF6^uG4RXP7`9uyZ}S?WFq@!Va%e<6SU8neNGW}S`z4bd!Z)t#?Fn`R6MJ{`MuyGXE=o_k}4m`#66*{^Rhn6N?7ggVu65J|`jd*7WP* zgHF03p3I0E- zBxII9idJT(r&3?|mpT?MR{~bs?s#Elx*A>S>cn42w7(9wXJU-c-N}_u03ADBQ2GHztdV^jfH`C$N;(>ozhL|sD6#lL}8I0T5w4B&U?mC5nLH=y) zn~W-1V5~07`4WNx{w$d&ik}xrV%1TeReW7~=0x4pt4T;Cx(d@mH`~2lTHVB8Z@FEm zHiI4oiJzysy8-X)shMP2-^PMTqa@S=F0bD`7n`YeA$;u8%lUa~oVeiHK%@0n3_X9& zOFcBRszsPo0V)RHGc~rflb_s?yDy-r(nsYcYZ6Z>u!+$!53U^rCveuxOzkVOA>79T zYlT&k5*{d}b26)MQzhP%z_24_PUxH%gp-zdXQKGfv8*Ml?X#toSzVTnr*+1;B;_l) zKbMZ(Kgyh4FJbmHQh!~T$ai5nbZUQ>xT1BL)T~X=&Yk2|Chu~b=@+gLJyA}?7;A>C zt$5)3#+Dzl09RGn-#Gm}7eVK~Z$in`>%EbKyf(iF4ZvR}Fn`h5a?srDe>U$x(%PBkJ~XKdoKev%|Zxm{cD&l@gQgHYZ$>=kC>WV5w5ir1P{~93lYz+T3Fq zLrmr}UYU?eB>$?n7(x1K&*>C%f86LWecF+)w<;W8rX9hai~b#*yyb? zC*JT_3y?~(Cb(E-x18S~yOD4^XKR8m{OUY=EDA439EiqXbAm|A9e{sE%)LNfvd5VG zZD8L?<;~2}WoB)HK}D~$CxS+6;J&N1)YGSDnR0t;beoG5l^o{)Ff*yti8tscGCPb? z2<_@tl+=b18fVmVAixKD#YAsRG1Cm!ph^f!d_b0I)JQlE2H=|b>HSXA^N=@B@7>7K z_<~OPHCA$d-0GLwjAwrk!%V+C6>r&N(JqZLhd!ytCM5j)R>->gJsD+cnueQ%oW*U5&||hEhxmyIUt* z3e0#cXDo#2ypLY+Khe*<-_`#WbZt*{EI%U7h!ZHqL1cLWCupS z1Zp95XbKR z{965#yW8-$nQU*-_K--kqW9I(h%bb0r$4p%#GLQGlqpD)Th?{ubJVT6VSzTL(aX5Jc7upN8e+r4xlMB%dc6AUo4nVk39Mp#4%QqC zuyBL?{U`(C#U5Avr~arF+|6+J>deq4?qQ+#vJ2j^I^T@1M%ZQ+9Js)czvNjW3b$!i zmw66sOVOb=zKyt?;A=_WP*J8rRuDPZ5UTw`)aidp$$kyJ!?)W+JirsExo(($T8Hf!b2qTQX9fn4-lNjC7lTi}qn{VQ zKlR;8T{@Ub_vdt#Nm_DW@|!rvRgv>1yeT5TXU56-p$Jom1QgD*y`N995Qtj z>(pNysw`N=K%0m4r$gRfV4%Dhp2h{NsKdMePYTBE8N1f*RqxJMnXQGu_mAmu;OFu2 z{8zH9UmK}pZigOxODR1ZopWOGQptY-qF!H>3({3can%Mod}6GyKg=5h#a7(a9={2|Izlofc&- z+!jgf?WDcg!S=e^MR1F$R?nWmE$i})hu@^Z=hJTbk)T4xoR<_XxCn zvDO}yJnI>`Dgt$7ZJiLQ%7lP{FmYcgEv6{roXHp8wJ2e%ERu|FUQliI^PS8fQd(R= z4Yq)FqB+ZQzQ$%bB%=WX(ae?#7DrPr40++O14zFGdQP*EEack@o$hczJTKzYHEtlC z*X*#nbUTfA@6WxMas&c$1+jm5Ew%N5$xCrtGf+32F)p7Eq68iBhLzzw$)Ta1!N`Uo zT$YNtCSi(mvuZb`z!{@*$FEd5CapFbDPqu-*X|3TImVs?ht!lkWnSj`cL76tmTl?! z{ub1W?!J-T{CU8INfD)aD_nVhh;zR@%x+8EN}akK5)ehch0GJZC?|h)60Z}ccradB zbxNEDsC0{Unt@hbo}Kt|f{cH;?=(l!&ftk6%IBylX=7|Z@gpy8h_^pBw7zQ_;I5N8Mq#b^XW z845*69R1~l(+<22?Fam5N}$rCcbiU+>7*Uv9c>5cQ7%KML$nDCey9J0eFr@$Dy9fn6&$1xHGf7&#H zV?~BMdNCOEQ!xxa9UjS1@jG~o(PL>mII@rXUk`?!FIRBz1|6_8`V5$p#9y+geN?LR z`ybFSPQrAf#qL373!i!N0YKg*e3;1}VBvuZf&UIHJWwI<-+_e(Dg=H33q=Fgeu9PB zstfGZ?^0v+GUI0^Ave`PH?J3-~17EK_@_TlIR3?^nD|cMDz3;WG|=f zT>hO|Rk7n_IGwo=d_r2%_{Nycm>Q=VMP!;pSv5GzKBQ2(E(X=V$pGnNvL0nEfi;>9 zmxI?0Aaxwi!Fn`3NFa*{9Z5CN#aM$_dLie`KTMVDklN#L=gt|xB@GL{bO|E#Qrq6b zAA60|Ykt!ve6KDr@GsOf!w}PTePH52kg9iLH*z+s%vqMY3 z)wLJ-vlnu10zya7q%5F{*9V@?mta1M*FlTq3tK!%lE=YPXYm&2FON7XN#UN!lSD2A z2g=jw{GdXQb-2C)fsBDx+zrZ+Fvvc3)pDxFzJ@{U#Ymi(KcIH*f5ZBa;%;C6V{jIn z-z)Ab^0y)Sf3V!oK>hcY`W~(c1fvjy#Apn~k?k)O|Fmv4c{kRe@LuE!2YX@K2DSJb zOw%_|Z*m&`VtpBJnENSQZ(@1F@)X*=(&=zdlc31%8j@l=cJBF>crVV~g#9Mtx0NGP%yN zw98(k`Nlh$XB^wV<$1LJIgoZErIIPq`v9CBy1tbi09-bme|PNK;OZ5%?1s2sg!v}2 zC;cOr!Qs2we|iZ0*kH^3s9M_%Ab~F#ef?vK?e`9unO*M*5c#7iY4;!4Q!$ukT~-}_ zr)L)6eUSfl@Q2s;cU~Uw?_S^Ed3nIUdwqZB zfCi zFp~3mwn?#Spk{8LbcruyA&6b8qUxP~Jd&wOyJ}u4y&JgmdOja5f5V@^ZiNw7yu1VqRfi!y(tu>9 zuj)BoUhr!$h{HM4Q5Nbw!9{zpS1QVog39ZN%y7tiOwloOY|ef$fh?hvtm$2Wz)$aa z7@h0vp)LZS+iLeL#uv)tU4f%upwDNWsvs*^r0b+j1$94)^alH_*5~>qVt|nLbB>kn z&!I$!e^AeG8^OsFW&2}&bM=>=BP~{Rp2>nn6SQi*EQssL1R1RP@pG4BnqoeKTFbF!E?z|_|)gtb+ zp`2gfRq^PupbO~kTsD43Sb1^40kc7+f7{Esfl<^C}fW5`-6VKQx(-I+0Nb} z?^I{wKdGXRigkYRb0VQ-+XqYrL@Fc9VTwlgG?6oe))q%BMbkVzHBfB~7wMncIn6vS zj`&qV#O%o{EE8XCJv~$pTLVb?F1C1kBgo_x7&3n9O(IZ}Lq?zPNfXgtlRQs}f3pyV zT9FE7($zSePltXK_|-U?D{u(oZqWy^gavARJ+COcwhfNvj|pB%P^o66#=vB>2hHc% z=u{hK<}stejXiXHHMa*)*K{58E6G{*Gr=e8a2?OLD1KyC@AA|{Vgbdei!xrvUOX#I zWa+-}IAYHm>oi;g9kD-jhf2u(}5yTDMavdg{^rIvTRY~`9m9N-W zd5SRTCDLd-2p3>FL`QMt-2uH8CL5zw(_Fmkn$#W(IWpx*+IeMY?$8T5f1KBvmt@_O zyG}@pOLVq<2LY2744b>f%cN;Xs^J+XH*_0O3J{%`{@SR*^|0V}Tj$Axv$*lm3b(Ku zZ*)GLU=AXIMsa2czF3IMueAMWtiv#q1!I<>IXdL==n>)=-&+p+C!G#|m!|y_f&brK z>z`7`pJYA?Z?qBHe%}O%e`7d~BNYA_Z^Y@HB_i*sjT^(;i(KHHI!4eginkv_`t4F4 z?UuPT^eJz|qPr?g zA+l$5_7<>sujk#%g`)jPLhg4M;yWwd9dqCHycD(9=4@OMfA_Y>fA6*~?45~9>38gc zd{3kN;>b(=R_51x${_z{mM*JY6S;HvV14_qGJpNQA@eu&y?m4T@SigO>Ue)U1N6Dn zx8%P_ec*ktWDoy0GC+{V%O6sI_&x)4bc(;31^Ptp1Apo8P&)I9bDns3y(Wl}vV}D$ zi{rX1yi&a)dZ~)Ye{QKTnk5mH#sH2SMu+PmWz?MY>NSd3Z$Wd=hqd*{Qkg6vg3H1J zojQ|bgBoneYr6UeU(I0UkylT^ZP|c*A|R!R)AC?WO7Mx?KfaXP^OiuJcwd9;;U7f= zxhY`{`e)!*MV>#)eV6JJJSNR781y>_S9TcsQUAOFc=fB=e~zE!{?~4h3#Z@}chbRE z4`0tdusp8A>1<4AFBV;P7{{1?fp9=X;!85LwHv+p%pcO(M?E9k&D=bS(|8n-m#ncg zKLc`HYZ_99rD1UCb-hX&Nt~>u5xY$b95jDEX-X=+cy2}lOh)(LZJ z(+Ce&NBCLRYrCwscp=-^H})~`_I5?)m)9Ie8>!pyPn2QbJqV>Y3oCQ|=riN0cnpzK z4unU&fA=>``}0BV+k;;p`--lYSt~ybJelc><@sd>emGdNnr1SSlznZtm}4mv>B4ho zD{$j)-D`XEnrgD9UJBOs5`>S=59NoKus!(k7&HI;*q`Zf;J4l(CtD(CW z`5fuoS%j{aamLe!E5|qtcOI@djYA(QF4HhFY#}gl* zlmQ|VtKWc%43qIy7I_%2-H^fe>b77fe+cPZ+Adzl#c8l-p*hz}b#ZEgC}*p18wfYt z>>*K;hXCd+nqHEC;z2mdmmEGAets-z6pou~ja`&bzi`&gVvYt%8|jm$YRv8~=EQ+5 zNOp(qqnte#PStZitMB8*BmC{3>CC`~s0|bu3Is3SD9F8QT03sbyU1R+1i^Usf0VWn z>tik8+B0G}uR9pHIU;LkC_SVaE8%%Wt>$^#z}$%y2=Udv34(kH{tj>b=Oks zJz(%>?s}`Ad>8q*qdEIwa41i&NB}E6B(oZajx(>Pk}zv_F>^~x)D<^ zTHu9iLp{_Ic-bB!HW2qLoVW~x*^XiRn$J3dH!eAM5qn&kV?tw?)PlEo1n<@C9Q@9? zyNA4gy;30J`F;X%?)pTze`<11U<;SYdbmX1kz$O(8!{ZzHLV5-N@*8%K&{a#XGOy* zAuy$W1v)A2=S{4pm?}#z+!L2V#^j;y`mz^9e`}IsLbWou!j}AU z(-G(yTDztP>uE713oU0s1;zT&`0il(&b(BsIy!}85GrGzam`G`Q1Eo!(b{l$&@=b~ zPA6MPl#G9Z1dGRgn#zw2s9ER~BkOx!W&S=LGi1{e{%~b8QIh44FZAdOH4?d<9U1@i@z}<% zzMOpQo^AX^%nEJX4Pz4jMA81&7X6@Yzgh4H2X+jiFciZHUj&9w3dblI+b7si&~^yN zPy)sg9RD|AY zUDt-mE_OG<7QS`up4)lPq1l+=o&ZmGLA*VLmrYa$KYvBVzg5=3ooofPFw{?81|#)l*asx9As)QKe&o1*!kdcL(C-#bgL z4GjZfbN$FR9Jl{}MXg?MaM@{`_d%Z0j|;vnXtH0K+4RlLK)Ng}fBD*@s;>;J@zrEl zmyL^Fzkl?omLJFL?U){DBJ{I~yDonh5Kx3EzL}0*n~1YvggnX22QRG8+j0A9vSNrj zPrw&Hw7v86Hvt?WPNkXIbov+!Hq##$d|OZ!zOq#ATLL}icfPAh-Xt)d^fjKbyI&s_ zOb{4O=Dmy&ETg_mns<)!fi$Kr{D=6rDM$8TocXqAGvkpeMM7G@;4P3XzF+%EnLL(d zM1M}7ZT8E;%ug{MjSm=R@-Omq8smP`u?~QfYP&FtR%+)s@utwDZzsFI8rHU6G!sj< zeXdUttGNw)Qd|}jz!PX1kBTV;23aqg8p+B-Q&rrfE0d!_$Kk1-%WHQi=rrrfK$Mt- zGg{v|L`p#>G&#!b*=1@bVUeGg9A6(m_DCu`~Z-T*%KH<9UYu z*}R3fO5vjkXwTltj*It>bb=%FGK(wYe*_qX+Tkx7>;WIRcU_l{R|g}1!lt&P)>&g- z2sO6)hy$9Ay@~M+wScd%C23z(`aekr&xtnxA-cX!nca4nP$A7hWzxoP?$t6Cisaei z)u3K{z>eZMCpdbkA{6lSjpTo7#!^5&RfFs@QiR&CnYxGg-$de)60z;lRak^dy-xh+P zR+p2l)Y&G?)fvp4tBAM(}?O+%f)KP z3#jnqnPg<@R8EZ`tY(I&gQZ*oj|N1|lP!<-)yTbxVCqYz(%`{?DAMSe9HxXucP<&J zMTaZ6zTAOUlTmyab9}jpst54ULqWM05f1ZOjp#VTe0TA~+x><=5TlAq><&Mj!hBYD zQ<-M>;PC@@W)sDK#X^Z$UOd@(D#92S3R;?zq_2^d>f5+|#576W`udn$bEmO?^sN3adJFun z`Txf{GD#@46~=k~&|ju)-<=%5UycwHZ%X}_DPG3%O%*PS=VRN;)B{;}_-HUD4++Vk zD*+W99{S^dzJ01u1n&G3#&XygXhlzY-*5!4sPqhC+^T8Ha~O-U(j{(kv{5t6giQ3l`fqN`oT9MkemA?1?_12Hx2l z9<&hs;ByGyq|!0IzRc*zM-siT@m%p>mHfx6!m3nRpu694)GJnvteCw^4JZpuJg3lh80<~u>&^3%b-+!?7W^HV{{rS`bdVoh-&Ac!6& zmcDpfPTe^^oaS6>@31BgicIC1Ux(xo9L-v2?gnAHRTp@N8}5J)R`#oKzGnch*|Ikh zOh@x8DVE8rJWEITamci*W~pM6%L8wa*=gN>NaQmwOXnCNhsXFzg(Nr1vkZVvMqUMQ zUNYsxp1dy59QDZsrzh>LD$F~F{(gGq*;3V(=z?4fI%Lh8N=QG3V&TLE@CtE*Z*EZ= z%eqlzndZm#dZmtsr%{lk(TxY4F1P!U7CGIdDD-3mC@vWWH+8Is!y90;paM=IJmu|w z1pO2HsGYwE^CM~FB>uw*>f)?%MKaJjhEa#mw*qB+or&E8ynBp&0g!HZt@pr_ZeEUCa z_dvakJ0W7fK7Jcz?!#N(G;CvT$$P--Zf-}v%d)XO+p!&l$vyu;BYWi{@_Wy4cV|qH z@{KWBv^Vy#!tFZ7Loe8!kI!t+lU(`k?Qd}U11@y(cyKq6i1OkGX52nWR| zewWrM*jSu!zC=`hK?I1U5$+Y=k2<|z>y>gK++{2rr-H5r3}huhnSNMN2BQQypq%wg zY`4ur?^x)ByeVUP9@sn_kw9VDoYelRdvBg3m}5Fr<}^`8I&jvH`h}_Q~e{o z4*20v_XAwro*sv$cKXaDs8QhBYD?;HeDD)q=W<^Hj^;9tCzqHgES>HV%N@~caUyTi zdJ@3yq;O4cr)zF8)KW2jyk6N(rFNKdO=@yHj_L|1PrpElA#`2&aP598+z~$piJ3B9oW68Pq+J= z2RbyGKIF-RL3VJQ##h@Z*M&)AT)pr>`fog!klTg8Hxp!1`38p9U1WUcLnu zfZvW7_dYEo=HJPGG(OOQnwq(zd+Bw2Y*alYhurB^aSX&)oIYNaWF6c4#s}nQKx)qd zCs2)tM-F8S0!$;uz5+o{)z&2Rk#VkhL%O@|8kymXm5Mt{Uwt*|CAlj_{QelI_Ig-v zHp2;r*^rn}8E5kXIBet#`7ld#qy^4^M7I*ECFIIszR(Qarlul#&quUI_GhH$XrV{aZzBqpPyaa;zDq}E(l zPYgaWD0g#TJ%BxLP9dYNs)B<^60jmi=JsiR9wd}1X-+x~ObC+ocn6i_e7&*8g11av zTwCyfT{b0u0z8lsjsx`kNW+70Lph2w>mfc99l!KojYfUIzpjc2!q2kCS;IY|qj)aW z$4tiDOA1N=5p7IaGuSo-gKwy(&gu+iK3ZOcm5arAGTbeW-=2;jL!qy5Oaf?d*`^rG zsStbD0A77?x!I@Ne6DO}wFilVug+Ao#sfRxS^+VC=$V_$fO$?2P9h+ESo{SU7jPsD z-enLDn6I>GCq^V{i{4=9XeJNX)?H0Gwo1%fLCK8yeJ1ZBc#{rS8=TnW4lerhRD~+| zYGxp69{su~0*A=wL9cw~WEOF`+NwW1*c0|(8~LKca+KfX@C4qpxs*m7oJ-dqRp}OL zKw2t)L9uB#y;exH_0zbjPs0tQeUn?3NEqTgM_M?T|2C6)7ouft@U8U0_%A*$nx%Ub z;~t5JHd^z?J7A7N)W&fj^yk`(|K1884D08szh_klL6b0s!4yS8_{O6!jG#AK1w%N7 zq8rUZAQC6wPfIVLHy=TvJ@&MFQHHzyK^VP%C#od-!SL-m72~_(H~F~(ITG*YlK8s> zZX-?fdpsEZZi0j0-IM@<-a(|dr#OZ0&$InW@r}v-npIKpyQ~7;8B-kYQLq5p$Gar> zUVBHAd*BIv+aHtfsV;Q9H(%`5<@8RS@L=bIG`*}wavUpv zd^dJp8+s9IXXm2n9q6-RWR?B%FXb0s#?HGx*+N}wc@|3M@=+Pa+1D;`VLaSD6;~nB z*zRM3skL(2HzNua1JxI(0UjkNtwGfE)BXMac*uG&B&jeo`9yQ@TOia#ne(#j? zHg31^xt+?b-Yi19W3ii=1I`PoLKs7G0sxPaeGZt0N22(Y;BX9}+ zw2vK!2+JAtLY*Iq{4Q`Tfd<_93COfFtqpt)uKcsR4IE_3^VBIv=yh`9nx#&+;glaI zbQcrw(Ll4M6_h?$AmN*ansm;i?zWOMd=SA zblS*tn&)u3`+CW;3F@tY+$H7ID`NPnC-P26Ic|rubf>lSY(r1iKajXgN;f!Ry|{0A zHeIK%^Hp-CG727D3*fo~>{vZgN4Vd}-KoAsOVjP??U(lAD=!t}*Yf2xt*&d@##vh=NLfgj?futRXG#4+o2VAn}?9Pl>vH zFfmMK0dbdT*I(vuemj&EII@N0JlSUqVwG;qMuc}M{FI91h7Eszu{ZeST;SWu!sTOv zE3NV4WL&;YaDBI+{xHF{(BBhWqqTbhD!LDGjh^10sPpt&`Q_al=-tC4I;*;}k1u5> zl;D?arEy@U2F#Ebbq9RRg7whT_+=FA7fetae^HS?e~xx26ZP%5zJ%Ldb^9WoHvoZj zg(ALtZ6_}whgaX16lMnzf7XF7>q}PP6x?0XC~{(lVhs6Vbafo?*~>O%)$C9eAi_s{ z9v;y-6uQInYB|^Qx$~Kj$>G$mypy(+Id7V`P2FB3u&*3EAaC)|&hmV=#H$EM&LUxO zn_M1Zc$bK#h>^3TRa5qa$W}uc;@W1xBjaK#8R+%WDJN-Rnf|ONe|mPxBm#g~Q3J2X zJ9($*3EuQEOs$Evh<8apR`QfR+S7Vm43I@`b#jvRO>8j)Hf=N)9P>6}fto=g{8{1U z6>My8@p-;RvCs^`d~O|tz6TxAE6{vPZv9Oj5Bd$!#nPF5qYE*uiVL7aB3(R55lHw0 zZRdHk=zf_g?Mtxkf3(oVlymh!mwO?3QK?IQM5TLH$bL+%ulBH1Fp!<+>VjJhHqWou z9Ws>0@D-{C^VFY-ZRe>|GAW@fav!EiaIYu6O5roRZUVgAcHhGbNQ0=LC&{zY<^(ZB zz&cIU_WP90mlCOXHPvoO8_jFN$2#ftA=P_sS^a8n&2QCwg|WrV&t+1Cj`D`wNllS31#an|ERz!vs6 zXSeN}G{-YTf54)RPi8VY%TzmOus|wM@vJ%-N?0jX=xiu@CQ{a;w_gUo)i)DL_X#c33#2!fy}6s9*i08>D$ zzr3+x2>Y}Zn2z@9;Js*s+{1OzduBvH?!CJ7yWTb=_PF65D1Y3YzCN!V4)!jM-8v!N zgK|N%N93Y+-vx>9Y%+L{0!-fly(r!LL$-&gWVm>_>UoA%w((JmhZ zR?*U1#&LKjLq|*8tqf`mWHHn>EFE;jh)YtZG_9t#R5%K}zJ?Fzk!@ z;+d=s_^#fC4LycP-1|2zvo^2}Zr8o|V1uQ@;D2dr1B^ zkq3OFuA_z3Hln`oZk2YOCh=R*iT>w;e-8Y3$U^kZeH{2Z_Tv@wBndZA$Q}-1%d{YN zOeQc%p5kQ%QbflB)za1W2*dc8%@x$YRKN4&0hRsU)tzCDhmBASR91nGwd&Kzvnx`Bu|-<10B|LS{)s$>p5`58h8qmBY~y^rv%qYr7%kyr(ks ze69fxtAazY#GbC|5Nu@07Nq==r7cNkKs4#TOqGKNB_0aGC#0_$~r`-@58;XO~pnq4g9nuFS$i#tY8BYR;^iftA%RC}~dta4s&N0g*s`4mrJFqT3UD`3+rdXP$vVCy12=nY8Nu@%FcO9Djbd z(V+heTj2k6$)C2tpO^b#E2Kz@f-nq%As8oNh$3hVhw)F1$hWOz+xtl5T^^afD<|Ir znDFsgXC%U~B zY@cVhZHwET2h@9)jQ3#Gc2B{!3BE5U!r#_lTD$y;zr}Kt^VyT`Za(pkyPE$zj%;Oz zpNDJwx3|H47X*H>4Q3zxIomdP`?lcT*|+@Ip95Uik1ob+?^wjP|E?ctG=F{H0>3|2 z{&@TQ-2?xV+u!dV_@CVVe)mB94t_;n-({I)GkeG`7e>zXf?+OYe_1f~s=~_In8ycN zFuB6CWu2Nfk}jqV0q)HjpOY8i6uS&y@l?Sh>&TIP7w!uoEgW7y?vHp`P*ku9EPG1JGeTJnuYC`F8olqVB*Yw#~E4}hmozY+yU<-QXyS5U|~hvk;?zb_}a@A<#nM*mDb{PyZ!iUsZm zu|Od>4)5N-1dO6E0YMmz;}HI74MF_IKs?^#(g?i6+=gNs6%V03yuIO1jJz=}*y{|4 z&#Nwzx3);({fbQvY=0OC(R)IF(*^k3F(}4&d_~EBh43D#-W16Ofg7Acze32}GMLy` z34%QmzX9L|xB;@)Oos2WPU<}@Z(E3XBjk4HMEV~3`Svc}pniK4$M)yL-lYQ@a3^oQ zKtP+M`K?%p_C~|{Tce@MDCC(@2Vq@R>_Yx!4);6C?UbJktbhBnS8%kr#(8{gIJ}rp zH6MA#{MKZMIC|_}yQmQ|z;yBQ@3Cub{%Ze&X}rAg|Lwo)9xnefQ*ZtL;678&`FZ5j zFYXZd-7Ws^4uKzT@gIyITEXYWKt&&5qt#Qmqe7t?WP4XvIJ=p(OeS{dtfojW>!HTQ z_L@q!3O^iQSbrpwSyihj&fRcFKKpUcMLh^8a)W*oDPrA}82G@<7aIZECc zOpcU#bu-UY95GriH7lQ3M(8T$FlL|lFr7U8xJG0iNTPfh}Bp#EzxC2&b z|1XUn(tl2h^XeS=Hg?^!t)=vZsnG?mtD|Nt(Kw5fcl3C1Q0NW&sfGhJWHcxeI8?^X zv=m>+oe-Uc{y;n|P<^op1CH&{csV58l|0;Q2d?Zmy)=+(D_7iFwB9qMIKXXX)M)vL;+x z>6SlPo9E>^KRt2k!KS!Cc2$BpG83WjfD8sn17*YkB{xm_u+m4OuZAa)Sj~4ZWw+~rB2_z zB;;;%x{nv!cF9c;5N~!L;QLOBzJqDo$=g*>vTtk9dmLf-+c86&vhQ7?_>J8ssEA~B zfC=pq-T%t&gP+;`Zu0T(=&`M_@&`RuzoW;eu+e5<1DWKbIsS-z+wgYlk)QXj^?x7j zT|eiL|7UyGpPl#H-W7k_yRNp*13$&ycP3B9$K# zX)_(H^Y5CZfgk+Be>FSy!a)c}aYc$CmZ%UH2(xlYfrkea0q7%u(PMCS1vXG)EVDR; zUwWfUpO|^kQa6VV7>aF+u#@c@SE}x`Ype2Cw>hePMxOv^;;AlOt3IU3Q1D)~LLf9rqZmn%C`!Tzg+SD&_y@x~-KO7zHAr+P$`GFyu$cJAC00LYF?+mG0W zO2{wpZxatly4!&4!&{?0Sid1L4eb$;J%|GBK)PT5?u^TVZX;CdT$?&HN;?r3Ro_`xzjNJR>Q@%2V zKN&ii!uVaa%JxaN^Hs%ogdrZ|jH1$No6*!wKAX;g9VWe^=(R zSGfRRD~{JfPw#6+W!6A8aHc=Vx}~N%O%=Duvk3zOE$-aY6nVn)beR?$X5EDxj|LDY z4cvo=ny_3S*QN-E9%*T58wJ)aBnU{@!mRRHr8(3*Y!1FGd4B;iPEiOb=RUDU2AqzK zKouI##!+*yf`rJ%A*q){Ke{>QiUzOE6KxCg^0XUqL^w?117m4O_;QAuuy+93wXPmR ztcBfRML`{*?s`wk5K3U}5!KLlo!9tzkF&C$+0 z3wbUupL6^j9A&z^INHP=E2?2(^w}8KWb7`s$Q>v>p=vKV*VGLJYNTUpMWF8%&TxwNe5>+XMB1m7PUx_N|DS#y$!fu>U@lH#*i6}Sf z0_noU9DlBb<v*-NzwC^BpR8;QC^o9g_fXL0#e)2w(=<1XXI?c1G!p> zt&>NPs!Bek9Qe>D`qiU?e(o2A?U`>X3(= zUi#64FXYWH^awi;*^H|u;hPVWP(^@bPgJZsGE`S~6Y;|{mIhkLCYL~32Q~bAe6Am~ z@N~_qhl<%#ZB#FKUMFsWGMnQnKO%n?-+Mc0a_E9k&a7g!e zXf3H)lc*&PWWLtV=WJef0^i35ng+#9CHw(WpV>wlMci2NQTeCaqkl{I0{qP zZ5}7+1M`JrSC1JtZr=r>iQpUVSX!7|uS`TdE3%z*i+th)bCKOdx4LP-#kk9?^M9g{ zCM(Bw#z9PrFd3huZ&)R*m&*8$7YILVL zu5NDzRP$hyq-Q|I4{iK7=E`f#pR07a_q5cja%DfO;Aqa}Y8^kV`ZdYkvjP@?U&VU`gm}-(xytseDDhIHFd?G#Hhv7 zd(4KkGz%io^e^adb3LJb>@8XU-Zb++{t&T^$9+Bb|J&8;f9~?tOp-sQY5LFP%LD(} zCXYj%WI_EOk7Q`Z!~YZgzkeVQjQ{cDmH+Wa+6{jMUzT5!_>bq9{L!`b@_!Sf{Quj% zeel-5eP2H&kWKC!PniY&dmPL(WwU~`@GgV3$edi zy#Gi*yJKO6U)yCP-{@q~q3>ybhk$<`Nk>jY|u65MF zt7cV`K7B1JS((mTstnlJ=&YOUU!G;pe)$c($l^CwQo?sH*>+AXZ@e*-s`s$qk5Xw% zvA;e0r{hfiYH|9mIDvq#fdyFO?L}XOS+n*4^&VHyP|cFx$Mkp5x|=?Gzkq-8hJE&a z0srI;`|SM!{>dBm*?;>5{F68AGgAfNSFQ?UGL4rfSwI$l=>3y~6)68~%v%@@C?7ih z6rR<)nE3#rfI?`o)9psC>f>ZJi3Cb8>6;223m6(65N)+$jbVmW0Chp+PFLKOI|nVA z^^l?hV1dOQyKh=9LJk3>EofXrg4&|ASlUmTli;9Ni=uxinSTk7_fYDjp)r-02|29> zw^#|_eLPZvTBFCKdbFj7X%8nf=WcKtvLxZcdT!qIILG8|(;*>)MwT@6oPg8yRV_qD z12}l7RYnpW<#4-?!X=^QqdVkG+Xd}}b0SmwCU?6>P?2yp)Yo=1z-J@rsD61BBx(Wt z!?JG;A+=3MUw^OY^>%$h#N|GEr2>(FrPIc3qFxpzX76BsJz&r_(6{wt!Yvwj1n zPDLj0*?*brCdVO%hi)Tkn?qP+_OzjRImf9GIsj z>s+nOPi;_FBSY?n*k6^~!D9v_zj7F^(C61FzBMtV=8Jq?D(D8v%l$OffQ~Ih!*I6( zYFz0B)qll(jgUtk$0FX0XIelB!J7wMR@cN%&V^%^^g)2}*iB(5uk;d#pz23gg@0Ig z4t#S}I8oSq^vY>%yG}Ze#PO=(Mn;Sw-ToU>1rxw+rAe^y%o|s+5B=;SC)(*oJf0h0 zb}65U%ZUfI+NaOP;EmX&46lJN&)uZ5%Vo7pK!2QGhS8uDs2(6Fu#bSU0>Fc40) z8oL7+Je!+rSXJB(BDB4hN`taW?Ir*GH~=iETZw8b-s`kpD4bQW<6Z4+w?3Zj0NzmQ z{(rctw(2ma#edNRS@VzE!5`p!e7FYulmRAjC`A}~&~ErtJsncx{3!3ogLgCx$A>RE zE1w^DOono-T#XbClZDX_tS7p-5=h|$NVr-NgKI*_@hWV5q^Ik2h7XZM=uxR0!G=r{ z2=-rojY)OEKRDFdlpvuB*IS?2H3)(tCS4acUCI%g$@pQs&dpWL49$JYWy=pO8-JU3 zLA2E_*{F$U&bM3YQj8L|+a2IXLIkbAM$wcfE+}K9NBju|#vRiA6B%XC<=i_Jlasta z`$5lBC7MQpl|L(}5%{MEw1=}?<|F15$Gh#Htt#q8q-)T#WAG}Ir3FS?XbO(fKk?1i zifHJ@E`5BL+xf;Glkd3W#!JbK(|`VX{;&P>|F_os;I{v0y&oJJP=Z2f2t#lTC25F2 zNCF}WnjlaFBQ|oo{gOxs`IO(r@5IhtYDVleUi90neq)tk`euXl&Jv?=ca=)wy@>fU zeoMbArFXw6bdQ6k*q)xE)7^=ge$(W=k&xa8BEw&p=6fX5c5;OOYKR`bSAUzO=sPj> zzOlWe4BqKCLF~Pa(K|O4r*FEP>%{T2LapYe!RrSvvfP1DY(b(xDI_X$$T}BPPhKLXS zbt;iJ>0dkfcwvF{z}1%!)qh`pdwJ)5KA%iv|L8Q&%M-BAH~Lb4w$m>G)BX>2Z?fbl zvULm3d5W6LzAO6XGrI%&Ml_-oFr$|Mfe?u9>3>jW#W`$32_LS}T#7`NYe<|$!MHw0 zBe9S5BkEg1)cEkxpoj6~@U(v|>?@0sVM`f73|vL#bc>EdW|G7@O0acynM`9#k7Mkg zosV5VN%#W)a7f6q*R z{!5$mmgbn6E}4L=kd8aox&Ok7Q_|z)FkZCQ4A02w0V;W#SH*a_YOiR0ED}DU=nvDz z#GAl$atyNMo+Ar!D+0Okaj|u~(wNA~^uaAfmjQ0C)C_xh9)H$5{$ko20}^BenHTv? zK8GyTI3B(`i1tsy@Y_f3zZ{1DvpN49hJRze9};FDMPMjFkQ4#qC`IAm7LzFirSRRu z^vj6@@>6BOy(A$2U-11g$@l1%lRJASqCHKuv-fwleY+0kTQvQ-Hy*NQ=D&n-B;IEt zkzLDgpZFd6c7KaHXwSY;;2sRoZ0{D|-RRL@MB&{9FDZB=={-Zkq?~ zDY$I_4DWd_4B0!Rxwn!d;C^6{fq$7N>-L5ijQtjcrNQu7 z?-&fKE+q`!jSVIAe^M5l_Qsg{vrf+6j>N#{nD!CUzJK_>{dGtKK4RLBkywUye*Y;F z?~U2(=SU3v(+9}Ms)2v9?2lFNX)xfg@?c-Py-ybccQtf+)FFRi9~a7kRpyo(A)pVG zSZVi~@ULAAW>$1Anh>H_DH89816P~4!P9lZ_~9&@vf}XSa-7$pt<;EBGam%Pl+$9X z(dxV=o_~DjK20LCK`p&bnayJx-G@!cFdY)h>b@s}+*M38^q6G(iC{x}fdEN$eOwQP zCcQ1nlRM24b9&y{3W?7lah?dsOP7&5%1f+fHawy-ypb>ZLe)vGC+lzr3ehRWZh9Tu zL?DX@-he2Sae6%C5}_>Jbx=C1R3yh}h-n8ce+mrG!OH+NGd#rRu;-(2#@ZA#eoYh2cehP4Sd5O#(UP9 z^^hMWs2#ZB;E9Imaf;4UaCKTqLz4n@dmNz$!$sZju}tU*aK+wvb<g9QprKO24e;+^9iog?dd9)_z#g6RoX9wd%IRF+>w+uoqS^=a0Q zMjy{e@ESGo_YKtHSI#nZfFYQ?b0g69+iGXSs{dU3Afo zm{avvvA0AEm>>1AB%4cE@`Y)!l4`D(DI&qU_NajI4eQwz8AUC!l+S6XE{a-j~#e z2P`umd%YD+`>o>W2TYbFf2c$He?C`q{^mX3YB+y3{s)`}K^skmcTGc_+~sy44A~WU z2t0eadq{+6I+%z`pTvk z>zasfvE_dim+y!NKYy!mX$NR~#sbm)Rk-xSwU2FlCzJorwgK$>_)oU+Llx+kZ7`w6 zXg^eee*6Zu2bV;HGR-J)at@wBFAXCw-?D?HuyYRu8{oh=MFOL%(7Xcpw#+pyfs7|h z-OYT)kkby&%y_W(K%iRfc@fR@AOV+UCgvO?v!VOyZ1anv`z4aS6F`T$`_1tmq3>Vc{Tlj2_(#W0WaFeLiK8HdfFMe2PyC-}y_5aej==Vyki3QJH$F$9 z`}x~O2l2OkCQ0n!hT1;RGbLf?W98TBqePv70Sq) zf9*z`zX*BT>+UX}*`C7QZbtHV^A)-4FTihuj~sdX!NYs4CKBz}^W<&E`R@1HV%##?_HbDNZZ@}BVsKY7fgmDBiibH zM64gKeXQbN5$hkW0@#=F<0}4T-1e8gZ5k&4sdcw<*8)>HKXB#ig6IXaG5A-2Nev4h z4P$yd#Aant{J@rN?E5X{t+XgVDHavy+n@T@T?Zx*YouOGzU8&*~anDjVRnMnM`WM?R+@<@&{bdMK%`@x&(d6kYgk8JHkeci)cf20?K zs`EasNu=sdM;LVR77iY>qJi)^UM|3V7WytC>UDx%CwgP6Pygil11e}*p(4kPYWi0` zY$(X=u1%qqL4ADc8)qTdEIYz@n`d+G=YNP0hA2{AdG zTaiPB=;1Qt7FzGmfH?31lFtC=j5&qP*M7YOx3XIZZi@7kI+U1thAr6*_12`X9&-eb zJU%bwl6N5m#t+mg!#*w+e?U!omlL?4fG-@e@yM0zg-f;KVwgUm8gxKXNj!C05}ZM) zTo|POl|P~=mxtAIL!1S&i&dN~?a&^fb8_#V=-|{Z*78)|Qg7{*Z?ujFmzMbOZF@Hx zfA@3)e&_M_-P4VgZRkD^S%EVCbFC%?NJ*<`YU@S+%o^jX)>WXRf6>dxmq1oMdNr73 zicX}hD0o(OK;<7r4Z>Q{G(-c`m~~1gj4rY;xLDYgzYRy1l~MflvM@?QupWDYH59rL zSzPW<)MbGfCs3%Aj!IIW7A-q<={>i>d(3-TSXG*ZJcIS2t$um2C`bdz6T5)-N8CBN z&s&;V1Ko|KVYc?De*?oD>EM1ODwuLLmxeFX9%AHGQ=4;|a!p@VgeAjhm?JeSW=2Y5rPx+dI0bwE zp}tJk5S);%Ln~bv7EY}6)w}0ZBaRI{T5MTj%n<4e~HJQ#LWm@RXM>U82S}Z z4=EUm!|W6?*|q*EpLEXf!#EbW!~HS2(%p4$*9+C6%Jv6VU!viZ^cQWxE>htxvH?7% zhl2ORLBh*h&~h-(d-k0U0;KbG6*+W}Q5`mp*vpf~o=;H+t8qNTXqByb?eER`a4!hy_^3q;2m0! z-z4%iCQpp(@K8uFfYfKV{1k4fWo39h&Mad-%mGax%( zD95h+Dhgj}-cneALz|Ifc&@eynq-;Yjd~k}d+RWM6?tvFFU55h68K^LovqowFLQIg zXQ0{)7>)*Nj`AG^)#i_DAN%}w+XQ}RpZ{)~!0+ty-)-~f7VpmQBSwH$>(sTc>Y2-I ze=tyu(b_A*VYDO3)A=Q&Sh{pErM+yhw3$>P8UI9^ityn_DP0R8kDS(;WZ{!u^K zom`=bFUs6-Q%~SyJUqEmdh}L~NzxZU><#N}!8N{oy*}7i zIT|RJk4%kj|7c1e-vS=-;;!@g0Ye$ zU1~x~R9Ta_uL?ommSaV^eKJ|ZSvfzi9s0`K!e=tG9nOH#`3Iwyi}*RBLQCk}K{xBHDC?vYrQl#Cpbta9tGKzI1Bon zK;qE6cMt+G&6iq|2O12dS*`@RE;mucDe+F1fRYCle>-|Rp^VFvJgctS41qw>wms!k zk5=C>H_GyA;!-FG&qwPqzV5DMKxJ=M?)^ZOk>aa7Rj{+%+$j{9!=UOCfVq&=`id_W zY=ra88k4IkKc#~?&@U@7ZU}Y_%*4tKqzKP8FHpMMmA70~Y-C{g3)BF^bPIOJXfeXr zLSKoBfA-yjz@5gIJ@jz`ylDQkp`%BziCma8I?=OBmautJ<20 zOCNOxz^6u>Un0i4q)+IRa|oyD9gzz=>m&*@egQz^>YlivQf={mP@oGBKjgD}l+L#% zj=dY_aoan{)&$5f6T~RcFo=aTN+Wo6h0)J5f8e!Lh)KKBgjse&J}M;V;}bQD?3HG< z96CibD1{1lmqxd1qucgJf?(}bsJyw(=LIx@T09=m(v}J;K4Vr7#i`7pF|BtewzF;- zt9OYv6t}xsTmJBN>6>h<{>e-GC;)ou0ChHkoQb7EFMzJcUdX!XcI#Ue8g#P ze>eqcr7w`^oJR1%JfcR%+S1-$Zj56d=vBd82dp(7W*(W~U3 z(b3{M8sn1W@qBhQR#>2H-7MJ5fbNCSf8%P;jZ7J6ezKkUV2~K@BcpN8E}gRdWvj}Z z%E#9Vx-@mReMGY|gUTvSFw^-8c(+81GH)>h$r%b!X;~6an&x1X?9t3tDJXHvCi779 zL-vp-WN?`7rKD-O)uwoNC4nnmvI??ZHIP5A4Bf>8&CmFe`Gl1Bxm9!zk zh}csG82WR)33M+IA>keLp>H{f-O=}p#{jgK-=)Z&D2UKq8E%_9N%!Nuy-YCw6_`Z7 z8We1kV%ZKxF?25n*^?sB-V2UF@AJHLFX)4_cazGyLxp%(AV%+|?``q$f4fs=`?#v0ZHdGzydxL z9)1Hz;u=?cG%C*@01Nnpu)YXYa5ALdeh*XvAEf5`5p7|$M}gHw@@j0Cpq2+Nf9_`-F3pu;ew70O zz7`XFJtcBEdP+q4lgv>I^5Ror2GD&z)5tsV%6;u4QMJcpqcuNR{>q#+(UV2X1#<{p zxWJ3c^Yr4wgEYtSF-p;T9B;)im8+$p9WoVHfa4NYX2Cm+)53TeEvjD8Fj>SnHWHn` zmRGtFNF*mtdC%7=Ni>OBU_13V-_s?+Tjl zq{nU{2ktC?quSY9eg`J@Y)Oj$O2ICF%LpZJ)jIrbqYk2bxWv=Fz;BnY+hQ=b=azBu zo%BuLS>9i&U|@TkN67twki0qlHe(j=G8V*pmgHR{2xGf==N@|V{Ul(|3B|jd$9Cg3 zcu#7>dxR7 zss8s-8P+-db5wp``+p)T|IVI&u1^Gf6DShLi1S^D} zoqQ=|;}%3lfAEXzfgYAb0ZM9%&YF3;Pk4W!?I*Q{RDaDH)qUztH+*~^ooYk!LUwGI zSLq5^W7frbyFEbkF_2U!0MPjqT)AuAo^(#*`Cm0&oqs3~Gc}Hx0{T-5+w!`=R=}QL zMypig(+1J|vT0(Rt^QoV-OwCU(It%lba?VDOF6V>?eMfJJQ)zsRWy!&Vmw$;%tKz1 z#yXT#e}A$uuyBtR9zR0CfA#^AJbM<<#H41Qyym~~M}R3K7SihR9^95Hr*5T$6h{mn zXZ9eneO4ZWhxD*Q0!6PnIav3T_X6y{(@3M!9&dd_&41g4ZG0WexVEhGw*ALhr5(Yr zw0GV0=Rp78ocANj|Mr~UM|TQ@5S)TZ0wE|8$A54H!C@GOF$94q0-KtLxftVBo-OlRkOXVP^7L&L zjAnY42sn_Uf2o(IUz#IN+SfX1j;#&T3x5Uo#z-Gt@;_@#2LA1lty4?}=b}3IKCxZD zUR$))#dN2RG_m?@G(Xsy9c-lftI<3IeXN4@JY8`r&v6&Vba=tFD#~_jh67?mnYPfp zXdL}<3j1&>;bM7mVZrkl+=c$<72fq2-xvl+eXD&t|47wusT_S3If9|H==iEf1AjNO zdvYNYyQ1-`6blqUZ-JVjzT9f{0Y!yP6rOK)=51(&vp- zyQmAlVwCbSUfld3E+pZyR6r#51Ts?`ZO@sJw>ww^T|czYASR7?XggqJNq>z+#Ctyn z$Hs`URiPX`lezXp2O}}g+Ve7)ki9u8Vo|BYW`YF7JM9J0Kp|)Rgs=@dWH_QRUy=8!1uqcB@6sSO!i+@ z+?;wUR3klmrii4ft&VI(+q@crn(XD zFuMa|W=dWUr{EFvianm}LV9edni)__y{shHd6iSaLw%=N>YLo^Rz1-{jilXOQI%X| zdIX*+`(;gzcFJB1f`FVOapXz>k0Z&A9W+&Xt0KvDsG&oYPt{`Ph~D;d6uaxvRm~wB z%LmN)PwrM^L{*H&z+kL%C4t9(?Zj4?Z_)&QpgUSXZ2E@6WP0PQVtxgumsvyXk(O}r ze$kVhkWtYvxI1du1DBmt3s4#7$l2E=d0b+@XyTh|2VEy9FGR4(=Mg?G*;Vfj=+I;` z;&fFU_F$z?uJ5qN;n4d4CTB>JvR4R6Jcw6)a@6L#2gisN4(CHF%EzmJ*xt5Z+%Zn? zub{gUV&I+m8wC#ndN>(CdWEAhF~!n}5aDd3o_I&fa`he}>=bb}P*14JNL7irdr>`B zU6&)PIzK8mL=MN-s|G0F&u)3T{5A%p**`2=4*w8z&G{dT)&8*g?IFY&7H@B#imqkY z8%q23UBlgdRI~mQ;``@+vwYyY-^OM!XHuVdP#+iNAI0y&YxN$;t;B6MWQxpZ7ZOZIZ!tWK=7IN#m@4lV(rTsrW#xIlRQs3yT4Ef{ucv3N4N)_<)s^Qy?WSkX8T~PH_gb@Zpvg= zGL?F-Sv)X{Y5L??O!|O9RRa20#zh(j{a{(c8oui13Rd|hCc&8OLF-{L`u&*Z!Z9LY zCqcKLUplV~IMp%f>1IOh32^V1aiIqr1~%XwQ~s=%lA#AKe~nVRSn9Ui${1#Y$r}SV zIo!U_#Z5Y|#Pb#eU1OX`HN17%LZ~BO>Ke^$DSc+jV4iPv>qWDIMK@24*j3jXa zgOJ?^5F@b-DHftIM*Q?hbmOoanMJ@o+-#f@$9I8q3f*}p{_a45(fz=23%k4UZ~m8P zq6a2_Efn}G2f%zkVcmi_p1$3VqTPvli}Kqn0I-Or*u7URfUqk`J&rqGQ*|7A~&aM|N2_^s;6=F=1LN|RDlFxsEGR86^Dx#ZSo z;>OV~4QPDuNl{l3G^?6~Z?<_K40rw_mM&s{u06eAraGd zH|pIuntSV>?;&y5_FaJQE>6}bF?6$X-!9W1hmicoIim1I*F0{50Z3r}JIiX#;lUpV zeI@3$&ILauu)r@EZ2y$Z1y}VNi=TtJ1nHx#bSrKjH}zCWmvah|e9wBB0vM6t;g| z7bQ(ZE=SC-SW{IfiU`hV;K@`8ImPaXl|55l9ZAU?mE7qf$croel>!KaOKimvPpf_& zj`=B&%M9+tGt?1YPMKtJAbg=(ILpz0p9;l_8`m77qw&mhQRs4aBWE2llBlIh@)js@ z4Np2H;_dOGAbdjv{19IvrfM@#gs%9?k*7lyo*n<19N0ldCz}Tr2!RK&Tf=;2o0`AN z3#Xnf2P-nD77FGp-fcfD%5y1=<%wj|gXJvZQMg3gwpIBe0IOf1_t(Hig$#;+vgSJv z>vB*dcF2tj;vnnTkVU#;glOweRkxM|)cri;PzbKi!W!_vA=$w}Dj=^wgQacMW4Xn? zm6}gfq|&;IH>~lL?~}vzlH)Q5MZ28tvVxVJ-4r)g2jpYhBuSIcl>oLp0#{a?SnZsj zF4}zYc?}ki>`*Gy;Si=S9v*~$d?8YQPKk2~3-j6oGu(&EQ71^YmmX=|XPyPe3Ya|Z zsDb;H*0FlmQe>=gV%~e(BjWy4!|s`cDSK7D8vFKV{8#PPDp-9_GUY)!dA8bolVwFAhT4+I( zd{9N@#)>N`x-lCRQK4P|D6dwr4Gwyd`6JR#L{x4+Tj~DdqjI|ENG%c5*lDwmBQY;W zq2xlI9;u}8FbH4ih8qBXR~lj;RB^~IOEFjYOr2vVMCEKezt$DI9q_dcwAGq%At^wg zw~}=g;ND$KlRXb|1w5MOA@%qTBZW{1$rP<#(fL8aEDJqh_2CP8$KfNcd0AsRU8AL| zI9rT>`zn6`XG;JC>v~g|v%P?b;mc=6A7yheaXF65tM@o4$RoUe8*W;uBBnp@3NgYA zD8|>zQ#tiQsSd#Na`ldn8xF2cMrrkRnsAY-UVLDndPca*MY>y_y)Y)BgA&qpN{FTq zE~>8tV%5T~fUwo|n{$EVQvusebWC;;v^Y=Tt6Z^@OL3Gfz8rg* z9WJ;uX8;|mV2<0hneCDhU@XbEK%kefMhem7*lMnfX|+BZmr|z(LVt!wfU$k~yd9K|m5oYNyL7@lJ)n`>Q7SzMzc@Z|mRwQ*W3Bz1769ov{8TC1v*> zy-@rSLY4XpQrEj8mI4=;>D{<;f66Xz=n42w;M0ztfM3BU%YXT6__Tiw{3q~fM^C_S zz$amcPrvea1ilDu*$MZQ&corBxlIb`=dQN8Y|yUNGSJyWAv^aqtu}k*B)Gj#^)yfX zR59gHc7S#04t+~M*L-TZ^wp0iThtHG#L^6eKQuL4FuP&(R-QtRP~_1?o%jGI+hhEi z*J7Fii{CM4mVfwrR5gzzBtxca(44UmTkG2~s(**`fu*xmmp2j|9ucXHUpIvQ z8(dH|7+Q5K*LMDZK^WX^uKp)!@AvGPWPjY-<~Fq38?*RByvNe#c=k8%{T9|f-S6x><(${ZQA9r^MU}}T^-V(rW5nrfR=*|Pp}Ro@9j1Ap>x{FnEt2PS_trGQ*s(>hX*5-yC|iR)FpYi#mR5l1xZzSN{6bg4^cgQ07usm$x7;P1}jLV(edbs*L_N;a~vG+VnILBrACv>Y)KX{#X3qC$<=$<91#h&|X0v>gr|$K7jW3!-d9LmG6HP;6H%NGA=_wcF z#x0s71+XK#e6X(*-CHOn-cEm0X5so%7)30IQ}C`sS|uj!@$4SXiGaF=JuHDj8J2Pz zBPk%yqBrMoL;lgUwhlFh7Om^Qc)MvePFWf#{reWKyykD z=Di_|?brWRAiC7VU)Fyq{fQ<5AIGHMPD%CYB~ijvM>d{H`CdOObHqViq)a!B`I6|| z7-yg{5xtKEemVG;YSOGl@-iFo+-rhM8t~X-PKYWuVfd-#__k{e;~_*B{>qBpv-6Wd zaW!cf#MDhA@kj`~8k8on4yjn9lfnZ2u(noRNV+Lcs5*mozxRLM9Aj}{rzgB}$KqK1kwJ&Jb0|=o9$bSlF$R$DU%5U#0 zkF&CS$){c4VhuYqRRf^UW?T#v*6kWk&rcyoWg+N-6MnxfnVX35sBFm3UFR`H?Gj=$ zcsLx#RK$M==~=!GG7t%4JEirUk5EM%sm4ODfnt+~>$d3wUe-l->mRB9FiCK}@psIO(XWcXQ3OZp!UlPg*E8FZ&zL>G z39cCL@6oRvI;BziN1FYs<}BH3&J6cGVjF5jxAk6gO7y=t@3$oBujc%r=#ARQCy1aB z2!cEDM1R^|vU`@`dxr@Y?dRINE$Lf~C5Cs77UMhA+Rx*&y~vIFc_A|v?=@vO`Nn6- zUUaq_oW5nSplpY6+b7Z6!Y@zuGnetN2O9zNmkqE7Dt|t;Cw9t7eQPg~x%6C);)+EC z|5K<|1m-t8yIob#Vci7QRvFb-cSBTvP9-WUU4H;3sS*t9#xUs@j8p-^#Vo>I!}Gg} zuK=#cxctrsb=FkV#l*M8ouFJJe|tiebn$fsnj^aJS)K5rL&@DO+SBA%k zx)^&tW8%kk!-kZs7xVOdDQKEk?h??Xg!^fbKd25^M8OztG2_U{(J@G{zfv`<)+iOh zk0fqJ2P)D>ih;6{U5U$8Lmskk6U*}mU!rClBmnl(g`Do!XA|-~j3=sVV&l1y4BQIN zu5CySnxeF1p@WZPWMI#xPw8tpQ7v&c-TE2&fCR;y5t7e1bJp`1K%w4h(n;j)g;MQ< z+$#z+R3P^pY{Ss{$7rQSd-MBjoeBH`8Y-|RaLd8Z6DQzUQ)OpTzr$=X1fi9zFZ*1j zb4@K^`>L9kA+iS+e;O~np9NfWGvW?)>vT^K&3t}dX!+N@=BL z=iEvAo4EoP;s&*#qdX-FIWu5M$fw8SrOHe-vBz#s?qQ{>Gke+=e}sY*e1grqd*WiAZim`hnCHPvFw4wHC*0VHJm$X9AeE{)#$DmO z7y%kga%nQ`m*5z~%vzYrH4Pv4pb*jFI7!0V52A!m>|CAi&+f#ZHP4VrXjm*uLl=wz zUSx}nYe1f^fA`J|Qsa?es@61V#n;D=-sHDd+h?q&%sD8|CD1|#XL%R_dxEtq?)JXM zBcIf45}#uF0N3-1?3_D&8%jB=RbgfH4S@oGBUACyK4xI2XwDVKZhchTIch_Z4IS09 z;&!Wgf*{n;B+rxWygIUy=zx9LHc!q~$@R=YSLdJtf0WFmS?2m70Yb$~UCuH_UDjB= zv^0InRpYd*bBR9&Ce-_+PcJw{netNBmPJ#zn3ceD!aNS6mEc9Gmq>-(qeSbOIZn1s zWj1AOT$4CjZPA}hLzTE8;R)`Dy3v&%vw&}sq(Zo3ag?uWhk)Gb8I-q zi5Q2MUNssM-l*I8sVoZU!h{Q5P%I09}DY>PYBQ%V_&7F6xM zD5L530pyO|UiUwE_J7_zXK#Gh_gT{Zf5AmV^oPClLw4`~u>TMJf4&yJ?Zbc3z3>S{ z{9{3E2=^$Od%8oQZR~>Kh0x&fsM#+c!%$u zV|xKc1nqzheLH&~Z+XBCzU{L3&`&{S{TX1RuN?trk6Q4fLa1+B`nCR>c%H8(~JNQmVj0t8hyQ@s4R zh4MUqT*-DmGwwvx&jb%jbH_GZK12Qf?nefEOtB7hb@> z$e_@iXTx{wTJpJZQ~*~be;8#tBv+hzK~^t|Dr}z;X{;3i1HSFx!{C+N+%cz) z98dlYMTxIb?cETZrQh@I|8hM3FTcX4sQhX6A8koU0z^m<+W0+;e?k;SklR)Z z2!atD!yxphRnHrD-eP0|?;5IkwnwyG|N8A(3ciW|7G*JXC+B;`8}-xBoV@eWJO2jX zAvD`_xyf5!c8iTMv2%C$on%SgsbCa)=Z&K+O#h1SXM4l(_O!r=cl$E7SN3h;e`E9! zwYNMI(VnnLsr_ZPe__cxn~c44M7t;kwBL@!`{4L}9YptSM(+nmp8h4@pLQaM-~MGw z@}xpnEtP^xHD=78o_l}@=QFkicUuztvn|Qr4$r_pjcL+HUC;9#f%1`g(tlfK}+3ICgD4n z_vb1la)V-Nx?Rr(ee5=_JEMH$FpKOZN_uwsS5mLD;MRB#O$-K021&aZi6M+#tE^o2 zh_W1Ic*Pu4f0nLh2k6sFK5N}XX(ii6I61}WBZU0h@dSnb8K=BunJO7!2`7Vd&&NR`goWX3zDh8%33{#pV`$KHOkpe`GZ5uX#CkcRYvUS)tET#Mw=E4cNOy4ZIOzpLSrtEA;X^uN zi?7)Nf3OTE+Du8IJDt6&!kpRTxj18e2$q?0J}QKJT{P+?A!j=C%k4dX33#o4&=h#k zL+b^efiB9kLL8#5$Y3#fk>1hpWxv&WdjQX`F?zts;AnQxl!21o!~2nGk9c);B^8~< zfH8p)S)jGxU`~`mY%#fw+%koUQ?TrUHx*v=f9^{7^hQl(R*R3t2-(s}vR3mz+R&Ba zalq+QcyK&<__5>d-&RO`rSaNfo*`hQRi^>bm#sHgAlTik>gU7H3{lMB^VyibpN#?U z12O(~gG^dg2l_ZNt2cO-BGr#4Wo-C{rx8?}+>B_HLwSKxD|4d4dtAd+cjg)eF7urs ze_`z~kyuSywm#+4h?iP?^Yj`ZPIxfVG&e1JGQ{8J)0<9 zq$ZI;2x%-3$yLSNSCY0V}?d4)Ue--jL zDx_2@>P4h*?4VWflwC^8$ZqCEGq{l^d+3or*J8cOS{@B6!2xH291+g04oiPsFWK2X zI{zPYZ?^3wwrq>O^A-87doFdgTe}a`m5`_ryirStLez+_KOm>QY^QT&uH5_FcCtCA zrxBnrHKiCmB6{>*5~B&?(21BLf8VjJwU*y95sLVX$&srMBTV~bMYo_lX^swbYDkC1 z@bsT8A4>zaP6cn`J?fG5runtABSoI-XR@dVS-FU|jK7{tGX>!|CXUw{0HoMn%P+3E z#M*nDFw=?msA4sD;xuEBYl;3-8G6jWhRJgeDI(;jDHoNhnDzwJ?JuH$e+c=E`NOCo zBO97Zu?HHFzN}aLVk&T5kj~rkR2X6O8$a=7{PzWJrpEcscK!q2imXYp-!30y7FF2~ z9hnbW6RI5n_HFa%rtD``oBx6PeUq~J>0Uq5arPYqgYaV#;{bXjhEfPb6EH$x_hIvER-Lo?-}$#l1UGZghOB;KPD!y_|W4=ppQTTc8uG79i{sN2t=|2Q^L?OF>@~PquNcAavE+VwJ-{!Jkr+!}naVrhsjD^hzo!f8x*+brsQM!N^1c1Z zx2Dd8KXUH^zHBb?e+B57KOQUhHU8vH=Q^c;>b(1PYHu?Aw2JFXr3m z%|{*vPy}z-Q6ZGn2MG=B;Z803^e<2{XH>*ElQcvl7;cr zY=1v9jLcs}pn8C3Gtth1;_#ept1{^tNLr!R`migBO;-HL8wQD1QA)d-#4KIS_uj+z zY&pJHjSKWtM4^E=vGiC3GdTzn8AXf?73=geIyF}4fAqPIVA3{dV3QpORWcU#lw?g7 zt~~+-$O0;NUe9!BDV@J>;M+qPFl5YEFQ60!5voK$_mJE7zNcPZ{1_g})hnr}P%5I! zlo-$}VKC-%o`GSSJ)U7X-IUo0*9Q#i%N+vuMEOp09q z<-)pRfBv>&@6lA{+}n82Z2t6UgJ|GytBcp69#$uFen=7X3`SfrxZ>Ox_C~h(wy*%H z(AjnaZB0a#97@eDO~8N?CCC7R_%r?*-t};CpOl$!`b9XQV=1_X72RL=sHw*1 zo)wkPL|iQO6{WFsDdjt8bj(z(2C!Zye^l=_EcJyOyfWdG%QlzMg!zJFznI3mHVF=-v*1<&J>&k8okcw46E^l?ihCB`RXvgTbEpjgcPF@TfmzVpdoX+5) z(;mxH&K}^|iJ-QoXqP}CW1#U6w>!fHj%ZBUN<6O|R*|8K6`S2@U?1G%J$F(cU7jYP z^v=b0Z7z>gq|J()!N3H#Sz1cZ;!DOR+6|ns@+O~dP7!Yw3!UjL^pfHfj!jmFttt>q z7(-@lh9On^^Q4C|FoB4fDI=S_e~H<%z~qXn@3?gwND_5ESrPA~jCWh{E(O{%A=)F? zS-7hWxiI@mr?Lc6y5lWn$~?Pd9&qVAdHgCZ{3=EWui@?AuMEn}o^&z4IcNJjBjCeL z4uN;PCTCTZHIL*sCW+Fl-oJ^O^fxOTvZzn)zbVZyKE~+)Zo_`nPx$YD2l&=sq2Rx{ zmm9uFyB|byC`%>ccfPb8aKm&brpAHYc{ldR42)+)~fxW*K z1g}Zw$XA-DsOb8nawt@uSpKU(%Cp1I1;Jx(;0I7;OWv?UyFCNb_g&y}4<&Crz7Kr- zsBcw3f_V3&zO;%^O$lnfQrY_;tmJ(FCgG`b%2!Yo+k$A@?m+^wbob5LE_9URzM?Dp zLw}`SebNQv??oGh^nABIy10Om;?NNn~fHR*X;tQNXW5i*d@`J|VTnKhP#6HbX|1*X?m zrIb_g8qiKN38HX9rWuInHiszRdWh5W5uU9gi}b7aDB!x{y9f!~>OLV$Pev5oo`0t6 z^H#pZ1TSQwqPS$lbl??V^S*ngX4)!7C>|2e?*OXL^GgF81My zK`E#{!qfZxEb4gywEOAG!Ss3)-;>!4uSX!C8R8ng+egMhjA}YYj8}I8jEFGV(F9Ki z&b<-lv~H;91&x8TPg>M#hNXJS;(yiGvza|>8uPd+%roYr=M_|zXMVv13S^DV?3B7sfvOBmkJnwV>b}izcrJPR?r0JHk?q7~mZYx7yG+GIn|!4`yMNMAKEZd3r9w z@9)m{c)fNkz(~3~IhPDI=eQMISt@n)Bp}dh(dsC7IN$V@F!22TIiIl$Mt|it6uqwv zDHE5wJq8^h5HRYnZBXNa;a>{L)BW2?Ye7Zn>U$KTbQe?d^*qHdbJN<3d1v~{!D7r6 zJd9D~9`KQ}R9jTf;MJm8b1=_X6tFOn)IusF`uNcLYy0#~&RF}oznEIp)16QR))<+l zs_h$SYh@8HW5fx%Kr1Tc;eRvmyr2nf;$kK4lhn!b&s?dF{vPnl1|RcM6e28Ms-o$q z&haVlMm)UBY@Mf{VIT1OjMbmwKI6siuLk}QZLiLj%`25jZPI*nihOP^j0@ag+?@S~ z|I|!Zh+b`MKH;m&sK{)pXWp(dxE5@?zNLC0%ljLa8nDFW;<71W#(&*F6v*)q5p+*_ zrA%SNJaln}fEK;Zv__e&3u;=PN-$Qr=W1OqUd3MZ{7V(}IOFSUR7Sx5`+UuwZmb5F zyr;{FdcKwWGnJ%SXXt>l+HvL0mmMnxZ*B3pKZ^$EqIb<%Y}X7s15md!i@VQo2IcqC zPAykHSM6-kK^Y8AwtwV08J$^J8L_6cwzQ-hhvIW<%5fwk;PSd5*3mn%Q!sbR(B_G5jZGP)FVi@YodzMj_BYt}KPc4S~>sf~Y@PCZU^d2%hWAk+7-jc&K zy?sMSdt$h<<^5J|MqjoMH=C^oX>8i2%bt=G<5_Vs^5p=mq_9mcGtP;;Uq}0_usp$3 zc}-HndBj<%VV|$^vd*pWdPSj)?yD+5Vga{8CKa2cfyj8oRChCU=bngBUsnsi!_uyL zZFNTk7HEi4dw(5k*T>$Vv)orao%W2zv0pRBv{c{&>jJigjwPC8L6TWI=44zM5eA2J zafYiPzGw zeLvs!NBt`jhjE(1ag2f>9HvN^yG2n2#L9LGr%rGE+HSHLgIjsh8$9OM~-_%9Uy zh+lo6AoY>tr_s+m9C}pNV(8b#0?9!RPdr*y0euJN1O5rRCAxgDHb@l>*Qjb zUo7jZjF)uTef$%7E8u(4j<1>>Z)@L8t@DY!xIPqhR9&CYR&~R8X#b#42{45u{ZZd4 zVSjYVv8YI7QihuYULmi@%~kK#j+#X9eFi&1lc_p9Zt(1hxqJc#}a?!nfKTV@0m9G>%2NX zvfTtZNQiqt#^@t~Md{BdbbLf?JUJR3hdE60mF$uqjhsCDGkHPsBP@Y`ge5)-4SzB6 z5rjsM7RsJ!@4@%@njSnL0y`q?ej53i{yCa6$)SZsB8L=L{t2&#RSfh=%lG^IVJTxzFt16faw;O_}M z^IBC;S7I(nDpFu<2aU+Y^X{0^zKk7Zazi| zVGF}61usEAyc9~&!8>pxsABF|BuC?lWIHKv^b?LwkU=m;*=cxGpYZ!ToK63&yegiK zYW+-yx-YG@F-%Ad)KqQKrl6ziljb)JQ>f=$E}O;7SnQ?Y6;dFohks5_3M6K2)WzoZ zRN7>`>9L)>RqhRhpc#lSodAVAfNl?8w@!q%tobdh#{jD$m17a)0@58s)JoT-w<%~I z4cuyx(LDM+9TMW}7q!Jtr)BR0H+oZDGcwU_dd?S(7i1PeA z=hGs`vXj!!+2T6IEBOD0ojHr$r1 zlI$s4W)UDRPxiq-qY4Y3X6lmnWt~n1D9vu+&RcbXRh%8$lP~*M|4G*#+Z1c0Vklcq z%){Nm&eb+#39?HubCZ**EYnwcEKHttMIL@^l~~o5P5s5f7=NQ7y;NrsV3aR1e}6sC z8)JXpD&Y<_^?fVd5d*6$QvG?%W`rWdmLeSX{|ku~-4sxmV|0&N&P$+ZOTe4MbdkxN zVh>=Fz$6P>F@F-p?1<&bNbd3Xa2((N=78gYxM1&$gfa2LB3~Y0AcjvqCwy*Du;p=K z%&_xuUSKi>!|$p4zIuS+1?KW6QkuUAsJ}~r*|NqceY(;$@bI0nM9s;%zsb%hF>Xcp zvFsW`@HLjq3**6`F|&+?rNlH%fQOdbI9O&NIEqT=B7g2sgI1|4L$l~h6yP3wtNUAe zErvXdF*H&u6Xl#I>l4ol*h(=UpOsg^lj&SBNhU|J3f%LEz8H5VlN2-IyHoBCco98K zSPj_;yzlAikaC8$4oXE(^HFWHjR*RGukfQ%Fx&vV97C=rMjxrR(*>az75>uCD-t%> zwy(*1et&6sLNyEgp_LGOGH*8Ph+KF>D*J}6p#TUwRD4L0k(3VywK?>1CT~=Rm*q3X zH72GgxDr*LdFbm@-iQaGjqQFi%n`oUD9XC#7J{3e6#v>@`={ ziDXeUu_@!;0%gOCh_YQ?D`Yg=T9ypR2ynx!w|__$YqBHMbP^ei6}(z4?UR&@!4p$W z4OaRcEvZZjLeJN#weSKJ0|%MY}z_V(7A~ zZL;C@a+7I=ebPSRx#z;l@7{71x&_E-UeKHUlaqt$m4n=~6hr3Isl8jUzd^klNO8@J zXn*l;ES>%BH0}Sn(frCUO56nfjRqcjj(!gvwSSx383BFN&rcZ1dd^}84q>t0q&bk( zxA1I`Va8w6#9V!In~I^(1OB1`QrrpQJ27TKO=sJ3`D{rOCd^;wjZoy(``eInKFBja zq|Mj=?{OUC?vEz=jcfnk|K~iWKf9Iu{eP>z!pT2e@jXl=Q0VX+Az=(cXqNl} z$C4NhA9FLuP&SQH2uk7mNi_B=47o$KLkJZLAN0sZ)vL`T~x!4CjQ zrvH_qf0?55A3ZSt8&4;$JIaq<&ipFykT}~BVUlYiuI-S6mB)iBQv7SfP-^R)48vf`84B+``X^is12wZgY&#&@qhtO=X%-Hr-*+ z9a};d@pqN!{TT4w!R4Q<2KatCf4LetSOg}Je}4^*Z9xPyOb{4O;e&z^LXXw@cEzjb z4xE^hZ;A5-tSaBU<+S>;xM@W%+-2N`YJ@53v}c`X2$;e9U2VMqZoN2Zcz+vA$H#b~ zod=1mmUw@dMI)aYIu@?^$uv*~(e+-R(&g1>wQ_8{8OEkIMmLTjn7{IKsQKnHkB@h0 zWbf`IjjBeIxhBUlMSZeu;P0wQ|MdIkqkYc|O=ST*98JAhma$Wf*&f^1NW;gcjnD1d z)5BfIR7V@y=FMblN*$7seSZ;fwnaH@-`oHh7~oJABT_?ZswmQi_Z=xgC49_|uP((s z@A_x(S`t>k0?9lFMQ!;Ek30DQMVD6XZ9xJg)w%wMA?-g8Xa89!`$ZT-DH1vgzxW2|fn?Ixcb?1HK(BS^lSnH}8}}qOR?>$pJPdcfTeo|j&v5X!#X-c^=`$?n+a7Q$R zejd)(=yqW3sSQ~m70%wT~l=W%?V%!Ea z*mHBb%-x;|M1NB-6TTx{PM&3-0I4_ZIML>`j=bpIEtQ;!x56}nJ6xuD+T+w1*9|`y zbs|y*ChtMp&@O|x-p%HWg)~5Mc?O=slMa;)x8eb0C%wDiH@m*w_KcDVj8&g`&td%T zmbBVTS@1%r8%)9^7wf4EaS?`@r>PPmUmQ2&mQeMcQgm-@5r)FMqmsrE@z?Yolg{;9*meXTa?9EIQRV zGnAIlA95h@t@Hb1%IgLVTT(Y)FyY4)vVvkrqCx66S}1h#nqhCxV#oPVj${vyE$%62&uFo*Ea{AhUd%WaX~x#6VWjt%r^49=Y|0BcCX?sk z#^09l)ZE|SJ0-n;px$@bv=+}9^cKf4zj!2qcwGb(+GrJP@*YsAhZ2?peSf_L zcWai1+xviB=*;^dBO(ZJRtHS827rsE-xv% zO{0?7&t=fs>0G$UDWLPH)s25olS z=8ti!Q+X}!^+~SK;qNzo72A@&z?vJfgCM0Dv%bm3FX~x`^W?Wy?4FrUuM(dy)PH{U zS3LFomEXfDip1!Hm!aAR7k{sFLG)uXgdJqOJ>@!75r_j&B_D$e^5YqtK?mZ=h;M_o zKeubsqw5fV>?>0IXo^xFL-^#QqPky-f1D!sWRb=XrPV_d>|;h0{h1H^4#-I4(=yJE zZbn2M&4e9N7XIr+wn>IbR5(L>c^f!0touFebWYx^1427#?FWKR17$-10CqG{Fi|q>h)CJT#60^52VfuJ=_ zqnaMazqqpPaf$ddBcU8R0$h@P!V_|IVdMPhjUHNq@xgQ1qu73Z@+rq1+5^x>nL0XV z(f0iCuzzj)P?HbN3UbIM?4j_egVCQwB!8JMMn}2`e=`y?&nYr&wRWpCktfdlC9Pjs z!2gnwZ2ZhfcD+p~bx!z7PKovu5svG$O%b8+q|X%B9RHrEDSOHYeCkab6MmaMd){BJ zFsk3BjK$ezW&L*x-5nLZ^-@``A0>P%0={_Y3V-i|baHWi_t5=Aw6kuytuKoC)k62h zN3u7C$Q=dtywMH)wK}Aw57*!GV<~=5A!D6=ex=6Pwp5um^en!kK=0=#EAYLX^k1n- z(<~0UjsE6k<2hRwsHM#X=q}u3(+>niJvp2$3g4cBT)!6OqDK2p(0I9B5$gWnL%LH^ zMt^La*v?POnK2Z{#$^V)q(F`Jw1@NehRbQv3h^lg+oNkfT+!4W4>L0Y`}?WKZn-c$8299G z8j0Mg4)s(iVv+gKLoi&5AG;grI@3GmviEFu%L4bHB?!0Tb&uY!=F|@@gH~(4R5mTccqqoz*6AV@qx)`Qg@XmZDr&*3 z8j3>z^w9)@s(N(Ckn(Ea6XB1V~-mhupAgE+TD%((omGdw!6Aij}^BUb&2K3I7By^1?fm1*L z`JjM#MpEb1JmKvvb)^$(XV6zQ>3`?B_oI;z)g-r5MYP}?&`IjG(@;q~Uw;NY$^61L z_uwS`q_*)dg=lu)Y*`qU836-NU7A{JyYEmX@p$dv7G7b;xZW3`wzXkpX-5k8k=f`W4B#73KE5I3#TGZn+Z^jfj^*pyKtdW-ITio9%|rT5e&fGyxR&V%OO zu&Yr`Hvi)9SiI41U@e}ivwv9T4@~2Fv49plNB?q`U%+cKCB6R)_%Nh=;($f-N)~0 zl<%#6Hpt+THuo*F)ociud?QKAfCIDUChtxNdE=Wd5b)1oSbtdWJUQ^N;I~y3Sw>wz zLc8U}9D{Fz8)HvvMPiDBe6d~U_Ed9Z?&?mun*V`7^mo=jKUf4^-S7YPegE)({bpf* zZaDOx-Q*VrL_eSPqY=@8z7Bx^7@=s4qA&!d36#X?{nPl%{Bj2?htUP~$s^&9@!=O6 z4Ez|~ilPJA?0;AZ%8!5kNs*q257-k!M=Nl@2MHelX-^`f4}TY<4g__WEt1F7R*oIh zS;P*C;{QVaj1C-s)cR8LIEnrkeZcfl`6tm2xck)LaqPo=``C&=)q6F9+Z3a4$?@RDb#Gf=>{sI9qzZe$FjR*4$D2 zqI}aZUdJEpgMMnzDI9b-F|NYKSYHeG-1Cdc5AeIo&vy^%-yZz^vEK{z%BnlU_SL52 zDAem)u>krPbE1RLhWg(v?eDG(__L+`ot61}Rt)?or}y=R(ph%7a13K(YtuL-vQ+$w zny%I}VSfmFswWp`m#6=(uPZPGG4`l()}wIl)Lid0;&I?gvdmeXl7$4@GIGwM+Va+6eq74trnsMeod#z3XGxlLdj(@|sR0w!NKqdB3vdSNKTQj#?IRbkGhBPWY&3rPM)G zNPiE*)n5jk>?0KdCtv1M4}L029XI%rl^vq-*^%DuF^I~J-XonJ^Pm*`=N{bq!^iXi z`Vk^Y52d^>)Lr_+pg=x%!9TY?rcghUqWd}zSv~TIO4&yc<%_=Q9!AMewtAGp$Rp+@ z`CoSikD}Ig`XT5%5o#USi~5kJhxiAT<$vW!^WZQ7&dR{*}Iuz zPT}Z?4ZZo7POf$xQSZ^NH`VT-{qi@Kyq@$a;sgb4n?%zdMvH}QPXPh=WPeY$lFr~2 zJvEVhnzno}alG;}Xtv`SE@X>X18u^v^O)SkN9Ferv2{;(cx`O`1oVe@)n>7>E)2JI z{nd`0u0h}YAh0A3UHccZ$4V4Yi~Oa(a?mRW<7%Hc7&%|BRzf4da)lWUgdLvb8rT^w zL(n}K8e98hMTwdwjWN!jwtr&dJxL(^nGz)3b#LeFmInTlgkC_z8}qe}2Nr8RHbSI9 zMRT`lJA7<2r97R)!T#JYRy}p*BaQ(MpHARP_|FG+YHLr6UJpVUMtX7jpeR~toudjJ z*y|k$@iwn(UwwC{_*Mtkydcg(2AEG_ULQE8oG zH3=;j5e4Vn=k^*-Qh#M;@i@JKJCRcq*s-2vJ-DiVDTTWl?b#>C@pd^cTb1fJ=~OiP zjzl_-Aj+!Oa~!4N0?Kr2=omoC=S8^r>3g&6G7FJt%_S6z`aQ)|HPovMi@Cq1_C#w~ z1)__G4uq~cpcXWYM!zW)2#Z`X){UYrqo`l#$3vH39!L2G>VGretK>CE9+xTNUMEPK zGDU6`n9QCnm89nfvu7%RUgW8CvbY_TF_teeqd645U14)xd2hrJ4Pk@E8Qh^gwMKZI zoprHyHRp!k3wI7&5D1A;Y6u=QyM)N$?R<;bMYacsPA!Bpw>}frkj?eY94>d!V&W7+ zuS&bQ_goe8IDd5jVxk%UWokE$&7CKk1`icMLGT*Hh_0ZeeOb;$Ig*y(WGdZ*qiv>- z{&gNqwwpz!2?0nT-tC;7$cYRirXcrGFH$@Q8F=P0+{$U1w0O=&Cs8 z;PPZf=>ATO4MLv{b-9>ZQd>_Lz2u5j8{VanCJ&s@qDGT{fR&Jcgq8m4sy|{S5kK%l?J?4$hl z88jlZ9S{B^tke(wh2`9+TZO9oL&f+1AFQPOUxk&F{{(+4@mF+4>eFc(M1J3GmN1IN z29=y!N>ftmMZL2*)ANa*XKsFtK$FMIJz(S3z1z*!h~o)s`zv@(>%yJV)5!SmP^Qd> z9D=8$$&0f%%{XI&&i?t9N;?1uzG_L;tui#a<8_VSt$NQ&w^N@%2+>?A5HlWc6lI|S z*9Hs{n|6OxpESt&G&|j9W&(7M;6S6JMmEvxtFP3QA4fE?ynBc9_6Gub$ z4Jyp)G{csXR3P@6_8IvEtlOk|d8WyTYTsFHTb|qv!`j@!r8*UR`*apdjpeXmsWGof znf4I{SrVVn_iZvJzW{RDy7RDjHu#f{OyZJWN-lrWWPCBVSDUHe1iMsP#8dPn^=Dfw zPT0}TGAeaz!?k93zzH>250O(%3}JyFYZxa&hCiVC~Nqh z{Jekd$`!YKKN^@7O%#_1L7RiFr%9q=K(5MM-*@`@ z9GCwotTch3Rz9w|gEty-QrlPOdg_{Fi1}}@5({eEh|0j+NjYMtI!7hP_5n<(;2sk7vD+Y2=J}xr0aiJ z$^#2F$$}+#TQ4_Em~i|M6SEw*vU!F12m#;G&hTofae&+NrtlPO=IVy)y-} z;N-isij?Li&4i1i_ovmXH>>kwN0Wa^Q!juwbH4gL84@${b`o=zXgbg48}^{$Nq5@H z^}MF^&3AUHvjx)#?Sa1J-YIM*dVo?k1M*wp5^yNE?v#4Mbl-Biut)~HWmN3-$V~}@ zksHOVaSF~t-Z*Upr5Znq=+*Y=QWC&=7TPBl1{}XV@~*!b{)y0oLSwU`86$sDB|BF1 zDV+>17T{c~5PPu2nyA&Y)pTlQa=z_ubCYqgb24NPq4rM^ zy$O`D|3;8|VU}yc=|VNr2xb?6YKRJmSQK=|6LH+1Dnc0r~U`i3x;iZqN1p_tK z6FPxn-ZK|%It@j=c`~k(2ataU$6$Q0byo2bpZ5bf@32v2DI~T_;)PY7SuAinrQGa6 zF&0D#H^7l&T;rE@XD$^$_-7`V$-wb^9~&9#^eiADduOESrnp}EauA>Y0ail)5mx%E ztNw_U&|hFB8pj|QMu}r!1|lh(Brz0$en}xfv!iOWgAtrM#$9Oake`3WzYw+YqgZoH zQ%4_fz(YLZ*L9`zIG2Qv@(!|NtdF)uOdl#_)Q5kSe#U1A>X>VRK16~DI$#+_{Mq0P zb`-OskF&wS7pIOwRQwsvfzpF=Mc{{0F8!%p?YL=&OFPip*Sw=VoIb$OzTm?)5j|FO zP}Sp)H+J-4uKy)g;<$f^nYss12Bx4JKI&(-?i2TPWGmfyDvy8QAMc+P2zfob(LH}wG!X) zNz38R(DvtZt#%qol#qb(`d;$~2zECql@v>41<{KibEh^Z^2vY5rM=b!F513j)+_A4 zElWcL0VAT4K^UGtBLK!_ze_~@k>`6a|5_p*U|ENy?1*SNWV#2k0fBn}>Jf~qb9(;S zd}z76cC1_Ld=67f&D> zp3xVxC3+@3j+=jgYFY!~kq5(8 zIGz>n4Tu!*2f|VqyO%V*S-se~ehaXusj;9;hIafDjnp!xoJpBtaoC^h>#jk8brI zSdQixeJI}SDca$nm44<^K3pa^J>pih2QccF5o^D6&!m42DF^aMqxLWdecV{T=y4xa zarmHBksq5ZG(O_l9v1hf!v1VV=1}ZTKdKeb5lW9~9ptF3!^o%3P9Gh$Bhca>R2BfA19N4DZ}{7h_y)N9IRcEyjb%W)Xx9o5zG9) zDq@*`6|sMuEtRiLq>M%XykeKcXLWfz+aXFWzI??;T$=sa3#3i7<;UIvm%ms4-r{O~IRYowu*jxxsjFuU(@Jtco4w5W6qr1)WRQj$OcFVHTENx#E^nI>42oFh=X zsPo#8&+;HXUl=VcgZ(bfuBzQK|dCus2#dOI6{*oM8F7&!w5nm^k=dUNAX{lq@d3ba+DlB z_Z_3`v4z49hIx)1{ko$+mK+^3`Xkzp{W@sHA8pnhJbm%t!=WRlB-qh`gYW|*(aF)@ zqo~i&arWU=#z#C#{!BX-!HzQyW;*uan16rqjE+Cuy!@ahQ1oXY89Ra&^6{)XGkVDp{iT$A~9e2km5msd<8ZuMLs@!q{9RnkIH}P>;oU6eaRalU-*BH z1IZ%4^U&App#oMuSKV>pi(`Fw=pQAp-;V*u!DA`^bS1zKlK;P4$-h@~1%4drJDbLm zQCKsZ5#C@}lfH0@NMFpADr)2owqhkiOVcYDGu=ay*w_x(^ZhNA?x&#xW!TrYtfb9% zyC4>4l~5VKc;$weXzV$fSR{4+(D;9A_1+}x`cTTt`A!3SKzx`g+R^gB_kKwYt!UI2 z79v`-$`iO;2m?W1XU=DvqUvXH)t>b0X8T|V;*Y4KfyzoN632VGCiv$Iz6P6fajrbjp#I%`r1z^KhDLy!*cpGujcYno zGoB%)T8#>xK&$|vFao+=w#U~CSi62Grta;{yc~d(jIF+*dSq$Ra?ckgB*6Q5v)o~b zpLKkFiAL4NpnxomkGGYtQDvE5$P&bw>f&Q9KuF73fu)yf-g?_8(;?}Jn7LadnFO)m zXzC%feeV20v+!sBAD(F84Tp*(NJ>1mHW6z~21yLMYdBiS|om*#$_N5pC7wxJiVi zUS<1r>+T$tiv*WPQT@)9amwABN}v|Nx0S2mH)7GCv}P?#hct=ns%?MFM~IJ;SZ5o- zFR(-<^GLez;A1GnX_h)!Q_%4gD!@w!hU(h2X|2kSb%)N-{T5#}+lixzP3B~AUs*{k zwB(W;>9ep#&+Pko_P(mQ0>89nmbC%rFL?L#WqL*iIVAn{eUcE)#D5w9d;|T}+_LB; z-ocpeStNspWWJBZ^Xh;6`>BYZwdnqI0DXmQ`S7FticeBY@Hj*f4)AkT7Q-5zBl^R3l9`Tl>6LkN1q;!4*6hI)ij z`-CNDbKagsbp|uwH6~|1-CPJ(Lk{I4&h@d6eKPy|y+JJ2d;M+9q zWxY_wHfhy3P%VpTqcXa~!)o~K#Jx$3%2WA~6POQIVKAj_F$B8TW+x4B9CbEr%5huG zh1Z_VG}S`wafyH9Bs%5Uf=SuU8%HYebd`A*oT+CUVjxK(^^&GvMS#f8qx8BK=yM9% z);zJdgsn=H9682lWQEJ_$_z(W!o59a?J)|WOTeJnw@U)!cSPs`v}JErOgB_GS+~xA zl47L@41}#DPb`o%vWADb?AxxYIillYnyT5rbGB>TVfKGzeFU60D#$zu4cn;~O^lRW zNqgzyO{l}wFG`ooW%F58u!WzV(tIAc`VX{8{^lro5~i3pl0 zulIH>r9JfgEGT`KK4zw}y4Y`PW&fBm`nKbGS&MQGl`xd#tH`O3EYxx4%j2y4uBE@f zwkiBSuI)MQzc~9F^+AA*D!j6#^>bTscep3=Puzd%4-Wg_SHC>u$5dqk2>(!$p=gXj zF^VK{nxq(-CTI|_jN&-Rcly*ufdD)UPqsQL1Lm#yR#~AN3!_lb2r4k(p|J9lj}+ve z*w#vAR+h7JpaKR*ucus3voaeHot%MsErG!hk=h*f)6E4CWq5I_WE zJ%fKn7z+4Tv3(_!07fxuRl1&Wy*8KxUl`x~Vb(Hqy_)r?Y>S)%o1P4~0%99JuHO;7 zJ`?}P5)lOhz&rm<7gK0=^N714wtJXM5X3M%==I;rMCIoJ;Ck-Af!0HG;nmO2__noZp;R>QPd}JrtC{nJ^-^$33BJ~U|*Q(?k{^jm|*dje>!zHBNsmNmuY(|17q(R^S5*gJlph|9lf4 z0aFEVCS)5OFre+mY|FGO?ye;@SX(7Q-J5Qaeb@%|U|0TEIKNin2nB!U6i5tqTSIedy6L5D(Qm{<2-k8AQ2*CC%1xL|ahG3TbG0umR z$|2ZOF2qTAkV{Y%;~xIG9rVA9^M3HpI4^!tc>fLO-P`{<&P)D(h4ZF(!lJVPk^pKv zd?>eP@+Qz|q%2A8n-hOdCQd~Mc`o;J8Q;~;N$K^g^3Y!23ynuj+G2`ubwcEmP-t_O z%>_S~V>7;z)LY8>I9+yaf%&!LJf`#BzA;P`XG|wRQ=46_W>CBMPjQ~MSUbWX<7qtz zNx)mP=;QYh-M{X?$g=-m#d#n7zl-y>AN~J`^Go$U9Gj328^3=v+=z{&=V>|{=eOP@ zmfKTT;uacAsJZl`WJsQcmi-;>?IB>ole6a<8=T};q|@z9TXs3jtuHY}Ota;O!K(Ij*7Lo&nVN$(DZ@foy)@2)Lbq4=~X2UGG^b z`E|+!?p7oyIlwK40Au%+|AWEQdXFIf+28|AfX?6c`z$`^-E;iL(6l7X>vZDy$BTad z4fc)C_WMGO^FCnTun%<;dtlaL^&oN~TJTw|*xQGiN%rg(I-~XGCjLXtf}r6#{{g%y z@>k3PP_}=w-@(6yLfeCa^8SE-xB4j}*4Jj~&HCTq|7N!zU-ZOtDwN!{m*zy1`t6Q{ zh;fj$n~^yGeS8A@HiU1vs@*ixin#AF zt7X-Js8peL#+%I1AySOx-1Q9|pLFDv^(v)ZT*iN+4*V6lYvMx5aC$TkqGgpe-C7p| z=T9vJzx~I%9jXR*kh_OO4~0T}BlnlDO64D*UXyIw=g!YJ)tsb`RCvXKIGk4sPI~^) z22kQ{R%|z3xt}_Gz3f7e!q@yz4`=6WJWTohurKlHWGXYOI$6}0>S*6Z$+*|M z>3~B*cor(2a2M=d2wWa`>>nske*hobVR4ut|0~4%jblFryq}Ky0q+n77~PU6Md1X^ zP%xMp;qZ!c1je8Qy)s^c__Un?LY%h<8Zduk-YgXh6imDm93TZyZ{Z;K0Q4S;Z)UVV z;ayCD!O)5)E14uRKy9f_4YvN5ZR^s&lXjb<$6uD5xAFOkpBVA0i8lop>KA$n6TlU6 zJ$S`6pgG-)Yr#?u4X%pG0du7oK%dbBG+*E?@fyhhIFg$IF1htr(#^LiUa#*T@lJm_ zgW(8F{)Knu9nOyxSq$w{d*>?Fyv_3Soo4-Kn~&eaJeRFLFb|T&zu@P)KtWg&Zs-T~ zwcp;C33#O=(EfE-K+Y8fCT;xo35JMhiqTW z_e)-ANbb;-BJQBL3XD@%G}v=ba4>&nYAW{Rt=D-B<`BJJk1x6Gjq3hrZ)JaKPW70N z^P$I@mNi;)KEM#l?^J1gd8~_4r~Sp{VhNWO{DF(%<+8WW2O@ZP4KCPuu$ykD9W^TS z@bKKbaLlCVrG=(TpBnm7XknI)OJ42yydRbqlc{}pDvlglqZ96Eq4~HT#nOMY$bOe$ z7cJ~P_4we<8q^+8mvm{8s#JddFoFNA^2n{OaeH9vwFJxg^a9sJkkCPcD zZ<911P_M}hMYf?EFNbz6;bVU)_Cm+p#`3PAtTMd)X{4L|C>53_#nZ7jWmoW+ryJ%W z8w)dZ?sDw?+8dOy)LHuM@tssLRC(pJlFr{ucCw?=e<6xfkj)9ZP3rkY#tXb}+)ma%|GB*H=K9j??+==|N;nNju{YEmEe(PC9=k$xBAvq(7+Y z7(&M-+V7pmm=(1K~OV=UI4YBrrf6V!5Q98fApefal7<9cpC2|0X+z|BcB7~W}cwo z^01BMsfp-x7|xVQeguCceBzkwxmrrOXSSOQS51*NHjg9ZmfEg|#a~!?d|r5cKb3`k zSaLq__YN5fpLwb%JXp`vs&q%*!|N~gRXt$lESPFUsUsZ0XewUS>pVe+P{|+kUEV*} zUtcvM7fBQO?##VtdwqM|ivxF|`U~~ejmKIiqN8%)U$^pF=&ye)BXmlJOntD77^O_- zs@Kho&h-dSS?{%Re~vC`WJ$a5mM!T_z7WsC?qKu$Ez}I!d$u2*B*?&D>Q1nRmMdy$ z7oQ84R}{CxUFp57?hXq$w^R=`QePNa4a>uVY;ibnZ@Z%01Hl<& zbtj%LqLOfCz2tw$eRaXX-M{d(iOPV(nB{ufCquhu$hF9OX=ZtY*z717KF!_FD~8m= zbnueh_0I0PXvLunCRB%s3TkvRQ^4#ib1=Lv@)+TLs@7U6vqxxe>J7o>CgwzEJVKJ) z({RP}*e*gR_(2)pUXqhl)dNqS%nsiB6lFG!qRq6TTycLY-a#!nj1;jiT#e>l2lc7E zv4;0h_JH+MtC<;x(eU`R?)`Y?NcNWD3m-kJ%-pZwh?n&QXu`$F;ni{PJ@5 zvV~d#r#uxB@9_6CZYVl3pZxgA$p<1mWam0GJ=z%&ERdk5r?~!Wx@P#n4KD9zJ=E&* z6cxHnspNm$iwE8ot7hWpVX9GyJls2xK=*;I-8;K&_#^Zfi@X&BNqt`ON`l*fzMfFE zeXc#JILMwDaz_NfsbGaoN<%cUt&wkNrfd{^_|NBu^xLL$KO5hW06rA`>Jz7zyWU^tD@D1(0@pRPBrWgBRk;ads-3Km#m7;NdS zYy~5?Vh#WL&wQmb=;zc42D>&Ojc;WwG~h(V#Zrx2?wbAH5(Y8*~VlZ>6HmD!92@f2-5rT5E_4BceqNJNDC@DadsHUYLp7 zX5Clgi;bYzSFr7pyJD4mJ^HF znjSZODb7o19s~ zGF>Ei5uZVHJ|U;6kJ`c+@E5_2mPG8@SgY)m*G?`0b=W1YP8PZnN0|!+ixCcMF)K^g z@=WyCgA(@P#MiFw#PObO-;RAO1{{B@M!P14G?NG@<>o_($nD}5X^0VR4$FnUGCu=% z;Yaj1>Mf4F52}MlH~nbVq(X_h>7w!ejB?bnSkNR7qT3BB&JstzJ<Jer|acr zGv|1JTd9k6tf{b{!}U$X4Yk*`+O0m@dd#safo+nmR#9VtCrf=DVNnf+Q?9VFBO8bj ztOy%ad;R{r=JSjSm&wy4!61_WM^L zY@HlKc-(1RdgWZb@@UNNg&%)udH2?AC@&?GUqoxTvE*}6NKRc&cicIoZRfOPC=PO9 zKb`)k)a1Xq1JHNg*Z;U-%5m=HSi1u&&(q?lD;j4RM5Unkw&!hPmoY`hL~ zjm4O=?(D1eD`;`MUOhAWCVAfMIY?|-nB&_R7!o1S^L6JPs5U68`#C>jhj+)w2`;El zGeL|4RkTq99cS#-nq#c`mdi3YG51>Gp4VDx9**#JrT1qK_vHG3^aslm4sEEmFS+pE z{^Ql$v(o}C{Pcgm5y~{fq;_^S>&z95%H;tZVZ3pFbmVNDY*V??@FO0utUz|C(#ogW zuy=lw5DheitZgPEyc6#jzQm!??wt^OQ?9Fr*cBbUYuD z=k$t6$nbZ^dt7sYl~1Q5{ri2VpXiJD2bAb<&;11@5`TZ7L~Bt+6BvTSB!yB4#;nW; z#R-feDHsJ_*#u0`pZ1b~$7&8_@M#7(5fH<}fKaS#sMyq0F$VTqVrmo5GoJ?~ZGHBY z_<+t20tS;nnN9$L1^5aEPMmJ`u~Y_jT=E1c8&;NtCciQ-!+~gmD1d4MxRq_)DRPs< zuT*MfSuuaI@wKgY#blc@A_gZdGN6Gk@_#1@Xu8vzn-ENbt>u-irTC_n|7Y_u1`whU z{nkt3O7Go!sh}-cx{J{GmF%_M-$te^^I7lwkEjS>BG7kKMEYtAyR3i1AaeM76KuWu z@i9$+x7}mt2NP^t0XarrOt7c=+gDk?$d5Tl-_d_G2$*7j>C$`iXF*8cdtGDqJzmW3 zf+}kHipO)StwqO9Ij%SEr@4r2er!-BKBioBb!=5`)8?s#Mf5x+n&5h!#Q>&yx`$iXXP{%S^hwG7iF>s>bW3jObI9cY{!!;aH(dRvD+92 zh#M-Sv}{Fx7dYnXb`$brj)W2G5X_26#fNWg3I*^bJdR7RXE; zkvFst&DPQn`Mv)aZ~VXi=i~}Mx5odc=lX!O|9qYwm;X?lVo(%=DF$X}1fh{n1Cv%n zU}9i@T@;%OM+&MTJOj!I1f-aQQVpd4X21djB|n9;bW?(*Gyoq8-`ubXAOMR|u>OCs zf)eOlBcO8)B&%DB5^zt0|Be0177aKiZjr<2wyn38Z1{#mU@wK*T-ylH7e_I0@%Ywn z2Oc97K+W~Z)V9937If=3mE~Y*BuRmxBgilY65W5S?+O5CX#N3bDr-oz4hzg-r58VU zeFfHIH3|MEVt~G=7+Onnyh-_R)1QB5*Y;NS?QkC(AZ{M&@Hs%<%F4=5<*fOl_0pVl zf>i4R;|TXV*L@*Dks%9+_G8}urT()8%l%&3DuPj9H0y5>{V-?Lwvf25k5yduQ~kG6 zBC*!^Z+j{!4aTs`swjppc}9;pk!q2T<<`%rV#Q}J-lz+ z(73$Zhwp$gT)cz$yD+{`xVw(S{OHS`kYaC_-p4*XdCGBy;qKyhzP!7J&h=#rJ1!Ck zuQlG5fFR=W%|dmTH$@ZE_&k5C)c|hGQ@_4YVZ;PCI@{|vhDD#(jJohXD@sHx4s>}O zMP%7?3-#V>2goh%#o5eu?L9u|p{$4zr%j>61{}G2$mU9=2n)~IQLT_&$Qwu=3i<9O z*liYRRwz*rvwLc?QpuDuBCx~w_3cat)esr`7m4yHqGm1W_Ket-hkJk1R!8MP&@a4{ zg8j&fha&~iCw^jIMTeovg_E5i*|4*_l%}poefcV z@}KNz;Lrsj;g}-0qE6M!vn@YGVlfb}{9WdsQN@VVG?dO>k8<80`zMm`BuYP|Cz*=0 z8JlwG;G4&ml8%zY1(JWvGDlqL1p@YEFK6vNgWoND?ofJH?a)JP6VySuczu7OFO~Lc z#D0Kw7)zz`qsy15q34dr%szY_<>x~3a5orU(R9|6HhY7I?bD;&Bf@SFtAXznnok-` zgJ7Szj?V*a+*^e#&%Sa!XW@kH)wfX`SRF3w!BNDlvpu-X@Y#P?_xvb7-s)vhy@BW~ z$YmO1eYsz<8a;aJ;fxg40=Nt%j!eHUjC6DHwEJLtu) z_KV5f;y@+305@c8|B{2I9x!$8_hikwf#>rh>)lb)PhEc%?yA#LSqkJu!G%c{SVF2N zU(e(imqB5iWzLh2FrQdSs(&1`{`s4BV+B;PE zGA~zIC0TznnRX|WT>OJ+5W+Awen6DXEiz!s#+T;!%7%NrJDm!sWb13Wqi^mYolT_k z%=GS>{BUC;QC|rzcI(ZZ7VIC#N`-~d*6!jX5-QF^(y$vf&xe{isKA&GZof!QA@__% zAo_p#@&`)_zhFThCe=KP{I9UHKRVw(FtqQ^^`m$n$4QdHa0Z1b5YI~z$Y(5#%0Yu0 zxLt29Ao;d1us(vPVC^7I06)Vvc?JavzR%UKG;pm(xAljl0G1-jCaq6lV4aPVAWjlA z-ziWEf)%a|tl+LJ3;$I*Z%zSHm1G-N+q8coAZ8N>QdY3Gn`~S!2Bk2N-O^ygjNW{+ zSCaT;w*jR$d29j7)0H#ATL>|@2oTiyj|*D{=qe}VUo1^OXLm}+FAmjxzmx-ZawA%#5zixPa5KHl&H2==64Vd*6Fon^TVR+8dBW@#|5i+Kqy%5yc6BkFl zJxH!G%+^M! z7Hg`4*1<2dc*};+WK?%V*cL(l#djDD3`?Kc% z!3tc>R;(X-3bQ{YFNk@+X8l#>Om82K;_VGfSK)Zq+Lde1gZwOC7L2z(q8xum?9?}U z_P{U7JWaf|p6|K929)G*^cV)0bi%~9s*9(+3;Q4jm*=4Nrz1znhMb>lt-mXYI6eZ` z@lO#2=Q?xyeaz%kp8eMsNB<8g0YEiQR8&Pje1G)6SkILWTXCbGVDi5_#RoEecKDC$ zT^K>46zIKC2u@?niiQkKW9xs%2##SCjiEFNe`*eZZ}v#c<~6yZ;+NSZx)rV~+R$5S z+zK_tmetFA?(GefZkvvPN&u{`AhcGkYw^2++zM3bX66k-2lA~=h5^fgwdh4bobaz= zoG{SYBa6*YAO{;`a11_1DG*5>Z$95-41gFE%>_`%Q8DoF#_~-zmwyrWa zr(oo{g6ev$asH>~0G1yxHNKMk+u@P$nbey-*DIgLIC(#h2tm&7UE{5f7^lp-`KoiP z4STH!ZC~{q@2py|4e%{w=pTdRSunF#!M>E?ej;ar&zf5OhtZ2Q**w?r%U zTOCWYDdDJHuVaqNx-0p!U-Dwtci&JeGQsSiP!jPY4a<}saZ<$1a;2q&0vD_m?S9;m~?Jj+I@a@XZV z&&uuL>!sGZ>~Vj`rNQ!CYpL+w-0TcD)cX(^=z{f&T3ANf<8=)YJB{j{pfi7my#z#g4GBl=#mXFt;(CS~Q@7dCdT3)2Hc;h+!6kHLd$8IpR{6xt-esOK zBi0^eMk^QF_umXN7E)-nN-v9UR$R#F?voK0VAKu@4j1{E*Cd)Wk3GT`Px=T?8ucJY z*c>B7f1T#}hY9D8LrV#wc9@1L@O8dbH=Iem(=&e!Zo432-Cr$5|5(gI{}d1GJ&wKN zT7|MdmZ*8hyLXBzN@MGj8N}xu4n;OkKB_|$c*SESQ?-2cIl*9`ezx9=`w=I!tbNRg zo?2=iE|L_9*OZS4Rn$ZgJJux>Pp8RRtK!DJs+o3MQpp}Bub`{WLYu)!#|*w8wW%JC zcf^11YP2NQ7w<(fcawZk6!C&~sDSSM=5W0Bdj)=as=zjja$oOC*6rT+%y`@Tc~2S= zJ(N?{gdkNA5xwl?6=LCi5IPq zW9b3Ta=VO8_J(FJa%Mar6TQsg`a3@_q!ys&h191Ue$7{TJmo~bNAKbJa=4Rgpxl2)GAX`u=FWTU;!0%OW7-iUhlcud7pcpG@QlM$7G7OrgdB4` zOv&c4Ckk8D-zXG4*#}Go)$8D3@ucFeU|w1~8BX!JmtgK*btwmlcf z`#@sQq62Q7IjA_9Ulpw>vu){a<|!-iA)5*b*)9@A0fqu=;{;r7jDm`Fz0|cPjfqW8 zwat%3#lLw;Em7xU3*{r z44Sf4iIK)dA2UsJ8&4kUCRsQw4v;oG__u9f`ms>5AX~;c90%E`NE{N~=Qdh6_Z+I| zIaDrI=)>{Bf2}9^)-3y2Issd@U;+zmN~QOYUY(mz=?#BC^qWwr_^G`9cD2x-T<&LA z3;pip{;IDBfyN%o+UTymw@E1syDY=-DW#rz&xG&l@kD~Vf7=I+(e=fGJ}2rV<@s`s zLfXpHl!t=xChB_+)|~-+H}s`KjuopkJrd_-;*XU>9NmW^Ane&q>9sDAd+fC@eP?hu zLFUFCM9zPWVUfmXg@~;Ttris0DO@7_*h)>=YNPK=%AntH;{Um?*RTS2V=K0y zo^}j9`2$6Ic~~p=di{Y7xwluwvz`ZBMV;&i8~1nI%=nuN+j7lH!DGCgL({v1&49|09vDJVfQ7(&x9ieV^%QY68^=*m8Ej6v7GX^etDH8#f3tt-cX zB@ZC0BmkcyH*EljcqhQ9BS8ShG9ZKfbFD(Q_1+k;%$MU}|851+6_UvfPU%fH3J55= zCD(s!RJ9<$GqGRgimqHU*_iQ4i_sX!Ama3E<|(>${8sQ>p*4d+qi==H7z2zshXHWo z1enKQo3ZgqWQom2CEpB|i!E9dCjYSwSOP@F{z`MtB8(|Xg61ZHd^$mcr}-fLtqr)z zJd2;(fPajt(64anH(<&EFa>?nfBu)K3jKfSP2lINh5qDnKg+&>exVC>$H7Cr2CSzU z7g@}BCnu_*ORxLX@e%LRsmDAQe*{ZAD(U`e_N+U0Py;f-TDj@fSY33BSXpi%(F~k?mlq4jPjO;WH z7`*5YL1*N^h{dbt@HTh-3wBaYKCja6I3dQ@ zS>P=rMcs2GUbV!lJVC=C<)z_NahZQLZI<@;69N11l6yxPLp#mC)~uV7&)5ErWVgFV zI|Y7=1A(yOcQlbKX0Q(ANBrGDW7vq&VKD^@w%wLs5nUM1nU9?$W1eNd#Y&`jaZvcFASM&1k*1f_byUgifq}ChHAI>+wFf)(QuL5 zRw!N;As_IfKNl}(ch2zR(N%2q)D})8we@wmV>cJ&q@wFRJgV$R|A?k)EUoB_FNx{N zdXJXwf$GaD86f%Eyg5K3~F$`=$%F z+f&%kWyS6U$uWE8;*s3(FV25@UGVjOL_J5C!An(pC3E@={VdM=@A(NhE1=oKU--4oJtrqRR zyt`2=uJ`Y_MSMkgNB6xbXk8auN9j#IjOze9ccHy;_nku*6zD(pxbplZaE+@9E4!(#TMSea#K~ zc=HHir`;5*Ee|%397%uJB97`#gY&8&>I0$Q(*5n!$Rm47gLyWETg5?YWTbqx%X>?E zi`PffnQ58uuFZNf9~8kgtuJ$t#ziKV(WgoJA~p4tu**L!rl zyGd;)3E4_b8$jxF-wURb_S}ebE zqPH6)xFO;T%DLC_%3Jm<`n&Sc#e;+|D@y47?(E6bfs8|8_adN7ZV0)XGJitkj@HHo z&kpUbzXum4h$z2|4mi8pX^rTdntgjWbu%DH&9KuBi>8?*pwoD?ZArF#8rF2(SCnL* zhQhJ-#IU;-g2OWj`|le8*Xqglu-If{ zqrL2WE|-1depeA*%@=kw_q+Wg8jdzm?T^@?n!VQAcQ%5oXa7DX9eYe}ZkZWMS=F3D zH%$A23+BL87^4inb@=`48^<~Qzv<)t;e+wb@(qPi(C2?!Km9NM(Y4@*y0WLAsoS3( z{EeyohXa2gX230!Kq&^rQ5r+hm5UKHjS&u1}%2WIqZ>LqU5u!*@;>-`S^cmUCZwgMfQo79Fgv zysbs6A1j8q7P>w-8y^q;!gJ^#u)#-fP(XCoT~D51>IAKSeAx}I{@Olfrzr)Oe!A;p z;(YNO@@k#?!3+=hl&z!|`hCSIf8#@St#U6;mcl(99mU$x#&g`Y< zFm&JNL*j>N_83ldBC~NdDh#VoQ9eH$#!IclNDn?-Ha+B!1a(SiVxQP0)}ARVjnjw0 z9@24~I3iVzNP@e43J10n^1Yf&jtgs{HeG-EID1`A7-TlH7~+~v@8wK6+ydSgN<<1! zT;Z1NPG~ljA-;cyJ8alCN2LhwdSifUQK2Z*P*`rQ?bG$cx%wsoW_g{Z`7k=q( zR1n8qCh5U?y9LLnmm%h5`g+?pq<7c124jm>cl)tBZT2sHM^#t((VTkt4C#sZF3i1W zi$~%Y?pME@-BwXOKv&U=Xm9s^7|f&9*sK$H`i#cYy+_gKqMXlzMp=nxH8xMC_w^^h zvnhM7OPEM5_}FWZH5AvNcLIOcclC;H1#b^OUq*+Y(+4*_kxR_Rg<-cgn)A9k-cIh* zch&GwwcOj_xv_w3yj(h$x~O=5a-yktdRdec$x?D~)ODuaUY_1=7+u1>9AoU}X#3$9 zxM^UqB!!KE3JIpv!~4vR5gcK%kyT$0CxyxOcINLAt@o#Q4ko@maRk?yY9?(-q))%0?T zj-dyVn*0T-yHKo%^LsX~K}sxi>`T>XFpVr~afqd7d9HAo_u8FzKOd9WG2^44Q{v?o zmx*T7PbfKA)eZH;=PZA}g)8E!V(rSYxNp4D5p$JUf2s&Vq2*c};f;+K$31?1%@23z z#K+l0Cy2cWa-#6%XrC>GWtNM-*J^V|-Se2dY2$O3M%whGq(h3oAHk5mFoM>lH0%m? z-}TrN>WHR4N%pSN@RwMh6>9xeHQ_do^i!G%G2ME4wMWEAwRRYb|g^c`A`BEKUz4rX?C@}in|S=>!4t7Lq=byOT(&^|c0 zyIb(!?iM5jcXyZI?lQOqf&>`c-Q6_=cTI4I5Zo=m@`m|-XTROE`v;sl-A_MN_o=FT z@ANc{m=027#OiQyx&N#@n#pHV?~sBPPMhmLjE5lJGcQx3TkUqVHit|bqSKQjQqIM$ zd@0iOju#xZ$I4(~N(*7qJ0Z4@(wOhjfguo!6UfmV@SpOam;Io&0iR5-(RfUYVldx)~LSz@5-gi zbxHK>2a#tC8mL6%hsbY!=YfAE6^eQX3_rw?X2=EiR;^?^vWpDotZ$kD?GS*I!_UKT zDpxu}oWrHxao&maq@6&?L%cN<+-vmJT;APvH}?s9o*Tw1Cngg}x$!c; z#F{<7@9?S!tvs7rUpNSx(+Ocjd*^(uGm{o~7QTro*sC(LQT$NbWHc~lvg4i5^8sm9 zH(WQ~_bcW6S|a%RUTg!-b(6SJ#~o+5n>oQul@oK@dYlvXQD!sSo77x9Hg*+Q=;PP~ zI=u#XpYLz|?aCm`dbOTQZOY$P8J z%f%ZZ^ijmCE4l60O~zgJwtnF_{cZI;?xk<;lFyG7)b|>R`%^}%@lOKc)@i|tQwKKn z`Wt@C0<5QK_uu(yx+|9bf#D2>j<95#VZQYiy$l2XNw)A6otYcOI-Z>yr;Gr4b9OzL zdHN^W=U)%_1+51^;pVllitp8Gp9Zs{|42IqSs4~qyZb$}YCkY&$OQptAGeO^d%ksI zTK1YNl6sCrE+hCOa@7}}+Ck(@`a=8}qWjQMC{^&Sy_H(VP-mtx`+t}1Je<~g=m}39rb}#ql7EWE=q6Dq&zT`u! z_{$8S1W|d|E>%9FweNvi9z$ zh=#nJ#^LyxUk}OHS@H6h!jUXt+?#BXxbxhhH3sM~PcG8fB!=_51tVR{WHeMO9&g>% z90p}8G9m__T^H!gb>>6a11X~t@Ra21f(55PBtH+40OUT^ht?k66V}}gvd8=jO8jmr z_N`@2-+$dB!!N4nLc1w@Qi%&bU@hBd4dnmRFa-``>XC)jVPS=(iDv3%18*#yEBD!S zELdY|IV~6%?PXZ_JO!M207^ZD!`X~oWNzlA9>hxQ@dL}cj`*s=;yFSNFD8JDLCr$^VX zy3kcK1lAHP{pf#qY|1n_M1T0`L8UUT_3h%~w{SE%QcX^`R4+l>D%$X}SIDOu_S9LO zQN#M})NbG!b@9of@}IaJZL2Zj>|wR<0!8nt&Z!7fP7sRHWW>-&D4f%7>4|fKd3ipc z@f#+RNnPZ-N#S>qY%Eebgg514wF{s%Ge~>vm!zWQT}^OQ+vn`LQ{2c;eUP%v)?XT6 zmy7IWyZ5%A9Dl+eWD#$}{8>c$H@|ehhOPFYU>DWnCrZ%lI829>G7RjIp(ChN-J8}ExM+^^@{D{kJ zDCqFXeoPto9n~2g&WnWb#8tpK$_^SnP=)7e>s06Jz2qrO+m7B3w-o*kxrQrcH@wL$ zN|H9Cy?yPlpvD>u0R*0HvV+|J=^)QZs zo7g@hDPO-`Ech(6k^HXUJE=ZjJj#VTib-gb)a$BblAFd~`f>P~Y0L2x6D4Aw%`w)g zkkm(@cR(XTv?|3@TLx3v;{HY>w^%|N+ippJ!tLnCcWXpJW<_qtN4@M0$;pfQq-xA87n1R@QEfMEMfgS@XwT=*?06#@T;D z0Tr-Lh3-SyXH@?K=y!(WfX%6OfG--$}iW__yGsG zeO-EMR)V)FZDPh|le~$7G`p^T5B=0ln29{^^wlwt@(?gyHc)+Zdx@PkOHT(-S^gr{79v%`F<3AJVqDlP32LRA}0|2C; zT2FX9ki9)AQZqXnJRJWUSjYz659-bAvWMS7cryz;;h|vO%yK_?fA}|(DGVMN_RSQE zhS!F8Gt*+=%fSrN@$dv-(;^YR1m<55jh8>~onQfgI2Hha7=+OckKfFb1|I|_>db*p z0ejLef-lBL_%H4Z4L#uxpp3up6rdRA*Lb_BGB@G zr|Z7MZ}Pnb1NsZ^^At5rXuR1zA>H@Ba%V#ttx{57)hG>N)fHW z$uF)&oCcHoHY3J^O^i-NeDpUKrg6krFt%qNG3~7wK?X~R=l`f*Ndb`m{Q~@x9%ST8 z3JvmEMg)tMyo|{E#tFK+Bq0X%c*4VjK5QZ)fL=EK2Sd1t=!o&pS@G8kM2LUGQ(3GG zi-H0GmJt4{uGha2#s^jVk-{~1T_Pre6Q^;D=!N_*hOywpFbQY?;0YO|oB@N?ddjBnU7M79A2ZKjNCqpI#=>(G^HqY)Op@U0m{{*Ru`t71rY~(*kZ)P|d@&LHt zmAR2gJO3RGcfCYzcD+Oi|L_KSVM84ndoxF$QGZJQ^H*HRzU@x{04(bM^Vd8gftEn< z1~M{2(`|TT>c&QI?#9MoW_~+!E`|}@|7Lo^W2(@w#Qsm}oPRw^q|FRP5*;YdQsTty;yYM-khDrN_Bl>aFoDK0|QXyvCqLS zpcJu(-`cNvN)5Xl3;^n4(}C-jviroq})2FaU!D+Dmh5Zxk< zn?ndroKloGvq}`_jP&0Kc*xxKXI|GlRvb|F3C?@aee$cXRb!kLFjePg9CNVT@9lBA zz;1s#;~0VgvR*h}-oLRfW#f#1+pwe&2NN7YR|n1)+&6&yEKU{pvX@01Nw7RJt2n}7 zY|IW0G`N0}PH^JESo&)mF64g=V>RFzDvAOCn7ppgG@wm)?AJLoofNY<4FXpdJhTH; z+*$CBp+_3m4m>Tfx#32FhtcJY+W^M8N8na~7lQZkxH@QWQHJE;Mh*RQXQ!3)-3bW* z;GhNoC_(W#xCG6-R(KIe|CAHAF{V;s003^1|4S8d^B-3{3x+c~>hRhCzQ$j}`E~}r9q$`Bmh2(CYNUT_u!qL9oI4@_utp95#pd7=fqJrE zmkHn;UI5}hl01O%S=ZbAxL<4vygaTXY}|6oOCx_(0hx1eI^hN~i?f;NptHCE$8n*Vp?3aCu&H z>qzsTVpz=^6a;GEjPNoMSUCMl@V3`!>%yx{w9uejJ$x+CD+mSzUrvhNJeNhV0Ul&a z4uLIrwx=y1n0@p4Qb5oFKHptLPzGKe?dk~5z`bVNM4$x*bT$(#gUyUqf)21WdR+wA zZ+ZX@KxT8VGlLNh2%>@zuQ{okU>}^(fB}MJ@H#*^Pk;_)VO}600GqNu2}rQtLb5$1 zxP<)IO$D$;LaVQn<@)RRsX>9=1UR6thXin-&g$1iG3A6{|E;JP7^6uNUtz3pplLKB zyyk*)f+Dc^3ReVOU=Qdw1j}G`PoD|4z=@iNC5#3S#2kq*4y^1w4k0QyawKBHXUw<2 zP*@1Z!DMT!gu`Hla$dr(>Tdv{uY@@Nll3^lXK*2EB@aEhi)gA0@6N><5$WRug)H0mco4-ryvycN6M^UA#VE)bq9w)f$hjoxaW&&D?J} zpdKNF1Pjmbi*O%o;_ee#z2S)*{O9mqx6nXl5IQsw9>}rzb^BCxNJx(QudvcTnixF3 zZa-nk-srCG2~ois!9o+|g58%R5aEO68bl*10uR6mgXrwd$$PUA?(_8 zO9TOCi6JA>CVf-yk1!F4`JdN~Un!hOulQZke>MMKIYWc~wvwVXH`){7fIr7gcn}eQ zl{gF{a(I&pWDrVJ`bLKoMkE1_PyGv#2iTm7BPs?<>61hh_Etcq>TCn-uOB#@4ck>zT zrGv8|E_>T8!;D*V@4xN=)=@wh7R1CLk)GH1cYTP@!SqW3#8_bSJcJks?rhfxVrj71 z`-PYq4EPgEi~&CSl0du(PU}u8u{0RKmPNb^1`y^E@4S6lC@Q0V!h2O9`t^qdWL!y% z+e}?S%m5z0S|#y!@PbbGgSh662sAN7`~duKlK)o7hlKEY;kVZxir1Ztqm?6zp}V=g znS(QntFxPntBb3HvpI{Qp_RRrtDzyYqnCc1q75V)djIJ?cI;JNjlRZlu9OcwE}{^2 zh=I7+l(v95&-5_us>8b30V!)ZRY&UW-k_Gvx^xRk4W+WZ*M1V_L}VXCFM}Zv-bzYF zNNqwoo)eMUrjl)iH2*VQA5F_&i%JsV>rg5u^X;LV^ zcOQF~PwvX^@-DDhRM?~^FN-T8h&ebilWZ{&I@gFbU% z6=b;L2qaw0CxAWr)-X$Lo6X(;@Tl}7Lb=45%KWC3>>0UPhWwe(>PhLY$m4PL^}YWG zjsd=Pw~0**qQ^-ihF|5WpW0ySmycfbRflWj{S0{)e4+mLy%Lf%%PZs8&MABk0KEJE zkQ%;09iXnmGB2+GZ8h0U_)=elsNZSny?U;fS7@ z``aE{sAfpVcjG29U&;a#kCW9WvpS7BDukJWRO}@sV@}|C#wgsNsY9*=M9L6bA!3S>hu84X zum3#cQvEi}aDm~)YJQcx>(Dri)a8vdKt2CeafT?rYVI-9_#rLrIBy>1>J(9dOB>S; z`~F)+9Bh>ejnsXlaGijs%Q{h=+3mM0La`?*b$=V6%XNvGqNBo>pejUn4-a89I1j;Me5g>}CpS?olL;r_&o<&!l$a4)w$7C>CB`&AtV)C@4DkDmfUGsde~hK>OVRn81-cH-xdnBP#;K@( zF8V=v7%jIS%7XM7Vej;@sDIVnrCDMh;$h+=Hrq82);0zCmt!}GnY+U!8vj+bR7+Ff zVp!n5OKx+!!nyBJ9!s`}#a~C^2%$%~MB7YTUSu|n<62BMkL5Rl^6~y8PU!qQ$%qG} z3p6cj#;r+!$xzgN-(zkI5R2m3|0z%pBUvk3pMWq#anvk5nwwIZ#A{v_GKL()g`l(% zM1=M|1K=)UQKw~89E9BK{QHjynld`F$LB>qrP_XeX^u-5s@|fQuiBv8i66@gNI6|P z#;Td##H+IvNMHL7-A8*l9#Nf>p?I`wqk&>GaWtRGUHhq_X+d&deijXmjwb99JD2`~ z2xU5F!f9QbkzBCxXgxZIO_e&zh-!AHFRF2MSha^@o224I zG;E9|2Nf)>iXA*ZAK`6^Hssrqmf&^j3Q;s291YVHIa}mzpkh`BVS4wMoM{vFCl-NI zEik`16Sz;aHjclus9<>@81UP0O_F12s^I3FQHEYCNn!?llL?e1gWzu1F)|cEiPMytvAN!9YwRU!%#)LG3f*ByKzssX zTOif)_lo5rfdYq~p5En}wR~yAg%Nq6B1B(V*qX*?E1UiWNVyoUF;k`}{aLRq3xS%B zjHdf32Gxvo){vBTyAk9)5&Wn7=g)XI=8nQL_ywL<9h0mHOr!DV5HU~)4a9`R>YT2I)AZo|X!H4lFQ!z2+i zzsM!CuD4R$kI9s}pR!c*Lu#kT2#Pz3@!Y|DI`m%Ia=<~jntCFPtU1hY%2q(pV^PAH zENT9x!aIa<)*u@%Hq$Y@C-xBO%4o>mw)css*U}DskRoe(#Qvk9nwbKM-EGb~bW4be z(e)|!aLt9}*T*N{sN!eXdTyGpz<_zt23Jt4hrps6GIf}zS+_)++W@yUA!;5x!)`-g`E|ecr){s3{;1BP9{yimMGYn5z$M1EbaNxY zUlxC;D-4c};fwy#X&}=^kopWnKHJ5Hcr>R})^7xuF&slcFS& zV>LTFXUj3&>tW&{;yFZ+YVTtkh14QqHo>t?hZD<7+@%r=*vMVc~Wd${WiW~EX|Ytq2q>xWwz zktYcCC2YfP81x*Tsg&^x3Tp@09^$IB_KQ7A~ zrD)g`F&MBl@Dz-=hBf9buu|}!%DXx23B-Ii3%8*)z*bDyHkWzb1GKJU%#Fy}-Gm@8 zIEIMoIvqsjCy9Vc_?ioeWlqjRr;yBsImL(ym^Qtf@yYiK4|i=;K)pRwjv4$*f1qfv zK+RlVqY3NZo9Op=u-8r`ykVjkq&Y`)+|FCja*PB8Z5LcTLLSo9(y5SYNiBQ!jM}B%pT?cv;lL{4IqKd4j;C@KLaeTVeam%O0d%ZVg~py9J@wKN#S% zKCZgIY|JKX)KY3CwK-sXxnm_+XrAU94-agBq%dGU`ZbSIYj_dyxG7T5xexjlH3%QSF4;ZxX@5vjR|^A!9A!~u z)i)QG&c!)QH%h0)L6^~khSu`YGWmIEW{oPJ`v_(^TcA4_&sUv@`!15;)Bq-XCQL4E z!WZH51tI0llFKHGP8zKh>?&?YG@0T`^T)e=+>R_X;Z-0-zHBUy=bE%DY`H}LlOoXr zZiCMcqML3)AdHh`cYU@;n`0mTku!+~uZ7OK4DBm;s{J;E1=#Sfy)(Ch{xv3|K6`526)H^ zFQLC~);|5Wb;kn!-?qDU#-&ynw@G5yTzk`_qfAyQJw*wZP)g46>DDbYN&1@G; zy~S-c8aTT(v>Eqc4Aa)lQ&X#1V|Q;m`B;*Hgb}}Kg5L#BUXAbWO8NVE_&K?Gxaa#* zO;^yeUkYje5Y$OEWW-zdH}yCBa&y9}xC>n+#MV1<0m>0CgN&8)#`ggqs9mYY zDCQq>>n0vOdi*+JE(A_CIi#}5+eJBnC0bA9@e=g+%ZIBoX5*>9dTvNMRsWb}T}u|c zT)3+uw)uLMI(7^02RznpWRHL>mOSkTj+97=2OBo7I<)q!7!$4F4(~V~Ms96&r{NqI zP*?-yJ7L46O!c(*2G_3sQYapLBNj1)gK7>6w#eF*j?W9r(kh0ZT>FvJO0$3mTv~ko zMYn%GKj^F_!AZhFp+CAWIO2-A=4j}NL`{G1sj!pXsXgsOkOtytpDV*eetInyni z%sj-AxfA?yDyykiGO0QOlOCLp3FNXMI3u6%)-+>Wv`q!vXhyA0j=H_CIOD|Gvm%OL z7}S|PAI@?{1OhEg0_8{t{~DMBXm)~Geq+?uJ+#1yDahtZq5hg*jw;f!gN%a2UZ!KC zr6)YXC0qzwRH0j+sf#`>ond$I%3q@_CqX&n$#;^j(ILSk|KugxZ?t*l>x=WrLiaH)_+DB-dI|01W32eE)S~)uUR@|H0fd3CVuu7xapq3$xnije zGuK290UAM@y*#9Y@B6rPSv_w|NCw>@?HyP;leiuwtE%j10Bo-M+U(_Z7aG)!t-&bo z^X)$7_UwJUZun%tMCH1WdkQRdh8B8%^dX#c z=OZJuaDZ>tE~lOL%9nR2!9F>zc^yw}BzoV0kkCna-7E)IU3`84AsA5Sy7ai85b?Y1 zQhYuF^*I#Qaj3D5Am)nnM0>-D_*6=4q?fM#!qz>v6ma>>@!vWL$Wa6FlA5^{B(z5g ztg5N$X?fn2f{u(e^n|U5=p-KTgK1uJ~TpKt*qo|k62Eopm*U=dfZmQX#Uw9QS-Y2QzOzChA zREsuQN6RLR@_j=%S&L*2xLKakq+F^vnK5Aa*<`or+L-6{fVE@eHeO9$YWb&q+U?>Q z6Y&@a_S5Q3hnX+jXU$3;rGx=h^5Uk7_R$d{Ww$06?Io#;S^h(yeCm2#72KlRI;PZu zQ-d6@E*I6JesZPh?{_U9_=?WLCQPM3iMf0?Y!+T2Bt@uvYAxR*u80;`^_MK7rqUa@ z8M`34$o-eO?01~z8Opj&4LY?sP~JUmBcZq2QQrQ<%W?MBoQNe^tg_F``c9i(3FGEn zYE0T#&F{(>zQR2L{N?cLMAeeC@uu|XC(D-L_-D1m!m)NZJ&Uu!0&98k!3Pmb)So`0EaTytL zk+Y&2PnHFerPD6Xl;JxBK~}CGn6>J941fyiq}pe^EW5o)z@R`FM1UvI!{1SS%`V4! zsP;?jvNDP-m7q@9o^0Zn?5s$my#MK3uG_;&*4MRtDU*ew+|PoR9oQl``sCFK6u=zM zG1!cT$GPPc>!snc0F04A)Z}cs1p3}RwvEv*!O-yJvFBl9PNC;t^~DFMb3Bxatq{n{ zzNRjTO2YOu0V%mUj%5h%{-h5CCeM&7pNfU90tOoBvd(jFFR^TtLMX%W!-2w8NP!if zV8ucvX{8UKq`wrnqGo2?KdDl}Y>mPqZ~l4OEv$v*qmJc(Yk;1N(Ph~|X4-1VDHI%r zpwSWh4kh@LekhthV1!m=_P}=nQ^FCkwKb%PjojXL^4VW!CJ?avbAk9O9Kt0gMx@C2SiUPmKU{WNTa^0~ar z`~<-k;=qsF{w$8C$!*Xb&&YZx6hYm3bQOpkC0^3C1%!^B6tQxQJrXBY3VgEgQ_z>D zfdR2>a7p5_(>(|lzVAimu(4uBFZQ7+hMj_iQ_HtSy~ArovR)tf8JQ=@Zp`@wOHEY$ zb@)`KCi_u{<{{afyQ{NPjeWixIrDN@pG3mQrVf}hOe1Jm(Z9{gK`VRL1l{+HzvW;* z2IRVG^xp<;9bYFP`x6n>6MQ_bnP2Peu@&JuPm7+G9v(Y0y5QtXlY1ZjEpkDg42RnZ zViL5`4D;z(?YO6Ot-rUtyZhnB0ksG16J6C7wcqH7Ib%X-7RUWS>UPb;kySsh{Cq`z zA0^OoK|NQ?GTZ;Qa=6EW2wjB=v8EWl>78W81l&wsY@~0Iy-bS152qg8l+hU|Vimq( zz7z+C!H`zL9Z07xKBXMHVPDmAb``#r2s2JiH8go^QhUYwFGb^Y5D&xKw?~0u{+0=K z1fJYdhH;_M}xZaRAu z{C0@d5EXE#K_kjEATvmHBBT(2nx0B(D{>C^ZM3bBQrcINIJ4P6dDm_%HB#2LS*MGI z;(=qs$^y*hxLPyFajCmDxe-o_VDpOIg0zI#OSCgPi_4XCz!qx4xo5$)#ts8%T$lh= zgTB#pSEZ`ORG`y`4fSvs?nhoRq;N5r5yo_Fl+Hli_wOA(?qbaNK_=9g z4pEpWi2x1Q-Z=BYHZ3+L3y+l^kU0m6dbhR`w{8T?lzy_-Thcb89o^y*xUm>-$nQ6?@pm*nd%gAAk!# zBVpUe7{}Ak;jIa{xxt$!!{w{%!lc`(z0{zEWns1}Y4A=$=Z$!ht zE>yQ(=?Oj|Lt%wYm-Gp>hd$!~+)rewHKq3rOLhei2GZ~5OV7@^9W z4siq?l*li3^hzsgw_7{5?}2!wJ;Ir*dKoX6@(#@T2WXbFFFotCUl_8#@9%Ji9{sfn zeSHr|Cd;mwT9Divy|Ic!n>KYHWuqIEqaWEGOQsHD9x3TwU3r66wPXt zMGWIwUCA=}01|rN@8k<~qleoo)eOds%QyNmeA3FuX=n}KLYI5xBnzvj@#JKrtuCL6 z220uO^n#bvBV)Jj06MN1c^k+E3f1SXmr)^7Yw81JflaX??N; z8@-qKq!X)o#N{3qrB=fYonI6ZX${P{cOy%t8BjF4%bYvZsj3jb_=3{3q}MmFC}Y|n z0d&uttLW>UW1ilzi$QJKycVKFi`zZFs!KVg)(nr_zSXVxFx(&OIXBK#RJcTh=#Edf zQc=VuejIrNFnz9{yY1%c$Z5{)IwLkc2^m+Z@*+O`&^BAPs^oc`ewkZqP&1hqbkmC= zFFeS3^co#O7Wm)^)c=P3-m}W>caWfr%Lgi1L|E&Gj4DgaKOMaS{dS9+2z(VdGv-uB zPNBt5^m~cJj>$JMzbj~Y8G5# z@vA}kLK1F>zg-(CfcxbBW*Nkg|r>FSu2eW{V&+lED zq5IH((CrY;Ff@uv-6S1SK|F7ioLjo^Z95T@RR}gaFDYMZXfHEJ2czD{0n>A}CFJ8f z;ximOVKst{)F1flIkH6ceZ)G>ezj?p8@tumDe=>LWznQm9}&Mlnk6e|IC++@Q@T!X z@2bh7JKzTz=(EpuzCIaVO9`pZ@J#x@@0RdLK&)Q9KKX|I`VH6r-xq}rj^_3*F1D}t zPWsj59k#jAI}bIHGazm96ZCR~I7LX1-HZKZo%?tSbyE?`%ZGCrH<6pU?IkN4K1Bi7L2>6`)j%k z)_VAk|F&uK&V7z5N>PFTD!yba5Ea~Nv{5WuhY{ECA=dcoci*KJ5GBznR(6^M(B3kT zFBHAcZw+a-PixaqNRvvgHv!$tI5aJTOnh(coeYxsO)}4I`mPVU?2!DTQO+3<|I^~N ztxKn#+2!uTM5%|sW6RirtcpVJxY!^OartyM+B)?-w? zt`ee^R+C%}Q(4ImME0ffyC%()(CY2)9R|Nf_bue;C$!5u^Na|m+a^Z;f^>%JOQn^*>1ga&)?MVeBlyAJy z`iB;2LRR6=i*QOYY8^tqxBa-L2s!*0G~gw}8RczT*`dns*{<5@aWmk9ZcnQuzPmN{ zJ9aI4)#*>6aaniv@2+_!G6rkR14zcpa9>GandFOdKE<*&Q)BI%9_9vVjcNA7+=s0p z>VD?%RBufel_do`av;{C=vSCSuBB;_ZQ{_(9cp4vCHZ|J2f6Q`YBshuyV5pN>siC= zG?G0J3Upb3?S_@0FTc!N6Pp~M6SN7L5v?ZhHwJh_2OGXV|3AJNx9ZWG?0tA{>wmXjlT=6Tw^@-p4jCeDrl6(%LO|4k3~p*za=sWu ziN)FRPgltiU}i1=4M*H<8l~Ko`{2?Eoq0cwG-aY8v+I z)xdqAxHCdajt(A4#V7an6e~d%#hGFaGA|MQLI#m z2hwrTcP7EZogShj3PE`-bp7&Dll=wdJ*R1zeAX0B_J4>rD{Cg4$zDeB#kKRyVf z769Jtn%OynbU?JCg>WT8IsT@Uyn*0QMXVg8(5YUKVTqF~t@0`Bufk|f zhcozllz_PXZikd~omX+-IwfTggIclpou5nK3+4f5UMN;l!hBpm!`h=(oE656PfJ8^ZjD81Oel|Z4m z-~Oy)#tb?Yj0gGN5dV8A@skxkcR>RH1jYZ?QUd&cJ#^w>^?JzTzd~3gwsBaN`0qaa zCctA$6yu1q^CtRoQ!TYAbSOa~ahotubZTydLMEQy*<9Q1Vcp?I)H%BVvw$}T5nXj9 zb=B**!~NK^e%$#;Tsvx-L1C*{VQdZilgfv!K5m6&m82D7KghscKLZbfV^i!FGp!H`&=Ylj=mdIzF-#xXB97{oz1K7|k~zAHX( z9jjqJExwe!@#Fxe=zSHs5zJDkx}UZE&9XOtIe759?!lQQS}$f`&BRrAYchl!cHqE- zo_<(caIC|~a~nlC+DOzg?j1jJ1!;f|l#+X3AGYQW9L)D!c(&jTnBDU~=(Zx9nG@qv z=`3M9!P7s|_4g33&~@8<%Y5G#(@vyiHW+4(p|dXfjEOyHgw( zT{Bg_>91a6kf8JQFN0P^8<}#JQ z*D3d`0wS&Du}*1eh}7+Dpf=eNQe4e=Gq-sn^Uf!+B=v= z__m;YXNggQk*3Jni3;S-A2rld9O*FHDufu&e`|7~luSxL)+|k~XFpTo-Z@EKnj&;V)ZW^(rDQF2n>gmmpeQf33_T@H7L>N0b6oBIOfXqKc*mV#@ zeXlyin$v~&0d=6Kciz+S_>0(6@7E8&G}er4*yqnuoQQ0&Ik+D(KV|Gl_EFW`U43Xt z_f3&I4ZI7Ca$OMdY`ri8gfMuX5}lpwZ`gSeROECDnD9Mo^!}~=^gSmpO8!%aro=Jp zFry&)K6V2o(`~+XE~6x8eg_Th5A4!L_Su4DTX@uGe2F!Wsv;Lw$6q8Lu;zWefkbz& zugqz$w4fd+`L4co5y?@y2rYZ$y2d*f%**S77?P=;{7pP+KiV&s!Bg}gO^zljP%OvK z^%{Lefkg;WMLMy+fQ8#F>U#0(Pupz`+k7X|b`A4+JNPm_()IwOK|5LrD0^dF_Kyq| zy=DR%o3~DcyWvc*Np)BN_}!gH58&y;C>4aqJ<^D3qVKH&fY#6{LcuapL!kzB);tI@ zg;j(~_fQO@R`#s*GSOUm5@AC6vTZ-)-bIvDfB(39NHO{32LsJ`O`lU)+}%K`89oGV zfrN1aafV8mL$otZ5cv#8w}dA#ZVFnV>^}V{4|!Lp%zIynd0>I-*FX6A zhcki1q8CXj7Xg$@{JHyxVoM?%@+^K|RiS5Ssed{I+htaueW~M!a5mHq7953YpjALc z)6s;e6fcMhROd8j`&z7raS8AVy zRmDIPlK1;I!zmZSfNKn8gZEo9jjSZ1LPmpn8G4yNmD%P1`PUQyJA57RL{bY*o7`Ze zDxPAyNu~~xpi@dH4Tx#^BeH!g1V_}hcbeByeT>iss6 zE%NiwT3k`jr@mBwT(6eg;dBa4Wx56uTslG9tri4eaC|v9`de!ds-e&1C8`b)`#L8v3b&stA>j&k%}2QH-b0udX>Cg?j3bu}b~17ukUlJZz_ zEKZ1txO{6Q zqO>_UjC12b+02jp3`-C9NY_gpp&S{p{mF$#%D;AyWfKBh*R?nEb{G~&eS*uY^}B4R z5U~?)Ka73LT*yo4;9AzybTTla`GIfOlw!*$A&kaKwIvdV?14}fB<}>M{K!i2obJ!KQp)FQ(^0-b3HpKC{`?v{`9n+BScbU=Dj@K|Ic2XhE34C(~`=O&VK z7P?HPBLb>&FPu5n(*hrgrguVUk^!dwyQ-|ASUT5M| zvqhO-M(Ck)9}wvV%Q*fos@^d=vS@4Dj%}+dwr$(C)3NQOI#$OWcWifTc5E9Rc5HR{ z<($*s81M6Mja7SB?SD1*J=b+jT9-%k2D&~EdEe-HmWVERpPfOnZPFN~5m?Wr_I55q zT8goNysxK~$;yR{ggRW10K2XIdHY?Ye#gV_hg|BV?!;d%_Q_*(y^Qw7*OTwg5javQ zf|rwG`fDUHrfj;9K-OyCKZopQ#?;nCYBYK4Z)n-|{luZGbgM4ycW7TusLi!|`+kl@ zhnpx`9HdtHXmHtO?FDb#Phr$eSqof`#xsIg9225$>(QFGZ`m~j)RC!1N7#kxT8NZZ zxq)N%Ou!;R`z(7<%Cou$?g!AGgdqGnY1d#?KcZ`5!gPcW11-v%&l%>3!{~%fd6Q2& zG?2upj3NB0z!0lg=+J&v6x4L=yil%-E z))*`(@ebB1#tn#07jq=VgJX_G<`R1VkfrH_0`To^m}98Ev4BnibdN`?jTBl*3WUv~ zIMs+tEe$5Bpab*!C*2Bp0}gtIS(*0=`y1xnQs(XVf@RdF%X0UWsIV%-A5ye@-}umX zUe;on>aBHXimi{S*YKQAA@M^TCyoVqxjdPyFJ^0%;uUpyO4GLwU4*i9IEh(fuo6N< z6A{+nnvPDNJP4J8n#vhc2mx4F9Vv|k3ywssOK6&6AkoVo_|;#H^BVH3Ib;iKBj`Q?dU%-dca!Sew|41~}#wgN)BeTtmZhc}Do2|FC zZtBz#$qRCaZI}q`S`VddeRRebq8wTMSwiQ~)$7L>BW{_9>CMFtOVyJm#Z_^fw%Be} zdb!ns3~C+7M_U{fG!z_lY?Zmu1$=jkC(_qn!k8aca`+t#htx#DZiGBHLt}sTmN-o~ zEFDSXsT}MQY6iEFC!}gjp)}vLkjX@jOZ2#J2}W}HTN(}~pb|at1=(Ar)wJ|j4{n{6k05w>@9=vA zePJ#EY*i9Xj8M?DOwFzOf=u)K{?OVfO{RnX9G^CCfDKLlI;tr_I~1r!O|D9-s1V=^ z4|+rum{Wa`Gxo}vUr_oR4GD<)BYYr=g8W@fySl>9JK*GO*+pWC|0(Qvzn9;xpW-z% z)c(Rgjb5!1vx|?_Qau^a5}kYSL!rCovf1pEwh;O%vV0%$4@+@a^7&7SPB}q++)cWd ziUEyvc3ZFfK~EAK?l(Ow~Xfi zwEUxF4_%8d00s z;~?C(k)UGoCLJLZGEFl3%w`$Q*9z>FC{*RuTm&TFUvlKtjGO1COjsMFRP?cYe1xY2 zACo2<>2#9Jo2r{+FsNeWY{s9~$~v3LuBVe_@wj2xGt3*Qrp%e>EbJR3z7wWI7nOnB z=;>=5l4uQ!Hf~sYcC)Cvu}dQm`y3g|$pH99*I%<1La{M2!T&F(Rq6gpSSPlwmn;2qyr z^eb*e%l!wvGun@b-Hy)z%sYr((bSG(AzIUHVN7;)d(#+WiNVmIq=Qrk4b`%wRq-%* zx-X5m^IK*E4eZEDI@H8R5Strc?>b4mKMlJOjS zsbtEk<`r16vi`AmW}dZr-XwD?%ZR7=0F5rj;uL|j9)w8~{$aZONw$?8sTHNG1?2jx zwrfZVz0|BuC202+XT&?F{yGzn3`Fzt{*v}YQ5RGFsPFb+QE7B(gf^gC@eRwGm)Tzi zb9io}-BLih*xcKf(jyOy8&b$?S-f-#cJdqhS!GUl@w6l$xO%>xb$GdcMW@3*`zs=VY1YMiPybK;XpEB`H={pi73Ow?}-(WZ#`!HxwoJPzb z;U!U;SF22g5Y?Q-cSYU*ZpR0M1|9bmlVG=i8Asm?5kvU@eu)kreV2*WEY z1>VFKOfiV6cwc{47<&^kW?9ry--3Y}56qTse}nzrLKg?Hq`f(h7W7D-Mbx0@{w?o1 z2(adac0rqOXpo9+*^w+rq>y?XT?0LVC z`gZVsFoUcwl@cwq(y}gAZVpGCu-z5PT|*o6x6`AET?gl{Z=zkT%RTavHm73GJU6kj@(SH8u67Uni;{Cu9xpaLmVw}FYnRVIN+#ObWUS1VLkl~D zQOe=dJZ;fP95qIl|K^8qO-u0Ds)ocT?RJ>bBZBB2dYaFi1>%1`{VytqO7s#wfxXw=YBDAo& zC?OqUQJ5{Ys{*dNCcfp!%jmN}^apQlZW;1>O=aZ57$1`otkF^w%raYoO4mO6%To-P zs6mig2obzjTe72>mU4<|_U(th?RY*hjTG3d7EJ%Oo8PCo*I{GswVxZ zkaiv1TTRkAeu88u1L&)#aaBz3h+1vy(ICgrfjWF>?qq@}HW3|RS^(AITW^DhVl}g& z%kR(~TJv)_C7dIH^;0xBJ-a6EyeHs-OE7x3pOmP6wHPwk(D7FrTQjgkKCLhFk6Vy` z;v)w*z9V|AT*O)N2$RD<>gi1k?1e{sSA-UWf(z#o7VI^%E=|0zFi|{*qg@!g)dsN$ zI)x!(f4jHC`4-{xHP=AU^#N*}00AloI?$Y&}8|cf+*Z4H5=$mmT-4zwdK&lD9wPDxtzSop9Ck%M$ZUDa?8y zI=Z)CjZ`DY3Hiy)w0#TgWGw|>!-ee#421Lxm^V^RT+`p=7&A2qWP8lYzPq#wqfRqqca)`_sig{KxzEuG%1IY#9R(FN)YUcG47R#FbM6Gz=4;O z*Sm;QMdMi+SXi&VV#M4cHb_%PxH6-@$&w4?ek&~3_8fZ!fukiSR*vE|e%akVu+frM zbXwowpImqAF}C+-UNiv6Tb^7VIl@RfE9f;X_9mR(>s7W-IcY$WL#^xTXB-SlmLeMbciOY7f%ZT>2it(r6`g+R`CN_lx#{HN1|JG3|B&~d3 zNWj4AywZ9%hzQaghCfXq*IEDvlK)cEpG~;`-&iFH|J0SLj6h)88xIfwsv?`MJ2@Mn zoXzUE`74q@6s;2ihKi~hJ^r{}+wn&bjE1`O?Wf->Nlc=ZuxFniR%YhQ74LKdQr@)5 zb7xJ7WFoG(ZR^CvE{AT{?sfFIw$tv5oe?&wR&9%<(ZNr#$HlJA0|F9`KI2U*z~sxB@s@bjc8-+Q)B(-~(y+d_WRFkDZYg zCC9)>biERNsyRcrRWI9_VGK%bQvFyAY?>AykR8L&AU5dX?Be@5ul^7|gJssXOIp_F ztsrRQ;mD=XgP5NFZ{SMgFp>k~++Y-CKu>=bs_*Ze^fv&RdB_0$YtD}JZUMhXU!1Ot zDI-HeiJtEEABx7tTNA++dSjwbs$zt@;(5lH8CxkbALN-VXTe?rV4{fd5_OmTtJF4Q zI0i}PVH55A$kL>K+OEW}tEbD_Jxa{`93XBmL3(<$!S|UUA5v;4-#0oImUGBAC6q31;HyAw7&6?hxd28H@?0?F-D5hOeM?Q zv&yy8Qy=HFp^bz$n8|m(&xa?>>VlJt%)TeoX&V>i0;CsG-SR(B**}$bFPAyQ#-O3d zVW`_x@uSJro)1;{77+o~V}lW8`VJg>S;Es;<`iJcJL>MEmq--~9lq?z8@_)z zi%~V%IS`DYLc zFivpZD*Y1l$0vE)xD9~TUwbaosgwk~iFiz5+x&kW;S{fJU86t3w7LFHsd%Vs^5fm& z7QW!UL^Ez%BmFfMoFi(dlB2{zk*_RFLOaG|w zFgT8Z0nMphj>$*TEKz?gwv8qK!3Iidtp*}XW|Oos3mpFWy*NPTtUDd8NInPw5GnC z?CB^q)f`D*^-*xe-O+68kw?1i28SPlWjMdV&c1!WgT`eOr69>p(v8V-AXy@R7;_9Z z?&V!!4{ouI7}Y@lk6KQyY+JG8rONC4;Z2zYg`8VFg3* zcR|@SSUtZjNJWtjnSnHxC~$2nar8{zdq2m(d=Isrk(q;)rL@7Sr8nZCSQ}=FrEn3^ z+|#z}iMd#R0zZNj(+wWtJ3QrdzCayWD_lWzvCaQ#OGfvV3p&S1{DE(kDkT(d{gO#p zCFJX>3|&oxwTVs6<(XveF%u0ds~=}p7big+1AYk+OAV}zp$p!FnLAskaOu+@==Z-l z2bPfCcyHtLD*H}p<3d0Jkjd3!UV5u|yMqCvX%P&2Zp;!KUfaHnnwIaVGPQCMNe`T9 z{tv>J<1t@{O5hmnj@JUNS|YsmFCGFCf@lZ?$5XCNs+O#=m0aqTN~kNRM*VBIw(xv& zY*TNt?^Ha0OdKl7H>6v9P$1fOS^JsVR`bZ$iskDy2nc)yJccg4KtE*~!7KHZmf9(J zoz83w(z+6{JA$mu-13kQ_hFoFQYbGXIY=}@vQ$Z4Q&uhuh#`ZSD2)5azJI>JX**~g zmBJoWAlH4#NyMZtmYJeuL>z<7bjpLmPbNyQ+UXrXN2$Va=3Iejgxa7?ZlKr{uZrtq zK@TTFgOY_vDw9Hv0rBORU5mn5yK=!;b>U5;mU!rS>f4tSx^8AO1{1=To5v!GW1FuE zkp*tyyfTfRVw8zbEYl>gsFz?ZqpfDuW z?FfB}wz9_$RfB`4K}BKunt-N{{){0eqM?*QG1!Z7X|hAp4vNxwTw7gHdniLp_=UOo z*8ozSVo=29hy7wjII%`q^9F`=Lf+UZb(9*gOuoq@6SDuvnQK2yb z>wJcd(Yxu7fV$}yoARclUo~L<(XhzWD~BQ|>rOisIT-XUBy=(Yp?|+V=){*;{NbcA zL$-pB+lRD=zz6Zd_0gzLd5zE-%Luo{d`xs(_R+o&ya%#f2p;oVoC-C3@hmW-S@Uho zWRu8_Y;6k_Ba83HEaFKp>#S)w@CMHL9xkmBX+8yF#q|cq^{YR>Fuo%Hx(cBxGk~9< z`EvcO5QH$7d$?MSjrz67@;tLwj>J7Gvfvvdr6}#ffHmlM^azBQ=F*k;(AhGw^ewN3 znp1?rScd#J>E26jv`kIBru;Qg?_S48jc9oEX}UHRf@6)L}q|I1gTVPI&{3hb0#O( zbr2Q?)p^u@p_k`ci~q?Ew^EY`Q&9Cv6yXyhcc28d7hb#Bf0b3R5t$O|$eF77Am{Vr z%`%eaZfIET$U1sGG4o}Yz6fbpFii`+PqJgk?lRZ470FUVuxo3d8^fESt=!`+*kTQ~ z+-K0IOm~Coi_0dF87YaX=p!6ZDno!Wm`$nmuVvR5l?YmIU{#Mu9-}4uh%jxBzcWZ{ zI~4*kK%52ceU-FuZM%F)HyRdS&MjqseS84CeU<|`QzcLC_nw|U_v6SyQ#I35rEjPl z-ZMHSb_Z`wT#tD&g&xjpy5}!W$|O3k4|fryFOGM+;%>Gvi5FFy7(3{vzjZ^JlHPrC zKngr+dsQ$MxT8)tp=(tL9FFzXEo)FFKe0f34Q=p8$@8kp)CYxd+1WH~40Jy2TnKSh z$NT2yTV)!Ie8l4N6}y^yDM-$n%fakh@(6!Ay>eO=7mnLd$5>V&f3H16g^9Dn z*`&pI=oddb2|viCDfV1N@wh8iAG!`DeMRB zgT(_PUhK%~drS<(rbK3rf{1Eq8Hag&HxlFf(BV$2Sx{XaFbr0pch!il zBJ+L@&69w@CxamWOa_tDBeta$Cw;h8L&Sx^>OdV`k!poFo=a;#@w)pL3z6K9$J4f! zAMYgn$0E9>F?@6l^J?xe)cq|JyLyj)ZLw`FIG{$T~h^~73Ii8KuW-|p^wcw zB&}f*93-zR=6ad#EcX_fJabISv1w(}bFxyjw` zsMv8C=6n$#(7_0g-c&gY>nc*EFTnoVxg9DqO@@~s+#t$0kNhVD$j|aFG4u%TgNBw0 z+R%@9+2`lBc!4?W%<*gZ0eGl&n2+x@azRYil-=&+^Y_rdJ?7kl6X6C84 zZaofflj7?2?N3HyaRY+o%5DydB6UBPKxYirc!gb6}^vx*5Iu!hKwwg&rJ=mnK zHb)LhBS{JJYOM~;PX(ek@myKTyHexew+;qhp}FS94o{Y3%9?zuFOfVulAdvVl(hA|4 zM6o!thoy<=Uj;JUBztov`ueBScyvRC2=mFdxsUHfeaH+SP=q^8#~spzdiX?r5%{r+ zMZu3;>A&Xt;{n+cYluNm#vn%Dall`zBrkAPeV7Rkk)OIzYs?ui4P*W^GHURO(bWv6 zvzLz`h&Qi%_9;UOtsYQ)sS2 zlHV0ZQafYKt}!(| z8j&E%atzkGFP|=Yp+tO=N~er#ux06E%4T)YON6ewN5;w|*QcR?_v+z+<+xt5H1{YH z%(2C!#o9sK;CKH0=oD5?B=NPE^e1(YLt26~5EtZ9#A9<`az83REU>#WnDr&gJP1=& zH>y0JzxqHe*cLo~&=smE#HOomAK@w9w_#&YJ^_=#iT<-vp7;2{vV+~~Ja~Sq*rb?? zdwTF#$}BZ4r@L@RCV<@IaW5&Oi6%PxQTxHr6DZ90zVnOw>Pq>oX7rj|IsL0++1XiO zyFN%=GsdyZhCJ~yH!? z^&P%cr z#!%L`uB+4QzC`kHMDoL&1pW%1bq4z&sHQHwjxdUd^yx3IgY%~eLFsE20U#pD{6ZyC zFKhpZif!i_Df?pww|bbF*j&v#C2in$!>(E5tcw7S)Huj9+bbS$WI95I(m_q&OalqR z>)gb-W8`@YN;76H?ASmUh+*);)Kv*nJU_H&RfOa79`aVZyNBC(k!RB4NK}?p{DtCg zAP_m;81DE+J}V|&6I7W(SMUWkC{E*Vw^N=HTU82wO=yGpB0j@YD{4-ay`M8>N=FL{(gGmpuw!U;$b1uCVT9 z?NlH-tw&Krh9jL_WYaL+rM_8l8jK8L#iA(pL{tmjo(4MGot$#3F z@!x^oo)%KrQ%*FM&Y=;FE^1$e^_DbX0NS3LHwY8Vt|&3Hm~?bbSV(H5rfTIC&_2B4 z%Yki;%_=Rym^%A2?F46SEHlwu1vI_7a*oLiO3>l&B@f`u8%7V-;pG|31vrUe2uOM- z^sI?ul$b0&qS9t$@?>yko^j3~I)4P_Cq|iW29)7Q^71v=r67x$RL>rop{n;yh34MC4%byY!gI)sq=u~#UoOB236i!oQtVrP7 zG2k4bC|zxs%b!e9=X@hNjE)F1H2cQMblAz<(Y-lXu(d+Z8ZOB5toX)3VdWg(##N$J zeZO5OEVPrv%E{o8&=aGVTOgZGFJ;ZsO}&xRmVi3lI);@FBk{63ZKNmwXngaMjIjPU zS^@$Q8Uut#8mT`U4snI!hJSZ)bVFY-snHDKW6Xrpk;kWyiZ5U{>n3Vv{xI~?U86c{ zGps1*K96U3lC|)(xt9?F1(C@(Y3?8XxMX6&AcAm_kJTJ3)>im~MRTGrBc9DtlUID(`rKWRTp&bIYQA7>fG1zbZV ziu_2AwyOrNjXZi12*q&1?xwZ#^VGA>{kH0XwB~?3VK6=Zas@&28(rN{XyqwhJ2TiG0pXl>mfy2UlKUDBO2}Hw)hYAE07wZEZ~!k?meL$Yzz`xAjbjJ*Rm} z7vb4C$mn)*{rK;JeAAK2cZikun*n{h2DCkwxJ)yl(&kITX=G34E+{$?=xuR&h~r+a zLFXC0qoS#``vo26Wsc}%$T0U!PIuU`KPcK$x>dIH*>>fb>M547WbBXrl7)nP*U`kV zNn;+4IneuCYeiBi7cz1pH(!iV8l%aX7`e50KyBV%@$!ZP?lgTk@M50fAZl@Wty|yP zN4s5ughTKo17{Wq)E#M{X&N#inXIyt#A8uzdL zK~1!3$;(TqH7mTW98u#^f`ozZv$p{cQD$kGuta*0TBA96Rjks|tq4je6qVlqg*1`b zqL=2X+Gd1+P}+QWmZzA^bPc02Ii^D`SLi&(4*ecAao0%6%g*9nw8oa zDEiNMYwryt|DVX1e(}f%_j5kQ3itoI0sp$I9>wv-k^8#D(IG474&L$}Q^zf6WUFQOcS`-?A6w$`#ArJAPOKMvj zFfe2qFtE=)#DB!1jBM;o>}e!DKzvZ9V*K8S&+z3dmcUvBaN=T!?Q3zzl z8p3geaUNIBIXRStZI_gmRQluXU)|Me?u=`qPTQxh?=;I_p3Zp+n&@(IcL_L56kO?B zrJ`riHIQcGDB`|a*1~fM-e|JuYOFAIU7wn8jq?n&Tnc5H94F0=6@zLGx@ff4D#tXk zWvPcamGZ7F(x%P|to;wX3v#qdzo_dIyNUui$MmU_5z_g(?6ZFR?a!&(;k?-CFKwmz z`RKU|%%HT&qt#pI5q<_KsrOZ6l5L7PD>_F7yV*lgUMtZ*OALzbb8W-5L3C?3pVtmb) z^ql93k;$Ic_V=43q!(Pvrur(*boN3IXzhEKo%Jef$UqFZkUP+lws!;<83#@+g2Uk0 z9|zw-%eF8Zf+c-i9vrHr{m|y;0^JN>J1r*GSm4G2!&LEd#;9R08KGHPHer@Zdh}l> zy3UIc+*I_)*E_$$-QQcB+sev_xLQM$5tf^+?WTl=UCorrd>r|^ZKdIYInumok?nQk zcYLUuW)V>=8hfC~A^w7_6v~Lu`}}G9CRYm^KmV%QG>h-k{>u`kW)OYk$4ko|pg;Y# z+Wb-RQiSeb2#ItWR_!hnJnHL6xAt^LV;e|W7ztAuuES@z+EgsWnm-h*vUhz%garNb zOFvqhx@u+^lW~u`8a_o?_l;kjb~2IO2CvdDrYr#R%qyUZxwA8J!X_F8*#y9|z!%7x z;xynj|u2Z6C8Wxt94tPHE7KKYJXqD_E?WTq^l*Zi(_2KbgBps z%zEFZ{aKJdbOh6RwCwbHFVBQhpQK3LASPF_bBWTS{v~_Kcgh%LVZ}J-xVGjdQarEV zClfxywi2*Vwrm|7prrDgizVf1+8Lj2^;p`XTHbUU?JS2WY@7`z2YUpj@8_j!0Y>?B1(*V#R#AoJ zz2$T;WC613U%&6oBP!dt#$;m^g$}B&?!HJ1o$d;|n&G;gC0fbP`?*4LLBZWOk?clC zry7Bl-H1b9GfR_iPrCfq&rL}x*H8LZ*-mEzoPGK_LtK3LEIY&5db3|rl&=G@ql`%1 zkRgu$9_I>TI=P_9g*dgF&#Kl)5SF127s(=e@=D* zeq6n~Lx|Nz)ht`+BvX(+4cStC0yO-6s_L%GaJ#{s_huxy8ZqIy3oEEagtE&jVQB|~ zb?zsBEX>v&lVblWFS$;#{UqS!*M{6wJh1o@)8deFd4TUTcv0#$y%v%G7A}!#p0N_- z*zD<$x@K~sCIM(t;w-WWWvy2xj(;Ei zVpwXMN`?6s@3*1*=iznj`r?m=7PfDtIc<^^hGnQmJHl%~JkbnRy_c&QxC8zAn42$$ zkoRWNl2#qE`Izx_oaR|+Gp)Md@`5O}RYAYhOycwh4SkLJ{%DV0;}F>G;3)BeE+{Z1 z&nbDnas56{TMNR>0=Ae=DJT;iPy#FvkUBP`*}2F%ac{nQCmp%(bj_$(+| zX~wz-_@zRlD5z(^qxDz*8vPLadYzw)-RuBQgNH$R5SmHTEzNG`FBUY+t^~o~LIUEj z$STXk=ysUom;h6Lfe^#Gds6TKrB`s0^cdNR^)YJh(KZ;OYA7WRlrUYnEiPfo&~~^- z^qmH49@;}G4M4HzpO0kMn!tTKL6>W378H*9?eLsw=;M4x0DHg2V1F~_f;eRKycY0c zMK-4TWnTXFP9EYE8!2VCUd^OGx$=p}H(qDvi|%yJ4x`rOxxsWWHG_RMi1v)~8$4#2 zA|=2PinGsH3E5s`v<7)fxKP0@e5i4MOkFL;H&!htVJ}QG=`l*pVt3&=0`~gZ#-gE0 zJ9Hyym$TIz7N7p$0G$QB@xRxbvI@73AJ5HE#8+)u^ zY$)7-9y$zja3KjPLwkL}Fyv!Mh2MdHXeCin12)W(k(YPWi=|Cj=#8N-5lFiKrA3V5 zD1%C$DWlPKm>e7SXw8G#V*Pgug{`#@;$(rC2C|e3+VcK{R;ywkC>RbmhjD{8(7L@M zQrzC$3CO@euZ$&|?TYX=B!_*iaCYk8s>NT}m^i-PT1^>XsMZ(-LI{QP&`4}8pW>+| zMJ)(A-c@xjq2eV+Ww_r@irV%0VL2@aA{Ewl}}<116cw?I>zKw$N%{6b#6 zl&342`gxLc14S}@yg|Cfc=fs2@*<=OqAlmOVEd`9GVc*M2ubTXg-RodnGBln=aCZU zSM9i7!hQ8QnR#RrxV{*kRm!cWyC$$=6ht06avGB+!fhUcZ_+0oDYClbWQnTCzaOA+ zZYuwNr4Ib~3lCvI+l$d^spxSP6Z*<2VDxp3yI(m6 z8H-`drldS>gI@W3idcHyzLzHOYu^~C4^rM=#rnWq%NMQQ;gFH$_K@u1GQN+GI2NOMLK24t_qDM>&C$l3EyiGA^F1SK5&defu`U`nyWgmv zm&&Nh?=Gtpv0m14tLyztsoE2x4LtUqyf_9Jm$1^B=||}d@rn0;xA1kh%_8IK?YG^q zfyl?k1>Nz-IqNWyA9!yZ_M{o8^z4XM@2<-7bB!g0u-G`9y$^>{dgRiv1jYaC7)@fa z)%J&TQq!~cEjEvm*1OS5{brLg@^N^sb!?`emav+6kTDKG|}Nc8Hct(zhHJQ za>BQR?;K-vu|5W_0@SA0=Ys4^X+eTT`ts5o3+jR_IMsLBbu=Jg=sw0wXXMt~x<5EN zc|Bs#Rjb4q#ha!%a;ov7IxasnXI0(YQ6j_Spo)s?V8huWh?n0FShFOz^t3rKJeHOB z2w=ehA(S1RR7eqtylMWeY9-n<*q%}%3)e9eMl9fABR4dK>1k^;5=_+d+($;TA&)wK z2GwIgr&r_^=p^%);OSD4U5pH;8BN0YNs%)6yRI_nyeH19_F52@^Y!U6gRP)@x$q61 z_DSlO4Zx#+|Bh36In%&bS*6ze?!tU-R5i)e=I=W^mm3IJ&apjLz7%8g`CH%qDxpZl z8;Jgrg)PY39P4eVWuYzERvobijUBQA04Xf6#Rr@n77$J2ECAqLKJbsZ4uRR~zqN;j zZiS9f8K1=$cL=T~UYCsr;w*Z(E!9tGAPm%L?&>Jct(js2KCN9Pe1_p)o#qj-Z|G{t zJ)2WF2MZpYk*3$wf}U3sQcxy?lUBw{RkDmS(+}QZ>e;{H{V_a-8geYuisy6D1d$>% z4A)kjvM(5+)xNmsBMq)Wq09nY4Z4 zWU7-Kw;36=!wz`EOD)|pujTQ{2Ypd~Dzaob8Y&iWi_aT&Ed@Uh2pz2UO1D0yqW<>rZA zePFffkORCTLYt=qF6%l!%}H!~&=}c6zSOuPRWR-kfH^$9z25xr4kD>2s4A1&Mb)(* z8|%I2W6uf;F>*sO-^^oB%ZD>xoiLrndDQJzOqM0sW?ou6-qNw@tmEBVfy}C)=%F{7 zEcsDnTuix`v~n*S-H1R2)B<*1UV*FtaDwmyM8?`AlA8SX&@Z31Jhz+w3ATJhW5T1+ zT99H=GgOC}GbHf=$E*8ytn!S-SYVY(JC>FaSt?B<%RbqFIA^-XNqU|+`4n~qFN5Lb4n7r`(=TkoL+f7e`#GNxl;I>qGr%WDGRV{A zu(%@J7pNZt5mQM|>O&0oeyi+Q(D zeBQS*cNA)rW0E2Y3a~g4k&*t}PDN?UQtZ z5#MgnRJ`}KE9103n7xXXGmuYk&bcrXjUwpK_@yNjfbHC~GNwMqamtb>uPxlQZmibR zo6_XaFDhQpLuy&ItCw>mHvU5{1czHNs*Nq$JM^-c&`cx{1ndjQ=OP_*%r+7TkQB#JSd-8`y7D(r}bV ze$6`sFY>hasHDTF-!b=$FS%Z_NHaQs_2ovXSaqTkh#gQg-IiPM>3uKq{hB;jo;B@K z4PNO|L=8ZDo4Q37tv0ZIachG+dE)FUJ#X8s)<({IODV%#5?5ZoU?(diuaXfWhPgW1L!qn~EBLPux`8TjDRtv7x{TPU@+-fa-v={fBu!eY zYgm<(Y|M;8dt_Aw(Xh%g$~i6HJ-PS9b3P7ufv&yu+T(UHyCi4$Q&VissxdLlD3AT5 ze1l82TWG80)NU`)U4%N{I%n!+?i0=EsKCe5*N7nA>3}y1`3HX&{JLOZW)M9Ex;!nU zDKjdfR^5{1G5wOt=Ee3dNicR5F8h?0C=j^ZDt@zTX8q=q(2~rSE|qFyLNV%oCC$5g z1I0t%8P--+5F@ycVqFYAY_~madC>Xh0!0Zfe*D)YrUMJlpEA88XDYg)B;KPcJU;_>5HnPnc8wu{37!%OG{M3Q_J{#49a zLoMG-C9_-}yfd{wsd0Y3o>@_QZ18XyuMiZ z>sRLQRA$t?G(#5a4nhW($Gcz`Q?+qQHJ0#5%z|6$pI(&Nps$iq*)xd2GfhpZLAk!Lf3bDG#*Zy|R0k7xUb@8T|OFdtAfbE+KTpqCy zmN`tr+z3|DU1*mch6U(HT!r{?&Q`#S()P#UpEt0Sr2U`C5Zb;rU9e6Fdnwp%G(Omx zFMfpCAEBBpET`$`mN_GLw!S|v$6Vg3$qlSTnf>Qzx9i#nx^(sFYf7EhLDfeO_RGL3 z&6Bw3R0B_|=d>dG?8S~n{uXQZ?r1q(3(OtMYTP6PeKDswNRI^GGen}x&7Bl6mUQ>_ zv&_R|dKGA^oJ?svh#N6#B*@L)H7{T8MbVCtajy9hJ9OSzSo27i&%ZDgx(m7FHf%vu zD9zfD3*)lvjUx%2jPwUOkRw(uDjMDoPUfHcGakj%_~?lNy~*;nzR24FOA~XaBP90g$8WtH;eevjGD(KYR}zdLR^9C?%QU(`W_B9g|qwS&$>6{n7j{_ ze~dpm?-2&}9WWU<@9(YIqB-;vOLt%OV96yMUeCed920^34KW`{pk?3etSyS#^*b@x zxmu5OcT)0;mIsf8B|gBc9WwIYp(LD&eZ`%Ews@Bc^FL*zmHU%J7k8ByWmab?Nlu^& z^;AG{{+Qy;`{nk>!<*CA$I09B@k@{B>vM-N=!5yPC-CF)5|q^mig?PpeAiokzj&+d zei`)w{oOPUc$|L}_;`3f4J~0^IoLg}Y-n^X3XN(X`S^b{MgK{#VM4U%gFZ*wNq$30VZjK!E;po%Ur>@8st-3!l&<;QvXwnOK?3ZQR@$Z5%C~m=z^8L?uKu zM60zNl=r02-Y<+<)r2){>kLuuYpkN0If5{vASLoNg~MV6>mGId0=7KF-UF;W0cMyy zOq%4RNt-@Chvyk@p<3qs(O1m&-xf3>E(?Bveye>?7hya#@$<(5%uY5k%Fk9|wlbpC zEH6{fm{g2oR3fqe)T7rqf-$$KQjyc*gFf?zW!nBxRLt`TDh(||XhTUnB`5F@|cw);aprwB9{!J-#`5LR>q zg`7%(h8DuTxN+gXd8}b*(KA4MTqS+vziPB7M(g((tQBNSW4TP+RPkuwVHb?SJH8iO z<5i|(uxsOKx3amgsP*HuCCFUI1^hrlEeZX}nLu&H9x4K?=du{NUa&#Eqe{776}DlI z(u}U++M=pvUbhIRtSQ0dDL<8BmgRH-wXGSTYB+i@s^g@egc(M`z=|Bd#%oo48)zhW zCR^|!A4|&m9b7-BHmbYDJpe3tJ-0UY!8I#hdO49Kepq_4aEguhvm<(M>>1m6r@MU6 z$oSsWUmCQVJPV?`tO4w%dN_v|>#gR&cbP-<0NsMxxsa!e3P^j>zbP@7mL>*(8mAqV zE*z(89ZN2#Oz7bG*IXTI1<7Y$d20Hpcmi)Rp;V#j&2QG~(->|?urM?&y@za;EIQr4 zIHmf4fkj~}#x)OnQS>WdFX!;<@OUPP>EgcZX9+Hz7XPu{TRGC-MDAi8eX4Q~{U~^W zk+ejdaJPLv{ITcFR8O?G&BrnflGEXMu;39FJCo%TfQ)QpsJQ`~r&;%hc7BEi`hKh!jl+sR+?IcoeFw z7SYK&0MjL|hEzY&+-A)>y`m7kg52w)t&J3XyH(;j+wO3u6R3Kfwp9U!WUaFX$9?>V;E)1hs%kj?q2bOweu> zI4O#b?;-G1O?uT#6$zL>k7shXRvnSQ$8x0O6Aw~r&@sbO?>!pI#gOja?eW1*$Yyw4~WO-W-vr8^mu~8_rMpf-eGf6<_YEtDt^(%Q24}ZOz7tSmuoo-Quecy~!lie+fXjr~vl`*kd;OCH>0XO?r1~uS z>C^YHCG-ALNzd#xC&8Yuv@)(Ntm<#DzRXJTlu>BhSB*r>2jZNzKeAe~Q`UkM_7D3b zPgg?zj=k>nH=pUqbV}V;9UTlV$* zms4FU5Wj!xpKtGf zLqF$8iPONhNF@IgHbR=#|IghN>|dJwU-i-dPZQ0yNa+6)@fL73cAyOg2HOY*M)-dZ zs@NhS0aSg1xubN*LSSXc;)MyBJD(J<&tRLp3{#4|H(mZ@Hyl@=8m=cqBDqMJoBxu@ z`doAROHWL61Y{mmD(T}5e3Cw#Z*cl_8&m84U4)Ny*A)%WEyS1s4=4>`RhC>@c~l)S zf|CRp{b5P}t5E5{y0jn%HE}(~NOENtL5~Xi3lR7D?T$vI7Mo;}xA*li$e`ap0q+&o z@{RC#_!mNlgR$L$gfm^`SqjSPa)PRnNi5BHDeIl$Z_yx))7h_#w+7QF*;^4oufy65m~f^%b!S^K9!{`*v~VcYCywHN}=S>~Cb$ ztO1_^Fuk)TL}`?^6HEm7U2kpfXS6s3j~F;dUYAzfphsL76PAF@beup;%{fep!Q3o& zn7KM$KK5BkZhXDB{eIKQcIA#hEgsCST|2I}eO#!1+S{O$4u1^6otTO)SXz{I1vIhE zR0cUyiH1u#CJKzLNwd(xXH`lGtiNCRLjW+Nn^St(y*iih#_C+5Hp~zjnd%_SE;N~t z#3P8Fn6eUKn9IRhp1Po;V7|${Nq-TzpUNz)I~%B3u$G@tzZi#y!XRHAVO2+rT_!JS z*Yf_58wl@=IwMszp&jM78UcN9foLrGsF=P%XJ8HNVyenEle@(!@(cq)Br&e8ufQAm zPiWMhNoWV0Ar+A5%#zY3qk@)Ife=4Aj@XKhmR3`}ItIUwPK|;IRL6;V9t7`}dd66h zyAHvJr9V0vN|*4X2|Dis57Z?Z%v7_A{|D4&hlj#S!XdqLu=nGd6TT-LXRM@y5s!?^ zI0%8*4s3`!B@s@fJ78J{4`+%c3qa_g(H>+BIj$}V5t~bujwU|I%}mm2?x)T>k*b-5 zZUz%p{@OzpA-ij27uMW&cUYqb%#ve_Koql+`j*FIQ6 zFtB}3`OR$wYbWXlui=q#__ME2+#H=6ud+tSL-fOQHSZBY6})rVD5aZ|6!3sJQo)_H zQ7h1bnuFZwTDiSqGcu9lv9tR9r)SFx9iICrG7%$u^;*v8wr(>n$R=+z^<4sEG|T13 zs6YO^Lm1&K-Ehs4N{Q?WlFy0zpyQAb8?KvBGSBG%JzS3g{9F(30ZQ_$1S}%j-j}G+;{x-xm`5?&i zNFqCE4Lhs#yA!q5^Ck&=4<4=*wh7q=iG87XzL^KE3#Pv(LUGUiom-D#-ES_g!!Phi zu5l6>`xm{!17)TJgq^f1w0sG~EkE>>5p7Us<~8Iqa)ac+W~^6!7NF=C;?fZ&R@l&w z$gB(fCPQ%e#=J@N(}=<;%9!%`)U`*rp6EXC@ zvktRQ+4yhK_^I;^K^<2CKm_ZK53U-IXdD=PXBr|bI(OlL8fnp}O2W3fbjFcBK1mny3^hB){ zrehR_1I~>e_)9AKl9Tiq}ZpZpRUzpq#P zjQF0pl6)+lbk`T>AeI`by&pz2a(rC{KD_(~ZUO?VI{C_DUZe*O;SK+Z3h9g_O=TPg znfw;iiy=@w3{W`N^hOQaqd+7%IPD5;=gr-kgvX%MMc}|=(S@$a`tb#+OEBy*|m0prtoGx1`hc@PkFl}rj;jW=)ouZtE3>6I zdcx>-3>3y#Z|E(k8&km7$j!A4Rox{&G{`1*{&7JLVm@t2#*&(g8MS@Fnle3C&LI{g zpv?dNOt9jJW*K}!71zK|?{CRkQ07!gb%X|!FlEB{-6nYhSn;?TwS#A`x8rCE~*d66n4TijTid&Dc7({W=Tl8af~q`(W9837N=dh~iH{ zSIkeiv#R2@JPb%F<&1w(h>aa9J{ z#DBr%koJDf*p3p#I(I>$7!Rx+eU)#gplYeVT{!I?5c6&3-Y$w-MV?(?r26Nda0G_arX3!h+1N1#fYHn3=8tq5^|c>btV>c3+9WGgyAs5n9dT zI|HrX>SP5z7#*j*Npd`MFB_{bBH&>a)#14})iUKvPyf?BXlskhB)C+VELnjifN9DnW(rYOJW*db-0HWA%jJxv#_CR;C72u(8x66sn>q|q} zAM~8)pDbEKeM@@$`*Zl(UD7|M)z9PL@J1ijGy|C=C!V8-%$oGoFa@7vc(=3-w}ncr zX6+gEGKv~upWx_=H_LA9`7BJ_6xd&za%$47BoB>y-XVxz%sR1Bq_x|6X9q0yNh4F5 zHeD<38|EDJms3@&B>?%RUgDk)3_{uthJ*ZgG)gIRjGip@t1rV|FL&-!TtK7UsXeXg zRnj8IoP6-BA-q2&LD2SJEt1_Gtf-)z9NZ)hS%1v|k6lxtRn<=hb`s7_zt{$T;%^Ex zl1NddhpMg`N4qeeVe zcmj2l(3*Bn9?|zUULpj&U^zs_I~ELoGt4~kkcE53K6ZV@MxA#kIim86mK)b_oiD^@ zwhYAYge!^Myz(&m)*u!LKW4)z+#o$(ENJCPB;}*QI65nZQ~-qmoBcZi=Q zXq0;3u0*Ah0Y8n#k|x=ngdtB(PqB{|PG;SwWei9Mec(p+%=QDlq?`u@|N5PatgMn9 zL?ZW`Y9M3nbuFc0OctMb|E7e^SmT}$kbH0L0ZVImhxUW(>bVJzsS699V-&k(G9jbQ zS~4h$eJ$n=NNWmW1mC=owxhczF^66LQlk}XVcZ82-DzQeMtx-Oow9aIIESIYrhYN_qZef${E1QM;+-%$7X;4V zt~ScpD$2l8;Fb`DJA%!yhH+us@Yk~oqSmcd8rmhMFSXq^Rlj^#jsXcqV^oS}M^nk# zi&&Q=LY`ehS=R+7so27cG8Iq?rgMnY3|}#_pQSWIy2El)UN?cdBXj`!AZS84Y(66ppplV_CP!0rPHcxbO;nz!x`TkA%2X(PH4jRVuhr7>g7|bB zd;pa9dTs#HDU=OONdL-rP2t|hk{133m|fNT&ei(^UwTI{3roB9A2WOuyj)%L-_ZEL4e9@!pvY#G zhFxOFeL5(l4Pn;^o=m(Dke&=k!RR4rT{w@kO5i7&B8yyCkU-&1lh{H@^H0?XsXJqt zh6Y2+WEFox3#9T1gNsxNrI2xMZJt8D$!1)lPBN%Nx4*+hQmIh^juigds-Gdl9l9g< zZE-wzy1uo`jr!g?C=Z)eX|uh$87Kv0$ervJXFb=zJihTi1dpfgDI-3&V}qLP8V+X? z^y}T-Q%IryEV!>|1VgVOlWRnAr*!2!yptU(7$K$XY^0B5!fC`}*~t3sjrgHu+>L8z zwGVEab9&-=o)&QdL7x*-paQQv{nEw=^OXURiARWBY|UL)SU}Ez9;OK&4He#HH?IgS z0mM7*Bg@TIO+?SB_a~2gd2l_Ue$`4F6*(j^OlJ6M)ac=e_7OCx9C3tJP{!o#4Or{~G>1JW`pe7fd(~$AX5FXfe_bdlsuF)P-V=wXNY}nQ0O-2Hy z((&d(TS_{iyo7;0tx_~&utNHG=C@dt93Oh79gIl_=qJ7=-+X%w5h2&TBAJKmMa4Q* zTM58r`Or~ujOBCRXJ>4i-Y{lJ^Y0G*kxg-(B#C$vun4HW!r3;1SN&u ziM(1zXNe{76WW56gbZmB6ly%9w{nBqhMIR4EeoCQ$O`*7i2#QMZMIH~h_}yzH^W-0 zp!6lysi732RuqfI*5Z*LfC5Y_RU zYqU!gFxh8V^6G!T2>OJ@{gCRd`ON618Y2f6-J?`AJeEqdeH1x@k7xKk_W{9zgm^5o z#MK2PkCJqp5WgC)e8Rm1e~9%Q0dq877`OF=I?-$+s>+GZG~-gl!f({^kJ8QQkr$#J z`3`d%aBHem1$qI#MY(wvpt)_&SQv^6y$@zcEQ^#w-c=o<9?Cxg7s}}>9$G|I_@o9a z+I444LKF@;LDPj4Pv~yBkZd$0RTYTZ-NV4sX}+WSy1g|ib`v#k2Jeq7axYA-8EN%5 z#K8C#&_i&xx9?=N@2l}xF`4LvD(+HsV5-38tpP&R1(I2OTTh;*v3h8f4^v9mdq3PY z7aNeeGkN8C|Tl7*iEVb2}BZAEp!ouB;NM6q{_)fZvuqit_5bU3!F5*RdU3LF1qsUikpdGf* zqPYE%s%*ZS!;%}1(7`{|-oOrJ;ISrRTN!0J0ja}tyI5B)T~-;2B@sa zA&g{tCD{w0m zK7x$V6L@Er&dzZ}*+tc`pq)iRSzF(aQR4k8cE&D|n;ofJK?yYMnnf+$bMWFH>nA3dDh+JY7g*@u zz^`RkElZpDnKBqI+4bo>Y8uWw;D+?%bb`YC&cx;>>!Xr+#E7C`RS7EP+_!Wt459rL z4At1lq5Wty8RBf?1n(=`go;WHb&Cgfk?cX8msHXWs(WKOGppV}{cKUtTS<6f^pY;L zdBD?K1_kra_#-2{TleE9J)r*!0n$l}p#i+uSN6H!Lr9%SBT>|8V(cgkXNLU6F~$;RT1 z;7~qmcY*f7DKp8vHz=GbkziDh?sN7Q_E=_@>0K!$q?|d=@Pxme%K#_XRYk@QsD??h z2P1L|qo2p!@U^hT-UKA>fpal=FH& zyw@76lI+(uS5Z%XM`o1NS9tPvjM(9@cF6M$rvwV?3lP;78@_NYJ*?WlNAbDJsg-S+ zB^gjg7T`KEogddJ0KUdd*!}!@PW?OvEJ50qrFd5zIo&l2D{%M%aq zaH~x~Hwt|Jd7|0uF(c2$pb@AdIoxyPA+$TJ2j^!`QDFk~03&SmYL_zQ>zBfgv1l^` zW^KH~eM6Zy3}DgyZARQ&!ZQqF&&aRoY_H9#TzsW$V#W-ci|v^sde07LfpBX+oL3G< zu`viT0x1DBzS;M0G3^p9&mFwteXN_50jYQHn}qi!LN_0=KVA*V2ZC7he`mTxRM^;VcXt?1Xhz4Go=Csf)Ll$V zK6cE4^hd6newb4FUtX40E5417x&NZH$G4O%!4ohQ_T~iFhpLHd>Q-QWmwfjN`!s-G zZW_$tQyDdwHLOpHsOShTip!}%yc??K620um6%USI1{M(m^V~WXSj{-*-DW>?+x#zG zqcs~+vUEJDugZpa9#Wx_;kx^h$7qLHrg}ezzZU#PdsLlwA zOQeifBuvOR#cM*HAlVonm&}#aXCO9^KesXGc1rzJuJwQi?%PA#mBX-l8?5a^PZPLS06zt2vzo6<=BZUkLz+6*p*b#MU&d5f zyjL~F)-`h@%V_ki;@Dw-saf2FcyG-VYXk#RHiO8LF-L^JiIpJlp{XULsPbT!6T9gp z)b#kwndb9L4f~0^E+Gpv%Vw3NJnEIp*UniE<)vK(Sj~yHuhAYx>B!hVd?cO$-j}EC zz_et1pk4@UQP>i6-cCB-88?k&C9iv7VArqoNTO?+TUK<>_wq|2CQijXd<4vKKEq!K zzUOz=kB_ zKF~@}fA>nb{9gKUfU?8RpdMR>IbsP)1xT9fKOoN!4G6*gdArjS^_1uXoFdFBHOQys z5CUvF`J(W0S_fiU+mlp+vUivcMbW^4zrCdMPcFtQ{I9~!z4^7p_0?Sy4HaKhQ%0o*O_2@1Mlhsr*_I6Xfh*ri??vY6x2AlX`oevP021uq z7?)lwp;2oEfG9@}5l!rf=-i$QA!*6S6~BMCI=6vw+?L@DKA?;F)> zr0$hd{Vu@ z7u-@uc%Nf`g?xV-`9yl!$+O=1S8L2fE6*1+S;cJD!;h5KNWd8|`-h!&$Xx0Gk1H?< zhrHLf@DDfdCo3yK>v>WrZcWTT1Z6Q6$>j;|E%uwF$ns=o8-b~-T=5c}HD#c)Ji}Qj zyB?7H8RZ3R`VUR(Rii=ik}FNQQhkf_*Lz7=&sD6aa(>ST6EDvFk4>9b0O}mZ{;mtp z4e#??1`UGvSZcS$dqG!m^)__(Up0#{;=gZHHnHNw-siZnpsF=Nlb+lkB*;u(w)>)` zVF*^3hM=JP-}aMn`@AlYXXC>KSSmeSse1B(!!sd@&kr46Jx>yLqL}rsiARPpI&fc2 zyPB5<#mX$ctrNSn$8e=a0?h;qafBGaS(?%_YALse-o}GM?+PAFHqa%Cd{R%2rA3oC%N`us zWrjI9yK6j06T`!ikDLoy+j)iB3XgkE_2-L(gd|trc<;Q&&8&U~%5tHcTE$jP<;$dZDA+fcPb6)nUvwf1W4|q)xcGmUidzmW%C7fxa4O)1mqCh zoq%?wl3vntg9^^tb%))tyOzj5GTFEzoE^4%A8a_CIz(nUF;59cVp>+WjZ#0Q2F;>! zv=AjU6T8QKj3f5}))ZYQ4OwgS3z6_NFz#0uKG^)(B8w&x9GJf$FdRwp$e1lQ)i&=(RK#*N)Z=C zto?ZLZ8!~KlopZc<+z05xY{h3{t;Sb?GH?M1;@RuQH*##aHcWAc5_-IN@&I_zC7L$ z;b}wUd|qkFm$TvJ$^{T~bq_JZ*5UoKl5mg5>1;@0U^ecNzZg~wtH5VH^A8xk@RBnJ z>rXs|mt1!MG{j2JL{~HBn>#~*obA|SZ*x+@lF%hT z8mUtk=WrpjU0%idwiFE*uf>pv@hJhKN}BPE8^d!_j>$hOlnAxh>Vu#s1i-4r7Ie~z`?Ds+YJtz& zvHJryFI`wL%8Ip`+D+M2Hj$A?Gw_%?X0)!AIcr)C?a={avlrW(EJufb5feYP*R_Mh zj|CNYwMS9e58!k1k*<{_t1bh3?^nhHA$?Tx-L`_`re-CTEi|x=i^r^@VYAfFgI^X-pi!Yn z)Z1Z;4|YU{*do+UU2OsIjhz-HKq%Sdgt7$c{mCg(@oOkTJi_U(`UodRIg>Z-B+WN4 zWyD(OIHq@NcCzATOt?~~b*8j+sJj-TeU;ZdzrwEz{rLScexdvHZ8oql4D--aK6jZA zzISZe694gI&E}w3QU-D4l5zQMT{S|4)`I^0WwTfbGd)0SPOT=VQR*uRY|yh(@4E}I z6=9ECSzw~L_b86yxo59UL_zGiIF)p}n}{P#jOE9OrtrQx9;1cugzI+S;Bhu=zNS%8 z-gDI6H5DIzU|bBA0U1)s18FVkhP9*m%MoyWr`vBhbC~gZ!cdP_#d{cwd|jON{c-X{ zaFlT2g+%?O3K7E8aW>p48qfTJtv(v~Y(4Ju(Q{37h7qhp3%uR^t#r|i`|eaJ7UZ|p zzbB(<;m{TjW-dT219u*NLHKUsufutcQ`j#m8x;CuT&Bft!9}CtbeHTTC}x3=39R(i z(q&zfZ5!*JWz-=`aJ}mg?HmTP(~;tM44w4i?od42B;3`pnkP}*5xw_Y?!W0FTw2}?Td3oudIdrn20ixFG+hlwU?OQDk}rzc_ag6P`A9|?_vHe$Cw?2 zVo$%b%M1oWcSVT%-ZeQ9x)lS--JQ8S6pK>CY_BVrZh~?`3~{?FL;8`W#GsGro9d>y#dqQcbXfF&P7=YBjl~zA zyMmKx5l&l>jVE)^i1_ijZS}fN*Y+<;!98^rU&4`|fA>;{%vhVIB6Rt*EUsJ<5ih<%En56$fgXk= z8|dyYDf=!n(w&ws-6}e;xon<6*~?+dTVh~Md?42-R$KI)wYJ8HoRYmk8B{2J-&E=y z^!X#a<~hO`zPd06h4V@;CHbHbYvF9C6fcQ91>Jk-n0H9aAnko5U>o)D77_R@Sb6OI zB&yJPglUcHnnbeNaj1mPS{}8vL}9}u%z5Nfz{~r%2Z!A6b-Ij+#07f6y`SZ^R@K+r<&UaK@U7>tBSFTH5Eob$8{VaXFG zhp!~Yml!5xkp2K-Ss6TH)$|rh7_@~gTndhG&blzVJjZf}B|!s>8L1_OH?9tLS-~*!z^A_3 zV_m|+wD&#b!Q21hzBEZQ=zTY7Rw`=Pm82i^=2){&{5AM30wr@x*`?%oBn0OE3yAw~ zV)}sf>e`Ll)n7+PWYdx>OozoHj9FGamiuogY~!g7d^qTb|fLb% zi;*B_CxaIm4yE4(=P1~^dxyBOL3s)#pDJs0q*2DM(Yq@mcENN6H7>*tj(NE53Gp+H z-&!=G)&^)F5kvi@z1~yrHsd!m3BR~4SYh>W>DS={#kS!vNb0t9KEkT6#2cJ3;=7I? z+qroOBlLdv-W#TGBWDkD9bIRp_b4#nZdFM24^a`DMvv+S&^cbCYihj6zJr71a z;-W%@C?-$Ywv3hxF`?(lyN$-AL2I1>kjVWIWK^_tTmh_XD~A!X!Tg7$hEdBstzyfl zq#fNhBzfQ64_bDW9QyG2H-0xjGh{6oU?y3B8q@6Rc|53o`JoGy?{YtzeaQhvYNla` z4gdOCMZ(@3dg$r%%|4BZpG@kKw^*s^p3rToCh2&zm>%T)d%+EVn$gyn-b0E+{iHWJ z*JNH}PBO5`my}Y~ee!Xgq^dzGtMsMrz3dZq+J&`!VLB4}dPi@0l!Mb7iDQy_tx>(C z!lxK6(^2)FlNt0!M_}z&MDZ4Y|ADKRFJyN^O4r55KitwS$1}uQef$TCdDTCBR&Zu( zFKWmkozu0Jre(*py-niLJBk#!fE0A!60$_76Hj|8*Y{;U61;CUDEZP3owl8&Y)ybU zD0pe(pkr*hb5f954|HY-Vw+n!Xn9ECt6Nbub%H-D`o@j^muQZ}scs73mEn%nvA`3f z3N7YlhxNz3IS%?@Wj}v)nh%$P83+>dDA4kt;kKkF;|7U-N88(cQx`KYGy!`=^l_@{ zsdX6e6jQG>2tzeX#pJhPcNAdY*%*3DNHAd8+>nwvayY6VJB#_%NM5)Ld zjJ}~VYNCV~gx16#wi*w_t9p~TA$K;AO}1~^;Y0SY!T~21pV0nYf{czQxinZ!?j8d0 zYD;w_?b6wP&3q&)9bWUq-`0^E%vcZia4zM@o}*sRbV9GR2o49vAsH%btVVXDNXp&I zCM;B3`$FH=8E+AwY7vOOJ;*vtl$1}OZpemnzS<+F3{$(~FBt&Gm-c7abzk~$Y$3>i7W?kTY+Wox|DY?ktre~m0K=c z+)WaL-`Pg-?prNC@#)t!;Laudc2Dj8E9@okqiKNb+Hh<`H?8t5SDP-Frm^9r75{a* z(v77zk3?13@+uj45|kE8JS}O}h$$1^$iV)oJ$T9K9B%8gtD`emF@jjIFUJ64alZ1f zzWb5wpmi9gUn&`!TAK%cf`?PVH_5<`g5q|0vT+$HqE@28eaBXHqsj~#B5vl#*&G0} zFPvh&Se@F|#15zz(|t)Z^(`1x?jawwTd?XqT|Jq#`78wBf?mO~q`?1mhAZ{*SkEv+ z7_p*t8M!auE$Bexf@*DC2gR-0xmMwn9jZLM^|?afZyVqpMyJc`Y}d@|^Apk>;Frq3 zbA~zrWJ65MM|>pq_r~CJ7VB;wH7zg+(`SC-6GzSPSlPc3X1{oVU2#)&&g(a%pvqzF zs$F3x7ZC#iU-OfTM|&KJhJ}K=?@k%6c*aFLScTF!H*;+@38;Tkxuu3B>+R*txslhX zQP92Mf?DJ5&^WTov~ z#nuLXeBAh^d+>?MQd2{Yl#A??Fo+J}|82t!VE(e-nWg0)b4;xf`od;)$KH|zvoJC) zlG+G~Wz!KWFM-cDkrRRu%C+I;NP{l>Gak@#1eMRNJ2$nR{d`_^p^vh6BfW>eStIAB+;}%nT>^FUX@y+QBO&%Z*o*g1cml92 zwvIJXs;-cDenhe=%diT9AKqgu9vrp@UsITimH8IiZI4a*DXY7)r_8=UTFqsK+5J=~ z$RXt@WwCqXw&8Ns+jwSbTkb%KarNm$7)(W;x;DnvFYP~$30CaeFC*S&c1elUc-xNN z4_Eqpn*E*jz^+IwZEKxCgWlC*ya@a*aVUQp9*FeYD&%t4xm?xNZ(oz#6zYu?i`!*M zKTF<_$Gc$ri+{rLGw>H_K)4O6ZU`m^eU;_e16f!p)-z#LUtL;a{`uaBsnCjb{k4or zN_X;Bhs5d_lBtRQ(ATljQTxuebRQ@5$lu4>I30!0q-~4Wp%V+KZFVVyD_ykRO151qE+b0!u2Vi7 zBNkAA_V=g;U~0JE+YJT$wSa59IjbAEY2_cx5GSl7n!!wEupNGhUO$J)qSW7f&}6o& zUi%726Smx^iuQ@rctyKueqy}6v2)d!`GaA3v^qbjIpUeIRx15;oN8&606}%K&6G%2 z-ehpo){}kL;V@&f| zIrdbXN6&Dr*j$_%)2!9<=x%O^+IeO36^tsK`ddvmHuAlG{WnFp{>TQAJaS{cKeue@ znh>2Z(J#2bQL43ji~~%M<;}kj9JCD7vM6>GOl2ZqD1;6ELk+~<4Mau1d2wCb^1&y> z7<0G2$DlS5>P}O6O*tdj2(r?urYu%1N zQ!}%j>A(2stBwV`TunbAF7328K0jzGX}dim+L zf7kJ8?O=*S!yGwci)M#MMi#&;l^t=p4Y^- z?w!A@ArfHYs zU=zC8F(3=Mef-_mq*eCPmp|w)_7M$Ms)_5BWr(f$Ebj-!SeoQC_qN9!^Tvtg_d-ft z6|||}K40mBy4=H$T|2_OiM_z;W)m22`UqdVtpkDJ+_{U-Lo2`h;q9E~*YN=w0UYx0 zb|YPKA>N7vAAY|k2Y7yg6o}~0oRHqw3)dKLYJl%gW93fX>$n+T<VuIWBH7KNP z8hCgmAmq1E=|dL15#9w`|rlTu8gRH71KIMM6#Sc@x> z3kq2HnLR-PXK5lgevVUmm(BCaPALZcRI^cprUW+ne6opp@cWW;f!QS+!f%3YDFr+~ z-vO{r^pe*#F`R4Pf$D&tgl1$H)93WTSn3g1aroudA=Gu-vjy zS6}Y%59#R-RPSD5!y(jJXanbu^dWXadPp)&wDfHn!%bnm_&WmQg?c}{udn#(#fh=$ z>#$_H55#q{>iD@=QZP`^y_qPP-ECl7c>yac)^iptZ?}Y=(IL%E!J__-QVj%1%d5GY zbPTnWG3_R?HW*sAR=B@OwiOS=ghw}h1CGbA%SL8UTOQ2&+b0q9e-dhxl4vK|JR*Nt z5RDEB|KYs1FB^t*|5^hb=Ymv!C1VyLQ+{H;HZ1x@$8zUR&mIyTZ1K-Q ztx%c%XRqsAG!q`!xne9* zwEks$oWqJ|hR-u>>(j=_5S}tfflUNI zF_KgV|GxYSRk@shYUYleWGCokEDWY7S{(_84d|5V9N7E=u&@`1;dP`S)AGA(ueI0f znY8XaWhu?t^G3N*GXpdZuPvI{zUyS9GpI~()2#|pE5&20d8THe!?+#;2w-2IW0(BC zHc4A&)+Wn_L%&|E($>z1?w~FrY1@CF_o+3(4=N0Z;Ab)5%IsP;*3+8k|8)~`Y0HC# zN@&D!vNCBhA=nZWX|~-O76)swlZG-R=lLvenBCdzl2i#E%Ww^L4;elJVZa$DRs zNkK9OE8e6<0ut?88vA^b25wX$T;i;Sw)auyQ695-=p_D9r^wI3*KKEP!YTH>v`>*+ zzT?4th+Isw%eGi|JAe&N!u7@IIyt_dR!)e=;(to|m z8@MBhNvDA)-bcUl<|_6se)6FLw?|7prOUK=5}+N!m`8v%B)RZW0swNj`z;}f^#zst z^(*aBy%40uy5R*|E2=@~-5QVL)>=a74Vtgx>l6N0fx8xJn9}ba;A6^ydUG=RCsa%* z|Kwqh?%*$@1W=705uzHy8@rhuaZ!(41WJlp4hkiog+Wj`@rT|B6EB=Ft(R#Ql-$yN2Dxr*s^f0f3Wv&R_k zm7H4W=Si5An47hubLL0n1UEXba54GS+%XBg=ZOntM++#asVDL{S-%f3 z+bqN}GJ7uBwB&`Jj5bgj-CIo1D3oy@@3mhjh1}x;2CsO!9P*ZG=g0b-(L!d)0jkho zfj2fJca^>@mV%w-&_IX=7oZROOeY4yz9!KMEs)|nDov;$MedXxe7VG`_xY{Od<5@S z6cNRgHFN?a-a>)$F?RL==jqe7L{tAOnKx;b!9%P)sFYg|(!YqO;^AxJs*$zyS@~Or z`ymT}^uopsDn!l0)R%_KaHsKxLE7eLXO^{m%!e{LFrH70tc+j!V=4n4@W^8dEKKiJ zw)=k{P@$sqVr0aZt=$t@81OLeYi~iKC^#d&8%c7r^;R;zNPjQ>Vb=tq%W7P-k;}Za@#$XPWgqVoBW;k*-W9ra#foKb-zuvUf0EsF8o!{2e6nGb-O z85&667^mac;#2LJ)`Nc?uKY0V>2Yr8kbw|OI^tMfwvGv4ufZ4O8mRJiwV1cJ7vEJ1 z(!zK8Xu2V}FMEFzNO_HxhnA}Z=P0yP@#^tj7al(!8yp%%z!fO3LH3PqDPPaXd`~C# z`LYHikPc96<*+5w3&9X#=*UHIC<1euyD_kkH5)S7jNA{hIcrkNdQ&f&OcV|mD?mIq z%$uk^#Vgjms_a>ITHY~dA8OkMlktrCcXykCpsjwms41<>S01NN9YPi;clP-OxWhaD zw)jBI>^*nRm~Dcq)=c+mIBf|J&DAHrLfe1stgX8K zrlIin_%=qN;~fq9Z@Gje11SnInc}3VOd<}$Pj85vE^4Q2+ur`0hVhA8mG;ma5XEfAGwbe2<4eV#0MmIyIt)# zBQCzSZaXuQWbGfXv9o}b5-@S7^TIPI_oN|5sEqZwwOVa9q4HyF>P;_k$&*7oAOsy^ zhcdnPnuIc%)o3eTqs3mf;tmJQG#HNg>w#>Qr?HY6 z^;ywVgCE{`Gg~XnCXl5vb?GPh_nK{aOEm~mW!kTGT-c{C_QOu{75>>H_Y%J23@Sv= zTlAr{SecpDUHVOh3ka_NXZ7sH>Qf+U4S}8PsYm?t9qb(yH~cMAxaQzazv=rbd970m zLG)urGlDp?U%92M{8By9;oEY`NEIPYiGT0spPp-RI_yKwZ79(QqPi2;NO6j&WHTX( zO>Nc?G7!~$iG$;=ORM$vNPop>1|5v;0NI3Lljb5gt4lM-c5z)!YH*Zf+kILV3 z?nmm|&!I)tEKAr!`cxbgD-t(Lb76=fwB_|Tg5Qs0Up!72FSB@iKX&Hjj^Cl1>FUc=wfiWBej=t5l~eb` z)oGu*PLtgn&p1BDNK}&*(X^CMYQ{h()ELYH0b7}S0?4hQT$e;VN;RAL(C=e0lADRf zld!u)#c5uFNFPYDSqr{wBS5~OV2_gG@`1z1z5$AlN4l#J;5G zLsJWjIv~c|dj=I(T!YQNhD~iMrhb^d^>}y|9Hm5##&=st=ISaG+Q=zzZ$MkODb9Q2 zJJ%p-DR@_@2}R59M{DpUo?JPYV@S=((KYhTfDEWs-R*Om0g}+Om$?4d5)R`5UDS;javufPI*4|}h5bc%Cog$*Es>e*S;K~j45)@oFFTzweyj+FS8`xV@76Zq=@QMbtfr9#lAFkCQkgXeTxW_p0#i zLvkU@l*}ST-|>zOOk$|?QJyl>SS+kIxQXwOeJC5x?Kg7Kn`e%(pBU(MaEKiVwbG){ zALvcASM{Lnw{{_6C@z!u_V;oc^#A(05_qV-_HWG^*>^+MlAUBJlq@Zzltgx+Y{{CK zCQ1pBa$8U#B%w^n7AZ?AOVVa-U(q5d;s4Bx{N~Ke`@f%eem-ya`JVIa&vVW_b0=lJ z&V^sbW2_2XA5^~JTV2mdT7q^b4vA2cp9i}ElU(Rp2x|=zc7FeIcJCUFR-=vA zgAZ1oeYS0Vrt>a*x&6E|qE-Bg4>&AJSKiFX3Q0M&;p^E0e^~0)*nVVpi!YE=Jy0~d zY|jCiiZva9()S9PKIDB!8<;nhJZ!VJKm130;EMQy$j)x%)R8iaM~%AB*e-kiI zv2Db*p{QtefUUNO{%Hx(N~UX;YbRsy#3?Ai?l(Qo;p)*MTmza%Y!%fzX%j z3Hc6@I8NUBmcU!p8kgR_dY~Qfn0sH8*1oEg4-5N!HdNHGYiE5t6TdT$)!}aG3WMa@ znwvk%lkVP+i=RI4#`BAr)AsHAPJ-)1iHd)I`N4`TZG+2R}HoxwXAj0=sw zJ83`jV54k87pq{$an__`Iuc73*L=ITQOaXV^ZMSLy2+4j8puxilJ$^quMpxTLI?zTMF9aamP z0~#`4Yps}A<(BpQL;5`37=ET32E%xs&8OLJHm7T{cepqrP|G?P-Ze zS+Wfh>KxXGZk)cHQGDf9lZ3O}OE0PCQIYE%IX-->WaYhNZDHJFt#ayuzHNI@#tYWv2W-)D-g)EWq&jN z&Wg^g*S|x)_`H05lK4gJRhfU;XyClX6Q|gg@qG#@mFrFjOSt*yezf7!rU0Sw?9Adz z2kZ?bj;EX!h%2#}ywxWM!=;UCYhRw+oz}mua6~trPS(B{B6GUR!9WLGA9#lI;73eeV`+Umkc$Zg6{P ziulMbg8B`YVve6|!C1+*>uZeDw7oX(_;_-u%vYEqT-b;)r~s2uE8-g4W|q8+H%&mxO1J?o-n^van<#-l3P#3DdvbxcUNy~NjJW? zOH1=Z6~2O%YgMiCy~P_NykeL-9t~$6T=msJblm5ip4T`1HOqT{?R9*)=po<56P2P) z$D3T)icVFWIV2V>Yx6enhr+}=q2G3F#j+mPcBPf`u%!4`jit5dI1b33x>0$sWcI!^fbWOGD#dlcjosf7lV}HG% zFxlX{u|HIE1e>+4j(J+Cw+Wb8yDBDq*4U{pr)}*o*30X!vCB;9+Ksxs2gN5WADR08 zx>az4Z_&YYWkI25P29&U@9tOLECwf9cZHOkb*VlQ759$YqxTT^o#41JmHghy=U4W< z;E6x+v5Ir`%x=8>S%u3?VSzj%v6t>99z5vdc~1QP1Dq?SqWDX!DOZ z?+&ttb?sZ+^|su2#kE~~s>TIGj@{!}eY^AQ(cinw(|$d+WOX`Vgx6Bc9(#0g{->e7 zcQqkBmWTbezYA*EQ%HERREfp2BB$)?Sn%VxrhQMwb51y%y7yf_qWBQ6n1vm;vC7Zr zf_43Bw^>G%tl^io8sLl(ZyMMzjvp!8*5kP9+OY+Pmw2xYIjG&LxNdWhrKGBy+0`3|g$}Jc z7jmPdZD;sp^Y1CV0l&XhM@tru?cC>gNc*Hi^}ei4lMzP!e33~)?=>>n-+pu7$L>F7 z8Q;bKDO+MHY;XM^?_K$p(b-l{vyMX zR6KiE*lqMQ5J_iQV$qQ+wg1trvoE4-f2_N{aAkt%+tSgg<#lYL?)afm@0kppJt>)m zW%u2Zdz8QFd}jKzGi~uTq4RIjZYnnyta4nxq&%|W*%+%(?f${vebWsQ9o`om0;K&< zUa-3LV*9FLbL&(td%seZ!Yx0(-M8?=udG-*l1{X@JrEqU^NiBR;*sw65!F(SW?Xx3 zV1CcnUa-s>7siUGLwDdV>@46=?|OfSaQSiW@2w|0rP#k-Gd^1p{CL3|vxo1NI)6<* zc%OajaO(?)pgYqawsZbXB7{D<{hednsP7j~$jyfxn$bB@mtMMTw z<#TyW;h=+$7L6TL*I*rN^*g=%R@{Zeif6|Nhey4BoAJeNYuvPSWx&v4MZc4Y`>&d9 zd1u>MWu-q4>*94v=3anJG2zXhi*eEt#dvZsEf#&3u zk6zoxKbXbrZeE@BFezem#e;1Qd?_Bsx7}x5Z0B;|Xzat&Z{ib*3>=9`wLwwE!{0Zn z9d^7{<2P>a@gR^*gK^Tz1F1r5;b*iRIG;|9l7SB zCj1D;8D!_US9F0Rt76lkeslg6uKr1Q*0|<1ZgOic#QCM+8?ViiRwz(tu~PLPweYJk zSbAbY;O8n`d%m3HAg9Jit|Mn1O;emxISf~tr=K{rDm2LZ>$3NfPq%Pfh-!%Q?Pkl@ zc2w}WoiZhS_pm{3+^(mUe~UlmD)^o&y7&D}x7q6~zvm*`cCg_V|GnF%ws!C1w(YeK zC*yd`TO*HG?d-^p#cP!I6cGz$o7Pot-C_0AJ;Gt+>Vp>N=ta+4rnk19Qr%y}r}&XS zV9IPX?r*TeujetmH+P7MF>jP@sTsZCcu6-{_fKbs;MvCay2_0U53=Z01fThtbnCup zNkh-3J73tghz%(htygXHGk$Gz)}iOBy3PG6dxzcp#pMI5+5CFPmg1YoZI5qxaEmLa zWO?RyllwUn=Q(+FYQ8>e{4G(=d*I^e)yD=eUHmqzR9NGsc7G!*GNhC`E@9)|8#21L zrF!rSZ`wM(l<*1JPsEercP+2G}I;bPwcGI=oNl%w4fT6q>#^K0aT8`Ip_v9gEp zo+>WvDvzYR{Z2+ddQf}k%F;T|$-YhD|DOHuZ^B=rpF)O(dku*!+q^Xn4G2ov zhjub4=Vvuu_9!gsZU4e&+0uW5O(Z5Sr6&64$NG6kq8oRJD!jbCp4G0$w#x9N7Sj&= zVav?yzUX)62mPrH_Ly@g%fBbur$`{hMn|FM5GKVn1&+mZF9ZA;$1?ISkV2OlN{ zZ)jK&bmMh4A?7*j`{S=lAJ;MS4jp`Bo21~2->_Iv=Z$oLu%e-`b(x`Q2bV>ey!!aa z{rX#ZxRWtgZAAC}Jn*~9IOf)yZ)^7ZiOFh6Dz51Ee38Yp7q=A0Xoj{>i+&!5?AKE=wn~n&*YxUy>WdeU<*F}*{HV{r>T!m3>3~GWn+N+#G|kg_{rV8=gEK+r8qgk-pwbn-pB# z@KO(-1CI&K?Q7*GeU~iACNAk{w4DBWjYF}xPVVzyko4*#j@Y|O1+^K8v8-z+i5oLZ zI~B8+^E3=q`g}INbbWV5h$qtLE0oL)p zlmoB#yGJ=l_M2oe)jDaajUV6nS8U?CpwWcPh(P}dg@rMX@_BT1FMHhCcKEqKppIIh zclf1jLOe-nEFk3Q2pS{ok0B7<+lnS7DU&S2kCch zy(pzDwqrbX>z%8YzWi|L3A8E;Pg%N8&W702qvZOhWcqx>)Fy#DnN6u7f+{PnE-TpC z5)=Kb$~E*T|K+vz>ouAkix&6POa}IbT1_*(FbEtz_)z?}+w0zW1%1mEf4%9i3ZMMD zCgh2=)R45!$h}IPV{1zA$(?=Qs=pn{HO>0+VY&MDmE*T<1H_k}R^yDgBebGPu&TN1 zjA#75l$o~b7b9F#D`cwU>g)o? zM%IlU-TgRvV{GTa`AzC^EAH^~q*>zIPuh6%maTd;Xki!bkN5Ij9cK|2By;vVL8|Y@ zn=U=h)vGooB%Ku*ku80x``nA4{oTv#u4m_;?A!3>a_$t%q|r>xeH`&|=WqS_?BS<^ zma^s5s(z_S|9QJN+xthAt3{LKs1#;mJGD>vvVu!w|I@TrQF=0shEYze-dm$G@?G$r zW7fD=#m)1-XWA=CefgYtYjMKnmRi>rnw@H2d=D1>axG$8Cz_(Z=&{p+AYB&D*zz)w z&T8D|w%3UlA59Le91G(*jvKtOAvU(**Ghi9DwpC#>DJjwMHwGWN@WgxZPx!{@HyKL z$E;8&(Jk7k-@5I3d&MRLqR;8ytu>SB+mGQ1*+(i|;}bLR_QHuiADyomuk_5#RehiK zd~f@C+qT=E7S|na!S!ig5pmnJ&YDZwc&XT9oj%3?ZD7k#u`211G=9HJKs=e5`OBSl~eQFZFWd_rdUANhL5=WAYU3PH(_CGAq9dCQIc9n%U+cB4AWs4Q7o`{8p%cWttGm#&h ze+YMT*EQ{xalU+}c%$UUbINBw^lZPkKx^AkhwZjoLYLqC()RuKS!L^OI%^%tzeQ$A``5s0+|Iw_@4Eg4P2KK%_-Zr)%2eNX*x%uvV9do#V?|D0>6lyM6`-jgoXgFjXhD=2gBrz2~ctGlI9 z((T{2YsdOR|8jjzx_0(=U04e)=F#)W%~u6P{{A$_=lo#DpA6B-Rp(oII8^8OzLwN= z!yY?s?BQMg-RyAtcBbCw2iG3%CTPaWmf00eUfV0szDYt%?A5|VvCw-XtDg0R-l}R4 z?osS)zv+E`0oQ3fZXEA*^-$E;81_m2>A>lrxQ!7hb#KCM^hLW@i@zFkMP^?#f@%Gr(Mr=uD)GfRp1KJuXSU+@t8|Zt#bF~hAO|&!NWCI`a*_w z#}i*MpL=6&80BKySG+kduZ%ZpFsGZ%Zh2i!rhV7j7aREAZ+g~~h0hfacR&0b3(uZ( zu{01a3$s`F^?`$VBYviHI_ND!Fjr}`u_nJh%&R&y?7bDAW_TC<`*<>DdJS6j*Tu)wt z$IhPJ+nzOjtN7?Lkzf;e86T}WF{Lo$?>jvz{Sr6!{$$Q{G0W}Uk+BP;FI29`=oT?b zRgnvfmNQ<@f7nDB%UH5@XGOYYZGJ{QA^3FS@Tuuu(^rG<9b5(K^x}G^A1O$DP%6H7 zz~a`6OC~?RxRlN_+M_AX5&GkUH~(7ss?no7GvAA}zb7qC>W{j=bvd>)2A}e6&AJQS z8Kn42bX1H^aJ*9+I`^E_S|6; zMZ>MHYh=ZRWm;mTr}sCVnnoJ~;hs!7`#Z~uCCv!*XES7?39%gkQgc*AaScvR7qY1Jc2LJ{`6=apCY88*FpRad_p zUw^g!H#f((OgWe1_K|kAJ6~%wHFLYNb=b5eehVri59o0or{7Uocumgib9X9hh z>O-{WrKewT*}Nq9YDHYZ@fy|rB|g7eudiBm>BjOr$!h&I^Dfl8+*V)q=gbQ$=09;4 z^?eN zSk2n<>U)w#>)m|z>0IBt&(ye&oJw9l8W8oOqO&T@z}Amt^1jt}+>iE>96$GlZO{Fl zFR$@!{gG>!3&- z+vd~iEQl8s{cgpULt^DZiO~@o zcUjp4rKb7B*+f?l#9%u%?a=x;ukeQO zNpr1=8kC%}pJ;Fu;_-T8^>bAHP;iE5o2~G?@QAMd0Q`@Mrk{34ZgPx_OYD#m;=GtD zQ|?rw_4G}}#u>}3mRv#@f&%f|2MZHr@_?e zbA5!-c;f4_M$NA%jhbWfeXZyyILe8d}$-jk^=3cu6y zwZ|?z&#B67ti1Z+l>E|+$KfGew$C=bU~5Qy_r6D0UoK!UH>AFJ^&7`koJne3bf?d3odMYWb)|1>Tbc{SL^#f7hHSvSV;5N@vCd*ADH5{ ztIuwp{9|;=ha^STqG-zs-O7%?cd8bP9dM{%*|cr{^n`xuw2aoa z?6&81`p+}N|AcaGI^cdpaO}KqhLPZ}mbhe#5OD#2E1oC&%z2oERf^PY`?ie6@DCVN z4(5Cmd*dU1h*SO34pHVk4n4b8Hh1L;iFUg-?OA*Iu3+||Z&yuKa7#bLxTJUqr%X*u zoSR|Yu$Gxcm{k~F(#*-!4X=x~{aiCL0sqg3nTd%X{{Fj&*rWkYf`^I{!!5&!D-j7w zKnXu6tLf$z=;0sa=B5!6O*<&&0;J4DEPNRbhYWEJ{@pmAAONqECJhl1tq~R-;^R$= zm%n~mei=-|3cmmVKXp5I5wY*fa0msXT~{BX$RVYqE9UJ;pJswIsiX} z3%`ducM%CwpaQZK{y||rp+O!2nqD4Wem;zZDS;_iiG<}?iy=-~g-Rp$unP8Cc;9su zylb2E+UtK8k?=h? zor7Vh=M)guZsg}-Vyc#7Vp9A^2!Dx$?^`p}oRk;PssNKUCbPc^Gc-ooZ;$+>=LgUP z_>g`(;on6hybj3_7!bzJ7~>XD^J#B(#djVjyaXJWRQN{?e~E-cSf?>gjhePCtx3C& z8wOqoKUIv-KE&iraEqvE{e`w=Tgx#qT?}Vp!XdODun-fR3bp(Tt=6lnfosfTrIkPM zBDUKECz4@xQD6zJ5_SZY+!TZ#PG$nlAr3`&Q9u$^m*mO}Y;Sf`cCQaO>-PnepP~52 z9Fd?=-f3fI?(*3l6J%3?&Tbmm#qIcQ)4~Z z7%VGrBGiiLXjD#E1WtV0ik7v}M)EA3jeMB<3LHD)gdr=)PPpYMkcecgf67hA$kbk@ zweTu;rZD)RhSMAXkOCdx^T{6VJ{rXP_A|TPz~c2_IW$g;M#+eb z!IAlNvUu7x8j6CCI4UzSsmw`2^6=g<$~-Yd4SR!A$st@-@lz0i<~TlTebu*44!s6_ z6@tEG5d@bo1yN&;j%!49S#mERTKc9dQ1|fpUt|YsZtpu4&&p*i9 zCyD{xJEM68wlH`Y2!Z;QKaRrE`IR_+YLBd(}XC zeQiuGTvN`e8mn`RR*)QWFWcQ^>Q3V1F!e zqSO#qUI$2I-@7pH= z`Q(WhEo0hkWQZg-YK6*TmRE4O6CfmaRQe|f{3Q~Obk9PjJDGXuI|Em1vRqN^i)|i=d;JK!Ndf9VniEAn%tEwm zaPz4ldfB_DYQU#-eQ2Xf@yjeE)&}+4_v(i&9UxchLZXKM&0R!-6bDvrgVUfMKib@{ z)B{Ko;j?6pl%yk~APY$f1&o)Ug_}-#Lh93>P9VxQ@Od=^=O#qPEun*4I;VlF2Xpw# zK}|#;%wn@s^{qimLK*i!mJ&eZpbcAuKse>G1#59y)S|@PQ=K*jB2)OtA;e*eSuoS3 zETUN4TGSvSTIcPeA;bqj5_tqN{15VAE$URpm!vX_KxiT8Nl~+3TrxY0)H<9XHDjJ1 zQoQ_NhG!rN)X%w#NJ!C}g_LNq2+;ds@;b5~8ZIMKiR&^hAg`BZ{apdcg9U7knqsLD z8Mp+KTTj#OXssuYT(@F&{v0C~dJmV9{{ATdwDStG6PnSlI?m#*tjFZzEVYeg18{FOue zq0ueoH48a!ixWW#N`oy~?07#4f!_bBD111=8XaJR^g;ciJ`lTYhZDz4?a)?a)z&pL z$s$Zl%Qw)j8MJ~Zh*Uc?OznEQu0;d?ZqOd0+Wi$zLMS1ee}hS3IahLoGT83}EIgRj z1{$lHZ6FhUNTU#iZNLd4gQQdLGo7W}Uw?vy8|$DQWRODCdW}q^7aryxu!lj}QZI{f zs|JhBfVI(F)^w9Hyqi7h8zi#=fPA68QCq`;rBAU)VrBMdd`>Jb;K#rvG9d$^c3`TV zg>1Oa!b#^&?@V(0^dZS^g>YLqJ0m=5Kkf(oYV>9L2A?GlGpu+Ko3Z@q~&X0__5CavFw6Qov^e1o*|I24}E zdud}Tu4@+NjN`>{&gcqyQO~yeTF6^AuqHG>G~DoY4u)<;W;>IW*7b{un?YwZ@svnr z_q~S@k%k2-s<{F-HcWnuRf{giEwpjGL!g%tss<6dx*Au8Cu3D)^^IJ3sx6DOeP zYC(cSr%L3T#o@n;&(ma3^r-EN7Ck>%OnW=JjrdFX_^xNbpBVfEs{*2_LNc)!R?Evq zze~0z%Fy%);ed>JT06v7%A%%T2Np$ zIt(m*lKpJuXRrnfggx5b+_a&<9vHCE@5SZy*r{&?2YEo3<6hZQU^X5&0cu4FZ;S0@ z2h}bDMWGowY7<8Apm}mo=0Vm`s~n3|!in$Xgx>l3oJcqApvSL}5#pQ=f=ie{ z|00K2!aW(Y_d<89Tz-2v{{^*d5}@r|g3>6%kH@hI(fPNi#7CEAu=FnlTC1{ zFbJWGCJ^Y%NvT0MJd|Nezy8L%BRJ5+Tj zCUJyg&vr6INY~e&(WcwO-H$pZLDV=1J#_J9^M(S%N_U_O|1YaDHM}7=b%D#HM%@2t z7V>8Yy70HGd3G%U_>yv>tpM`BXCYR;IMEGM`)@vD+fVEOjS`@sLtUG@h=g|n?34#{ z5k5iQ!J+V&(LcyPjPYZ}E50~!RU&~PjzaeMGunX0hJhN^me&k1EDbp9-k(;vr6-Pg-|bwoNt=9~cKLKMzjL_%8OEM$v6TK;5BmCpCVEaw3x zKL;SqvZM^#AApm?O8jx0)M=}cJ-7A}n4!~(cH74C8VMntnZwRY=aO2^`?0Se$z9l% zP(kL=Z6>p(N(V8%&32yjK%x&CXk`TA_i$F4(VeJgMJyk8Wrx(j4pFCsKsG&|g?y@I z=f&)H;n=Aa_sj(E^A>D$1biP=+}ZwFT;?uRaY~)Ge=A@LQq-c&>Fq(18Wv*pE$sB= zTvx57_$=u914I}^%-lsJ)D2N!MXl`g#jW<|uS!cW(@tOzUFqe0Ai?yhEp+E5$4h${ z*%dx$Na}ya`T}suNHUopNOto(2f%W`#KljKH?NwGAgGe}+>;>i>&mlOQs!tsR9H z3kpK}=6&+; zQx7JqZZwm0Uk2-l;-v3e1zxqdn?c!K0{IvX|M9aV7_v??tWYD|0~4Qsgo?IqQTZfd zG0Z&#Cm_V2C&1K0$h;gbo}-hY_KAb`%`Cy75DW0Vg>zm^`4+237U_l{=On3Df87GdVoZ#fb+;`JBLC9{h zVYlBTQ_0XJ1(lb@2T7|NR~Q(TgR`C!OK;>9$3}OfzTkJix-Aeg;z~D?grZZ49Q)z%)DYI2X|z*493n&H>DrQY`-kwR3}<;I ziCx_TE0&tS4BSfVNV4K?aMKysSVbM&LzV=SK553qK{uK5m-{j#kw`s|0%8^cOA=}^ zwac6nTYg#+68*hpw8UvP6wdtBxac=qJ}bUplGlPv?@il7e0G_Ig`uf*jf23*IaqbF z6wqqTI)#F`9!B#Bv@4A4?Mn5O`_t#S_~@S#$cK}O@*62cvH4tF^h5y^qSmJrqMUG? zB()N3Ryn^s2OS4-KQm253HnJvSUu&UKS1KLY~#fj&>Eb;Gt<gWXe1-$^<&UGxH1Qv#NP%TV&@A0I=00nU-5+{x%!#el}XqU!wDECJrX_6&h6!|`D0qMRN z)}P3|fZiG&6e8PF5|IS9IE|a$!Az@_)U&~fH$hfaMx6LT1qr53ZNEiZ-hH=+Y+4yX z+gM+zBoRwurqMWVYJ_N3QubRI_Ba%FG_nkvNeEHwP&8UOT!wSM+52C&KGq7_>JRtOmo<*%iO0y|&HP2WMRQ42DB z0uUx6+Q;@Ty%gAky)*~-zV0PkEb24m{=yj4M`lbXZ@NK^{|noFSAOHi~hfT%RUNi zzW|w*zPvfal=zy!c0?4klxTv!AVP*nV)$R&^v|6i#Gg213AUI5ELy8%7gE^5*2JN` za`p0M_xiw(l%O=COU``dSx6iw5B)==RadMY56^>#!;l`(?y_ddEbeU_jvukXOdMH7 zZ*_UFZwq;(v1NF)Zk|}3Q!)x|h8XMypuJaw-Yg^;kFGmKcm5hMfOhQ$m8^e0C;A0)C^$WQ2lGW)f&I5PrG7Ih<#87i~|Rp;@DVEeUr7{o*g zg=*!7S*ig7`c$vYAnL_6sL|i<(c17%%q%2pI}d$`EIy8V{tZ?xp0K{Cc5dLOsdqCzciu!O9npK3c>Unpw<)4z>~65_Es! zE3E3$;~_aa$D^W}J>R}CT#1P(`Yi1vI`hd8^nvp2crwp#ba*NHtrOWuI zGS#CMvs9Pmc6xp6@1?PiUP$q~3Q5k-Uh@;bd z^bLIBMV;4iP|uz_(`x#g_bjf>lAjx^KS-mRj)P>TF2_-*^dfb281bcU!Smv$I*59l zR#a}?1r)y4971=Yr@!WE*fxPaQ&Ca!2__yG;SkO7>C!`F6Wvi3q>MIn2;Fg;bPwrM zfcbkM08v+xfFEy41na0JqMN8P4z>O!FufUkP(;*55(0Ikv_x`Q`bxefqE9;I78=Z8 z;dU^px=0%Mum?WSkXo@H8&AY3Q1>Q2TI0I%K&Sy*kzky;i%2*@z|0QgjsmGHc z^al{L!?Y!}z>1R4IVm7I8&4-gB(S(-wAyRseLwCFOkq%l6%mN<=~>3wlhI}+@b4$n z80a%qKoB_u!gZDm5yNmPs4PJl=G(r5eJo)65S(-FA`%)e&O!oHXsT;+3OQ-~Dw!o{ zNTF$9`zZu+trP;iFb`iwr-00Ve9&zN>HMI&p{e5i^Vy+YQqg?Gqr-D?6#8=;D28%# z%#wWX+aQ*fif%wlZ(3TpEsTjt?=tPGRQ4-2kcwME?Woh`f-^3lh#Fg3b=P=f+Q-lx zsNGv~*H(aS?$X)DX%~rD20MQYO^bh2_mm|AwHkOe>HxOz80#23-2ZwEUGM*tC~pV_ zhWh9_NU1$!gpNmuKZ8`M<#Vlj1cJa7JQVz3?jjP}qA4?_rlCq*CldKf9#&@l5I1sj z^dzPJ+DAe3r=ck@B=-82-9ULBKBzQX4pI8LanpUFEc!GHd3XZNKL%Pyn9?9IzJlU_YUbU@ETo}RnEpwLr)Tf)^Pth)bnCL~ z@ZKk|q9}IknJ_24r3fdJ}GtY}~W4|-e`kb0H^*|0%%?5Bb6vdhNA8Oc7gu#+15F-n{JNZAt%NQDKyYbfU zGQiY3SY4>ju|$eB-Sjz>o^NRIjvWkqdaT@EX^k=z!lSfn*i38=jcq-J~|!QGpC^rN^WrN!VIik+^#QLAkM! zS`h~R6nL7<*{N^;5yp0laMH0i>eS>g18F%Q60OD@b0oE`nfMQ?`9g#b<3B^QTELwl z&v9~+Oo)Z_i!7kqhcDTha{MkN*g_~UsQAy|>^dYRdEGq`jE?@eagO$`1HG+;4ghtR zAzlg)b3KdW#OA|)5Sz%IB}Y=Y(0^1y2*ExIK`E>^$pHz;N(>?Bq7dk*|3|=y@n)eJ z0=M2;S_2p>@1*Tec>MmuabWIQ=#p0UZT((r2!k-FgAkr`7m=_pV-^P)f*o@X7oAU^ zCfc*e!f~Az#pM0P#QxvyDw%9D?d<{zEi4#B>Gz)a4y&rjL+7#<_RY}TxVvl?cP<;1 z;LU+*kxi{kOmzXQH0^)Kty##IZ1mZ7;MS9kyLsS98(o>*o+w6=M3K%hEp~Y*ZUp^~ zXVO*+{tPT62Q8jbiS7*>AXQuV(Gs7#MIK)a+k0M&8ym`@apA8yWF4tL7sI|)i^nJRlLYe)YOesqj3As!jUk?C}m@DlLrR~%#k38pT zw&gY8AKO*<(W%c|6Ll_;0E&7?{Eexf5A!G{OIhQ46iCo25);@BG^I{D^rjb}w7Z z#IzK_aVYCg&UEn#IOz0 z;`HarFPU=6{)H^_Z5^%im@g;eL@_C_3nrIG<2+h_TFRyF>HyYIQ{mo=7y4t zIw(Z@t!bnE!;V>8YCan6{bo9A-b3>=3i%2e@wtmgc(r$y{aP1s`hC^J@7pv|0jC1V z1#U1@*WNglqJ9zhEcEW^?Y;OjBNY2x(zgxA8JXzYhFbriZ9 zG^Q?fl3>UQ0eiu9RnDLjcW?((m%E;lh$XN|;EFnAZ*BHj?gX3U!~?XRFt2Y`kfAzp z2KC>gkX)P3uwxBHFfQqIHd5oRQtp?}qufvaAWq*#-mq#|tPdJA2l>$Dlo}reX5s=B z*t7`8MZIK=ak;_q8}^xT8)#t{7EoZZ-x5A{0h1@K3PA+y!69RwBWW{TtF< z;Ea5Mdgd-7;er}v4*Vq?2c~%mHL}!G4PP%H&VrBD9Eju>pZuB7Pt2mSZG{2ek}!2MT2-9Jz7ONbgc>=hkDy^Ed{}G87E3T$EwdZdRnkL(*uD~ z1ZzD7@%+35{qu60$CbUXLC?emlc5_Xdm1S)n|ulShnmB;?}#P>1P3$1X2RSBt>+yI zOzxrtH+`m?UqV(CcN2wZs6c}LS%>K5cT$xw?rFLu-_1@6EcKcM{du2i{!g-`SDriu zm7!a-C6CE4J-2}1U`F4JzkW$ZH4l)xz=Kg8a6X|-I8ezz2Vxf-)epjC7$^YE^zWZi zV0Bdz^pW}T>&Uv7ps+%i5#_V6j{=LmB|+cpGkwIoA44#^!@>>S^{nrwz$UKHY%vIw zl9he`4duRBDa~097s`E~AC&t?OVL%C`wKyrx4`HHU=*D(dW?dwdm_PLN&kg1Z01i2 zQNU9P245vLDI@b%$%PS!5Qr(C88|}zyn^NZkSPYC?@^j_F;c{r^3K6HvAQz!bh+A+ zWSJ8HBW;eN3U248z&Ois^O1}yS5D^Uv>@faT{-&wpu-n~?i4{>?N_B8(OH;`5cdf1 z_hq=`FQJTVBtsc_!Hfj`;h?sWeic$_X#j_ZR5f=I335tgn2x7MD1(Ksmh{X9H_%Ne zw2vsRd=VKZ>KV-NfbG7kWN|GZr;(I@`6{}3_tB!c|2{NTT2{2;ZZV=DmPkv|d#Jy~ zd|T4l4OtLHeohFIxwfvSz%(=^=@;2K>o$Hn0jBDOFhWzwB0CCfMoW^uc=L%?NjPnW z$5`>`Ln$~2vxNeCu#5rh=(C>c)fU*2nknJ7#Au3xQkqlev6&wVQrx^9^PrlzB;0Nq+SlXcp{Q;B!lHLAP zpe4Z2sx0Rg$o2q|CtAIv?otqIh-gh6n$NL%0;ro{m5+vsP%{Z3heSyUk?b}r+eo*B zu|6W|HaiD`g;OBzNJkb?9r@m;Ouzh!#sYE$GyUNpb5-dAUj+SFtaSEcU5>5FtCE{!W zpt&k&0a1UoUr0tQ4fOHY6VC8qgzqx`K zzS1Sh`n{+Un1(l*R+Z0IQ06L?lcN7x(%EkBI1gB{8-1jWNPSld>~9s0li{)AhgD?T zb|%l!AuT0a$a|-^ko#4Z&@v>ENkNQ8GGJ)ysmX3bh|yp$6q+KIUZB8u})u;nMecH827>2ZiXOU>cZOJDgaMYdZaevmp)j<4*j`;i~60tD0tA^&q zJcJtZ^ipLM9?NUc%wwuQUAhsZGJwp3hGugG1(9`9iYv30Ch;t&C6Bt{28G777WJ0a z#TQ?11wKhHR6+f#rj3F)OoPZ$)!s7>9%%!Tp?s|CBqQ|UJIi6=jF!c>mYL+0h{9XK z{ApctZx3a%4V6+1){5G7WEr-9p%A&(p#eRzt!&*#@LQiFv>fdmp&-mUr5N;Zw<*IO z<-?fkal*PDo(#XGZKTj$KMira7$OiYQOX)*AU4G>O<(cvtSPx7r2;2FuF`sow+J!B0kLFgD6W_gRrG)fr$HcdPoqY&tqQh4J}lb%Pnle+QoQ_W)FYcco>%G;W& z6c|q<8r*ClcR7S0sQ92eMpKsNP0X~B=IC2sBbmv4wbGbbj0-}*LbOrS)Xesh@RR;qt8gs%TqS_aSECqslKLOeqEFsxx%)*Ujx zKCdX0*uy(GZt6{>icvw|CUE9y_@GHh;ynd|C%u`IQA)|UXvjla)@Q&6jZU!-(#Ti$ z!a~FMgzX6n4)yu}zQreV#&^tv3wGI*5pTppgYW+Fe-FrbXLWz%`uG=+SY=n!ZrM$J z|KDQ;dUyrXii#(_9o4{+xql2?UKsd8WfftQ`Jb`|hxi2T*~6&$_HS_MXac?M1jmC# z-`qtc*l^N}ho@2=4CV$MJ!JWyFj_uIY{srN{k8`-RZT2t*>vEh8J23)NO)CWa3rJg z?BeBAB}iXQV}}fkc8(m9GFaCZ84>L3J!$$QN;0NGp)7zgUQD~sR|+Rh$hhLW=(|fQ zSX)ZWKoO)5I@Px&vyjQVX#N_Fw>P$gmFYS=+7)4tx(v2EQbrsb9Fyi?bWq`xO1v?@ z8a#Q(qFwv{u=o$kN0(n*AJ|;~3{D^+M?2-}kXbG@2Ak3EL^3($ zBv(MjSAYVBW_`(X@A%+R_Yvs97yaAMRw>HUqZrcR*q zD#)>lb2=dJT}WUr@5AHf&Q2LlYf1V_3B_(U*Vx?r>4!!TV<0^l&G`{)!dCnQRg z!R!4KX;VSsETZf_x*bO1**IAqO!Fa4XJY!0OxY7kMOi5? zOTS^m-QmLF3f!&+CO{{17m-l0cMeMrvt=DW_ZNu2L&-p48)D~R?3hG5&Ay{{JDIPe zaa2Ts>az5!gMAh34%}vIP1csfZSw zlI6f=I%!;rzl%(%^9{p2f}@u6&&x9C?>#6)1%D|-r@Cl%=&!I>n;4ryI@Rhg^ zU1+}ZT^3SU1!m8K-3ruxyV=PQQ_xd3yd54xyp()bUip{+qW>#yz~ zv?Ey4(R%gjb<|(UAPLKQWcODS7$X)wO7Qu2YNdqN3+lk23Acxe6`~sk@KJ|$KbzuKhXkr0A0sfx|7xWhd z4EH~|WCDm&X$^8h z4z&P;kPk=HsOg!9B%G=N&)ltC`YiXPj&Duc?m}8aUrkU86MI7!h zPyOQm%duJb04cHC*LSbyBfo10d!U?DP(afi(v#O5*oalSoDIyKF@I!C_?9kU{PU2l zgu`;YE}Wcv96<=Dq6CZwYYWQ~L57s>!0c``o`HR&c`eza6Rpz(Whax5IbQ?;bBhXw z1hGQg)p;WoezC#fRXwFEY-hDb%3QdNai)>}1J%C!dp%^LoF~KnY{7Bc4K|Ta*}4YOEWKS(EZ-$w`sZ9vvo+(eylOsAj``}N7s)Ju{{?AFaHg`>u7Y-ebC z!g;6p`|-N zDlKX?r1C5&dQy166uMMZHY&f#J9{a9C+etSA@)QY+qtbKZI5@4KZ~1xNQ@i{n`ba$ z#;laZ8k=wVF+Av8N$uUHPWG;59@dcXAc$2mqm*?j18}mJ)m9PTq9l6j8hPB!xPRBH z0g-ziw0y`UGuo%twoK9ZI&PFGj?0y3b8)#vL6Mf&O%oEiW#(CbOs!gk@DtX9j=S0k z8I)2r*=~?;Q~V}Olm@Bglio*0vTyZ$9}nFgUfUe>DeCfjI|o0sl&owcCQkO_F)B8r zLbj2n0rs{j^cz04u{G!*1{^N;cMMM^UPO0+2!JwMy%S+$#ab4!X?=e0CrwlxSS09& z)_Rx$e-wq#y^s8$i> z?Dva!e-%^IoT^mJ_M1?9E7~$f$6L!qscTSe>6~#~28z>$e+RA!lNm;MY-^$hlhkSb zA62(=F5MdrjU4QfNtvm)=!O3nMzj5Pj`IywPj`j{o;h_n?8*oFfm2MtY$YP?w=*(v z0pzRUOk+1I>`CzOJap8*uU-B78Msu+!4Z^&PlJjGL|O(= z|95TCG)p)<#2^~mC}l2GVMk?H8&R3l^ zZW%5=yS7RYnjjWGL2@-RYTT8LL9K4y0ah;`?l43Kh>nVwDU=G|8cR2|v-JkfN0>8I zdK0FW)BRXut|-tV!8*6nYbixLKpZHDkBv-IclWkXkvE>ny_pyF3EAXW345vf=|g<{ z&zI@B(OT1Ej$#apvZfOe0g`*OO#$St~rhgI<1fV7P0g5P<* zmlmW<;eQsL*7(t9BT74qt$BJe9GgP^qP~Uylmppnyu*Cl<4^uUl5gus7w zLe<*To_vXiPRdR4{?-y^f;h!XmH(#YpvTGaQ!u5uoKQ^$XorbuIs7JK{v|z0!uhnG zVS~Bf(1G@L)fBwGY|9mu1N?Nl7wIt&?9g^+ti`9rcZH8lQS1yQpUlWn)G%DiS}Qsr zdQo;;Yg(b>HOu0@xV|-3?RhbxOCmm@ag@Ef3;gRf4BjXskUoBiy zh%)TB5js+!S>2E;lIM7wU=m++%np_ybJG{qd3joD><)N{3Y>la1x(;rk0$^>!O4J>L$f~SJzKl=)oq32AX4!w2wvPmP($|oId+L zukcQ`OC;n=SzA~v49JBNO)bnYH6;~!j+|T>42&n4(0KR5glY%-^VA~1d*okvPxxL_ z%4P3>$sqB?cWc}Vc=LNNDKeq{<-Jt)|kTu zz1;H$W9o|(3c%EA*nF`SdDonVDTJa1!YNtPZ*t6>NzGB9w{m!4>veMV@B8d2+x7W2Irq6yisx+l>Vd}tgkJD1eZ zcMETpvVilq0nB5*_#y$2#+c-UMIB{iXc=DE(`FrE1-V57Od&^#T$973@I|2ap1)O1 zdj<=_JB2GKA{x~()v1ix;F@oJgwLXv;hh~n-4nip!MSdp&2JOtkj|Tx2)dR%!8k_J zV1_*fRvD0cnXWY23zGWG=3ez>!yftIUh9V7`1itd0ou!Du@JjTA~e6Bh$~<2i#J~P za5U6j7PspS(z^;UD4a%Y-EUEY;JL9mG=dgCjWge;TYtMPF3_RPJPPuglI*LN>PLCf z^*eN`%XxfubuMa;#cvP94MCLyXKhxp9ox@kp?!6y$0GM?N*TBJfjrL8%B=Nniw0=@ z%o*Iz0nk+LL$dVv?N2ekh;@{uP4_cag}PO~32A?og@DPN8K9Jg)vzTgM9L%0FFo}Qfbda^{RZRs%CzaH)g&w zFpb9tdYw>S*2UiyR?3bAxk~4L`pVwfYyG7=0FOe_=U|-@!jkAH2i-+F(eQHFz;UI# zXy6<@N(>9`Uyx5{kMLc3sr|^nt~_JTL{U8XE~(xsRI|~W{&9ys)9eYdW@)Lmu zfVScr^A0^^O_u=L&zOeVxtr*^2ln6>(r+%an1ILzS0(aQvBdX5*HZj2s49Ph36vdo zSr3G7u^W8>%B1TlgwC=9pKbFVoh;W|8RR{;MV*rJ6S7_6wZ~;#?*m;&s#yc7r>q5~ z&W209qR*5PLFD=+X~d;eAK^Nr(d5x#z<{G!G0gLXxbIq6XMNU+`{$iPlMyu@ov+3W z4HY%+650i2749&glH5u|+z*$^nP#qoP!7_J+S~9HWyDN(`9gR|JZCoIuUVptC1()IQ6!Zq)-Gl^Fs#Am`8~B_b4q*ocxC#8HcqFNd}Ugr(jmd4+bzPK*P1N z?y6e4bhHPF+rm(K36v!abIQf7Ase7WEtZ+on$ z?Q=Hyo8c}&j56O8)a;joO9ALif}CbBOrlM5x2-OXVX@M}#QwR~DI zPf=nwUdWH3mQ~z8uLX>I_{U?pju>WP_R?2}mS>zr*}(S#p+Q@N>IQPgpB}#pB`TEl zHbA=%ztLNLS!=SiU6A0bIH9SDlw5j?jW`@8Jx+z7H8ez+SczZK-Fh z+5&}r`~}&Wc`l3l2|6F!W13HyJx=IFeKnZa=fHY*FZPC6M_xniCmC0uC_Jo@fXtur z06em9u2ju6o`E*38(c6`HJ-X7juVxee$fpwVF63~melbTrG9jFLdFB^rwdlDp3*Ls zTwP|yfw#qX&9sP2H;gnt?p8;lX@G^>F7`Lf|IrjOL>-9#qw=7b?{WSo`j*4^|H=i3WbhA0VJJf^;V>5Y%LREi6F%Ry&&iYn>q;EHMMGFcR(NV0l}%IeC{* zlTe&2gHH~mI*GwqVxMEaGmsrSV0s`fZFodtRkx?9fnBXv3){mhLr zF>UWz>>ySAxZ;?y83A&ePgDWY2K}sQiRlF2l|roTr9|A#(rInYw^VIo^JzTp%9qe* z@R}!n5(D)NPCaTg^%(+T0WFv7!W-bRPR#9fkbxiGV&hCos1EOrr|V;pRv!4aLtY9F z-*Cz;>5wtf;v-%MA|sG^;J{1o1DS@Gaj)Xz+#G(ZsBe)bq&{T~s3x3|mv%Mf7YXpAW6 zR8ywfT~wSU3}M1%C>5f=XIgnkhA3O~HbgqW>K(2u&YPm@#PwSt% zr|Es8Z*K18%;NodW3H;nKpkMA@tZkk&JqPu-_(Kt$>iy4w^~G6vB;;81Rgh#rYU?y z6Pq9sk~MQ4N1y>)+GuztFn>Ea4qY{IT8>1t`UKlx!I)xc#10LANzXiDozCy`;%=aa*=ieD8x9WxIvv)nA^DH-|7T^3h|D5g2tT0SV^AuO-bu2Qn4DJ1d)$wdz^bo3s$-l1vH=W~>K@_p_p$BZ$tk(U$a>3xp|1DgF>8(+Z5ruvBm>tDRvXT* zUkv`N3eihddG%A_ov4}fCy5oU;wi$qf$Frv?G4(@w5BoNq7Hx_VNC2C2i6^Jk_K>c zc&N>1_g(hbNaAvEbv-r2PsrevGu_6!E&N|!evUZN>40}V?|9NF>WaAmBBJq3h2diC z?ozX%r7uiSXUlmY=xwU~Jtw42n1Ite^IT0IHcZiH$b{u`PG6xr$OXzc(fs2JC88kG zsJ{W@RKDM-et@w&zxcYouMJ*D9Hd~Je$MWvWo z6&FV5MjRqYJA-9@vIel09LwaRLCO1ZmX=sozJmS4tIB017HrzEWFM5H1f}55a3C-4 zyvMZjTXqiXl`K~W&?rNFGpj>{zac%;)h0Mwj^y^lG=OHa9t~BQvo{>*9yZyM*A)0Z z!vnpm>_dM`iq$^H75<)JF+#o}R>hV8Qd3V8@`#;MM&7tv91YOKIk159qxXn{kwY8O zo2oy{Z4v6-#?yVi&fROF7_G1zGl+Qh56K@DrQN|r-V24B1Q^g zXgsXS*+V3(t`?ZnXZ`F3O!-pj2dDjd8i5T-J{Y4tJ_Ps!5bzCus`~`qJ1llsN$H(V zuY1+mU5GOaQDp_QwGBAvdR$!Ps89=57kncZ4FSDb2xgW^pHfWRbl#JfVwQ$tzLp`U zNDE79KA=w&;3FvRCrdRR63N@MYBMZK5z|&HX~U3{4QQY1)O>@)M0hT2sF9Da%gj{D z#-R1m?nE2sB=}sR6_tO9$!T=tz1;~=^)3#mblnYr2ge-&;mAKpwf`&PNdKzRg3QLx_seVK#$J2==UP;J}%Tfu9i zqgCTE-F?B{J4tKpKuRs)6v zC%`eQV|7*18Y0O#E)Z(7VZbH$vKVhJw*UGov>iWnKWGb2XcT9{Ap*OvIdx6^VO9+# zIA&TQeh5zKrpBz?O6N_N!}jT{IV$vVA?Ih3i$rhOz?znpL=;-HW(?pq4y!~?COspy zXw}c&Sx4D|{j|;J#O{^QCdT>{H3VD_%t)&TQ%xk9doE8^cN*#2Vw1A@R0Des>buwt zN=q)OAtsWH6v_|%}8A*6JsK=eQd|q?-=BKGL zGc_?~Pnyr7)4ktPW(a#eB;YRni#xkar!q!6`KV1+xlSvEp7kuh2KA^AF$5Iysx&0q zan5Qh$TxcD$=@2pz1+#Q>sl(^%(pY0jPB0tCdhF7(G92OPP8qLaC_rMv>D&_e!4l4 z*(0wcxj!XtLrW_mEzKw0j){?CICNb3FlG`5^MHbRN)8*~fa#1B))YX!F;_;_?y*>} zq@g-dH&v(K({kVTrNG7Br~%ZuqP!?ZcB{YGjSo9E=Ob{3U&DVcyw1#AGrJCKos2UC z7xjBtM!=syV+9?A8!OE3(sj2}7?S@7DqmB6>gm4Fht>621!G)#t=bm8VqF7--Y89V zP97b5S(<}q_2KH4k@9x&*@L4uLWcX*?f?T|X(LxDr>|*-@x!YiGywO~)2Rh;mHD~} zefa=7S3{XN)2N%Hls|(MEH!S;kun1F1|%teBr=}~UUn;ZxT6cNSltX4#i(q1YNko; zFg0gTi>F42;O?)Mp$zGXt8nGe)U})0ow6E!Pi0&pc6}? zxsLdp79lJ)QSycC=>S#1+A>;O_M}1>1JcJODD>~zV}5K;*GJyveZ*uMgc+T>h&5vd zTVn?7pFQ;VD@pUL4Q)6nNo5yx5e~@NTVrz&aw>JAbipd!3}bmsC1iY6eGorfYm%K( zvM5qky2-Oxwh==2Hey%-1f0ARUhDaSHH1?R^ztVD1hceEOgqn=%z#QKUGL(I?)_eTNI-~>L<)iW zVj{&wGW!pjz7}g=;U-c_j>?*^jfi2yl>X7jhs>`;zx4~|s0}uC#xA(N*)J%gH#@kQ z0dcsJ0KREEqpm?=_pT#q_$V73e*%; zK?wvD5D+SMS|SK)S}XxrxD@M0*z3=ZY9T(rWOOr@*#*}T-Y z%vrHQ5*A!4f+`3mfMu~yfrDQ%pm@~XH4&@;=X`AkVMj9QIGFP;>YQYy7;R3LT9}89 z+R56cw8Ik0A*Ix9#{KwSc(U<_08bk9Tc03%)wo@Kg_zLB-axJx-=4ZZKZJ; z#HOGdbvlgQqe*MX8j@Lp1?cz={i4Ti)kb^+D)xv?!+0PRA6t_Uo0ABw-Z=e`EJh-w-*HVBVP+{i?|e1!G(r=#&eR21E(m3jDlKZJbut zbjUZ&WUsg<00A>{R7{cWV-J6=ugAX3T4m^@~b<%lVLM;h5-6A2U+7c%Ob`khe*(Pl|^t07Tdq22i$HwMI?8e+Gux zh0Sg_nTQi;9eZhFlFb)3I|tv~&6g!V{(=`y+`r!kL!F}8i4Fnlnw*=$9Y$Xw0&_~n z6${|@^ixd`@ErXvb5t@TSN&fr2vSL=cu&RR7>Hlbl{mC5BSuQ{BCJh5B5Q zB*!R57Sa2A160yX5%&5ES1^kFBq2@ljyfqcwmC+xLe1W+$ec;(^Rnx(?q1kIk^TOY80TnS{HaqE3?_~n``24$mAF5wx zH$y|dlKq0@0*PK3+?W9#mI z!fnZlf}<20Dd@SuVXepA=ClA5h$_Gf6qB(66t;1VFFAt579JZEm!w-??QCJWL$stc z%(`150CnCz) z_)=;p4+uuM7*0xS4DK7sBYUII?4(nzX)1aW3#h7A9K2SVA_rL1 z0UYRwI5dWJ*cAx^Xp(LsUTauhe8YVjx>e<2v8rJbBxl;X(H({mTzoA?PTskx7poRY zEHA#BTUhD)H!d6Jyue%&{JCc%iO5;-)BM@prv3eeJ+pDpv0$y3sfy^YxXIft5I!0k0tW7;ykU>`Am+1YV7x(UM1p}=-`OLmQla1Px_-LZe3zk zVNYCGv0wMh>B|=qlxxvRlx2_#C;T@0Kdv*xkekk4>ej%FIVca2)DMR1%Ec+~w(tSH zXX>=G&*?{BeI8@(*OcuQHv6Nm5jRix%%4wP7wDm4z{z^I4gFTA7hFW=z>n7c-o9eF zL=DBrZlm)4K{IWlEL}QC>Lx+^q%(D^HGDFfC@J6bnTqDDkV)8P@GkI21qF2n06Ml2 z_rpVCvM}j(!D#bcGhFPDB|<(3iK~ITanik<=5w+>3)4aHogEA}r?;V{Wo+)4u9lI_ z@hj!d-6t38%IeU5r$KZYj)e1;M28L48DAR1Fi~ zIIR;An3uZOGVlx$byIgytCJZTkh0l`JLxiv-v|6oio;AhkDlXM`a8BqqDSV~&f<_P$zXnBs6-hsHP@o>`-6>q~cC1&Um08eR)=E~{tS9BV^@qWHEQ z$rReP?vi$*AZj@EK8@D&TA_p;p^e{3o;P-JT{AY!90=6$H2$G6px5jPfSuJ16N6?y zPL*k;m0$4akAnlMVzPf_qK+6xD7}cKd-S>(V~}au<8g6Sm48sL&M z${O_+pNYCuApAKB8@*xanrZ$w0J3mbQge{IpnD)5c4VU5?WlfB53wKg!Qe=yaeiSl z*x=AjZfI9<+z0%>al#j>)_dtc-WfV^dLRKXT6%6Q0Wx4lTgTz(zx^{p*7Z0&PLL1m zWjmK_Et4ks1Wp#Zpnw9hO>+r!HRY%y`j@^}p5mJ{3yY6>O(Uj;@gbK!WgM`9lKLdQ ziyir5rf6iN>y!lr5|l)luNARcWvgI9%-Ci6C31^P;#lVz6dgKJ?-(pPM8ZoMjR zH486tKuqehkKZ=_zx)oQMBT{qDNDPjTK=KQYXxGe;%A?CBG5$ZGh#m3jZ{hG2I=FJ z9qMd_4ZSp)+$ht@G_kbtM1hP7+T%Lr*cRy6sMczQZ0IS=V2%55JfINzoG32Wq^Y)UZcXc-$-1BF-O~#y0IWPxDVJpDGstdNE|t%uF|dhU?TI$3 zlszCt>P09!cRcVERXaPnyMCW0tyP#GpNW?P0l@=7<@NFB;B@Kh<|5I6f5xHi{rM}f z%{?DofbNE$VN)jm6k)0?dMkT`qPJb=0l`Bd`trh(c{s#3x_C`5{i`HG=GrsmWGa>k zu=$p1L&jdw+7Wf5wyK?9hWIB>P*P_KCiAxC9b~42`QP;p&U>Y+5d%!>@Y-Z^Yo4-O zjDsSwsI>7=i!>=OcY4<*Pb>4sAWNb{$8OnB>7B*Ycm=#Z_P1DRyjKii_p?qOe-kqU zv;N4AlrN9j-#;7Tr@SoQEM`>iqD4P606c{<1(A@LvC5YYvfV-)N`>s9hlB~p4xyY( zP;l7}EjmA*Rk_RQ zGA;|27u*zlwC))3Vkxk->RG+{)fycfm%OOlV_#b+F4b@v^BI20-@r{Qfn1p3zmc_ALKpN>QPir44W}EHgJrXuX@#E&(6q3$nSpRv#^NXG*3Us!lETKrE}XZfcjezJ)Y%RMAYE+5Li9W$$Y)4X zi%9Au(;HRc`-73h^q_EX6*cX29an)qn zHCPlVPNYI_@m}2qNSZHl^;a*|RBuDZt}<5`9f?P$7brj-b#u49Wv1@flx9W}ThtCU z3Mv{gQsX&0Q6d(b+#l4_AqVn$Im|Eh8osZqEMcshoO3@IrzzZOX!4bGF) zTMh5xL@v+^^4(8Lr2&WiY&yF`A|_6(ZWdEq){-Sk9000UG9t7T$gSsZ|5OZmIeoq#8pMB@J2`pzxXcJ_ z>(b`q+v7w2A!~exMvn`|53I)^=Tj!&8$gb0S(Pe!Wqn+B=Cp7H)0EoJ`8E_;wE@Tb zqZ)X(m-PU->5mn!z{q~_9E`_dW1aEH;QZRubd}%$NJQCB21f%)S$guDwIwHMQFr8N z2zz~14xm#vi-e<+OH4mymMXnlkwkTkZm?&(MQUQ}w1RuLeH%Z6`FV5o`gQbh zdwhceq?+h1QbF$sKU2pzcL39)w(=D^=Sv${O$RNlgQn{b)VCT*SKQz**pa2Q8e!{wuL{)otSu}F5D-b>^$#q=@7e-@EOY! zJuy#IZb_Kw@ADdJM47GJf&!wg1>bYmkwlgCIcqxJg>=nMcurlNmE` zjcV}R&Wr3c-H;w4ilfu>azD0Q;pRICu?YfY!%`OePOv%Mm3b&^!1(BotL#tucYh7{ zSXih3?8e^&Cb=|04BjOzA^>9LRiGC2K| zIP2d1@6szzJ9T#a7mwf=VtM1U@g0i1~2m z@K#({Io0C(#KWd!QVg)?iC{FMsy(^&|#l; z!17B49PM}D_RgIzjlpo!vubbxxTod4SktPES>Wv}X?*W)9nQ;f-yU<)%w<%XZv!Dn zUC>@r41?Dm_lR|J-I?TLrLy*9a#Rdt<)t`tKIDBT&IN+=DZ_w1_f$XT*7y^f^Ct+j zUk%h;KUo$%q>#P}i4T{{3XQaO8>T?rDrGTBkC)NNr~@!tm9bgQvBRtZGw_cf<{q62 z{DD)amOiq4E1F|qf2bMDLNay7!-B(Ef?*+_6D-eQwI+AiQ!p(_wYfS9C^`P9{AWuB zjs~wr!ou{|<`MYpi&3!7M+7+g7Te|6H#0 zaH2lq!J7({nI#sZ64>Z#4+Le@OP-forczyrJi4C!wLnbs@{PH%M_4Ej(6P1_^zFR7 z(XIR*rK>hMJWdZoUNU_W_}?fj=P|L&LI?yTpZpsl1bm$=6p90!lB^d>0>z=$p|#et zdy*Ux$_z+OUt*nWYw(8Lq0U`3D=g)vvETRG)D(QxYEr#`6~KL7de+8x?9D0x|EjC3 z0V$8Yk#lpYb1Pi6QoH0_>lbfW>$DPewH&-KuBVy#^!w>~^a%=<#t^ITBi@NwX48@n}?I+8<26XvjDTyrmVTZ05 zr@reOTJl{428exfGIXR5@J4cIBr&1xl7YzRE-THVb#{taubA+= zu|LohCalCi5mw+Bg`)W6u}ETooRT_sz|GuCrgv=PpOkjYuzrpczr^&5Zgj55CKY@p z>lP68BB6fMpoSK~$|*UvrRCnq{?_JIDt@dr8@?&@4cQ$AHcFcMWoy((9b-WtazT6c ze)$6PYE3~g*f7GlmN2QHnt^VR>g5S?Q-+(7@~ysNZx9wkL_|IieEMxbr;<@dO|VD; zHP{YtK0y9y`UiuGiYnxdKIdnj+Q;Ev7^c{wr;c~hUzE|*o1d_Nu>jr zrhu&tsx63aCo6UuBcmk_!(C=fdSa&t(Jd_28z1yC1QbmG#OWpW>K~g5h~*(1m{=?J z3_ux^2dopvF|EYyC9Mw2Jn%2J^XrtsoIFEyR_yyx{xabpnrJ^@;z##y;dOF=nBT+N z(qny<7xvubvwQv42Q`=nT+b`X-zE-OY44VCa2UVSpFn_x=CRgYciZwiccv!7Idz=L z(J!Il{Xzyu=%4E2(E#{&n;5}k6wb04rDHzCwoO0?0ACD4#?kVA4ig_C5v?>k8!R^< zxRj;zXMh%WcvTnB9|mN%sET8^#jq&1Ti+!?RX*40a=yI`iu7u z*fQpS{^&s3`#^(cIlxCmnVGZUTtnw2qEI>C=|}X`Q^7|7lsAjiLnZb8VgvgoR^sHJ zrNns1hQ*7&>!yqX0n4_FxPtg$=z%Qmjzv2jH&lOT;BWMK!EMG+*Mz!(pC?`_*iDj8*H-Tmzrw3rWtsbV|ZBVS|rW8IA zsve!DaRqe?IXnem{S3_{1b{t0=JTd$@D7OSimaXj>mErlnG>>Qq@pEQ>7lzUhL?P7 z-M?=E)*KAtmM`AlyIdgLoPRzZo+s{c{&M(ncJzYA+hR)c&(Cm3ewk+BkEcKkViJZN z_Jh(fWL;~6!l#?Dvn-z=JTIt)Eme2LQ)ItwwIM&pxrNWtZX-5LIYcohB& zS)^f(!HBkDLrg$XBo#Tu>t7;Svx774ay`cZyc3K@)2{uXFxK^m$DqG@m1Lu`ekEqi z;)iuZb{{vPo>)Y3$BShgieMgt0i74ir~{SZ365L*3vWCR_E6tbR*l^EbPnflJBj_SyFtvP5VYO1&thN@O_W-QxNI z%rrQD!CYyaL(^U2m7c`qCLJ^vgp%ydQgeF-c7Wnd>8yXB`yS$CW==L)h4S;fE#bh+ zfCfp1NwUuE9opNNHjy!|!%p8v4kqyULt0>x z122@!I{B9cOK0gm*8roJ6YeRaYd_BZ(t8$FGty|;E2u*iFl2%$@_})N=NMTey~LY~ z5U^8jB%}7G5hXt(Uqaj_lrqBzCql-+X_UFSn#n}k4AqQP&$NOP>oE%r<5SIm*n~4+ zaeBr-p)bZzN-qV0PXPEFGp-k|JoQaI}of&&yTSdM@2TRE>f&^~v$4~*q;*D?Poez0f6I0j& z!8THjEJ#TGLBjqsQ}R6M@IFI;WY}PDsMRl9F6CfbNhOQ7OaF|T`&U<}Uids-gvA5Z z7ffh4SelD}4Yci5)(tNG>bt>sxf>;#Tg>a0%S}u9qn#On2=Jpz4Z`)9jKh{AuEB*d z>jqz!p~!N;#LMlDsQc{2&{+nSH6Iul4$F~{FBp3F`Srd0()6wgrzf@mlcNECsO89G zZ&UDmsAj)!tvsQ=W2<#COgu^V`qzU^M^b!6KafxKvj`-J>mwXk%}^z``4~%y!etv} z{x#vRsB#^R4Lsv}^+zHoDkX%j00qJdW8Fx_f}v+ql@my*{^>}55LO7mn+5N#*=`DC zuKx@htcSBl4!Z6GEr2dy$qUH+ZsFPQ4>gW=0e`<))R^z+0oZevGK4$n;k=`vYh(vw z5hG_=DDAj(-4r({ODuxU11%k?w6nW+Y46X!&=qRZ04r<%vZJOok*K=IOt^JOG)fBJ z4jV*UMMnH%b$bm~F7gkYD|f3@`HThQS7rUe~00bNT_z}W6D-13*xC?+>mzZit(%t0ru?06)~-hi}=Jc z%ALD$hK~zqX)uJa?-r-2z(Af}G2A#0N<{}g%t+0Kb!q|d*U2`v7*f583AT^+^354=?CbkU-2uW>4dmo_-Ji*Y$&v=w3y8$61*eKICn54@K&Bikch$tRg0*cPV1h z*`-NU>&V1GA0!V+YU)Y;XaWDvW>g%nfu4SzUbE6Eo*DpR)cnBRNzlFiYyj}!e;-5+ z??7P=gz^Yrk+>PR;=m%F6o`|+1nx?_N&dnY=xzttAc+CJRhs4_=AXMFlOadgupZhFQdM z(4;CWaLs+Da3|U4w|a~G;ja9y`v;2+GP?g5uXqU?kPQoD3sNPUJz8uv>XB43x)hNp z&P*+jPVQNM3GA24G*cmVJ!djct2d{)1x5~I{;zZO6HjDq^iDqGj0dxI@Tb0=Duaof zmy!Sw&x;&dDEIe|oiuLNNLp?PmZBSb$wtJt%$XR(S$1dpkf*^DU0yjdsNbuFs801q z7oHM}i+Uf|T~)`Ut%};Y)4*bh&z_bBG`+wyr-}U?GpYzWX&N_uD?8Oa+38EU`YQHW z@F@9#!th`$qXwi#ix+{d;;feOkJl#qdCLm`Sd2rl0@sbWAoo}c@X<+quMEW{sN;3Q}1dVTV$E)@4)jKa> z;XfOxp+)y--p!_yDwSOaD8#1KaTXb`Dl}3?DX5yZ*H5|zaj4}{Wo$Kx6Kuu&n8&+~ z0x?!m^{CZ2&I!bSep9$If<&ZaXd6DHh|NcDheesFj0O+$QBjgQl&PNV)HwqJ(TDU4 zsHt?&NKr@u;4IhF8CF_Gm}sp=a4E9U1n|j~vhEYJLal3%ZYl|Y+na3UVIi># zEfCTcsIf)k9O*s&e~uNqqPJYsG*M+n*<>NM!+=|`DsPvgC*TED5s)a`&_+wL%t~5H zpn_kh!-&(bZUgzamFIM5=$3m*`yl+4BPi-MhP{!A>~Bcdf73G^6F0$6e53Wh$qn;auL}`y2Uj7yggr>kCt=MHO=iVAX*xs>)LB!|Q6g zqfui0mHk(l5dkQ!eO>u&;a#|9k#)NOztWrhg5676-wXz)@CLU3mU6zfEgsI<6>aBQ zX3fgVfS%#i)e-fZ!>`@-EX%(lbs#UNi0*0vV#DDKcdB}9JDF?I@*WoKAT5~Qt-LRH zGV_vpC<=f>g0?dQ*uJ<>%gVnX3sg!UoJO*mT{ng1X2@<(YIT8uBNeQH;2uRmLJVoo z!noJC*C1JRJRRV8>$l>&6O0oA{Hm)Tsi~)=_FY?2Tj{Hu;Es7n@kNF)jS@_c#$Lv= zz;ZOL!x<@U28N;n$J{A)U#6Z+^Wo)NPeD7pw~!48{@c0(JbDmLBN}`^cM2$f@3p?U zgaJM$oC3!DTW`kxvC;TVAv?!OR4N?W328+i-L0|?55}Qo#(qDy#>;|(WnX+J6O?g) zTRQ02Vr2zt?t5b*6