From de93be55befeeaa247559010eb9d4ec4acca19f0 Mon Sep 17 00:00:00 2001 From: Nick Begg Date: Fri, 24 Apr 2026 00:19:27 +0200 Subject: [PATCH 01/44] Fix problem with symlink paths in unittest/loader.py --- Lib/unittest/loader.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py index a52950dad224ee..869e851c255c51 100644 --- a/Lib/unittest/loader.py +++ b/Lib/unittest/loader.py @@ -356,7 +356,10 @@ def _get_name_from_path(self, path): return '.' path = _splitext(os.path.normpath(path)) - _relpath = os.path.relpath(path, self._top_level_dir) + path_real = os.path.realpath(path) + tld_real = os.path.realpath(self._top_level_dir) + _relpath = os.path.relpath(path_real, tld_real) + assert not os.path.isabs(_relpath), "Path must be within the project" assert not _relpath.startswith('..'), "Path must be within the project" From 665b7dfcfa240e02760f58bed5ca29ec01d028e6 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:36:46 -0700 Subject: [PATCH 02/44] Improve `hash()` builtin docstring with caveats. (GH-125229) Improve `hash()` builtin docstring with caveats. Mention its return type and that the value can be expected to change between processes (hash randomization). Why? The `hash` builtin gets reached for and used by a lot of people whether it is the right tool or not. IDEs surface docstrings and people use pydoc and `help(hash)`. --- Python/bltinmodule.c | 10 ++++++---- Python/clinic/bltinmodule.c.h | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index fec64e1ff9d25f..16413d784cc87c 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1840,15 +1840,17 @@ hash as builtin_hash obj: object / -Return the hash value for the given object. +Return the integer hash value for the given object. -Two objects that compare equal must also have the same hash value, but the -reverse is not necessarily true. +Two objects that compare equal must also have the same hash value, but +the reverse is not necessarily true. Hash values may differ between +Python processes. Not all objects are hashable; calling hash() on an +unhashable object raises TypeError. [clinic start generated code]*/ static PyObject * builtin_hash(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=237668e9d7688db7 input=58c48be822bf9c54]*/ +/*[clinic end generated code: output=237668e9d7688db7 input=70a242ff65f6717c]*/ { Py_hash_t x; diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index c8c141f863d26a..e6b845cd375d73 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -826,10 +826,12 @@ PyDoc_STRVAR(builtin_hash__doc__, "hash($module, obj, /)\n" "--\n" "\n" -"Return the hash value for the given object.\n" +"Return the integer hash value for the given object.\n" "\n" -"Two objects that compare equal must also have the same hash value, but the\n" -"reverse is not necessarily true."); +"Two objects that compare equal must also have the same hash value, but\n" +"the reverse is not necessarily true. Hash values may differ between\n" +"Python processes. Not all objects are hashable; calling hash() on an\n" +"unhashable object raises TypeError."); #define BUILTIN_HASH_METHODDEF \ {"hash", (PyCFunction)builtin_hash, METH_O, builtin_hash__doc__}, @@ -1380,4 +1382,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=1c3327da8885bb8e input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f1fc836a63d89826 input=a9049054013a1b77]*/ From 95559d2a7e0071342dff33dcf58f71a14d291163 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Fri, 24 Apr 2026 11:22:05 -0700 Subject: [PATCH 03/44] gh-108951: add TaskGroup.cancel() (#127214) Fixes #108951 Co-authored-by: sobolevn Co-authored-by: Andrew Svetlov Co-authored-by: Guido van Rossum --- Doc/library/asyncio-task.rst | 78 +++++------ Doc/tools/removed-ids.txt | 2 + Lib/asyncio/taskgroups.py | 33 +++++ Lib/test/test_asyncio/test_taskgroups.py | 125 ++++++++++++++++++ ...-11-24-07-18-40.gh-issue-108951.jyKygP.rst | 1 + 5 files changed, 191 insertions(+), 48 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 4e60eee44290af..f0fe91b363d95e 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -355,6 +355,34 @@ and reliable way to wait for all tasks in the group to finish. Passes on all *kwargs* to :meth:`loop.create_task` + .. method:: cancel() + + Cancel the task group. This is a non-exceptional, early exit of the + task group's lifetime -- useful once the group's goal has been met or + its services no longer needed. + + :meth:`~asyncio.Task.cancel` will be called on any tasks in the group that + aren't yet done, as well as the parent (body) of the group. The task group + context manager will exit *without* :exc:`asyncio.CancelledError` being raised. + + If :meth:`cancel` is called before entering the task group, the group will be + cancelled upon entry. This is useful for patterns where one piece of + code passes an unused :class:`asyncio.TaskGroup` instance to another in order to have + the ability to cancel anything run within the group. + + :meth:`cancel` is idempotent and may be called after the task group has + already exited. + + Some ways to use :meth:`cancel`: + + * call it from the task group body based on some condition or event + * pass the task group instance to child tasks via :meth:`create_task`, allowing a child + task to conditionally cancel the entire entire group + * pass the task group instance or bound :meth:`cancel` method to some other task *before* + opening the task group, allowing remote cancellation + + .. versionadded:: next + Example:: async def main(): @@ -366,7 +394,8 @@ Example:: The ``async with`` statement will wait for all tasks in the group to finish. While waiting, new tasks may still be added to the group (for example, by passing ``tg`` into one of the coroutines -and calling ``tg.create_task()`` in that coroutine). +and calling ``tg.create_task()`` in that coroutine). There is also opportunity +to short-circuit the entire task group with ``tg.cancel()``, based on some condition. Once the last task has finished and the ``async with`` block is exited, no new tasks may be added to the group. @@ -427,53 +456,6 @@ reported by :meth:`asyncio.Task.cancelling`. Improved handling of simultaneous internal and external cancellations and correct preservation of cancellation counts. -Terminating a task group ------------------------- - -While terminating a task group is not natively supported by the standard -library, termination can be achieved by adding an exception-raising task -to the task group and ignoring the raised exception: - -.. code-block:: python - - import asyncio - from asyncio import TaskGroup - - class TerminateTaskGroup(Exception): - """Exception raised to terminate a task group.""" - - async def force_terminate_task_group(): - """Used to force termination of a task group.""" - raise TerminateTaskGroup() - - async def job(task_id, sleep_time): - print(f'Task {task_id}: start') - await asyncio.sleep(sleep_time) - print(f'Task {task_id}: done') - - async def main(): - try: - async with TaskGroup() as group: - # spawn some tasks - group.create_task(job(1, 0.5)) - group.create_task(job(2, 1.5)) - # sleep for 1 second - await asyncio.sleep(1) - # add an exception-raising task to force the group to terminate - group.create_task(force_terminate_task_group()) - except* TerminateTaskGroup: - pass - - asyncio.run(main()) - -Expected output: - -.. code-block:: text - - Task 1: start - Task 2: start - Task 1: done - Sleeping ======== diff --git a/Doc/tools/removed-ids.txt b/Doc/tools/removed-ids.txt index 7bffbb8d86197d..5e3ef2efe271fd 100644 --- a/Doc/tools/removed-ids.txt +++ b/Doc/tools/removed-ids.txt @@ -3,3 +3,5 @@ # Remove from here in 3.16 c-api/allocation.html: deprecated-aliases c-api/file.html: deprecated-api + +library/asyncio-task.html: terminating-a-task-group diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 00e8f6d5d1a68b..45dfebc65904fc 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -37,6 +37,7 @@ def __init__(self): self._errors = [] self._base_error = None self._on_completed_fut = None + self._cancel_on_enter = False def __repr__(self): info = [''] @@ -63,6 +64,8 @@ async def __aenter__(self): raise RuntimeError( f'TaskGroup {self!r} cannot determine the parent task') self._entered = True + if self._cancel_on_enter: + self.cancel() return self @@ -178,6 +181,9 @@ async def _aexit(self, et, exc): finally: exc = None + # Suppress any remaining exception (exceptions deserving to be raised + # were raised above). + return True def create_task(self, coro, **kwargs): """Create a new task in this group and return it. @@ -278,3 +284,30 @@ def _on_task_done(self, task): self._abort() self._parent_cancel_requested = True self._parent_task.cancel() + + def cancel(self): + """Cancel the task group + + `cancel()` will be called on any tasks in the group that aren't yet + done, as well as the parent (body) of the group. This will cause the + task group context manager to exit *without* `asyncio.CancelledError` + being raised. + + If `cancel()` is called before entering the task group, the group will be + cancelled upon entry. This is useful for patterns where one piece of + code passes an unused TaskGroup instance to another in order to have + the ability to cancel anything run within the group. + + `cancel()` is idempotent and may be called after the task group has + already exited. + """ + if not self._entered: + self._cancel_on_enter = True + return + if self._exiting and not self._tasks: + return + if not self._aborting: + self._abort() + if self._parent_task and not self._parent_cancel_requested: + self._parent_cancel_requested = True + self._parent_task.cancel() diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 91f6b03b4597a5..8925884b9dcf73 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1102,6 +1102,131 @@ async def throw_error(): # cancellation happens here and error is more understandable await asyncio.sleep(0) + async def test_taskgroup_cancel_children(self): + # (asserting that TimeoutError is not raised) + async with asyncio.timeout(1): + async with asyncio.TaskGroup() as tg: + tg.create_task(asyncio.sleep(10)) + tg.create_task(asyncio.sleep(10)) + await asyncio.sleep(0) + tg.cancel() + + async def test_taskgroup_cancel_body(self): + count = 0 + async with asyncio.TaskGroup() as tg: + tg.cancel() + count += 1 + await asyncio.sleep(0) + count += 1 + self.assertEqual(count, 1) + + async def test_taskgroup_cancel_idempotent(self): + count = 0 + async with asyncio.TaskGroup() as tg: + tg.cancel() + tg.cancel() + count += 1 + await asyncio.sleep(0) + count += 1 + self.assertEqual(count, 1) + + async def test_taskgroup_cancel_after_exit(self): + async with asyncio.TaskGroup() as tg: + await asyncio.sleep(0) + # (asserting that exception is not raised) + tg.cancel() + + async def test_taskgroup_cancel_before_enter(self): + tg = asyncio.TaskGroup() + tg.cancel() + count = 0 + async with tg: + count += 1 + await asyncio.sleep(0) + count += 1 + self.assertEqual(count, 1) + + async def test_taskgroup_cancel_before_create_task(self): + async with asyncio.TaskGroup() as tg: + tg.cancel() + # TODO: This behavior is not ideal. We'd rather have no exception + # raised, and the child task run until the first await. + with self.assertRaises(RuntimeError): + tg.create_task(asyncio.sleep(1)) + + async def test_taskgroup_cancel_before_exception(self): + async def raise_exc(parent_tg: asyncio.TaskGroup): + parent_tg.cancel() + raise RuntimeError + + with self.assertRaises(ExceptionGroup): + async with asyncio.TaskGroup() as tg: + tg.create_task(raise_exc(tg)) + await asyncio.sleep(1) + + async def test_taskgroup_cancel_after_exception(self): + async def raise_exc(parent_tg: asyncio.TaskGroup): + try: + raise RuntimeError + finally: + parent_tg.cancel() + + with self.assertRaises(ExceptionGroup): + async with asyncio.TaskGroup() as tg: + tg.create_task(raise_exc(tg)) + await asyncio.sleep(1) + + async def test_taskgroup_body_cancel_before_exception(self): + with self.assertRaises(ExceptionGroup): + async with asyncio.TaskGroup() as tg: + tg.cancel() + raise RuntimeError + + async def test_taskgroup_body_cancel_after_exception(self): + with self.assertRaises(ExceptionGroup): + async with asyncio.TaskGroup() as tg: + try: + raise RuntimeError + finally: + tg.cancel() + + async def test_taskgroup_cancel_one_winner(self): + async def race(*fns): + outcome = None + async def run(fn): + nonlocal outcome + outcome = await fn() + tg.cancel() + + async with asyncio.TaskGroup() as tg: + for fn in fns: + tg.create_task(run(fn)) + return outcome + + event = asyncio.Event() + record = [] + async def fn_1(): + record.append("1 started") + await event.wait() + record.append("1 finished") + return 1 + + async def fn_2(): + record.append("2 started") + await event.wait() + record.append("2 finished") + return 2 + + async def fn_3(): + record.append("3 started") + event.set() + await asyncio.sleep(10) + record.append("3 finished") + return 3 + + self.assertEqual(await race(fn_1, fn_2, fn_3), 1) + self.assertListEqual(record, ["1 started", "2 started", "3 started", "1 finished"]) + class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase): loop_factory = asyncio.EventLoop diff --git a/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst b/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst new file mode 100644 index 00000000000000..1696a2dd1728ed --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst @@ -0,0 +1 @@ +Add :meth:`~asyncio.TaskGroup.cancel` which cancels unfinished tasks and exits the group without error. From db0ee44b93f766bcd7dcaba24924efc3a065f2d2 Mon Sep 17 00:00:00 2001 From: scoder Date: Sat, 25 Apr 2026 09:05:03 +0200 Subject: [PATCH 04/44] gh-142186: Revert the unintended value change in the `PY_MONITORING_EVENT_*` values from gh-146182 (gh-148955) https://github.com/python/cpython/pull/146182 left an unintended change in the `PY_MONITORING_*` macro values. This change reverts that part to avoid a user visible impact. --- Include/cpython/monitoring.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/cpython/monitoring.h b/Include/cpython/monitoring.h index fa6168d95cd210..c93271f6ca95f5 100644 --- a/Include/cpython/monitoring.h +++ b/Include/cpython/monitoring.h @@ -29,9 +29,9 @@ extern "C" { /* Other events, mainly exceptions. * These can now be turned on and disabled on a per code object basis. */ -#define PY_MONITORING_EVENT_PY_UNWIND 11 +#define PY_MONITORING_EVENT_RAISE 11 #define PY_MONITORING_EVENT_EXCEPTION_HANDLED 12 -#define PY_MONITORING_EVENT_RAISE 13 +#define PY_MONITORING_EVENT_PY_UNWIND 13 #define PY_MONITORING_EVENT_PY_THROW 14 #define PY_MONITORING_EVENT_RERAISE 15 From c650b51c32f92563f3319bb25c64ca2d2dc05ec0 Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:47:41 +0100 Subject: [PATCH 05/44] gh-148973: fix segfault on mismatch between consts size and oparg in compiler (#148974) --- Lib/test/test_peepholer.py | 48 ++++++++++++++++++++++++++++ Modules/_testinternalcapi.c | 11 +++++-- Modules/clinic/_testinternalcapi.c.h | 13 ++++++-- Python/compile.c | 14 +++++++- Python/flowgraph.c | 15 +++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index e0cc010f15513b..abb071451d8b59 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1,3 +1,4 @@ +import ast import dis import gc from itertools import combinations, product @@ -1131,6 +1132,53 @@ def f(self): class DirectCfgOptimizerTests(CfgOptimizationTestCase): + def test_optimize_cfg_const_index_out_of_range(self): + insts = [ + ('LOAD_CONST', 2, 0), + ('RETURN_VALUE', None, 0), + ] + seq = self.seq_from_insts(insts) + with self.assertRaisesRegex(ValueError, "out of range"): + _testinternalcapi.optimize_cfg(seq, [0, 1], 0) + + def test_optimize_cfg_consts_must_be_list(self): + insts = [ + ('LOAD_CONST', 0, 0), + ('RETURN_VALUE', None, 0), + ] + seq = self.seq_from_insts(insts) + with self.assertRaisesRegex(TypeError, "consts must be a list"): + _testinternalcapi.optimize_cfg(seq, (0,), 0) + + def test_compiler_codegen_metadata_consts_roundtrips_optimize_cfg(self): + tree = ast.parse("x = (1, 2)", mode="exec", optimize=1) + insts, meta = _testinternalcapi.compiler_codegen(tree, "", 0) + consts = meta["consts"] + self.assertIsInstance(consts, list) + _testinternalcapi.optimize_cfg(insts, consts, 0) + + def test_compiler_codegen_consts_include_none_required_for_implicit_return(self): + # Module "pass" only needs the const table entry for None once + # _PyCodegen_AddReturnAtEnd runs. If metadata["consts"] were taken + # before that, the list would not match LOAD_CONST opargs (here: 0 + # for None), and optimize_cfg would read out of range. + tree = ast.parse("pass", mode="exec", optimize=1) + insts, meta = _testinternalcapi.compiler_codegen(tree, "", 0) + consts = meta["consts"] + self.assertEqual(consts, [None]) + + load_const = opcode.opmap["LOAD_CONST"] + self.assertEqual( + [t[1] for t in insts.get_instructions() if t[0] == load_const], + [0], + ) + + # As if consts were snapshotted before AddReturnAtEnd: still LOAD_CONST 0, no row. + with self.assertRaisesRegex(ValueError, "out of range"): + _testinternalcapi.optimize_cfg(insts, [], 0) + + _testinternalcapi.optimize_cfg(insts, list(consts), 0) + def cfg_optimization_test(self, insts, expected_insts, consts=None, expected_consts=None, nlocals=0): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index deac8570fe3241..619f9f50574429 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1081,13 +1081,17 @@ _testinternalcapi.compiler_codegen -> object compile_mode: int = 0 Apply compiler code generation to an AST. + +Return (instruction_sequence, metadata). metadata maps "argcount", +"posonlyargcount", "kwonlyargcount" to ints and "consts" to the list of +constants in LOAD_CONST index order (for use with optimize_cfg). [clinic start generated code]*/ static PyObject * _testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast, PyObject *filename, int optimize, int compile_mode) -/*[clinic end generated code: output=40a68f6e13951cc8 input=a0e00784f1517cd7]*/ +/*[clinic end generated code: output=40a68f6e13951cc8 input=e0c65e5c80efe30e]*/ { PyCompilerFlags *flags = NULL; return _PyCompile_CodeGen(ast, filename, flags, optimize, compile_mode); @@ -1103,12 +1107,15 @@ _testinternalcapi.optimize_cfg -> object nlocals: int Apply compiler optimizations to an instruction list. + +consts must be a list aligned with LOAD_CONST opargs (the "consts" entry +from the metadata dict returned by compiler_codegen for the same unit). [clinic start generated code]*/ static PyObject * _testinternalcapi_optimize_cfg_impl(PyObject *module, PyObject *instructions, PyObject *consts, int nlocals) -/*[clinic end generated code: output=57c53c3a3dfd1df0 input=6a96d1926d58d7e5]*/ +/*[clinic end generated code: output=57c53c3a3dfd1df0 input=905c3d935e063b27]*/ { return _PyCompile_OptimizeCfg(instructions, consts, nlocals); } diff --git a/Modules/clinic/_testinternalcapi.c.h b/Modules/clinic/_testinternalcapi.c.h index 21f4ee3201e5bf..85edc6fbb5802f 100644 --- a/Modules/clinic/_testinternalcapi.c.h +++ b/Modules/clinic/_testinternalcapi.c.h @@ -92,7 +92,11 @@ PyDoc_STRVAR(_testinternalcapi_compiler_codegen__doc__, "compiler_codegen($module, /, ast, filename, optimize, compile_mode=0)\n" "--\n" "\n" -"Apply compiler code generation to an AST."); +"Apply compiler code generation to an AST.\n" +"\n" +"Return (instruction_sequence, metadata). metadata maps \"argcount\",\n" +"\"posonlyargcount\", \"kwonlyargcount\" to ints and \"consts\" to the list of\n" +"constants in LOAD_CONST index order (for use with optimize_cfg)."); #define _TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF \ {"compiler_codegen", _PyCFunction_CAST(_testinternalcapi_compiler_codegen), METH_FASTCALL|METH_KEYWORDS, _testinternalcapi_compiler_codegen__doc__}, @@ -169,7 +173,10 @@ PyDoc_STRVAR(_testinternalcapi_optimize_cfg__doc__, "optimize_cfg($module, /, instructions, consts, nlocals)\n" "--\n" "\n" -"Apply compiler optimizations to an instruction list."); +"Apply compiler optimizations to an instruction list.\n" +"\n" +"consts must be a list aligned with LOAD_CONST opargs (the \"consts\" entry\n" +"from the metadata dict returned by compiler_codegen for the same unit)."); #define _TESTINTERNALCAPI_OPTIMIZE_CFG_METHODDEF \ {"optimize_cfg", _PyCFunction_CAST(_testinternalcapi_optimize_cfg), METH_FASTCALL|METH_KEYWORDS, _testinternalcapi_optimize_cfg__doc__}, @@ -392,4 +399,4 @@ get_next_dict_keys_version(PyObject *module, PyObject *Py_UNUSED(ignored)) { return get_next_dict_keys_version_impl(module); } -/*[clinic end generated code: output=fbd8b7e0cae8bac7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ecb5d7ac85b153fa input=a9049054013a1b77]*/ diff --git a/Python/compile.c b/Python/compile.c index 5f82641a3948c6..eb9fc827bea40a 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1658,6 +1658,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, { PyObject *res = NULL; PyObject *metadata = NULL; + PyObject *consts_list = NULL; if (!PyAST_Check(ast)) { PyErr_SetString(PyExc_TypeError, "expected an AST"); @@ -1712,12 +1713,23 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, } if (_PyInstructionSequence_ApplyLabelMap(_PyCompile_InstrSequence(c)) < 0) { - return NULL; + goto finally; + } + + /* After AddReturnAtEnd: co_consts indices match the final instruction stream. */ + consts_list = consts_dict_keys_inorder(umd->u_consts); + if (consts_list == NULL) { + goto finally; + } + if (PyDict_SetItemString(metadata, "consts", consts_list) < 0) { + goto finally; } + /* Allocate a copy of the instruction sequence on the heap */ res = _PyTuple_FromPair((PyObject *)_PyCompile_InstrSequence(c), metadata); finally: + Py_XDECREF(consts_list); Py_XDECREF(metadata); _PyCompile_ExitScope(c); compiler_free(c); diff --git a/Python/flowgraph.c b/Python/flowgraph.c index c234fa3d8c3876..202e3bacf2e1bf 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1309,6 +1309,14 @@ get_const_value(int opcode, int oparg, PyObject *co_consts) PyObject *constant = NULL; assert(loads_const(opcode)); if (opcode == LOAD_CONST) { + assert(PyList_Check(co_consts)); + Py_ssize_t n = PyList_GET_SIZE(co_consts); + if (oparg < 0 || oparg >= n) { + PyErr_Format(PyExc_ValueError, + "LOAD_CONST index %d is out of range for consts (len=%zd)", + oparg, n); + return NULL; + } constant = PyList_GET_ITEM(co_consts, oparg); } if (opcode == LOAD_SMALL_INT) { @@ -2167,6 +2175,9 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject * cfg_instr *inst = &bb->b_instr[i]; if (inst->i_opcode == LOAD_CONST) { PyObject *constant = get_const_value(inst->i_opcode, inst->i_oparg, consts); + if (constant == NULL) { + return ERROR; + } int res = maybe_instr_make_load_smallint(inst, constant, consts, const_cache); Py_DECREF(constant); if (res < 0) { @@ -4064,6 +4075,10 @@ _PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals) PyErr_SetString(PyExc_ValueError, "expected an instruction sequence"); return NULL; } + if (!PyList_Check(consts)) { + PyErr_SetString(PyExc_TypeError, "consts must be a list"); + return NULL; + } PyObject *const_cache = PyDict_New(); if (const_cache == NULL) { return NULL; From 9dab866f9ca564a8b9c73393c1a2b1139583d018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Sat, 25 Apr 2026 16:23:40 +0200 Subject: [PATCH 06/44] gh-148588: Document `__lazy_modules__` (#148590) --- Doc/reference/datamodel.rst | 15 ++++++++++ Doc/reference/simple_stmts.rst | 50 ++++++++++++++++++++++++++++++++++ Doc/whatsnew/3.15.rst | 12 ++++++++ Misc/NEWS.d/3.15.0a8.rst | 4 +-- 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 1e53c0e0e6f971..2089984404cec6 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -926,6 +926,7 @@ Attribute assignment updates the module's namespace dictionary, e.g., single: __doc__ (module attribute) single: __annotations__ (module attribute) single: __annotate__ (module attribute) + single: __lazy_modules__ (module attribute) pair: module; namespace .. _import-mod-attrs: @@ -1121,6 +1122,20 @@ the following writable attributes: .. versionadded:: 3.14 +.. attribute:: module.__lazy_modules__ + + A container (an object implementing :meth:`~object.__contains__`) of fully + qualified module name strings. When defined + at module scope, any regular :keyword:`import` statement in that module whose + target module name appears in this container is treated as a + :ref:`lazy import `, as if the :keyword:`lazy` keyword had + been used. Imports inside functions, class bodies, or + :keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected. + + See :ref:`lazy-modules-compat` for details and examples. + + .. versionadded:: 3.15 + Module dictionaries ^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 9b84c2e9ac7017..648e3a9bf54060 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -920,6 +920,56 @@ See :pep:`810` for the full specification of lazy imports. .. versionadded:: 3.15 +.. _lazy-modules-compat: + +Compatibility via ``__lazy_modules__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. index:: + single: __lazy_modules__ + +As an alternative to using the :keyword:`lazy` keyword, a module can opt +into lazy loading for specific imports by defining a module-level +:attr:`~module.__lazy_modules__` variable. When present, it must be a +container of fully qualified module name strings. Any regular (non-``lazy``) +:keyword:`import` statement at module scope whose target appears in +:attr:`!__lazy_modules__` is treated as a lazy import, exactly as if the +:keyword:`lazy` keyword had been used. + +This provides a way to enable lazy loading for specific dependencies without +changing individual ``import`` statements. This is useful when supporting +Python versions older than 3.15 while using lazy imports in 3.15+:: + + __lazy_modules__ = ["json", "pathlib"] + + import json # loaded lazily (name is in __lazy_modules__) + import os # loaded eagerly (name not in __lazy_modules__) + + import pathlib # loaded lazily + +Relative imports are resolved to their absolute name before the lookup, so +:attr:`!__lazy_modules__` must always contain fully qualified module names. + +For ``from``-style imports, the relevant name is the module following +``from``, not the names of its members:: + + # In mypackage/mymodule.py + __lazy_modules__ = ["mypackage", "mypackage.sub.utils"] + + from . import helper # loaded lazily: . resolves to mypackage + from .sub.utils import func # loaded lazily: .sub.utils resolves to mypackage.sub.utils + import json # loaded eagerly (not in __lazy_modules__) + +Imports inside functions, class bodies, or +:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager, +regardless of :attr:`!__lazy_modules__`. + +Setting ``-X lazy_imports=none`` (or the :envvar:`PYTHON_LAZY_IMPORTS` +environment variable to ``none``) overrides :attr:`!__lazy_modules__` and +forces all imports to be eager. + +.. versionadded:: 3.15 + .. _future: Future statements diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index dbdd5de01700a3..9ccd63bd8795f9 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -184,6 +184,18 @@ function, class body, or ``try``/``except``/``finally`` block raises a (``lazy from module import *`` and ``lazy from __future__ import ...`` both raise :exc:`SyntaxError`). +For code that cannot use the ``lazy`` keyword directly (for example, when +supporting Python versions older than 3.15 while still using lazy +imports on 3.15+), a module can define +:attr:`~module.__lazy_modules__` as a container of fully qualified module +name strings. Regular ``import`` statements for those modules are then treated +as lazy, with the same semantics as the ``lazy`` keyword:: + + __lazy_modules__ = ["json", "pathlib"] + + import json # lazy + import os # still eager + .. seealso:: :pep:`810` for the full specification and rationale. (Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.) diff --git a/Misc/NEWS.d/3.15.0a8.rst b/Misc/NEWS.d/3.15.0a8.rst index ed37988f6ab548..ff7930aeb292d6 100644 --- a/Misc/NEWS.d/3.15.0a8.rst +++ b/Misc/NEWS.d/3.15.0a8.rst @@ -185,8 +185,8 @@ dealing with contradictions in ``make_bottom``. .. nonce: 6wDI6S .. section: Core and Builtins -Ensure ``-X lazy_imports=none``` and ``PYTHON_LAZY_IMPORTS=none``` override -``__lazy_modules__``. Patch by Hugo van Kemenade. +Ensure ``-X lazy_imports=none`` and ``PYTHON_LAZY_IMPORTS=none`` override +:attr:`~module.__lazy_modules__`. Patch by Hugo van Kemenade. .. From 5ea3ae7c97f06cebcbbe81b142ee4a2b23d980e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Sat, 25 Apr 2026 16:24:40 +0200 Subject: [PATCH 07/44] gh-140287: Handle `PYTHONSTARTUP` script exceptions in the asyncio REPL (#140288) --- Lib/asyncio/__main__.py | 12 ++++-- Lib/test/test_repl.py | 43 ++++++++++++++++++- ...-10-18-12-13-39.gh-issue-140287.49iU-4.rst | 2 + 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 8ee09b38469d4c..37eba9657ac5a8 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -101,11 +101,15 @@ def run(self): if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")): sys.audit("cpython.run_startup", startup_path) - - import tokenize - with tokenize.open(startup_path) as f: - startup_code = compile(f.read(), startup_path, "exec") + try: + import tokenize + with tokenize.open(startup_path) as f: + startup_code = compile(f.read(), startup_path, "exec") exec(startup_code, console.locals) + except SystemExit: + raise + except BaseException: + console.showtraceback() ps1 = getattr(sys, "ps1", ">>> ") if CAN_USE_PYREPL: diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 27cd125078ea69..850cb66a89ba84 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -5,6 +5,7 @@ import subprocess import sys import unittest +from contextlib import contextmanager from functools import partial from textwrap import dedent from test import support @@ -67,6 +68,19 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F spawn_asyncio_repl = partial(spawn_repl, "-m", "asyncio", custom=True) +@contextmanager +def temp_pythonstartup(*, source: str, histfile: str = ".pythonhist"): + """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" + with os_helper.temp_dir() as tmpdir: + filename = os.path.join(tmpdir, "pythonstartup.py") + with open(filename, "w") as f: + f.write(source) + yield { + "PYTHONSTARTUP": filename, + "PYTHON_HISTORY": os.path.join(tmpdir, histfile) + } + + def run_on_interactive_mode(source): """Spawn a new Python interpreter, pass the given input source code from the stdin and return the @@ -276,8 +290,6 @@ def make_repl(env): """) % script self.assertIn(expected, output) - - def test_runsource_show_syntax_error_location(self): user_input = dedent("""def f(x, x): ... """) @@ -449,6 +461,33 @@ def test_quiet_mode(self): self.assertEqual(p.returncode, 0) self.assertEqual(output[:3], ">>>") + @support.force_not_colorized + @support.subTests( + ("startup_code", "expected_error"), + [ + ("some invalid syntax\n", "SyntaxError: invalid syntax"), + ("1/0\n", "ZeroDivisionError: division by zero"), + ], + ) + def test_pythonstartup_failure(self, startup_code, expected_error): + startup_env = self.enterContext( + temp_pythonstartup(source=startup_code, histfile=".asyncio_history")) + + p = spawn_repl( + "-qm", "asyncio", + env=os.environ | startup_env, + isolated=False, + custom=True) + p.stdin.write("print('user code', 'executed')\n") + output = kill_python(p) + self.assertEqual(p.returncode, 0) + + tb_hint = f'File "{startup_env["PYTHONSTARTUP"]}", line 1' + self.assertIn(tb_hint, output) + self.assertIn(expected_error, output) + + self.assertIn("user code executed", output) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst new file mode 100644 index 00000000000000..09643956d98093 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst @@ -0,0 +1,2 @@ +The :mod:`asyncio` REPL now handles exceptions when executing :envvar:`PYTHONSTARTUP` scripts. +Patch by Bartosz Sławecki. From 6d7bbee1d5714a345dca5a7e4089de3c2fc0fb59 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 25 Apr 2026 08:31:22 -0700 Subject: [PATCH 08/44] gh-148947: dataclasses: fix error on empty __class__ cell (#148948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a test demonstrating the need for the existing "is oldcls" check. Co-authored-by: Bartosz Sławecki --- Lib/dataclasses.py | 14 ++++-- Lib/test/test_dataclasses/__init__.py | 46 +++++++++++++++++++ ...-04-23-21-47-49.gh-issue-148947.W4V2lG.rst | 2 + 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 9d5bed6b96fc49..988edfed6f4dcb 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1298,10 +1298,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls): # This function doesn't reference __class__, so nothing to do. return False # Fix the cell to point to the new class, if it's already pointing - # at the old class. I'm not convinced that the "is oldcls" test - # is needed, but other than performance can't hurt. + # at the old class. closure = f.__closure__[idx] - if closure.cell_contents is oldcls: + + try: + contents = closure.cell_contents + except ValueError: + # Cell is empty + return False + + # This check makes it so we avoid updating an incorrect cell if the + # class body contains a function that was defined in a different class. + if contents is oldcls: closure.cell_contents = newcls return True return False diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index e0cfe3df3e6357..6ff82b8810abed 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5375,5 +5375,51 @@ def cls(self): # one will be keeping a reference to the underlying class A. self.assertIs(A().cls(), B) + def test_empty_class_cell(self): + # gh-148947: Make sure that we explicitly handle the empty class cell. + def maker(): + if False: + __class__ = 42 + + def method(self): + return __class__ + return method + + from dataclasses import dataclass + + @dataclass(slots=True) + class X: + a: int + + meth = maker() + + with self.assertRaisesRegex(NameError, '__class__'): + X(1).meth() + + def test_class_cell_from_other_class(self): + # This test fails without the "is oldcls" check in + # _update_func_cell_for__class__. + class Base: + def meth(self): + return "Base" + + class Child(Base): + def meth(self): + return super().meth() + " Child" + + @dataclass(slots=True) + class DC(Child): + a: int + + meth = Child.meth + + closure = DC.meth.__closure__ + self.assertEqual(len(closure), 1) + self.assertIs(closure[0].cell_contents, Child) + + self.assertEqual(DC(1).meth(), "Base Child") + + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst new file mode 100644 index 00000000000000..f9783266f5cc42 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst @@ -0,0 +1,2 @@ +Fix crash in :deco:`dataclasses.dataclass` with ``slots=True`` that occurred +when a function found within the class had an empty ``__class__`` cell. From 85d3bcd4f3b736cad40e8c71df3f7d69dfacabf9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 25 Apr 2026 19:13:48 +0300 Subject: [PATCH 09/44] gh-134690: Removed deprecated `codetype.co_lnotab` (#134691) --- Doc/deprecations/pending-removal-in-3.15.rst | 2 +- .../pending-removal-in-future.rst | 2 +- Doc/library/dis.rst | 2 +- Doc/library/inspect.rst | 4 - Doc/reference/datamodel.rst | 9 - Doc/whatsnew/3.10.rst | 2 +- Doc/whatsnew/3.12.rst | 2 +- Doc/whatsnew/3.15.rst | 8 + Doc/whatsnew/3.6.rst | 2 +- InternalDocs/code_objects.md | 8 - Lib/inspect.py | 2 - Lib/test/test_code.py | 7 - ...-05-26-10-03-18.gh-issue-134690.mUMT16.rst | 2 + Objects/codeobject.c | 84 ------- Objects/lnotab_notes.txt | 228 ------------------ 15 files changed, 16 insertions(+), 348 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst delete mode 100644 Objects/lnotab_notes.txt diff --git a/Doc/deprecations/pending-removal-in-3.15.rst b/Doc/deprecations/pending-removal-in-3.15.rst index e7f27f73664df3..1d9a3095813a6d 100644 --- a/Doc/deprecations/pending-removal-in-3.15.rst +++ b/Doc/deprecations/pending-removal-in-3.15.rst @@ -60,7 +60,7 @@ Pending removal in Python 3.15 * :mod:`types`: - * :class:`types.CodeType`: Accessing :attr:`~codeobject.co_lnotab` was + * :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. diff --git a/Doc/deprecations/pending-removal-in-future.rst b/Doc/deprecations/pending-removal-in-future.rst index e8306b8efee1c8..74f98d33a4b61f 100644 --- a/Doc/deprecations/pending-removal-in-future.rst +++ b/Doc/deprecations/pending-removal-in-future.rst @@ -47,7 +47,7 @@ although there is currently no date scheduled for their removal. * :mod:`codecs`: use :func:`open` instead of :func:`codecs.open`. (:gh:`133038`) -* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method +* :attr:`!codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method instead. * :mod:`datetime`: diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 1f7014e9cd426f..3e7ae509fedcea 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -400,7 +400,7 @@ operation is being performed, so the intermediate analysis object isn't useful: .. versionchanged:: 3.10 The :pep:`626` :meth:`~codeobject.co_lines` method is used instead of the - :attr:`~codeobject.co_firstlineno` and :attr:`~codeobject.co_lnotab` + :attr:`~codeobject.co_firstlineno` and :attr:`!codeobject.co_lnotab` attributes of the :ref:`code object `. .. versionchanged:: 3.13 diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index ff893a4513926a..e23449886a38f1 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -195,10 +195,6 @@ attributes (see :ref:`import-mod-attrs` for module attributes): | | | read more :ref:`here | | | | `| +-----------------+-------------------+---------------------------+ -| | co_lnotab | encoded mapping of line | -| | | numbers to bytecode | -| | | indices | -+-----------------+-------------------+---------------------------+ | | co_freevars | tuple of names of free | | | | variables (referenced via | | | | a function's closure) | diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 2089984404cec6..aef5bbe151cfeb 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1476,7 +1476,6 @@ indirectly) to mutable objects. single: co_filename (code object attribute) single: co_firstlineno (code object attribute) single: co_flags (code object attribute) - single: co_lnotab (code object attribute) single: co_name (code object attribute) single: co_names (code object attribute) single: co_nlocals (code object attribute) @@ -1549,14 +1548,6 @@ Special read-only attributes * - .. 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.15. - * - .. attribute:: codeobject.co_stacksize - The required stack size of the code object diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 4b092b13959530..8a78dbd90382ed 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -402,7 +402,7 @@ 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 +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 diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 221956f3dd3819..df6cc98eaf1c90 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -1347,7 +1347,7 @@ Deprecated ``int``, convert to int explicitly: ``~int(x)``. (Contributed by Tim Hoffmann in :gh:`103487`.) -* Accessing :attr:`~codeobject.co_lnotab` on code objects was deprecated in +* 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. May be removed in 3.15. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9ccd63bd8795f9..8f792800fa64d9 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1658,6 +1658,14 @@ threading (Contributed by Bénédikt Tran in :gh:`134087`.) +types +----- + +* Removed deprecated in :pep:`626` since Python 3.12 + :attr:`!codeobject.co_lnotab` from :class:`types.CodeType`. + (Contributed by Nikita Sobolev in :gh:`134690`.) + + typing ------ diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 9eafc09dbee5f4..bdd35d39e36194 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -2173,7 +2173,7 @@ 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 :attr:`~codeobject.co_lnotab` attribute of code objects +* 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`, diff --git a/InternalDocs/code_objects.md b/InternalDocs/code_objects.md index a91a7043c1b8d4..cccbe71588622c 100644 --- a/InternalDocs/code_objects.md +++ b/InternalDocs/code_objects.md @@ -70,14 +70,6 @@ The `co_linetable` bytes object of code objects contains a compact representation of the source code positions of instructions, which are returned by the `co_positions()` iterator. -> [!NOTE] -> `co_linetable` is not to be confused with `co_lnotab`. -> For backwards compatibility, `co_lnotab` exposes the format -> as it existed in Python 3.10 and lower: this older format -> stores only the start line for each instruction. -> It is lazily created from `co_linetable` when accessed. -> See [`Objects/lnotab_notes.txt`](../Objects/lnotab_notes.txt) for more details. - `co_linetable` consists of a sequence of location entries. Each entry starts with a byte with the most significant bit set, followed by zero or more bytes with the most significant bit unset. diff --git a/Lib/inspect.py b/Lib/inspect.py index dfc5503dee536e..d3af61b26e280a 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -416,7 +416,6 @@ def iscode(object): co_freevars tuple of names of free variables co_posonlyargcount number of positional only arguments co_kwonlyargcount number of keyword only arguments (not including ** arg) - co_lnotab encoded mapping of line numbers to bytecode indices co_name name with which this code object was defined co_names tuple of names other than arguments and function locals co_nlocals number of local variables @@ -1634,7 +1633,6 @@ def getframeinfo(frame, context=1): def getlineno(frame): """Get the line number from a frame object, allowing for optimization.""" - # FrameType.f_lineno is now a descriptor that grovels co_lnotab return frame.f_lineno _FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index fac7e9148f1502..5e802a929b14b8 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -424,13 +424,6 @@ def func(): new_code = code = func.__code__.replace(co_linetable=b'') self.assertEqual(list(new_code.co_lines()), []) - def test_co_lnotab_is_deprecated(self): # TODO: remove in 3.14 - def func(): - pass - - with self.assertWarns(DeprecationWarning): - func.__code__.co_lnotab - @unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing') def test_returns_only_none(self): value = True diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst new file mode 100644 index 00000000000000..d26fa590b3535f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst @@ -0,0 +1,2 @@ +Removed deprecated in :pep:`626` since Python 3.12 +:attr:`!codeobject.co_lnotab` from :class:`types.CodeType`. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 2c3d6dc4b0feed..50ebe657a0eea6 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1296,77 +1296,6 @@ _PyLineTable_NextAddressRange(PyCodeAddressRange *range) return 1; } -static int -emit_pair(PyObject **bytes, int *offset, int a, int b) -{ - Py_ssize_t len = PyBytes_GET_SIZE(*bytes); - if (*offset + 2 >= len) { - if (_PyBytes_Resize(bytes, len * 2) < 0) - return 0; - } - unsigned char *lnotab = (unsigned char *) PyBytes_AS_STRING(*bytes); - lnotab += *offset; - *lnotab++ = a; - *lnotab++ = b; - *offset += 2; - return 1; -} - -static int -emit_delta(PyObject **bytes, int bdelta, int ldelta, int *offset) -{ - while (bdelta > 255) { - if (!emit_pair(bytes, offset, 255, 0)) { - return 0; - } - bdelta -= 255; - } - while (ldelta > 127) { - if (!emit_pair(bytes, offset, bdelta, 127)) { - return 0; - } - bdelta = 0; - ldelta -= 127; - } - while (ldelta < -128) { - if (!emit_pair(bytes, offset, bdelta, -128)) { - return 0; - } - bdelta = 0; - ldelta += 128; - } - return emit_pair(bytes, offset, bdelta, ldelta); -} - -static PyObject * -decode_linetable(PyCodeObject *code) -{ - PyCodeAddressRange bounds; - PyObject *bytes; - int table_offset = 0; - int code_offset = 0; - int line = code->co_firstlineno; - bytes = PyBytes_FromStringAndSize(NULL, 64); - if (bytes == NULL) { - return NULL; - } - _PyCode_InitAddressRange(code, &bounds); - while (_PyLineTable_NextAddressRange(&bounds)) { - if (bounds.opaque.computed_line != line) { - int bdelta = bounds.ar_start - code_offset; - int ldelta = bounds.opaque.computed_line - line; - if (!emit_delta(&bytes, bdelta, ldelta, &table_offset)) { - Py_DECREF(bytes); - return NULL; - } - code_offset = bounds.ar_start; - line = bounds.opaque.computed_line; - } - } - _PyBytes_Resize(&bytes, table_offset); - return bytes; -} - typedef struct { PyObject_HEAD @@ -2739,18 +2668,6 @@ static PyMemberDef code_memberlist[] = { }; -static PyObject * -code_getlnotab(PyObject *self, void *closure) -{ - PyCodeObject *code = _PyCodeObject_CAST(self); - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "co_lnotab is deprecated, use co_lines instead.", - 1) < 0) { - return NULL; - } - return decode_linetable(code); -} - static PyObject * code_getvarnames(PyObject *self, void *closure) { @@ -2788,7 +2705,6 @@ code_getcode(PyObject *self, void *closure) } static PyGetSetDef code_getsetlist[] = { - {"co_lnotab", code_getlnotab, NULL, NULL}, {"_co_code_adaptive", code_getcodeadaptive, NULL, NULL}, // The following old names are kept for backward compatibility. {"co_varnames", code_getvarnames, NULL, NULL}, diff --git a/Objects/lnotab_notes.txt b/Objects/lnotab_notes.txt deleted file mode 100644 index 335e441cfded3d..00000000000000 --- a/Objects/lnotab_notes.txt +++ /dev/null @@ -1,228 +0,0 @@ -Description of the internal format of the line number table in Python 3.10 -and earlier. - -(For 3.11 onwards, see InternalDocs/code_objects.md) - -Conceptually, the line number table consists of a sequence of triples: - start-offset (inclusive), end-offset (exclusive), line-number. - -Note that not all byte codes have a line number so we need handle `None` for the line-number. - -However, storing the above sequence directly would be very inefficient as we would need 12 bytes per entry. - -First, note that the end of one entry is the same as the start of the next, so we can overlap entries. -Second, we don't really need arbitrary access to the sequence, so we can store deltas. - -We just need to store (end - start, line delta) pairs. The start offset of the first entry is always zero. - -Third, most deltas are small, so we can use a single byte for each value, as long we allow several entries for the same line. - -Consider the following table - Start End Line - 0 6 1 - 6 50 2 - 50 350 7 - 350 360 No line number - 360 376 8 - 376 380 208 - -Stripping the redundant ends gives: - - End-Start Line-delta - 6 +1 - 44 +1 - 300 +5 - 10 No line number - 16 +1 - 4 +200 - - -Note that the end - start value is always positive. - -Finally, in order to fit into a single byte we need to convert start deltas to the range 0 <= delta <= 254, -and line deltas to the range -127 <= delta <= 127. -A line delta of -128 is used to indicate no line number. -Also note that a delta of zero indicates that there are no bytecodes in the given range, -which means we can use an invalid line number for that range. - -Final form: - - Start delta Line delta - 6 +1 - 44 +1 - 254 +5 - 46 0 - 10 -128 (No line number, treated as a delta of zero) - 16 +1 - 0 +127 (line 135, but the range is empty as no bytecodes are at line 135) - 4 +73 - -Iterating over the table. -------------------------- - -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: - -def co_lines(code): - line = code.co_firstlineno - end = 0 - table_iter = iter(code.internal_line_table): - for sdelta, ldelta in table_iter: - if ldelta == 0: # No change to line number, just accumulate changes to end - end += sdelta - continue - start = end - end = start + sdelta - if ldelta == -128: # No valid line number -- skip entry - continue - line += ldelta - if end == start: # Empty range, omit. - continue - yield start, end, line - - - - -The historical co_lnotab format -------------------------------- - -prior to 3.10 code objects stored a field named co_lnotab. -This was an array of unsigned bytes disguised as a Python bytes object. - -The old co_lnotab did not account for the presence of bytecodes without a line number, -nor was it well suited to tracing as a number of workarounds were required. - -The old format can still be accessed via `code.co_lnotab`, which is lazily computed from the new format. - -Below is the description of the old co_lnotab format: - - -The array is conceptually a compressed list of - (bytecode offset increment, line number increment) -pairs. The details are important and delicate, best illustrated by example: - - byte code offset source code line number - 0 1 - 6 2 - 50 7 - 350 207 - 361 208 - -Instead of storing these numbers literally, we compress the list by storing only -the difference from one row to the next. Conceptually, the stored list might -look like: - - 0, 1, 6, 1, 44, 5, 300, 200, 11, 1 - -The above doesn't really work, but it's a start. An unsigned byte (byte code -offset) can't hold negative values, or values larger than 255, a signed byte -(line number) can't hold values larger than 127 or less than -128, and the -above example contains two such values. (Note that before 3.6, line number -was also encoded by an unsigned byte.) So we make two tweaks: - - (a) there's a deep assumption that byte code offsets increase monotonically, - and - (b) if byte code offset jumps by more than 255 from one row to the next, or if - source code line number jumps by more than 127 or less than -128 from one row - to the next, more than one pair is written to the table. In case #b, - there's no way to know from looking at the table later how many were written. - That's the delicate part. A user of co_lnotab desiring to find the source - line number corresponding to a bytecode address A should do something like - this: - - lineno = addr = 0 - for addr_incr, line_incr in co_lnotab: - addr += addr_incr - if addr > A: - return lineno - if line_incr >= 0x80: - line_incr -= 0x100 - lineno += line_incr - -(In C, this is implemented by PyCode_Addr2Line().) In order for this to work, -when the addr field increments by more than 255, the line # increment in each -pair generated must be 0 until the remaining addr increment is < 256. So, in -the example above, assemble_lnotab in compile.c should not (as was actually done -until 2.2) expand 300, 200 to - 255, 255, 45, 45, -but to - 255, 0, 45, 127, 0, 73. - -The above is sufficient to reconstruct line numbers for tracebacks, but not for -line tracing. Tracing is handled by PyCode_CheckLineNumber() in codeobject.c -and maybe_call_line_trace() in ceval.c. - -*** Tracing *** - -To a first approximation, we want to call the tracing function when the line -number of the current instruction changes. Re-computing the current line for -every instruction is a little slow, though, so each time we compute the line -number we save the bytecode indices where it's valid: - - *instr_lb <= frame->f_lasti < *instr_ub - -is true so long as execution does not change lines. That is, *instr_lb holds -the first bytecode index of the current line, and *instr_ub holds the first -bytecode index of the next line. As long as the above expression is true, -maybe_call_line_trace() does not need to call PyCode_CheckLineNumber(). Note -that the same line may appear multiple times in the lnotab, either because the -bytecode jumped more than 255 indices between line number changes or because -the compiler inserted the same line twice. Even in that case, *instr_ub holds -the first index of the next line. - -However, we don't *always* want to call the line trace function when the above -test fails. - -Consider this code: - -1: def f(a): -2: while a: -3: print(1) -4: break -5: else: -6: print(2) - -which compiles to this: - - 2 0 SETUP_LOOP 26 (to 28) - >> 2 LOAD_FAST 0 (a) - 4 POP_JUMP_IF_FALSE 18 - - 3 6 LOAD_GLOBAL 0 (print) - 8 LOAD_CONST 1 (1) - 10 CALL_NO_KW 1 - 12 POP_TOP - - 4 14 BREAK_LOOP - 16 JUMP_ABSOLUTE 2 - >> 18 POP_BLOCK - - 6 20 LOAD_GLOBAL 0 (print) - 22 LOAD_CONST 2 (2) - 24 CALL_NO_KW 1 - 26 POP_TOP - >> 28 LOAD_CONST 0 (None) - 30 RETURN_VALUE - -If 'a' is false, execution will jump to the POP_BLOCK instruction at offset 18 -and the co_lnotab will claim that execution has moved to line 4, which is wrong. -In this case, we could instead associate the POP_BLOCK with line 5, but that -would break jumps around loops without else clauses. - -We fix this by only calling the line trace function for a forward jump if the -co_lnotab indicates we have jumped to the *start* of a line, i.e. if the current -instruction offset matches the offset given for the start of a line by the -co_lnotab. For backward jumps, however, we always call the line trace function, -which lets a debugger stop on every evaluation of a loop guard (which usually -won't be the first opcode in a line). - -Why do we set f_lineno when tracing, and only just before calling the trace -function? Well, consider the code above when 'a' is true. If stepping through -this with 'n' in pdb, you would stop at line 1 with a "call" type event, then -line events on lines 2, 3, and 4, then a "return" type event -- but because the -code for the return actually falls in the range of the "line 6" opcodes, you -would be shown line 6 during this event. This is a change from the behaviour in -2.2 and before, and I've found it confusing in practice. By setting and using -f_lineno when tracing, one can report a line number different from that -suggested by f_lasti on this one occasion where it's desirable. From a2fa63b78703ddf03b305b47501c407241ccab54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mai=20Gim=C3=A9nez?= Date: Sat, 25 Apr 2026 22:27:11 +0100 Subject: [PATCH 10/44] gh-140727: Update tachyon logo (#148965) --- Doc/library/profiling.sampling.rst | 2 +- Doc/library/tachyon-logo.png | Bin 112996 -> 0 bytes Doc/whatsnew/3.15.rst | 2 +- .../sampling/_assets/tachyon-logo.png | Bin 149615 -> 94008 bytes 4 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 Doc/library/tachyon-logo.png diff --git a/Doc/library/profiling.sampling.rst b/Doc/library/profiling.sampling.rst index a6ce2f30eadb2c..790d36001800d0 100644 --- a/Doc/library/profiling.sampling.rst +++ b/Doc/library/profiling.sampling.rst @@ -17,7 +17,7 @@ -------------- -.. image:: tachyon-logo.png +.. image:: ../../Lib/profiling/sampling/_assets/tachyon-logo.png :alt: Tachyon logo :align: center :width: 300px diff --git a/Doc/library/tachyon-logo.png b/Doc/library/tachyon-logo.png deleted file mode 100644 index bf0901ec9f313e0d6190da46207517927c8bfc1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112996 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:DtgX?6)Rk8T9oSW};_fZL8atCF_ zG#f_ug?K(6$Jt6B6y9&NJI`rpPVa$VNt?@O80c9wD0x2DIkyCS8mLhOEP|E=rJ*o-#s5dvs@Wld87-Bhfd04(fArINK~Kc4SF4pygKzuWeZ46 zLS2eQ{0%W&Z@}a0gW5cMxrhUh+_REIy`mzbd`9wnC{I>dC{iKM8(>MOsDiYdFkmtE zf!-#1vAn8Ryk4qTR9i-}HHhO0ygXFq=C`8ni!DE9WASyGg2hK=cpnI7UvX!rjPlWT zD^d;Pd!o)1#C6$d-w}S;8YR5q+_48r^K}B-W}lxR2bGO}Io;!xi{f$YuCnB|MQILT z3Cw&G^+~0;9Md|83i-bPd2Fju{rM5<_2Ybj?m+!nhBE&XylfvK2#ru+XpQ7!{SbBL z7?*#Nr?n2%?}JlffHi$?hP7{7R$zZ&*1*9XS>2U;)-<72SR82Nmio-CfSE3unF%)B z8K~fj6}cmftwE-pyM%a(lC&mIcH}i^ux4O#$NL%w8SOW!SO}xtb+}Hp!J_Z(8=RX3 zz$p(_J`CUJU9<80HdHRvn^01_L6#N^*LujRe56+kc{&>@VwKQ%MFQLV0}7YmTphyT7>?S@qTkL=I;q~sZ}lJDk1C$$96 zKZ(aQ!j;S(T!qI?gLe-~dvLoOr0_N3kE+)eW`v6ZzG}RxS5#aGdfdS_bQm;!UCh_A zYrOlWsN?`REDcD!mKf%F%`$vCPt+?^^szOB#OUVw$j{4GAt8&36WfGVg(zNnd#+uX z;v?IG%=nU&`fdEY1Rw&IY-Grb6JNpC7%Nz8D=XiY<})ipVM}_lwIuzW7GWC!Yy6~C zW;4AyN{b!g;3Gqz;p*emyVN48+htI!{^>$T`MVC4Hv(B!Rbq}m=fa8UCyX)wM>UJP zKQmB$v09D)n3ei>Z4oX=TW$fati1(UHb5&_v^}dAeyo=Es&%`ZfYIqT7SdWxxzd1W zO)eL3D^9SbQk8VTN-(;hn{)Q$SZ|=7M`b56=0%L^JM@9kkp-oujzg}|CY)h6|Mq+s z-8V6$lz?TDhb7*r{eU#KPD@OHOmD?0z#_a7n)n&`+|9owL8JMhu{-dNxq_M8V}6%> zKhGO|2jT2av&abDiv_sqMfE_vvY}o*XV*O9XJC0$uRe@gC*CV6Ro_B=AGHzn1}Ygv zz{*vM1;EH`7;van=On#{)d~d;9U{OKA@p4g5N*V9Wqw*34EcLmf7eMYXd}TI_-*?;1Po0g|zeLkGM*`8HZvsgK zG7EqOXlYEeMM#LTYSd6=Do{91JD|&@JqI&CA&GFt=Txx(g;cc+peQ^Fr`sM9B+7eE zu;kwu!s;P@0@u76_aPJ4l~szC;SD}14R810Ys|PpJ&K<{idu%+EXvi7QGXG@3ac6e zOylNr1FsR|0h)#`q+GC_79P|R59*b)y9TJ*Xgd%ARfH?6Jds6TJAN74Z5lXuErA>H zI1N_qq5+od(v-0>cy0_>cs-$$_dZS{eFLGF1EVF2tB9>bwg)+cTcA&=UVuVEnJ8&G zh!T{wzsE0{;$^4fP;Xlv>PVm_$zGhc5G|63MZV)WP&yh>>rr>1_8ljSa8Nu>6ENQ9 zWITQXbsuUwD&Y0F0%coJH=;g1W`zDh`QkM`fNJ#tDt3*Rh=$)jxzdJeWmCZ_*u2_) z<)uvf;iP5OyhSN??c4M1Kn=W3O=|^`q5~E@Te*Rng*5;y@oKrW2U!AG0Dz_h0gFMS zVZhpinu~e~6*jS(n7AEqY>W$7qO#3ygRd+I(7N7kdG0j4ejVVYAXqcIod83SF}UV! zfSYavUw0A^69g=!Uphy#LneL#mXw@&u^d0ZSAzen)QLE7#IXm5kTK&x*%BEi{%t6QcNfiG!eFp1)U0 zsV!SJYB^q!Dq78uUlaI1D;)#r9#x5sE^3b!0S=FhY6xbP#XwV z;;$O%fAq4A6G%Pme~+;SJg3^B<$>Z{{zzue)!l{ga#0@jpCL?fYgd zwkvj~1bS*#?hiI%aaU0a*?dH1*4DoIb%kw}>-N+at*$M9RR-wK|_L7fP`|lseJ2 z?XmcM5{|Xi2^?ig`|3i20oB_9Sbf-i-lu$6<|cO<+qKr8X42PF*{LXoHym&Wp#zps zv1ERrNhAi9GAd{Uv~YMT?jJ#|Roj(lUU`m!wVEx+On}yhd}2!6dvzvv4@x`m3jBN; zDnkJ4I@Fn{m8iV1`XQ~Iqx{(|fRYN8^6%(d_1QioU>L)dQoqU^vv#!hB2}E3+nKq= zHW?RlFX|P60p|#6IbU8%T4`bRO7ld=O&s9syJRc;Si|bd7Tm3{AXogginn z;fSEc_n|tKH%ohcA$?=~31V>5#+3O>P=7-83ny46ro6tgCYaTSJ0E@=^*$q&bZpG^}xL~XI$0@=T12Vaz z4(blCAA?~(?nQtYm>>iowwm-!?>~s2hc8*+$iH5 zta{EA{i|XJ( zQL*^Fo$nJ<;(&FY>>f--osQ?1%kyN^`%zz$=edIn>%Atb4o9vK5+b};k^ZY*+tq&+ zGZvsdJkXi1(E$s`FAcT8icR52OQ;wB6aTudQdy4_VK(jRw%X#SM@*zTg1Fa1W~pl!}AcSjPY|4xEKe)dncbEC)Vymrp7zU z7a&V1U%3FR!{yuO+E?D5Vu^}n`C*u8)uE81;Kpn@6ZBMXeXZ{--aV zn10M#0XXlk{qHvM@X=TQy2?JdJ*)2kOz+6ZY6WCfaCadM@VmFJ?tlH2%)q0`OYI+C zx4^#rq4{Vpt_oJ|&1y!>!!DEd6CuHZft~;%I{|azLY630RKBPj1f4Nac;e@OOlxF-ZeHAOsN+i1B-kHnn-Fe(u7&5&FiV@#XwmYkB>su?k=ci`aw>-4ezT!5v z7#GU~*_t(9x{v2|!L-)}z?$q71+M@WH?KvC>!T(L7)@j(B;uK)QH zetyt6TS}=v)fOOwa6mOoolgfWz|#TvuWYC{wNPBn6Ttc!KEvyTCIS{s=x%@|Fvct` z8?dGVJPlfOgrbO@e|RL+@c!Z4a;#W8Ed`|1@49%+L4cRL4@)1~Oyvtz$rN{njPf2j zyEy@Io>Z*WRoF?`Za1x2h5LQFPfq5^?H@xCsHVcRG>$Do)N`oIMBO?aKWCxx1`PtP zrt%cC1y#!u1GDUu(*Y+G0$NUmNQHo!!Ih(+F;Ekxc~M&Qa*mJc!+UlZ1D4t$*-9mc zijLL_^;kGo9aS3TMbiOG;DjyMBUE*q9d@&g{8u~SO?%Kx4mroc|2eg_5P!sJ`3x zYy}dDg-Bh#f^iMZ$Fc}oZ@!ip%viN7c+J!li+^V;5v}9E1rt+u&0UoCVsr8GV)(So z%-a$HK;U3$fD#w7*3jU6II!nZyFd4=*8xaRCk9w+w$hv)bO5V^CibC9mnsPvtqdtX zWqAHA0jz()_5K-<<3u$;i6bnyL@iVhYytT^0^`d*IT-^MhdU=- zj5-VTae=Kn0Vp9EP~sXkDg|p!E)CQ%p+nkTG8eEnb~vYu%_&n!-liqvd7QRmOyz6N z7A)EgfVU0~4fd2#MF1cW$oLoUrS1ps9f3)MBnO5Vk>Q*lAV$FA>v^A}!;TK40)#DI zfKcWMcJXtIugl0+MjsVHnd5}`Jc=@wOW-4z^Z258Sp+WLX95>nzTrfEJa5G@Z5rHB z9GMf`@z!JQKf5`VZ9+>y#k;!tQMsr)F2K&c@srcX(G!KrLNyH2U-by;lcNk)r=zYC z&^zk4{JiWgOtBxFongQDhgAeE>!DdG)>XHsa9$_CD&n=TUVcmZ`Dh6)u$zn_CuG_t zY%t4-fMUl=eJAwjic%3&pg@1l2kn8v|KJX^*9^}q;IZs&w$=bm0$5pj@jO@nt9rf` z*Ss45@Sg%$U&QfV2B7SO$7z5nk=uC*Vf0`H_FZ0$sTj_vQma)f{wza9OplUCK8kCq!2SNYPfp&V8YZLW z6ShLnTSB`F??UB8C>Br|2pw(^q@}j2QlN%1h(FLr;>ft2*ag6-nR%33{ZY9d0Ir9i z!`2nsb)F6G3MT6a6e?~Z24D#!5w2KHdo`Y#r_?L@#sDy#vU||*dUa16+peyIv6c5r zz7xC;_bZ;EosZ(rYGP}V69D*LZTBEP2a2JwEvSC{uC=`+xoFz##eHatycfVK2XzCY z7R~@%(YSsQ>M7I_uSxDWRyH1|qrP(9wX4RCB3PY_nn1AfXA=&$7~ha;Uv_QSlZBvD z)KEnX|2K25-M0Q*=0ce&GR$J4@!E27td0-@v|+(IWT_o}m5+7~6rOXa@69ns1N(kj zkI>IVQ_(9z%2_2UCSa|Xa{Nzxt$ir%EdbWp0$7*ecyCe>q8Cg>D8x0YH{iV8fdf~q z3>IJCiu)Hw4+Z=M)g) zBNe?+OI8CN_fpCkzgs9#a$K!6z?z$FJNK!Vu;d1I63x*}zm~i<9*`BHI;2&Y_x$hX zw_I}dyBe%Eqplkdb?1Ufsf=3qiHx{u^Vn*tLS>?U0l+$I%qahpF*!>Jias12Gb8-A zdw!j9u6<m{fvewii+WPR_7a~PZS`m1XZ@ne!2bWW`?F8Y>d*eb!C={)O<154 zX=~+jHyqV7K$C?F7^XF@!V zr{!xImgH*n;QQTVH$VABpPCW^tT|Mx-e=b&oiIqzItTSKN*S_gY(`s01yM#@K<}mJ zM)zLn8CG3ZrE!Ep4i|C)0N>-DnKh>n5ZH@$SCzS%qw<`%n~g=-V*5&0jEpOn5J~ll z9wYs;^0@*ms5%Gm83*VAll(2``n7Xvo-+wuCFq2_aft$!6M!0b(}DgT0u~f6UUQ$C zRp<(^9iq&+neKPvob=G`#b%70s-1HW-u&Q_fV4l}E!87nbmvT(q<@W^o^FLSK_xhL%2MYf^2MSn4A`7_;iS7S;z8Z5ozTloKJPf=kapt*U~ z2tk$vx5h}c?-C_Sd9R>SF^E(096ZiI8M^fSD+LuHmh?Mk;d#fOP=p=*45`xJY#` z`2DlOXq$XcuY^YNZTd4ujRtQGfN&Itz%mYB`|lGCUqPyL@1@{)(t*Z9ecE z)9OY|A2a5$lkBFfOF8^GDiHCJft~~_KmdJAOhiZcwx-#gFQ0&5(-ZNx zWCo3@q0v;hBrYMg64enbyZcaJ_l0K%EAG1!Dq25|ufdH5ST#_Y;)H_IPeo`V#8Qo7 zyOPfp@aaRcIVy+eruxM;qj$iP5kQ1ILK{HTs!nQUEJUKmp{nML-dQ@;r=;O2TBl)A zTaL;bI@&}H0@m}Cn5s?y(-vEOF)s#S8SQpcv+k%~JyhxvN*dwSV$6XX!0EoQJKOpB zs3Jwq@^kSeMV}+sK=mqmSf~J!QqQ(iq2uslz)~dvi+(Csz>>dnM;@=)i@{x@Au4f< z9(`<4jL$A$$M1yDWHPIfJ3uY73^neiqz_B&0)tw+zm54X%2Gr+ZdOjvG3qSqos_G2)nDLw(W?6p&btzSGY2d&esiWEprYK#m0Dc@NE?&@zgFfXhV!zZ z235`o0$L73S(zxG`BW8b>I`D0rz@Uv!?qdV684PSiU;z~J`3v71$a-ZD_rtE*2tKe zA+V(Rs&GHqrlXq1Yl#)JKvO(pF{o})9b?NhVX)HUAUbhBot7`4nTnfsy#{d<(k6TY zO5yu`Qu1zi|03$8p$2g(I&AT<)e~#Rp26(D96|(-!}go0_lje2OlB5F&PtqS4p=o3 zpbChTAk)r43GNDJ%m+nF#uG>R#0d*k>RE9J#%g}G2iH*L0$7@Ed%&j<(L7o_H3C~ef_;Dwue?>i;QJC z6@WGl+t*JH4MRp7jlhHYH`GMbb0aXirBzg_XHZj7-$0!PZ`DbJ4>?b!fL&jL>Fuw{ zD4frXzbajas?qmZJ%rke3i_}!Dz#xe!eYbUiQV?{2eDAEK^1WErsoNwcto7iSd#4iK@spmXE zDpPiS>On>}4Enn&ffs^`L^PD+y1ygm{sjL1f.Vt!T?1R8zzA=IK^p!kNu{w*Iq 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 diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 8f792800fa64d9..fc611cd5cd662d 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -272,7 +272,7 @@ The :mod:`profile` module is deprecated and will be removed in Python 3.17. Tachyon: High frequency statistical sampling profiler ----------------------------------------------------- -.. image:: ../library/tachyon-logo.png +.. image:: ../../Lib/profiling/sampling/_assets/tachyon-logo.png :alt: Tachyon profiler logo :align: center :width: 200px diff --git a/Lib/profiling/sampling/_assets/tachyon-logo.png b/Lib/profiling/sampling/_assets/tachyon-logo.png index f87e006b14f21544b707be12ff3a6ca15bd7f673..03e67823d5afc94c156953519418d5a8dc18283a 100644 GIT binary patch literal 94008 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�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≺;RD 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 Vw~RBgAp0jnML|>kr<{4v{|E4fh8O?< literal 149615 zcmYIvcR1T`^tQdXT2W%vtWBxCN$py-XG>6{YHx}dsgbI^sZ~_%O~q>MQM6|4Ek*^w zoA2+v-uHLqk6hR1bLErdJm;L}zVCBC@dkRD!mIhRuDH9R>TJ^tQ*Xwh+Ql!UlA;gf-i-*Gc_5HncOI3A8b$gYg$<@-t!Q70z$&SJf@s7g8PDuZbz{Qj%F!W+-$b{@I zn$bCR@k8|R>+h>3t1W%SuL=?cwLAyMKMYL{i>rmh-hBIsakgOe?F)Q zWFX1AtGNIQ;lM_UMvRnfvE_h_gqIO`nb^npcTabBnC`lRm%CPrDpF?enUI9Yg8R7| z)^W*rg{j?=WQCjEjj9hhH*|1RN{^w-@`LnRoZ;OwyQh-ox!AhBG1ha(xF5s|etR7o zaY2Q-LBTH=6iORODdI%EHhNwk+0V)MO(0kDv zd)3#i8;?kyuyR;1PPYEx28S5JegjKJc)F}tgLMXNmmStmXxy=l*>fURW5%h(PR>Bi$txx&x`$|p&F~#q;1Iz8O zcv?+uHU{jW^cs@clwL^U5uFu*`?Wj-WhhNf@FPv=@Oz}A2u^(@Qd8-%TAd1-$Nh5F z-SQ${Mo#<`j0Asi$C!tBPM za45BJjj}Oy&pRu0Q60y&fZaXQ#Q`Lj3nKf0{_&c_E^Fk&tzN)SpOhVRzp4IAvr2)> z|II3I6Q$&3<)2U9(;)AOEW@60UtujQ&Imp7{q-Qzh5dXetDKFO5#L!$1|RC?*!vBY zj^)lGs!8P|HQa+^i_=Zma_k!Ma}Y`ag`!N?Gz%-{>1Pt&-~(l&K&hGN=Yg>Ze_>G7i;fK zn2YZ2#o(k9nZ#tN5nU1LOmFJ$8?|mWlc+MS<#RrpKx$Fn zNv1WAwGGG;)Rh50ib8@Q)w=HQ{$3~Ie}Av!#ljKWt4|EkXkh2N4}7abSwb+D`%vxuB>|jJO;_XdCZB9(UBV=rlDHKrU zld02i^-zjqcT_yssbeUn{7UZw<$6@!`AbN6bP|8e2TLLY4-)9lm!e2C?V z6KShjn10GVuhhv1tJCKG3zvKo?}~|{rX=_yWNZ%3oY}V>r&L>Rean0LT*O3;&AqT@ zRsGEK&puzz=Km&za2mY__;(9z^|U5!`5+!OCwK^Dv`Nm7g$(~k(G-_@k{VaPXnM^JPBOzhON zIq0`Avh=cEu!v`s*p{KStwrq@a$JQ!F!(niUXrTceYoZF<^TM3?I@I+ux3A>1XynI zQ^EcG7cWN*G1(LBjc3bR{42hxzmt5K&9uG_YWD*Kj|rRy=(pi@I`;2sB%F+~oW1g* z+Q(Z5wK-irWLlBeg}ijX7l7PZ&xX?PvI#ApgaVn+$2B%l+Tz;(MOsfed37c6^=}(N zw$C%I;!5;=s;7qhyrPqM`xR~uqc3E?H`tE1Vm6%C8|u(Ix&TLE&itDVS)FxnMZvba zxB=$ecD9z0t(Y&1Jc%$&xRq#-KaCLNqikWB|IA*3&(jO1ep6LxbpFUi;o1&4J>ZCj ztK~jV0GC}_aiok$d0s9E0sg+NXZ4xfXeszPY7=V&k=ZG$|#`ZT5BgP4VbajK3)@oEnZ$;tgU?3vL%PT zi3zYTZ}(77TJ1IcGVpnuT3A6{dUov(K9t_yWg4L2&TF%miQ1ZQ5bKHixvEO@MY)(b zsT;rTF+rBVhw7FcGg0GJY@SXdJWg-BFd1>EoA(D$Z^RgtAvEv?3V<8a8- zO0;b9nRD+@1i+i@S@hw(zNP%q|E%M=21;1JclGX$s{#1HG_vZ|;dI4H9gbQsy>fGe z6xWxJOhX2nSX@5Q!K&k>ydGP=l1ZoOIP|Ne1Y0e~lae|_$%JiHJ7@z3QQ>;e6xD-? ziy6<CKXu^F=Xo&nRr&Q|p>&xH$rZbtBFP=#vu^ zQ6Tq!;SK_}TsFp0%8;DlQ|9I%yB%oQ?-9BP)|0&p+38PUZF=d54a*4U=%g~!#09f? zzSQ^pt=~RyS4!d5Ne8X8Lb$MSy>K|etx1`c3IK6&?SqLkK;3r#^ino^wxBo$w zaZ>e^06b=SUZL<`R|s-zp62(P`y5Ou-CNhzNX_H~K|vQQmibcASkiM;zb2gC6>LyV zf`~&TKc=;OHS1#EGI%N9dhCWI{0HIgi5x-G**8VLYP=zwtgxhF%K3yI zr*Q2tqb%9Y`?V*%&|a#2IV-7hkQ>oFi?Oa4Bp!|;*Jqj;p6<~7TM|MdN-_Bo0Wk|PKu0D! zoIQ{hmsBWe_C_3b%1qncs%_AyCa|RGz9jP&hQEJpCDGc#ZXkn~))l1i?d<_zClIMfgr;!8yL119No2=c zm7OpKD;yQ~7-qvdUn% zd77Rw?Dx0!akl*s>c4$&svOQpGj(X|Ma$FbCMZE2#Zw4{xmLDkQatyk){tvoW5rml zsHnqz*ne2oF<9AILCy7A_v!EOu0bep&g-TUvMQFva>ZfOhzacb-Lf|Bv4p`nmR3J} zq)b5c&_MNQnP*kk;Ug#xl-|#!QzCvrE2^k2-mIH8mD12Yc~7OdiYuIU$i%y7=5%(?5;5BfI)Z!+}K*}8X^yar-0edb>^ zsQ@B9IYG*c;Z%E5yr6V3@(p1d@?%Z@_y0NsCDz44T(6H^Xzy~=pkmE3Y+}CD^3y2l z&fyp7UjwNP8l^|Na%q1cgNYpm40faq2y&K_HlxqZS`xKTm&R`lvk8h`I-QZ>y#Wt& zTfb1|PIkfbT6=`iHf$FQEafiQ+D2MRL&(uzk)HS(2F`O$0&u9euRT+$nvRE_yFEot zWwfqdXr#~x14tz63o1IXb?Kh0K;ofIr)Er!!+ zSh5e3+%tv?6i``ywaMYaon|wDt&!`+k35se{#y=m)PdoNj&yt70zz-pHsp$iJoHUuGw7P#iPHx4CUwRW4&ohDKFrVBGBnJD z?^*u`sWTAH`S+r`w;s;>IyGQ}=MuHL4pFfvRUSRCp4AMMPB(ks=G_OQI0-PhYvZ@% zQqcsnYAE6KqQ&5|~GfYc!5z;m)};(@&O7Uag!UJx~!*b>C+R-!NQj95NQ z_VAk*M-c;CuJR=n=nlV^G$PL>ZLairCQVW+M2pKN%);H900#a}V;-`|)DAJr9cqf3 z^^G0y-4R)IPRmqeGod;9kH9QgjM@-#<0#UrJm9yU&V3*y#D)VEqVSg8=Ci%Ijj4ob z!Y`#xo0V*+(15yl`1YrbzK@%pZ` z9aemn32tT*xjod#92&9*0mK92u9?=O#Tnu5-S&7ccO}?7ts-@`mMR;M9aKfz27YN>;=yVh#Z1!1scL0Eib|=T(Mj~o87&P- zLXb#4u`D6sz&&T_-xhFB-sVqsw7{Ir=OGI_+O|R3C;RuZMKqV?C$k+ENz+mp@SaL`nPLap4p6Go(h6w{38{~} z!N^UMQ=o0lB|uMxrM!Vjc9L5858O;}Pl({CLjzR8bmN_h@x~u6!%M8NEl5CmGR_0A zid{J>smrmtyfgXjN9WFm0#pVtj2>$~{Kl~Mwqb($H|M)K$5}MCoM?qN_^EBOS#=tH zM4FAS$MkCaaKeza*?q&$NkWv(Xj%dLDj7Ak!d+^3%bd~V(321QvUYy9?JFGkpB_q) z)728WZ4}6M?uHEoE|>EK>6}ONLg2=4w6L1;bepc-_uw<$NtZB&TI)XgFJ5_dJU-!V zyRHu}l}kd2V7)sLC)l6GT0AaOeh)g@i%Q|kzmt80Z$Y9bXqt}`B7Ul4C*3t{?ps6R z?L*f$JQgoFp06%qm5JH0UraNpsflJ)EucdH)$9Vz(O|U6a zEa}s52Cvl5&8!SPgZJFV%c61!V>5aE`-p#S+Q&$0O=!iXfSHIJ;_Fmc?WtZ<*{k)Y zZPRW(lHsLRA$^5qp+s7St42gzDIHG4O&^55P5S#hMpp5x#8O^9)QZiRt_m0hSK=V( zuLfl+?zPY8<2mb!cq`7o|2$$rHPA!~(6f#UD8k?hkv4+zJW76mFu7Z2t7TFT*Zt{` z(gT~qnE++m;5=c3IrTObkL6LWhDAFOBQ8}kE|pc^ zLG=*McGjWB;&g(MmTqnrxEC6B0+&vgPB_Eo#a{)XdCYhl3ZJX_RedM4$J$MyiSf2m z{5ig2ImakM@+s1x&b;8_*Boq|2fJOVV_uJ8m*fM$P)CQpK&NhW%NpMD-|6e?)qC;+gn^%*~Ev`@Pg?NyEuN(jCf_ z@{O+CoA}uL5PysC%~|73VU_Ue>o>xMOCH&M$iTb`M{eUBhtZl^zkX5Z{Cup zHfl^(L}Ws?>?S1^e6)AWOJYI+Qru4%Wu7))WK?vsxQL%7Yaur*7n>BC8D;$r*KT&u zkH#tq`HtH>u7|L_;6rUgEXOpwQJlm*#;$8o;*#4R@U}iCYgVkDW)n0KWjNcaPziE2 z&@mVi&`BJbqw45n_Y_qFWk(C;_87YRDMzIWUo7!lIXucbbS{viM}GA0jMfi(`>800 z13<~7_H-X1H|#^*4V9>N73OPOpfHFue>sgbBPru$gOHImXw>D3M2E7iQNy@uugyjbV4~V;rs%z)CMe^CP=iO9b1## z?9j`oR41xBK^uDwcl+Zc31g5o*CnxU9y=A!Getq0!2Jum{I@u|a^%h)FLlMZ89NAb z0)^{CAUc_Y(pS+cK%>@6=t;?pH5UUJ2hM(J#BO4Q6t*xzdAB#1I z@^^UD;UdLUl2`J(bNJTab{B!YIgzRrq|;B+ZYVHJXG4k$TJuWo*JY7m^NwDY!R&>D zkn!oTzY6MxsvMzhW{LnPrn1&Xp41ZR^C3##szAEN7hTlcUf!3igdQuf9zt@QT9 zu_60}`H#$YHH_s1r~17>oC4)bd-z}!CpE;9IQ)UfIzK39BI6hxUvvE6NYResS-a*+ zz8CVlh|!;MIX08@P%v^2Q;v%7pEs><)Q&Vdrsi@fGSri#&=I-E`eLAX_rz4aq|F*2 zd_+~}^$MN(kbr#n;Ni@wIKu-lqiMhJsAy|^1WLd;wOI2l#La%60N0kCjlqQw572*s z3yhWaoog5Tg+Uo^!CjNC<`6{w&B*HMAP(=Kq>1jRxUh3N08=r z+zi^vs`AnwmQl%P(jVq=Csaz(2DM?8Ie7nZD1kg3x71wM;_{p4YSWnp3=ASRs_xz| z-0iVNktPyx#p`GV-E3L}r5uONbz-kRPBKTr;qH*Dm3F{Nb$A}z{NVGMU?nK;@tZZ- z$P+A&wf#W9`Tn5ebRjQA@agW3!Tmt5>bMIb-6QR%AJ@4i3PLY}G3h)1v^YGJIPGuT z$Pv(ZK2#CVWvJ85orNS!)p7pYmMP39)C_2;7)*48H?ah4!2t7qkjz(TB%$cyzdxU$ zfAo+q!iD>W^<8#S!&A{Ula-vZJ-*dE8&#_=ztyvw+pW+#|FG+DZLnlYLDedqjoQ}6 zRIthfGlj^pd~nuox>en=;XI3yI}dc8cbwx{C&`YUcU~Iljt5P>HSyrF`r)nNxxG74 zAC=hBJa6kCuSdx!OGE*!G3$e!~V1#H+^_Kh3r6{k+1>Z5#eHg$(M}57EU?eg?Bg zdg}d^xNCCt>3j@hM+~B%WcOU~q6lE@_;&zUMd$PuLHQ8y@&Vo{f3foo#XLMo>Ash1 z1xw8P>}9r#N`dARqeZ-kpkf$-!*vOu3Xd0LWvG)2jeX5Q-U!O(ghN3R#HS=YGY3MJ z)@cHg;+RFn*0tD>uO3`&cwCJ=JYKN8Pw~$?vi0&3sKgbS55wc-?bKRZb;&N4>BR6+ z_$ytJXVv;Mc-;i(=chYS3WZuk-)6@QPF25$#r0h0J$##U>~NjBCWDMc;ZX^uA1$7x7E@iyX8*8tp1`~X3{!sV!T5n*mKZ6jl0X=NpEd%b>IsWp=?aHkxh+-^O% z8jX%zKy%c-*y5|leK99}-f`7;K8*JpJG}0q$3Tq*}w)< zVtu4smU5|RXQ7HA1t?seAnJ+AJ=Hu6y(wN_B3Ek)g>a#wDH9Yi!m^B|d~;%)G%{fZ z60eor@A6K5Sc>||JnvSB`&#pSPaRJ!aVdatl?;u2dP`D=_`}Kc>lI&F=g0wRuV8OS zM32E{c$!5Z0e_ZIzM;o`-a{{`?bT{akd@gJ8ULuh2-2zJ=~g~Pq#0MsRonou_Jzp( zT9d9AmKwD%+?UkhJkh14aDC*!D-Vrt@eB_J-Z0T~waUSRW!fDo&vDIYM~~%tx6y`Q zR0KqySKJ35$(?qMd0s#6o4KM5-D|Uy6}Jq!`h_JQM97H$TG&gLOUkuVq88*xsKwD7 z@UnCIfdLN&e&nvuio<*Fi&9{MJ44 z-d7gY#mR4>4BhLYN*1pc2wu>P7=dd1>$pvJ4;>w6wPu{cbw7j~r+$&vbVz6WhuYK( z52(KNH(~1(zq6#9t)JNBUUiGLy{Y*UaNdY2N%jC*7yY7y(sP6M9TAi)bFHEuV)2o2 zz&?66hTnw^fc#e0`cynvoMa`Xi?olHyS;K0GH#+aYwK&Q5ux?@irRr~Sam>jc-Cz9 zhgR;sx0ak{?{rN(--ZLIfTQev%jbfvr``ZFf46B?dfM56>c^l4wFo)^jZZT!V48UV zu3_g7<2q@J;5pxUDtNqE_RDz|of_G;1JORIw8cwhdXI>`zo5CY+lAWJBj^dP|DS4f zMdD^K{(a4lUH75pf5k26)!&49Pc!%?}*DQ znpVNk#>n#%@pNao`A5KTm8jGbFBAMRTM0NY_a*r`z>J5BQSqzc+ohNW!U6q{rPGL= zzlBhYpm1EAb)dD6i_uI1rbM9(|ELJZ8mgDG)zI~Ey0yV2i5d4D&ramQFQmd3V<^+) z&@&5nW(s4s+2-iq<$Zu}C3U?Na*i)y+n&@?`Q?Bu__p%^Q2P5%nR@=5Qib?L(#%3` zn=Qs?W{v``X;$_wR)32O-_|XlbMg8MaId{36r#u?Kz79+hh>}Fb3(uVcv}~~$``7n z#4dbFvKB{|-!HjK(xsJq5Zw9uL}g9nGy~_vY&H0k2>0LNX@JXFOHYIRftEYnMIHNL z{3J8s_~}@0xcFB2iVKg8zgiR$#=t-pIHxitQoRh5av(j&$Zqp4_A0P476)x31Z6>M zO5l`NkYcT6%IJ77wKqVI#dyAwF&rJ)kT?~Okinl1L}|PYxje7Euk)nN3D(M?Tw6W0 zcN+4yog!}IrZDOfUuMU$%1M*DJ}eL|8+wp-P!J_!`^`*=GAK&q!;|4y5#ZTV)TJ3`ncGdb4&BPvEmn_2{jlVW%ipYU* zn7$OR z*HUz4BIm~?uHT^vrS|>Jwb9~GvO2`IQ(CXHRXb|rR%9`Du*nd!&nUHYH+tjx)&SQo^58k5w&!!hYo+V$CLgujxDFYW)}HSQu#it zz{#EB*Ir%D^Exz7be)YjGCzlV593-I48C7ZpAd>QJdL3eHQjUr#}d~9j(uU zlOoYhGKgfLDI6>vymnYlvr(n^ z>hgpw340j3#nDI6HO}Un9iiv*D3rCtPt1gb3kmX<&-?dhPmb7QACVq01)v=QeGeUPHQb#YwL%<{O=Z|_J^@n zEzUKg-nwFR9^>cfd4u(aIUuplg-q_FdOFrVP4O6p5m2lXYSduRAbm64}+Ye#Ek_uGi z${i0nDE$_d>98`CYL6R10n48=7!V&h*F@-*$37>`iD>HM0!#kZ0VLa_#m`aS;!w4zpn|1t?)3L(dgga+m^pPDgJ(TBQJESv348D ze1o@HY0*UE$^z0)|@`gniWb6JZ`q9WFacIW|Zlhk){z8#i_@p1n)#`kKo) z^{GEU$Gs4fAej1vE&BSu;D1SH2;HVg5FFPM`8RCg!35p*ca?6!oexSzMf>Bv+KMeX4vwwXQ~t7v;p%& z1FW7*$5`lQtG0e)YnGVdWKYKD6?)q0bxhzDiHa_uvAoCDV#SYP(WJhHVl+a@=m~U$ue`bJ_H-jkI~C+$zHo9J%lk7Y6=Sw zr(@d8GuV}@|F&l2Nx#4j92lzB8EIbYU>hGR9xPFroG3URuNZ)s=FdoQmFUoh-Lw+> zt>mR;t%SmE3N;!C;hPKtz~D!Y#S;GI{z8?xAiupiWxn>~ksLUNysu13R1{)tC`jhH z7yr`a8zfBKH2*t&9A=h`H0n*$bp?6NaiUcguaiTHoTzJDFg9r+sfy^3>ar2CFVa$Gtnq)KO^Ety6;770xW(9$U6y5$8Jn__x=(jHr!S zXP@mL@IKJ0nv`O3u`c@EbL5ZtP^?a(T=U;;A&*b}*5jRRS`fKia`S`6@4hd z`YkY$zX6QXSJtYU>HA-q2x6L9kh^cap6-4>1`abAX)W_QAXuF(xgSq+KCWhdM6S=O zvXO)*d!p?A{V}R?YL=V?qWG_HVOq{P-!VG?kl(3nUG64UqPPa2ysxa3W8#Whm*PxY zf45d8r*rwL?qfUz`_3<=1qa_NA%U&9A_#*oz|%|Y+_J)9Tc|~ZB|kt)L&D~dAOtqL zE1$fT5QU+-;Lb)(9X|_nWI+-|_@@8e!77Q6ir?K%&GCw4TzSv_4%6sI8t{kGCQvMO zccO&#<(lbLU9t}cH*^`LZgOXt+Sr_AwABD?3#wt>=Xiy-?p2{t6?sfGzEm}ZxN@-y zXL@1SIUR3oaceO$Z8UW}av%Zz$kv?LuH^2a9918eS{TeXSg}45?eZzZZfBWK^wnjD z6WC;QaJZxkj`!-QTNdgL`SLqu?*I;zf7&e8vovHJ)!ELBld>$Ycpd*V;LNhKhaf~lCCrKP3AtL#?asa z;exD*yw9P@uCjAYg9eiS4UKG{-!qQg9*-q700a-`OC`CVg=e6mSL56sIJbC|ca)uq zJr`R8c!g2h1)Lq>Job`m#@;SADH-f&qEo9IkF;vyxJV2_ahrTjgU`8iomxH7UUbz- z=|`r7_eUXq(+^537*ami1e`AQkw^Hy^a%~T|MQ1OT@q|8k=yRw=JG{jZaK5iK59za zu8CTA1U|~Xk-VBLXfnYiymzv56_3&-)&LGV0SZ6QWfhgDk~=pBqcz}MG>5I>ujM6> z5^~;M?ff>7-5Q>M|A2PxP?+!;c@kfI$N9aqd+{zROGz%4O91~ppC|E{a+)( zW@5bg(C6R?EBq%QO^&F8&k`0(bwcBbwhH_JsL&v=z366QVy@x_Gx_eJ z)=jAV*MC~yP#tR{U@&zYe=r+^4_gjQ=C1j!K^m--i&qjiKwoV&8kj{8v+{x1f<}kc z=77Yi`tU~72``8FDfrSyrIEz3x%*qbd4 ztR-^K#yu~W=$?{PJjE^&9=!n6_!$@fvG?I`Oy|F*jQ4(Z*@SPNaI4}?V%$r-E>H4A zuxC>mVBSJvyM3p#hUf&}WotO%|@HQtx$aBamx&=mpP4+EjNZC?LN!n$!IllNDX z_KgmR)*Jvn*PDLY;dMh5m1&2#mwbx5gFENqQODG2qC7sC>J49q-#d6jG!-)zk z4|2^$gAVo*oMm&&FnxYeMRhp6;a__0h-X}oe5P7q8i7~FkZf4swT|$G9K8^Er})fF zzKhNolVmmCq2-J{*xY+%Z65yGvF~2YoxV`Q3`L1!S#kDUrvuUl=N^(qObl>`lrQI7 zg7j(uat}=>JHKrAsN8hQnGjT&Cb3`oAyn~7h-I53qOV9 zo%S(4u&Lo8`p-Hd|EMZjy|#PRjqUN3+>1WbpSov^yK2J0(~K{V9&w3#_duXlJ8)Ww z+8w<%b<-%d-~!@y3|+`>jGf|@u6gchXOXn}h*rXQBUhv0b`q}GVYUKF0qonv#jbt` z?PQA*LjQY`xBT{DZTF&s-8}U^1ZQ^wOSW)F$PhjIqQ22g=L`S+8fRuhl!8%OYU3b$ z`;E6ZWsfR#a*~f_(fdk$X%- z995m880}TvTc!mnl5y87lrCgG-R`Wfp$>vWUQTJ}^Pg@|>x0&iFoi7#Kk#LwGn)R8 zRQDeHPO87cdq3RoK=?+#X6`#)kT2mgGYsSAZWd{Y9gL0oRWdb3bj7}MwYJ2-C<_^W zo8j@MZd1&L=wkP%@(FG&ZWxKQnnlM;Ed7lej8#J(Gvop1oT8 zI#9X^B7MRzvVi9XOZ#E}ywxZ1!+*^o@nY*sl&+l@ z*4d37cu5P0#sF0wk&Ktx2>vMU`~%hwbCG%{|CNpqUJNq%I(4Wlx|oee8#f_)X%dtw zi@}U({|cS_mYXz~6Arc|vsaQaz-r_vcxM=z>8>f(aV+cG_58_hS^Q4BCXk2Q3}%N; zow4*W#b{1!%{ecM=KU~MCb^o1cY~oIjVTlr2T&sls9{Y@D2Dv3EmrgQMtORprQG_F zhG7`}#Ykzl0d@%VU;{A!5A!7$ugLHsX}LzppHgmUp2PV}DXDp?-6r;Cag$i>HA$TS zF7&_=V0IM^g$vxp;zggZ5i>}F+5D&$FAJL66D<=gZO?z9`mGUVPb(1L^66TP)<=4! zQfZQ`Ete-0YNa2ag4cE+mq6spC(QzRsl_$6ag}nP@Q(D@uk%#@aB^$<+}s zb|T(4-XW4j??&=YQX4S-xVJj@LuqxM`YE&6xCtR}_FqU>lu)7ET8)Yq?!qyV&Wu8I_Phq=g4o6DaUVQbiJU`-777{GJ ziRRVkxI@J9X18lU<6uhcc$sD>9X~P)lIaiF+Q^7-ia7WrS*u$d-!kXp+s1AK*694n z>4I>xdI%$DSdBsQz_ zyD)+XL=ybY2Dx!J4eF;ooCNpDT@teZArmp3AnMkSdbnn+sgO5)1!M^=2eOP{Atu|XY#%NDPdrsM%GNg zUV8RZmPfEW+He=BbmHr@saz^c86g{tWO}WdS;&4rNiAe0WM;Sn?v#jhOUCft`qY3e z+REXdP2|vwLCM-9T-rq2Yw`NBg8rB*hwr5%#mUfi-j?n&2GMj5I0bwS{#red;B>k4 z`#Nr^1*4l5(G`+$;OVLDqrjd*Ypqpn;f!pT9hlva4VgC!pFAsU)lwxeuj{ltlGmPzdb;h>%Cowp%H;`+oVw%4@kYoM03u}IcP@NX?<>pD-iVy>fN zOvB22Q=iNCrHLeJ#>LxeV_y$R_z+)eINf?Jfg}X#S_4&tZE({x2GotD5)|LPI_zCC ze~U3RAIImMCWt%yGUB>iURaW*d8{3+Y&NcnZ>C(r%zn)_HJWUvE!v!v6ndgwGuqtX zvOC2(;zhzy$Fbas-^#oh4G>(aKu)uZwmnD2v06tvK{V!AvL$#1Fn>=at@}I3kF2 zGyVNQe{$$Mz!(tj8!OWGTw**jUMS1>=viCl7)1?`ZVUH-B@9VfZh31nF1Y8G)6lRf@ycQ{2#=DrkbP%?GsF)#lw z;OKD=Xd{MLqTXsU%#*hMy2|y_kgxuP1U1HSgK`1fwzP6K>9`Y2%RU(O@ywAc@?bJ9 zQE-fET+!Se2?-c*#JF!UlFa9i+LBEXPL_~)b})G@$ck&GPe7k`en=S?-PbXN`qg?& zp`~FqmKtHMawxiXxzOi{PnlGFnb^?#R-m|OXeFIu5l6!YtF!hhynygp7&!|(nRU7U zJ!>uIex5ma6Z3TCW(U(BN%X0P>!j(n#Umk#+;(>o7a$AF>d zokLFfkj+boSvGa}sj@-2_t5}A_%3as$Wk&Jf1`+}R{eX?(#_4o_}E6Aky|NOVNe&q zgHVB_6MsQB8?m4XJiv&SGb9ft_PrG}7QbrhdQcbh|MDj9bWRp%y>#WE^aXCQ9(RSP z=`#ZOl<&U;#rHTlq>{Nk>YaVG7`Hog`>NXQGj-6`*4wAt9i_EK zGU8*tZ4(Z^d?3)7Y#Z+Akx+`Gd%D^V!nF07o#7lk;@Ag2oo_jCWuM(*dgs3)<``Sn z`;L?moEyApGMMOE1D<2g@4KH@ww~`)d){hxdyhWy2%&Q9pScxIaLcfk4)0>@VZR@a z1{aPVl7nTYPS<_psCLe@69zA-5jR5a7!&(62wD|v=*-0!cv|l;CQS-*>s3!A?0pTV z@4-+#o+cl3!^>6*tDS6K_&@U1i(9g#5a5_&$y(diqd~(p@?Oixp@y;HAfOrk-=!o0lW;&}CEmFGHC`)@(L`@`gf3>d4v|9!X3+%?5y_Z0s#CEJhMci71Xj;SL2=HYV#Xgv{af zQ%1~Bw<0B=^bc`PgFVX3{oGR=SSg0y!UHzHI?>aE%|tiW+-gzfqNEwY4e{#zBm7)ZDnrm|;2QoHUiN>W<+B=^`9!Q(W7RJ@Lz}c#&4*M5@=SW%PthXP!!VZC-?oLz(R&lYS8xfXY@*dfKid~nrCCxw zJ-*7?4T8^J$ombQq4z1FqHvS##00HgO_oZ=nq>}tWjbt3u=|SKnt{)_*b9T)bkILbFo>p& zSL4{ae>Ai|c%peCld*fSJ0TJT7>lD2y%cWGVbVRDNb%d=-WtzWe}T0HQOC|Riu`LY zMfbfwg>#+`p#~I1a3{i=Sc01xqFYut7rU+!1SiU;*XKq0q$O3;+U0bLy)l~K&sdXrs!17 zSzm%i*-$EI2wa211`^xmyDWw|n(*hK094vmU{~+x_{W0lyHj!14qf{p!ox|Qg4cvS z>)6OR3whzvs4w??P;rcQV+QrXxFkP}nHgox-+X|;9TFRyS53y%#UAN~U_8RG_X3#P zT+Pz@`zl#lB~6Lqmhzr|@zUst0bv+vO2ZnZrkmZvm&q_Zd_HyTT^yci1%m$>Z@rWR zvWatZ6-6O7HtFfE^wz?$dx+$K!ydkhw+rvA_RFsc)_Smk23v*d)EE}(_4E7G-P89E z^gnFUNe!R=)RkWOcuI^E07`7}EN|cu-3xOEvo|3W=!pjTAq??jCUZ>$ zQ_@tPcBYuBob$y1+hgk@XJ(Zr&$XF(nSpL1_Sca-tku2hHr!(oA+;uBe}o?A2btv%sy zWju;(EAPP8dojle%GQNb2%EVI-uvEu2U1e-SB-M5X(l**Id!ncpFP*#ZS*4$aST!) zQpYF$9ItKRLN<@N`-S?X{y^+G^K>FS>{aKqp|Pha>aEm+|0d;odN`TuMxE_JK8I?^xur9wdMS_`wc;U_^ZyyKaOd3x!Jk{ z+ios2rJsomkp$&4Yh&9OXoVb;dhIA0W-0lzt|WsiQ^!(A?u@=s)@b69c9)AsmF z!_pz`_t*Uh$~?8sT8;A#ICCzBnqm7cN5vY&v5iNZOZ#hRCoNRwn>gYG2&J7P<7D7X zS1IpTl4E7rvasT*%~hK4^>tS%yVgap|mE0ps6wKc>XTfro@JOo8G;j~29w%ay_nnZ`()cvzb+2UM z5VkAA-O?zhkMUH$0-xjbz=LX6EB5HR+GM1NMB_0{9u5q^f-SQ54cFc32i@ztIe&Rt z45w7S08je6`)ca1m#tpf>wJlZa6GFAaZDJk`$x3+@FzOuglOI@a7!1Q?0gxCjWS8k zNZcj7A{*_x;PgZ@$#^S?=>6_BG6*ABrwzDH2CYx}|D7`1?6Ow$Dv-LiwiPccXYK!N z+4a{t?{?@(bV8;(t&ZAs)zb_;hVb3i%)_+7IqOzaZyyhe4UsGvfb%48!#`)W`hh?R zq$HTaeWA80*X)DFO;LJ1`?Jq>C`h}9;l`hefOLFVRsfcZ&5_MaPgD-a0_iav!Sl!hjCoXf{#4(PpxO~vZ_~( zAhLn7r(X*s7p+&>uR|#l&9ZmiuXAFnZIp>Zb6_0JoHk+huO+QoN^8a0Y_4BTWV2T{ zd@!Rw98G_3s82%J>HHkWe<0gX&f8BgUDx5L^YcY{t>tt8mYql;5hF>T$5Ber0;+AU5#Wrlo9 zlaet*W7@})ov40txXkRxmz9q|Z7Rh_IpB(ePwKRk_1+WvL2CUlDU2!KGHUd$oY=c& z{?o`@dR2uR!@wrM*|vz|8Ri@rfJ@tPVH$mNMAFqTl&gP3D&G)ZhaEj43IF^CQY|am zCiA6{D+}FK9IxIPK+DBX;G?oJ5bbbI_^m8s?LuUww)R=ix6-@~MbI0L)Wdwt;1FE< zGLZ$>MkTF>;jg@KbK|92Ns6X^2*EqTT)xMnj+;lllpqlqjQ#LAy^86bGHg3#@NyZ4 zKWuB4@3kx)l-ZF3#ht69nPfA2+dFq;mSuH*odnRgz2WivjYu&kJ z8IG$%R6~)v4+GUO>M!nB9$<_ihe!AGu=%^6?#^}cjWfgALser3e&j3qZ{}&Z8H(hK zzChIrSWBn|oDR5-lqE(v+GKbGuAg|P!!SULyY^$EVhCdCsKQR{?Wszd9qHw+_iYt{|Yj2POFD+yd zoHFv*fZHg3%~2?$KcZAIi-?A7QY!Qr=ijqGcuS$3p+D?7&C?j${Yh?OYWCK;dE_A= znqj<+Qo3ih@HxHH@dz9}@~S8!kac&cyLQDFhv7)Vj-k-}cjv8Fh@HcEhNbU4&zo=e zlG8doC`L587*Y2s9$2Y5q{b2^|M1id)XDw2Np9*S>AwG!NzcERgZnoz_wTG!2+;ZMInUfr;pKM)LU@DsSA7Lk_X~ z$P_7$9uIuVpZpZLb0>xfl|_UpnuOXMD-R?ZEGxu#pD)^l6GP~fUzFW$F- z!!ar)ggCq0mahsFtTbotcurdF=c=CSbNu%}=mP+HP=Yan62Ks4|A(TpjEZUt!!V7A zAe~BwvG~GB}2D#cQ*+4-2W^Wc+Nh1e{VcnC`ZOjwa+%c zyKH_GOc5&6R|>^KOS5@yw?P+<5WatBzj}AoEq9ZZJB}Z^jG4nv-q(3D0y(aCe4;Hl z9bdOf>r=Va3vk>ZLBChWzZ4A-L{xjXeK^V)oMMYPvA^rI#DqFIm}I(W5qDvdXS%>^ zvkZH(yS_(k0I{3v_Cq((b9Q2LG^pSh2x2kblb2b|F zbvm9fmi)mAxwqGE{5jyO)MSg1>c}>Lw+kvh!J3De)WB*h)n*{!;9vQKMieIcZe~S5 zb1988M2%3^_h5NT+br6ygO#pTyv-G^@?Ky5R<5b(RtAelw>< zK@`cAPXospNaqh|vE&>Q=ZVm=uy@wU$rsSQtmg`PUUGy__L0fBh|5=bVM8DsOdYjq zc-wYm&tp1KrL7_M_p$UHv8CqtV9Iih(dW;x3uMDZ+ zfV7>V+9VrH^c8#4?*-xy9+k7~^i}OD_KV|i-cc)$hW!QE*v&u4z%l1mKa zL^NuVl=h-?slDtTM5@!m6PK>F(rj%X2j)%ezx_cEGd;%3vQKpLzl^s{Fsv%3t$mZt z>B#lhYAB(6N>ITgO+6~BxY=h!}^YG}rC6pn$Xh?S$RHZg=2)MoWovNLZf${X8i9y=mh zL12YY7%Z=X5449HDXHy%|}UjfRFA?!t(Ycp+^>9#ak z)oOPrw8zkrH)mmA_j($-9CJTucP%rxoIE)u5IPmnZb1I!xWWfekHhjy+-J!ne2SmJ=M8s6l^@{=&{p! z9vMer5y1T|N}EGHXya?tywE{>+xFwQ+DKg(&i!()(f|kA_{!a^t2@rlLF;M1_vxU= z>z?6Y?4dZY)R_CAr!q#!8lO?#>NtnEEzY559gYicNjm!4O}Iv#V51wL-RY^}*_4L3 z4{t0X0|mcLja{r)^&4$KxK3=&B+D(Vh^McJ0;`e}IPQy+q%`Y4s>?Ei(?Ky1_95(AI}pmufM7{zY6nT;)vlx5p9iMF?$;Y+4f&%NL@nwUdkU@4KJ40@B6C2 zq@vFHAnX=rsC1lu2@m#`^b#V8BDwSpgXTZ|XI30n_U2%Z9~_$+mQE`*!SoJu)^6M_ zU<)NxK@4Y_lPQu;Liq+-uFmWVP!k%O^Q6M@q-hOAJ?iiqmd%+%1-n0;Py>(43-apN zzsOJ)SOgl$As=Ep&gC9FK6_A!^aN$GaI%ww_!dcW+L|FYAU->W2sW#4ixp?Lq0K+2 zN=;;bO6gqJvY!Jf6F>y(7e?2s-A;D#wkPA#&d{M`D7kh!ptGxnLj;V-VX*E{^ zXfJ^kR;^%yn8OH+rE7XaNjC@0WV*KzevJcACC&{{VDa{dLjiT|VIGqm#J$M_3%X6;#NQAOL4Qyt$}^&wAntrcX1 zK>#xU-RBR?FNS@OaN&kXvZuMH&AXGh;*C=K>K3TcB8U93!R`jO2K|0cnz=omgdSI> z{kZ~%M`I~t=%)(RscN-vynQ(yO2n>Pr9pabW60mH3IK{@AgLhKmY*}tVDGV(B9xrU z7xjc-ehdj4UoR6ma$qk3>nyc`+vMVNDwk<)U6W3wAO=U@4SU~FD7Wjk41_Gwm_6p& z$II&NkcFvt{y?Sw8c&6Lt%h(b(Q+n;8@#vAu`(sxHIt<;LO4HYK~KW>+=qU@y(>jIo3)Q0GDoW#X+ zFwv-Fw+I+4f@Kuk)FvJ=^r5t{#%J9w{BYmgt*qCX&Y<|Fv^G6+zHx z#wr$B;QxPg$IRoImKXbG26@$HR>r9d8sR-04%a#4&vSMsgGT9smjlV>r{0H@on_AZvM zbHp`N;R{oNp=*IO6V@j4d~pXy6~IG7*z{f!^wfI>db6poTj&Q{Elp+YBwo*Z6uJm% zNWb5;!9&(6CO0V|EAW5^*JG|%bPI*P1%h+`$KXitD_!OLj;!_90S~EkH`iVo3UtpAf&F>0_&L{^XxV8^Oj|l!Mk}OhNdx=dUCQGel}zMwI7Hfwq;$bdj|sP|ZyB%W z_u0i^2RgzPR7d@~pCyMZeCYR1zO24s+)N~pKBVrG+gg~~f)(rhddUBBE&* zP8xx0%EPx(dO~4KVeH3BR`@i}mWAX&Y&BN85k&>+As;OOQ1B2_OYaD||Lw8EJMXzo zYiaG1W&3?&QtCJV z)Gk|w$_b7M)8eB89QCb78aiuei3N~yh;u;0Vo%NI5cf;WGjN{lp#(NdEnYyY{(^6> zV`0FP+q?sonk`_(*qS;mpQ&Fo?uJtUa1(a{;rEE|vkVIiB@DdbuoXBISRK;TzCprr zACf+5Yu#cn&tBXy(aV4KO1>9!!HH~F{^l)gLvQ#mz3DtPf!s2?k;Atb!cnmwki!^>SK4;zoJGCWU&xa9hPh#+y)4e95qIHd;Ymw!*;;5+u0q2ko}&lkaINnIPAM51zAzbD#4J4jk$)H-dgUJ{ z%S22Lvff`lSw}d(T<@X$d4~Exr*D-~)IdL5LB!Kh<3yE4HirJ>eHur-`5rov$D2@) zoTNlilXTO)xg*!^ z4WhJ_e=%dK!W#&iReZJBjCVy9e||hb+HSMbamuI}n9Ts1C(e^Q3??1;yN0-&v_xJ* zNV)D2!U;f5Zw1I3^3GPi;GEM4m?x&?0xM7tC_M7Y3=G)#wz!78nK7`j;C#b4Lr<~F9H|YwC6wFqzmR; z^FPnYAJ0tN{sDy{UO21TK9T^Ks&SfN-0i%(5kJ;u zw{+txbZh9edHH?tPRcJ>#{@C`>V9q@2B;z?;@12Fmmd2%P0E6}Ckv;~8A z7)Y>fE(S^Ndn@Ilzq@v==;nFv<$IF1Zi|Z%I)qf@B~eX& zmHpzuKm+Xhty9qWZtBqFyqygl%;)7&*0NA7U-h<2JQ9mg(s)ys+9Nz-IW;2#^)Aa& zCf4XgyTn6ZS#0JkY(%`m+l$_v z!JWZ>^#gjZTCGELuq=3tded%hy^739Bk>6<6A90mg3I2Iw+&NgVMhk=OfFyWy6@kY z@w@uWeN54ncN=GEzVQ01(Nb;Mq7vh8$E0;&5$J(UH{De~ra%#yg>un4M?qrA<)AIm zcFAno3cOQe40jmU%Uh%aiunwMo6>W~XI_w+xezoyoMVqJbGK#M)Qmgh%bQGXZCuCw zH>U`By&6b;zcT-pZU)s6cA9)J1Z#qZLx<;pHRdp;TK7dBVB*x7(`8XDZtO(}zj-+? zXmw}Re zkHN{3wXb8R`854b#`_a2U#NGgn)ljNm|#lvma8>1;xaUBnkey4T|ii#_>ruHeOaak zd)UG=vod0^3Y|YW{CAdfwDm@RzprSb)9>g>#pq*>pJJFWK6(%Bg((H_m^ea}JAQ&| z|F9TTt2-C=csK|A>!ly7v4nFqi)mFPqnT9K<|cRj95-NIIqNx2v;A)jAJd|}<+p$R zS7?{rZ0z`3$zMDJN8w50*|-Qrx^mlk2NXQiGyzh7@A8f~A!`;sOPPs7jeT>n%z9>0 zZGC`bn$}m3mc&$4fwooE&x>JINnH^Y4rJ!H3jRsAUA~*#bkL`d8RniN{98jAn6o#c ze$Wm}2iin}pGY!9p3i40HIzYVoGAU~+(z>9@`2DrZXl*{RkDI`vFRW`F@VR#qab5`zo#(U2SB^)Ca|`Zc`iR>H)?bUz5@6smGMF7JW`UFw_FUtAp^#Y`k1BI% ze?M@8He2;+bzQ98F0nT8OuvztPObOeN-op!b9PeiJ9njVDDfnPA&h&_u(D?<^3m(V_ zZ1Ix4@>zHcKqjM6m9&poGyZLalf%NDthe9%XwI)CQ^2;C&?zA4E&RTz|9SJ1rSA7Q zXELm8Tt>h9#b0)CL1n+j-zEkwY=r-ODAT6)$i$cRya*nNW7v~G=Z)1Dv5vIatZJWd zK`nC3PajwPgEBQ@l?N{GlU$H&9%O9#`~$5e-BXU>f?qL%jl}YoPu=ZH9#w;OKl3ce zAu@tx-0{P9F;zoTkuxL&n2~X|(~fQ_1p|<|MtZ8y48ciGr^<|2Ts}E_U%5+E2(XiN z4K^1Il@E6Jj*ktCJ$eUcfVs%>F5+yZ6U^=9<KtO(Y3s?btoqFf)vi*O)QL)Y zSq(4p7LMb9v97xLA)yO;dvGH?@Wq@q&3)%}zBA53PHYN_Rh#q&NwrZ%Tg1SBct$n2 z8EiUH`lYq%>zBT58Qibp%Z0P94T(%0SP!Qh z`J3%k#~%H4;`A!=>L+7ei)<<4l^jzI3IY-plb-NgcB)8T3O+F@9-WlDrRh-9@0yfq z_}n09krjK`v>kVWbyRQwz5O`O?ibZ@i#&XfsrucKmc21Dr{hA@t$pfr3dG_kaXg5L zqI^g7Y5rzEcz19V0$|o?iTGMXh9bX~p&#_yCg2|?47gFStPKPK_1@EOlKXmOfwq$-UgL+LRrH{?)*Kks9 z2AS4?HVBE7>UWmuWK%W>O8^~g6a zr7`^ii(37nbQCYi3mlM2=mLPlD8S*#dYizKA)vJK91(C3#X<>NS{SJSq0T>?5YE3Pu!fNt1fQGV;!zaD z$t4zPWeYJ(F~1w z)_m5VE<)^q9pN~Y&N`Ftn zRPO(5#vtgADU-`#3oj>HpPU@Mbz2p{#*1K?WBoRcFWfLdc;XHLhd(x}gE*6ztR69XC|)N2@4z8xDlByNJ0Z^(`L9;z8FYDSf^&R zG4}3?k0SF&FA*b z6en`+wAgUW&U6Bkot?yrYdNCM++)Bmixe?F0V2ZD2L{Y3{4Yd!| zO10P$1pmPLo^JdNq;Cu7=p-v&A*-6+ML@`{g(`ukmnoqagL)^hK0fKqYLiDO!FhfD z_qVn`uDkyR#WtR@T&6Jq6M;vyeZ4^i^j$?8^JJ z2DoOj-whL`03wR@)Q71&LA&SfkXRSXl(K(n1`jc1!>r?mrZOBt>V3O=+E3@I`X7o0 zbl968KtVlo>01D}B7UsOBWSDm^iBAH4rE34kZZ}cc|#73vfkRRFnRq=ii9=|WDz6x zSnL6N9j2o90x8OS5sa|B3G;%)zEh1D5NQx)x31)P61@HE{T4YNd-wjF&}hH|96*2K zan4*5%OggQ+2CazBNJNo>piZdliggZ8HZvA#k#jL!<+ctf#a+?x3jzpy5LAWm#@%Y zju6NMg)>*LDl0YY+v-fD{x5F{kl_a@U|jV1_7Bkz4{ZrdH+(pCbQG=VElw@{<7gbR zz-0Osl-Qq=#~aH1xwZSI=EHh1EAx;Sd|{eLyZ5; z(l+}@`A&P{xpCMOfuq>9>6}oIvndk?V9taQ-U8vaDV(c43Z3iK01Jb1g6bfNYR9~?neW}{Pkdq<%c_3UTu>FJ)Afq{*{lSh;&U2{=nf@)^my zp{1ASsw8=UlNXjhaCJ7+-8D-~9uC~it=@u(*5!;%;pF*%BCalYAW9vbHH=kEJozg2(9)91zU z>?MDzA@ZnksLG*}6Qkl9z_WqU_58YT;z8ttoMJwHjH6jUo;tD>!D~~l7o7^$K2t-MfZN$oSa0_6mmMD5<@j^X<4OQ!|Z z=!cdIl*`zw+L>UhHAzR*yyemwG`Bs}{ULtkJ>?MvYm2O~`ZYVlLhTDwdaIDOVnT;C zWe&F%E_>+8N!U$+To-A>H{5fOtJhXc`44o(;pwf$-ck06Xt>I+_jHO6Ct99N|DsFK zIV(=Y?5qPfP(Lr`t6kB6VRKFFX(+)-igvO_U*(w%J%Qe-88|Y8B+8~$J!2mIW%<~7 zD&o(t>$N{##5JliZnJ{p_oNd|yzj;c^`M4X0j2AF_OJpMbl^;&} zOY`h$Gv1m~vx~Z*%M7>;Tw*UHMVmG4F@>DO;$G!w@egCEl$_wyD}qm0BsY@UjkcsZ zH{LI3qz>FL4@!P`KYF@>F6Ain&jlgllX0=0u{Uwiqra=(7rKA^k+BEJgUM$x5AmJo zJM%AfOr4MPABApHC!PvhH=qAW#SIrDH(J@SFU}rYJHt@o=duT)e%JnZ+s+C^m-;eW zg+;aMlfTyxS2J(9Yle0O&jXYcy}-WdJRzUcb{KJ&bmeH9&4)4*X#&ns%DB8#s&uao z2|G_wfLqje1dvAC_Qte98i@xxGb+-o0Xc~ZRYConCfS17!qQTA`z z{b7#k(p*xLLIvF$e>v%n=WMI=kJv8#Gpwxh{s~^(41Ce)h;w3yAunHotnF>w?Rts4l@10fil3Wj}>3)IHF4h++(4FtyV@= zkEU=V6J4Mgg>f_jwIRl(TBR~I5_LbFKMw?VTm++&tOeP({vk~SaU(j45%Iw&+8Ni; z*uy*$87^d}54t}UzLBhiwgOKBu6oZROgmLYzfnYgE$kv4JU5tjNM!5hrV*l)Ul}(@59uU$0TI?4@7J zxg3^&MT;RGHvX^Hd;Zv^E+Rr@rH^Yz2%9VNk z<@ZPdW|1wy5o;2=1o~ zOx4xKV)`k6M>iNZ-9@1-4!*cziLQQ#3PwWn$!uCyQ>IW~ppedhv7~QrfHj+^(=EX( z-_XG8-kn<8`n73ngaS{mEZqFCul^zm#K*<%fa@y$%r)Q`;b8z288TZHM@d%rj^+)2 zvW&&fIbEogN+$fR(-4C{{;;_cdd~)jp4C!yKYv9>!gquIC+j-E5#FuPKsRH(-pyx3 z=Laqx;jYt3th&weS(T@)1LzLu$`m6)& zNjGlpjp^ULv1csx+|kQq(;piQGPWXH`GTg;!S{$#UOF6wOR{EyNVuGAqM?1?z*ll1 z_8yua(?L6nB|z&aGXTLTi*HDkzH5PdBlP_r+Z8`*_G#h*^DH^s+5zCp5v)7R~y31osb4R<7B7-s7s zzv3>M%m2DU8>zY#^bLK;zO;-`uZlpjQ$T96Sc1(NnoP$kQ5KwHCk7e|MfzBs z&!BLKONx^hH57cmqRvpqjxc7-G<}!@^mkwbHZ-9b$Q|%)h}J)ayq-K`PTE3)$vUa) zr$veH-NYlx#ul_A%*!6e+L*x~cb!j6>>L{0i!!y`om8TmIuHSJnqoP_$hTAG_*t^7 zX|Reep+uOix6{gUDogQE9z>Ii`KAJFy!UPWs0CPOpdwbrFc$gB)b5ps!8NScei>BH*{OKH5#Ax3P5lOSzAsN5* zF#Ut1xoKg0qZU5rXyL_Mt0u2monPh(IswyFHPwy|j#w(99}e4K$ld%uJU(8?suf*% zX&8*RNjt&d&%d&#a)kmVJ9YU9r;V8#R%naj)<8;V6b+Fe)yp|`iHc!vP)7RURe=ev zou_2Io_6cu(SwNSmy^tN2Kv=?ycFrJppQp75H3_;(-j4wNiRuM2j$O{sH86}fDns_ z`dIxxqF#MHr{sA(g=R^Z=_KlTu%G88#zJMjXbLM+wd}Gc>2eHk(xMR>!<0*%?F((7 zgO-Hesq|JKMoHPeF4^-w5z<+kJ<}Z`Kvpgf=8I?#nCEpCBU`Oz|MqxkaN@?~@dhuP z^37$aQ>w>*pwpIJ!y7}a(3^2G5q7rc=F$Urrj%&G&n%L^-^wc!2bY<$S)^aZ{_IrK;ZLZx>6H}bO~_vud#l#9M$ za_`}UKQZ3H3!}EVmh#uF{C;aD7W{+XUe{0#d#|d#fYhGFD%*Oq7#&@Z@bth4Evlw{ zp$=9U;9zMq(PxAvG#f?6Y2~zHI{xuX%)%#pRQ|2(*#)(EmayR&ttrL+0l;{oT3u(a z-eI^vjakR|DEeHnFqN~=1H98JaZ9vi-^!^PlyTdBc{mSB4A{IGKv$C(2hi|h7mW0r z2k(G*Ki{iRf$VQzDFYX*NL)W!O*3oUledf?`)p0xl4ECTc?7NUkL#Xwe1sc0ah9ZZH z2He#^Z#OhdW=U+nB;*tR+K5@;BORvCK`PVuB$EoHJYXg*HtU2Tq+(LOF=fEY{Ah2h zWwwdy_r`J*aCO2kOX#rHw02VB4eyFB&^ruGc8e$QYxQqRt+n)#!=gtr`aYydmeG*T z7g@>0*`o7uUdm=#ZJ>WgO2qpf88C7gEOePr1T^mN1;=-O2W6z3SipGRwqeBjDNkl0 zcQ}^vyMnq%2B#gX9I9xFHPiFXhQ>B%G2e@;ari)EEj$D8i?W?5OgE$OIlU9hC6+Cx z^k#z=IqzL@8x*4u0N8ju_`0N+YeoWDzYaPn?d8@7pN0fxhs}Qf3>3j?H)Eh!e%mP? zI}eGO_hN(nNGyab$G!CZ$fb(@ovn84#OH3+Z`IAUwj zEAsn|kpi1UvQq{Q4Oi6ckjC^#%Ostty*Rcb$}vam`tHoSldtIi$Xl>c;ILa07q^_&x$l-p*WUXOm11FkumPe1=j)c)1Sl&hhvNn6S)f5UC5e zG@~;Bo#{{)(LX2Wuq9}m}O zTQpt$1+2fkj31REm5v0p=~FyGdMQ+$kYLszQUkFMsa)NX^0u4j0!68aE?@wttw2Uu zDH{S8l8rC3<%E#MFi!o%Dx?u8$z6GVRE-K)78Qi(5B1S;+e(h-W|j z*)@cUPkTfw95;{ss$&e#IecE#dw4vd&!9>Xr3xA+2or-bw4q1zP>(%R4Dxj1K!?hwr&mdp>@v{UoqfwfdK0P33!$$ASSn9-pz8ywrztnk({Kk40UCi>3kU%r?o-td(3<*VOZFvIE$h3~^^Q&n<( zuKksHtl2JK)eTy6NC>L)<4NB{l)ItY3#@3pSN-aZqwK`p`HE&nx(e>Q9p9Qy4W zI3?!aQ3P0`8*DBHN@U9V1mn=j6hrFZg8VXiS$6-H$Jb#XEs6B(xQ^dNpNISJjT5RC z=;q2&xj4*aX5_F$uV_2QSu;gSWrDB_m;bW?<#9F29!QGyz+Du@Jj@)g&p?RRc^ z$&lBhg18-66u3}hD_&d?z@x70!PWKyGCKjvrIQ;`M@i2m;*U5}&$lrM z3JIoZk0rKE0I3U=8}Ek+8t6C zr#?@p$49i__hwfvO+1B@J)(pxWLOtV?fx)@+wb`ZW4hoP&)Fgxu12S5k5Jdu8Bf8U zstt#188dMZkkg5=uh2cN$sY&?r=7Rh898SJN}@kaNnb_EoW1(d262q6v>z5+CGmp zZVhe~@tq@NkQI~Z{XhKF?XLv43cASZNc3Mfy{CpP@(zf@)EZ@6;G9frVAH}U?|?~$ zX1WWB^bEI{s_-QLccz&SBX}V6s&7}dUy-oF>^F|aJo9fAcs;0*^IB+Bbr3Qq8YlQ2 z0CLEau?0|ZTMHaV*`aZDvLtNLj_WzjIPb_Mo|SWh4ALW17LfWP`jaf3PDUiaZlMPn zZBaFU94OfHCD=v9;+KF`6-t=@3wA2MZ<1FU?o;D7!QGkn{Zhn)^uMye81iRvI)64f$h+43ODMOOSCR792>Z zC%ucfx(DavECm12g#k3M_f(33>VLl4f#0U5OJo3zZyF-6{pamY2=ABe(Wh{_6H#>u zc?aizA!;JC1uh+7MNUz*B;53U<^j(oZ-?5(W=aG=)G`R&d z2}NJrjZi(QON`%_2R$AkVxCJ$g& zQa5M|LZZSKd@!ni19>38lmS%4utrUp`&B(}eezjrlR@4P;~Iqv)TxD#CO)q)O)8d$ zXc?}G>2IgOUM{VSF4F@ZiC}&08T5u8qL5)uXOj(ef|w%Ura8_F?$`h-#iEh0agcKt*cO5m_BQ2$_tnI!#d)epmD4 zcB`UJ{B2RnaZZbaPTS@%sPv}1*{Sf~cg*B4KwJ7~4F$S_qJ3xCKv!1wvrp4r%YwN1 zZEAU`!mG2Ttj^{JA0km@e=zb?l7SMPE?St**+>&GS{dPwHk&+_1Kpc`&jHI)Xem`0 zolu3|072n&6xdq=7h&m8dL!%f=34~Bb9CkmOI2($Hv;!Vd9=&kMq&I-%g<^h>e1Ni z+a&vSRxn~GW*hGP>*CWDk6hek8{umQcB)lnyVu#GRZ5V7-I6v+og4*?4myR2(eRW~ z1FiX=j)t>(NV#{A>q(Y{48`zeFc1X%2e!!5Mgo^`qdd?bfd3iFDA<~ZbD zZ^Be#n<8#4e~j1&BqZVYgZ5_K_h@u^K%cab&t+r`%yjG^8nurq^2@Ov50iq;Lh{wr z7n=-OhAr4?)?pb2YY=D=D!b{PW>q;Yj4LLhe2$)}U(p1oFvS#i-jG6e+@W*)UtO(> z9U?DLf#IM%S??UCNHJ!Lshoj}3}*x?W+4N2f5M?TOl+Y!5$(i_W=TB9%D9wXge>)O zYRi#GS`DiMo9C!@Cj2EpCWp^-jl}mVq!|hc(5G0^V+HV@WP_604rb7p<+ko~F04^iufU!Nn!1U%=1cn}%PyTD+jjklBCVXfwaq40G~|0W z_P8wSUdJzioQ)^h{pQ6;>ldhvu8Nk?lHcEO>1qt=F7Wj@ccwL$a?{i8=(d~vM}`k# zZI^92fsC?Vyvvw4#0?O06|vPXzjo1DFZPF`+8)4#I}Yy--iarBz&#D?t7TxsrMtzGVjj!2nz+U|#P zi|QNqHMDzU%C<6=j5V4JsYw*!Zhy>e=L2h{XOc3~-@r^~^Wu`@71_1kP$pNpo!^~w zYC&!!U~?7NdP~2GDs31U-2kdm!Qw7^9k=#AryhSn+9(zqbQK?d-;7h=1x*i(L~nLk zC0*wvdQok8v$KL;xE)#Gi7%@TuvglLEMiSD8Vzj%??oQIPcX&`S+uT61~N3XFch?5 z4}4y9lt?n(%#@z#hgde&+cH9E4&y+W3QkQ(c~N~R3?0g)^KZVS>f;Saa5gmjFp9zS z;7~+-#l9!{eSMrC%1pKCelBkPBD_Bx^^}(*l-jRZP(C-LHxhoBHrSV-g&hC8B4yh5 z=qIl$QyQhWILG5)^J!!ODYxlcjIdoXoxPp$@$j=*rxRLvXEWuw^q7#xG3aBS(bQ6U zPP^|(U&?({gmbyrPb@9R5}XffU}p}@ zDM-)6-%n8MS&JfvwT3Fp(Bms;DP`-FEe=q^4hQ?kpoILj{4nei@i~?HG*Cf#;XB!) zhL)TPUstVzT{n9ye11Q8_EbZM2kX@js{2$bIUU@ksEiPN3}fZ`)(FPPWb0FbCT(C{ z9zSK&V={1|_8I(;lPq3HjUe$a!<8PSLH^r^XGpPflY4(qBS!rZL-6X#7&$xm65;0? z9@NW-Y;yFGdHtN4)j}W?H58Lh95vJ|)mkMxnzFL+Aj{ZA3bK)D zx@gkCZe!oHWNHyII?YRic23goIbuwqi6SZ3+1q>Y4Y)I_!L!LVl_Oygfr3P+qlhcuo09G+evaP*t@4WGO!r6JYUJu7GIU?U)YD-|VvI zN3CBd@HF~Sn-#zRo_8=HR=vj*84%#pg^0(|6@U2{&xTaH!3G$8Xl=8w=S;6Td3i*=y9wYBA)`&o4)XB^yP*b~fDL|)$@bfe zYs0%5Rvn4W*N71ZRuRsS7ta3$MU9?9FmPe`#GLTw+)^LY*+(jsQ#c1H zPtu{ksPA9V=E>ka(ON-oeAZ{Pht5%RSO@Bp`|%FdG(~@wJ{f{SD>PVv-`f}22XJ*;p$_#B%AGT{7S(b^MYhhrJn0z{&HTio1}ZuSU@t1Q%@na?iyW+DGkN)a zED@Jq;op)In!##{CmWS4Ay4q8h0c)tBz|Z}9p@&zKw?J9bo2D*Al1FOYqF zP>T3!RD=C4gG=h=FgNHMTl=AvK(HIhJy`rjs4D!>55W*s;G+6;AvrxvB+ZM9WXxx? zqXF7Jb75+mEPrvoM`Oqr!Qk3^_cle9iMc5XSz%mQ^t-#Eyah%W|9#!NRhtISPZQs~ zBW#(izulsBxSwJIPdChRTy|rjJw=v7e9Ve1Y(f0H6`4F;Q16{gn-3jo!YOT)Op2Mo zhn#Z$uEHl*dQm-ouZYxdn7gNaCt#BO;?pgf-bt>+- zcz>Gc;_dEo@|R+)M|0a^x*k(aK=T5VmGI5c2D}pX@MN@OV#a#eQm_#m7AV^KZ!m!- zC=C?-hAzIy4Q)Q7yHF#HkUXVprHWe%MXYnOj0i5g!+#+&<~@tSR2|m2o}`ML)~=cSB2JXFf+K>?C3{bbi*o<4V6^iF zF-Yva(PyKCk)%?tsa4cxThNQxCd?hHifG)UjQ?iG4qR}#dq=CUf7iYzMBDM2s#JE3 zb)dC13^%===91>XT6PBzLUk;rq^Nn0xr2h%$7c6qaEh+M|Qa~yp9uYivU5dEjPa$b5i&1%FmLo%)8hk}X z2)@$!4+uf`zF9>S*PN4R{t_c65fw4jO*L^F*Eocoy@W&u#*~REbUfaODK_!#1$E6u z2|{*LoE;?ZL*+?U-0K*zwM}e&L$l3ef&P3-&JHmyID8``3hbLWjx1-_v?~}eCIq7t%bRp5o7F^C&_WFBxJ3*p5WFuU2XYaUTr zHD;voy&3hrMTu%>kSJBRejO6Gn64~#Li$5R(;&(wVtbfEBgc(gu9FdV->!8;Z23|s z8QXETEpLh~=I-A>RSC$rcjnV z@jlg{X+^}Q(-Ebqph+bhJLz71U&mz!8JS8b>lL@x<Gb{2xGDym>Y7kAS(yx?^ANmO8_u7eLi^PacWa%SExl!5I4J)f-JWt_e8uLRaF2 zM*_2^@e(bb1yI*s5PTfw6)O_W2Z?nlbO&jak!IY~P6iM*R=MWW@EIea+kMrB{76&m zikM7Ydsa-PG2QiSGDr($&1LND8ko_meZTS|UArFFT##tt;=u^Ka=nsVA-nHvfIBx` z2{9lx6R~MtO1$UTOSEuSlXJIgws~0M<5o-&E@lAOVa<9ikL$qcr# zIt2!xf11&(aa6=4Vrwh7ZcTkML)2%Y96ySaexq4)S$$_&OnFs7F{O;lQ{los1p8sHi636?MygcQ6Gi=?gWggpss{O9Y&+_<+G34)c($8fbuu0U zYXH9>!aqW(0KX0VE53Z4jK^IqlL(YcQn~S%UCwv^16?JtwRJJ2x|m{J3Wx4Dr4#o^ zv~XeIJV=~KeB@zMyynxyhwlTMNMZRADI7W#oD3Md*apnjlpR$>BnLUBaadzihnx%@ ziboG^8Tz*rWk{ig@61hG$syQ5rJnlk48Aueg%JeTkf@8RAK4%FVdsUd3%Vj{2~*xw zP0RSCG<}N0C}MkocWJMATm@5UiK(usYaHGeuSSSy?1{k;Q(aNboMxMc*>hSoJtQiC z+<*vr{%6Nta#ut{PX5g9TRLq)i763`LkpB}iMZ}>G1ocr4?3_Gc>J9if zIC7ITT)hR1>~{DB?JGs@h|UOFZ%OSfA1ExIx%8Dy&h;4C=YaoPtp0&I`8tGO;>$l( z^0<4;yu$HCXHG0@dGM9>KNC}GV@h?jSC)9;9BOins-1TgCt_U3hqQ7$xx!Nj-{emA z$}y%8<{3fpR(8(`+kvm58qz02CAp^9R#%@9bx}|wozzXyz@cOk@HUTzwDyJ`#67Vy z7d2bI7T25)sg{GRIpUE*2j8noR9#mP_1&t36x5WXWiufwg=n+vc^!%7&uh|N2vLng zr>5wehRCsxP;UkmKf`7Uy}(1#JS@I@F|SzK`K(eTLNV8ZL5{qB>{X|biGOwf6}SO- zW{%<5%AbjT1}LFKgR)lWm$WVhmDXt%-hL@nmZ-Kr%JR{7Vl~@5 z0U*)b8GKR|vE7_3Yb-VS6m9pT839vrWo#%h)t%9FG;(vUrc< zRqWiQzpOoc$wB1@%-(8TXb{2^eG_4?oKo`4v5ckz~ z;Kk_8V=I?(;Nz9B5Hk#Fvezl15-&U`(d?Pvoi~qbvU00W)@~LPHzk@s1!zc}*d*wy z>!1&a^fx0V=wR|VZ@NbRpa z5+81L#eAg*r#%kre&DB2ei-PA$gcyhg#mN$Rr?~ED$JHMdFo9@t7Y2|!SCXM5Syqv zyJxi;Y(;z#1vi<0C*(ogz9PZFkMW3*bNuZ5*86ug^!F`HIDqsSrKcwD3e1v-aYWTs z^+^ritBhTb0-wai!0g#La0QA;+$EQJ#}pgtd$ao>98*MmNGDHB6RFs_i-L-}`XQ!R zy#{^Lw7AFNIgwd)MSW*!wCue&Fo}ve<~yIVI$;@^LMC3oYYpKUTQ{MgswkfUZbmf^ z!w>G=;ya5XQ5#ciPF~C;gyyrxF)?vVqUsu&#X;G$w0>Y#$^N^lSy8jbagjn(Y;7gb zq5C4HSl=~(`(CuvYF0lkpgHU5mD^vd8qMm*)TcFxYG*N}CbnD`TRR*6Ufh(Zc245O zhx0KCO0y>0f{EIwD^0hH@m2z6qA=wx>d>F@PbsJsD(Z^6+6Z<2 zl>k5k(D;HV9Q_#bJ$? zDNLr^1WHJel{av$#mP~ae&3AKvGcyDnCiNO_3h}EwER{>AWkrIA_Y6CqhkkA&%C<- z;}KM@p%2(m1mO&V0j@{r?{afplU@a%)Uf5XX+fYtI4!Lb&74DQsy?ZrJ{DWra5*GY-I&Ak>g`ucc89uSY7j&*z!u?F@MfbV!r zp@-NWzBf6AA%$Kb@aokLMDv{wZ0NK%VoEIl_1)^!r>lyD`<+IfLIcITD>9V=>sH^! zbU-kr7N)pCh-xgbt3W|KVhUm+i(F1bBZB<-KMR--ZiWJMQSJtA&aoN8FR`fWEC!FL zyfJw|bE@&Iaa>GkU2J6|uj#HKCL3FNI>qC{rzKqbu$a;swp>Tm;FDUwXY2qJ?LV4f zG;1CPlS)*(aKJu}ZrQaKHLYJOQm9Kj|7h+^_z_ZayF-u?w@?-JX?Yu%%J-_OSy@b> z9wHxswN!mFGk$XE?~Dl88=dHA$Y5tPO}H@Bb|+|7X4^ZdBBsznq6}p|Zxic%L@g*5qR^Jd=3-rUR53fTEG`v%jJt~?+0G4D*pg@L`42I@TY{*)&7Ke zmAB+EG$U}sF-NIXXzm#TS62vCb-T(sb&&2JL_$=+%h2M#hp7ZDj%`AkxnnTyRN3+~ zR7PB9IFhmJn&kWvS)k9JY^p9&RMa&xB+A4TI_i_kFa-yC9CSn^Neq*` z3?@w!86Y}?$n!(=LN!0<{RjPAHo!j7h=oxAP3w$x)n-OQQw`}M2BAPw5gSLGX(tE@cjuN7oGwRct zX7yv?sH}kN&V~M#8aYTR6Dc&*r`54I4fPgv<>07!n=7(TypaKteey%o0QmKh`rZ!U zk$5Dk6R7P=X-4JVGp5|`4Lq*9|M(9J52D-z{9EAUm3a4nc>1l;m~HwFK!Db2nv~1O^Zm}L$=MuTl54W zs-Sr}x&b?eMtrMvK_!%r_T2zsLL@|T5Glc_fvxn&Q1+zXqUf-T(T{|v460V9S^S+v zim311586gb?R>*ENd-*$=@Wc&4owaq_Ly(v`gjssuA_d+2uUbGRlJ2DTsPJAOt~H^ zj3%VQ)y2VYvy)4N5yg})B9ekTngjFu#UL?o33nlz^u~Ed6i)PJ30xG7uvPB4)6;U6HDYPzQ{Wh94jsOy(8Ns6uwAdgL zZ_#M){}PJp%;J+-vDJ%A^}wcWx4o#YeOOHSQV@C@_d%(_wU&0jh$>>Mt70mbG;1AG z*UhPgvx?Z_##V$x6kA$HeXOoCzsJj|XcntxMKN&`u?bTls6}N^Yd>jHP{9;iA~wZ$ z=XM=0riF+Avq>gVqqSAaqRNNw{FW5p2Y_3y&~up9GVx8RoGG6tfF-X~RO^dvtH3V; zFA;dV5q_8;g!b)>Vh^6qbm+ei69BV+kzeQE6H+tn$NI*XG(=y-lv{{Naqao3`W{$RBXymc zrWbBxPu-KPdlge|gj9HYX6%vLD@%YSBvbT7z_h@u02OdY@_&y*D#d()%GWw3z(L1M zA`0Fu08gP0|3uG%+ztA}1X<&Z;2no{)W!JTtOQ!`N!;s!K+AQO#g=m*r3#vJ)C24n zthp_k(XFZPEQ_sPz!ckCp*|ml7dBAD9?XwXqpo!rY=W(x_>PID>KQs3EhMm%Mp+d1K5;p^V`ZkJn0qFug;0SOb^Wq5aTmqaC zVP2K*x+2eDe^&EZYTGB$4EUdfGS7bu%v@fxUo>k2e+B$9==(sPrtqH#imL|%!EI~% z9^kFOn?*UTg3nTE6-%hkbY6uQ0soSq1bx-^V+wOMrCh)%&`q?`>1n%EkghTL1~6+a zAR-cIq#2?h$kW_I?n$VK5KKI&Jb%D>GpH4rN?LtromLkQUAsRsZ*zR||L9hfBagg@ zxDa90Aegq71t6(_K}>la@JMvCm||0XZ&oym$72*!#FiT>af)j!5HB5|qOf1GpR=%< zVpDy0M%3BKkbu5p<1MZ`uco;u(c-x=>!Dx@JxrfH)$PHgt%6U>dD?v1bgzQF0DcK| zM-XYC;=pHU`f+@H;tDu&5gV#DxdGu={y;O}&7g}Yw*n5OlmENqV8e>4D`CnTQ{W#& zUGoqE64fr{{%bxEy&15*DOEy8-GSAtaV$huY8SV2|Eq+Z_JQ#kicib<-W;~LdF7%R zDw@@g29ArFvjfL#ucNcZVO(b+07Zw!Nkj+QC8p4oc=nX~w4y$(i7Bqf=|i;z*ra`m6LwX0|hTRUU8brh#{6w-EuO} zgk5t;4igywMu(>m;{qAr9ud`HTJt`urmeJrhY<0YVgt>J>Zd3*l|oN!bpzj>!}p3D z$k7=5>@nrdklL8Qp}bZtdHfk(G381Tx zet{fZo8a5a49V96BDT7QYaQ0CaZF69KIG{tKB?fki-_%F3ay~(ID1x;m08W|$E2{_ z9^tepnp99%M4~Cf<0@iGb*16db?3yChJ}+n0i<}y7-r1Y_mRhd^}Hev05@LdbJ(vv z0rwQfFVj z#*T=Km~ul!G*F>e$CShO$+S$0DYdZGHL}fPXt#n?$BkBmPi0$R~x1)}I$Nh?^ zM70%(=g#H?Xq&qtMPd`|%((y_Z5-KlE!Q}Tx**}+S6?)xhS=%_b=@V+>PL3>Q3$&Z z%>~=0S9cV#-4M~*l`?7oiM1-3lm4_a1H4j^=K+T<`#BuUUJ1M&_#-+-^?U@Kwsdlna>^p6_KbH%5uw_ z1lov+WMfE_5Y3;%cjn0IOFPPsMO|Yl=k{n$Y-{IGVA_b;M5Kch9w3_g3raU9xRr?~ zo00z%x^^pYf>5G(H}C-PVjxDjANU|}EI&!nq@tQt^3uuii@0V*eEX1?!X|cGRR!w1 zvx4{l1l{){t^?vPTRR6k(;8|}S3txuSvhEr_B#uzSs4?z4xZZC*=Bn|eQ!PhON%X3 zWsh>9K<(~(b40cC(<|ZLtb-j{$E1o_YnI z#X(F6{2hO!!f&cw_(?)^;PGG{_*>wIQT_q&pb9@pC|CW@z}Eo>DYr|3zX5(6_%`4_ z0H09}+f4A>^@gA1J#To`*hb#{hF1-*ehpOqA42+R!cOxm+XS!%X3B!eV*|~Onny2q z`TckGqPO1Ns%^AvNlj%9TiQ0i?lkYQE^RLXMr?I`_YOG6N2v6xOoo29Z`RD=d96o? zg3X8D^vSvQG{q++TzipeqS5ND`m7jI7R3f8z5uKskwa`yR-MRcp@Jy}JuKg?qS<~o z53+rs7oStY7shfCX;ui8n}t>YmBtAo4&Pdsq7OlZ)>PIt+c=KzY^6|qS|Dp2LJDn6 z=^$j49_%GlOJG_^aR#SCY4X%NqV`&$gHYu;1bX2|2%<^PB!r6B2*9`FgqI}vxUM@3 z_)w-e;qkfFlBz41X#U~An=wdj7<_LAiCa^t^TCd2Mph(i9u-rp%P5ehstF3Dh3OPX zcXd1~;96nPl(?TVxV&wP`UuxLjF?1hZKu5a_SmcXC=i>)&ETbPsm@HghGwlJfzCbJ z!#NT!@%$;0)uN^wH{d#pfuCQkicLZ()NsntCQ?{M+VaA}9(!)^?Z>-FW3=-4Q~h z;S&Ve_Wvc&RN)oCKP8l^9`v1EBe1Xj0l~QoTbZ01;jTBnYTpgsl{+JMzWlWynuzc# zs`P-L;jxQSJ+4heG2;2V&u6;XQdTxq9zO3_t1FvLFVDEele&USDX)naF=mKj9K?C7 z^BQ#%imh%o7pWX7Ml-!?q1>@!D+NWiW*jy2zi;x$LLa!fXUEZ?eWV8TVUQjw^YptX z1bo@3Ci?h29jH@Jn7_S2eTyN&v~L@^P86UUgSmby)x=80}!uqU|i5@>D0u|Y>amjL?vE4tI36urkAdl z`0zuRQd?bnHf*#_7VgFDccUy|8i;hk9Y*Cz7`p(h>BZL}hksC7wnEjlm(*vK;F-pqK`GXOO%&gqRiDfT z?UK05&i%x|*$`FVo>xsPCR)62;GoHl_69JkEx?uf?ku>1MAh;1+!FO9UVM;j{aW?i zg%HVz0|q33UWczHNZgfp@d2`p>ojYgK+{Sf7%Q{`=u{(#YG?3CEjS}-Nuv2PgA~l} zAx5OwQcOnHJgH(biRR7?J?%P%?hQv>xmbYYDtvIKH3*T$H-Nld@C`+tr|=WNHxV`t z`?U;s5O@dhMuCrOUY6>c9Za>%yWjYem)-Gqzu{Hf`SRDIN@lY3*L)P$2^BC)kM*EF z-ps4uo#m7FJNe}OZo3Sb2-h4flWFA6Zb)jjIpil8;7ZOJ)kZ@^+Sj}8L(k9F`ydQBeF4= zuX8Xzj9IL~XEC@KF>NI7a^cq%TYDJrNZdo)F`C9mp*yx6&0omQ1gTzaZl1Hl1 ztQ=(JmrqY|BRkE7s77`TqIV$jB;f8zL$W=hk-3~ILvBYn27DU$D@0xhdJFI&;13CD zl^Yn8FlL3o(NMX#Jwh>J__M|##Ye`@oZoS5-j0vVG>iByh@^{g?L#7k279W#=9~`G zMoYH0U%<5vi`X8hh}h)voD2YO)U}oo$p%=JE%NUEUG^DQqu`(w8rbr72W`;DytEa_ zcMbv=TzfG@i6*j_3MTGKVfg{^SrJgp8b<>)xbY+A5y8YgiI*P0)SH?$Pm-)W0b9L@ zYcC_>#8y^_X3mqYU#D677(S~?G@|2B>oK|hch2d<*AXHcU&+sQPwPkZwssUSR}-FigTi%Y194Hbdw4+=-mLgA$ha2xqK+fv%wDakdEEn0}u355b7YNBdHY=SBD@=0}b&TegTFn<3@N!$(PxoH84BfzEJ;Ct0jZPMqt z5LL-#j1biO>;5mr_ai?t%N^{=~{EdM}eNM{fC4|rJdc);M zFGks&FMn;WARhjHULTEt&lB((U&&F8sY_Q>;l|^ZBMVkjM$ge ztX?KsT%Yc#WwGL76WFe|?#L8dfrqSDl~GkM>Y|`WRcvgHuS!V!i>jvO>3BHu0hB0} z`0yj@HVy+KQfz6qdTme!Uq3E|(gR?oX=}LI6p?i_Swf1pptcISp)0?GEdTwHXKZ&g zR6scd+>G#Uy#ENsd>60?{3a@&W#J#r=hY_z3b(+gAbwBohkTE%u6Y>L1Oc)U_y%@bkg6MCDXz4=@a4aP&oi+3^0WXvP4O=yXxA)$u?P5ZW}ZNfIMIwz zu0Lvgv4A*_WEpa4!x!&)C@p^IoB?7uI_P-SwYV;@g!08y|N&>JvGy>Jh44|cZ7#12tHOfa#TL`!G$J{VlR8zLu3O|!;=MDv&S4GdLX*JS+& z@!X9P%^nB8G4AkDA;~tv9|HGa%reT05bj3hU3oh0cHsK0!B&G$t0GZnB+#&5(4>79 z(8|gV{f_>DeWk%PsJ8Y8X^MN!8b?7RiDu93ox1UYn2MNkUj%FV`eEOzsb*D)W*^0M z7BF#ZVr9+5Hj}FQ?vj{d9b4Q=h4dp51JIR#@6D)B^6N-pKWKK}gbn`kyRBZKv z*vcAWQxTJD*18VO<{+LWXzuV;x46!qen@NUO+luE#D1w;3VaxNy}}aHX=Vu zpb9^7fHwR1i{A$Rr(jO2cv2s+81;DD|NLiPu$%dYSH6Zc2@lTTP-LDUi1#A|;Q9O) zZ)hLaHmxK&f^zzjGoO3J@W=(PXD)Vm^aAGMronlGkx&+^$X)X@^6H=ZzL^(SN^+R# zh{RR1jU(!NGdZ=km7VkbApIdba2BGPH5ax0z@p#$AX2*a5EAK>*CofYRq$zL>q{St z2Nir$4nM_B#B@*xPzV54n<3)!h{pJNBOK%L>DhtY!xZ!l_-+k}Mks{a3=_B2Clv`e zjUfk5QP-M7&@CRJArUlF-XLonR^O>fs2Z8})fh74FqL&&<8as%)(>NA>-!Wh6(s6_ zc5tnmu(jL3e`efJ?ucjrA!qeY-v4fR?wzm58U6kU(UdYmhi4K(vVQO&G_7FEt0Nbd zzOS1#4~KECU1a3$SS1LlneY-!XNvF61mU#G#lXF>=iM4Hr6MLu*rm}jeo)nS7CcNy4J8y%?y|A(YhS<1_B+A_4Q_d$VFIE0eG@ zBgZyq3Rtk|Hsy+utqg<0T%@@=;2!vK#T43N3a2ERxuEI#^=MWSJ9AD<)Ct-Qd4$K5 z8WP9%62j*Tgp^(T=Hu@C6RvSLeDE_-K@LbH|0QB6p+xmJQQoXVTj1+e^@j-y^Fem} zO_Yx#{3Y<#EG~U2PCMG8M{FV<{LI_=>yQ1}7wm@aeEF}S-eDvHC@At$;75V4W&i1( ztCpK$_}IM}pL)R2OOZjqGj}j@bKdjkZ~l+74H9mjZmkl_Jzq z$Tnv~(}MQSpY-()U(#%|WXd-l0%`1Lp;XtM33Ji7UBv*cVo+BCK}@_s;E{pA;#*=zJ63)^8ir|>#)^TvidQ6_t_Md-bU6; z_0sPkhjyn=wmTYy3`!0fMfmzVXMh=y*Q0t4QRtWo=`G^IVxoydA`| zzx{MR9_?i@#Z9q+jAhWyu4A)8U|cmBXoScV9QnCAqEAcUV==|Ou`_$OlUD($$(@w# z_d9ghuIsCha(K8KyXydQrawXAwnT&S-rWFEI7i5_-U^(bsp$GMZ^;gS^uDN&?K8vp zC1NR|g5);^{=}m_5&1?5?3vGJ{{T-1oFPVG_{)@&5(x?3#~^w=@Kc16;IH%)$rUl@1=mk1j8t0VV^4pAeGi#_KPrKf zV!SD?G4In$H==5Y=g;7piy`v77mFzQd>(L&nr$A@_Ptf@-2cDC&V0R;u36k4(U&*F zatG3n>JHTrY@0IFi)t2!>cHqCn034Z!>vFOv031=$cm!gPSpHR?8+|sCEtiDVlpv>ruwA1I~p>z z1x=Jt%wRF4I&Smm{-YWyVyhdPZJtoqe2%#4ji8_3Ya!g`XvmSD4NaW>Ie;d}@8Y$K z2zU=hZw9Vo@Nx>Miv>jX3@544tQk~PZS@j+F3T8av6^XhsF63v{+%WB6C zu)3B3%_KM))4b@4rbSg(5?fh8(>&r5CG71SOfnOqJ8Fq)7q*q*hV93;dspABp}rs{ zZmCb^@>s(C&r~V{x?Or#pE)Hc@(G(RnTRQ_)HL5yYph49Js&cyn96Zuu zlmbGA;GTV3cIh1-b+h-Lw$^<4#5B4Du8RMG$XirNLBC0me+aw)n9TRP-I{rOZ$`OW z(a(vPo}fJWjz8c?R^dY@J3RPH|N8;Ds{F%$_+NS8ftxvW=n~y-k+;6}yGI`R!k51W z@0B!4QKcfHF9Cj%5XHD^UmV+Xj@SP3zbw`1@We64<>}d^sM+Qr%{Gq%BJtA0Vk?_I zTR$lhx2M3VDXuf4-BTxh52I56rQccuH*yZvz0-cE#2vg^?7^M6-rSRNaTyCaCYt z0Ej6zHESM0;?4m*$G?J_Ljwyp0WZNgO5AYM?lm`RZuAn0HykfLy6*>8T!Gt>I%OJs@^E~slB!Y36>v2NnU)7aT{b(_lptUY%wl$eeq z_{w5US59c>p$D~l`u}z7+#TQk%0HF(@JpuewkW<^#b+h4mGwZtRa+tOH?cjck(z{30K! zv_{W=5@lk`o9fyNqG2wfteEQ9(%iA5YRZiu zYFfAiCL^mK!OpBB`>8+?#a7ozSFgjhk7bu$M|Aj~p_hIWIrPKZjvVP|$jSd}YpCyg zEtKn!xfw*hihyX}QUcVhD41-tSLh>k&0}Ez_rhl6&G@tgc$sSVEsE>T;gcDn*}i(| z*oPhfHC{qQVA?c-H@NP+1W->8&5GFa`qq8y7pI{inxSeWuI=OzVW_k5z0H~2vNIh2yCAV>;sUu58EJ=r`}BvI{P^2 zI><8ct%Cl2f`d_>vONbpl;6v{fcFUg5k#CKq*~fi!Xlnm2N4aO6j$K`qrfs>;_1W zE$20lVD!qGtshm_I)wTd%ms;N|5gf1aVYFk!L{ZQ$;4DPCtudZ*y=mP&i-uSdA)mC zeZwM}UB6Fefe_WjOVDXD9$RYnD;WpW<^5C*qN*BXsE1Rd zJ}byxHCn2o>J6GUw0pW~mcH@R>e_Afof$EeF^jyn0dpIrqt_Wxv9p)dwU#tpxdzmz z_GTnneh^d4X%4m@AJz(IF~trNbweuYpcp|FOtBLd32!ikhWhRTzBMa$W{s&gr7B{| zO+`d~?-|jFe_JDKHZi-W;>kof=~<}M069jOe0S76`;ZIUO`xKhwHMU&=EW4(L+R_D zfJZ|3+_=5NSlb??QP)}uwB2LMS$8}w`q6-C&1Xf`v?{i|5*9*@z$Uq)vXdBVisusW z7GjE*)6GxnMX&l~ocLRkm?9q^k9J%bulR8fP-455f6*18AyK>p`*igpCK2KYvh7ZOqlH?v#CbT4ZVi1gk7+$CO4tI>Q{ zVpv#sm`Be%o4@;$AGnOJkG4wX-9%AL6czQqEF*wX*R zly3(8Rv^NsMfKg;09Gtc07N2~!h>QC{i4RR_j2Jkz*cTU^$8(eQJ7r00fc!hjYVun zY-JPQtAw>ihN|?2OB98meCka|$J$dE12BimP-Rws^X2#2MC2SqSxJ4buGv~k;zOs@ zpSghVE$n^oqG%SYsu9@>q7;cb_~dE0)*X2Fx1%QE$&QAb2@aL#dW4+> zD-s^mU^hB6mT4K+I3Xe~=gkk|7cM-^2~DyWad z6dU1%4}1_qJ(0eHJXL$XS3`Y)sC+uKvomeNP+LmSeKBdEJMZRt9%`oY8`K+Bjl|f4 z>AK`!t(YQtelTx$WFbj#HU@rzilRow@_h6!p=RsZPcrXNCrGrlSSj3gjl1yoo|i1V z?$3+0wZ_;K%J2xE2mS@BZ$#w90zah4JVFQL6oD-2b4Ga29oM-(zULA9MBrs8-==UA zebwUaZ977=uSakIR`Yh=PXKn_FJ3p*NRiOS4+`=kLOJJg_Q!zvAn;b;&8U7%L>dYh z+vc6G|5t3iBVV|DQn?%vMHVA)=#D32i$zowBVttbHt)>$11|-h#8n65v9gb%{7ZyK z-}WQ_jN~JkP~dhm^VBDs`LBBt@e)9cdd1jPiRRxY@!XrSl|RQ6Pm?u&lJGu9K`SFG ztHB6M^J9g)# zwwr<`eGZKcjJdeJnXxb<#|hENe1mAi1}qSJjl2c*of$MUxXuIVXI5MBtyM(KX*4@F zwpv9sr%4|_?@BaxNo=`+h6ux$Ljwi5x^Q2n&?f87?VC?yn;GBdJd$YUl8Q~$cjrP{ zsI-qZjSqm*=20}cg=qF8_|7ipXY#~052V%-xu+81XG06ZAY|nSJKg?ovc?g7S|yr& z6kFLCe-Y7KiBu*pqJp~K45}u`=T3+y?E1~L;U<-kHZdut*vtuFWRTkF6L(Jih(mjb zxKIT$s;@)$qAt<_KZ#8Tl^;R%Z!%C8AA!$VJrBk+F|=gM$n#~d4{0Er)OS6`mxArp z{;os45B$c_wnaXQ=zZ-S>Dh-PYd`gH^!uNB$Q~8psRU=EnYX)2AOuPXk&=tRhN`cNm=YmM@e(!i z4B%-5fx_i|JQtt0|9OI-*_$Pk2Q&&E;7MI(&7YTYbd+y+(TDW-i8u4Rzx&-&HhnPL ztX`Y@mzw96vm%pUel3Y__w+qK|GS;PTdc{o zm)3Qzdpth6*_7wcRx3@}4-=(t)ihbFHT}`D>ojY%vTH|USFHg}0%8g)_dKZGCq3&C z(X|^+l2N2zxgs_v?eY@F^F6>n2Yshc(w_i+j=e#NMOALwY{_lF_x}ES zi+^o|Ka+r~xSNps4phe~WrPX=&#^#INj)iIZULS}D9c@(Zcw(_dEkA(UjiQ$cuL-q%U?S*q6)$S%C~~NJiph&U#M$ewW;m% z0{>i9|7uYG6<(J_3~xdVrgjHr@lLdR7Su!1gro$s74Ql%35ncGzzQMp!ny5pQEn0f zS)HC%+dZw_cgpdf|D^k(Qy2UT zGiwKnSSuoNFMPsTjIFNWTFbcBGSU3S>6x!pFr~JLb-32E=$>8xe~IFD*J6A`<9lBZ zXP*Vi4Zz9W4@eXw16RrO_kfQyYaB;?OgwXDDi$>WY^tgzh7yl;Ix(} z2=9LVPZED8G>=2n25UXp#Xm>R{5geZ{T3985P6^grJseBf7yTI9wgw4jvzJ@xr<<{PBAePG9slnW3#iArdkNtD9-H6gg8aRW44tku+&9yzids zLOWgNi8okK$h;Z1AyL(C70pVmq#~bsxNvi$o!%Tr;?7>|xF?=iZyFml#gv+m^}o%n zw|QU^QYxC2HLKt2o1eT-3WqUV;}LrbuRf$LiX$|$6f`a5>fTq-*;mQe4Uj@lY`9z9-h#NO7^gldOEkNxChDjrRa|3HqQ&!5NB_lUf&8oWL|p61_}*QZxowAx zp9mlQYFKDMqw*xeguHuHCGMzC=EDsa+W`RDT@({HbKZ<;29W^|hh{}6wMMQ{+Q&Jl z6;s-nB5k6YH4g`@L)-y;P7pQmp{nc5gK^Sd=mQWZo2oiucY7A!Sr9QjF{S$CGrhrg z=MdWyDYW`S)W508CaQr+RHx&>*sBqZB)kgzp2s zA9y0u16Qxw7uHsQ|0(bqRei*5girsjHwGa*`M$kQ``>up%YGTJCA^2i%l;Aasyas( z4R83>BWIHHtxsf`UYDeLD9yBHjJRScBTrE1mz##PNwGC?aMcyaT1Nt)Qgb4(DL{wn zB78C@CT<2Iuv|TF3V_eZp8KS%KA9C$UPYp0q!?PCY)ilccq5F|secB6N{;NQQXdr(qX@N|#BM5(wphxl)zA$CK zSM6Z-*TB!K`gbBGZMN#n&Mt7*@9oW#a_0~JIwozRQ5jXRk|GAYN(ne}e$C(h!dIMq z+Kos33%=pmm9M_>n5~rxSZl=@A@vH~M2q#7%I8lx{`RvOkDech;|)xJ!wZHef=n&l zG>%mfk-auB-mb1UPyF%w>&jYK=pgS!`mmm2|f2e5L0RevaMt^uZ^ZLxL5*b z)D~^cV2x~BHLV+c=_yf4opa&AQpW0 zHnMJn_o@0fAtrdY6GFQJAsl*$(IEyxYD64a^RVJds#!IVQB|icJI&2F#H3sN^V=^& zSgV)XTQI0QZ`fjGoW!DTUW_(YK4Txvf@IQ6-P5T=^E zwGMn6J@vggQ5TOdZx$6(+ECXyiuy&wtgqWFjRJZvAd zmUo%7bhpM`&UTM+Q|kYr|%{(%! z9^U=?KQ(pZ&K)#Mvt{4%vY%xwdNMUVgZjXWmXFD8nYix&c`@*Gu0}8(PqqU5H-TSQ zfPWS-j5SV5vqG+hXIx#4+NRDG`+*0KXe8Ajh&nDcjG z<5Z@`?2RGVsv$N*3LSj!CbG?0)OV0AUu?qcqyRGn zL95X^hz&r%DO!j_dRn&L#>X6dr@W1Eyj2`=0kkngzUqQ9d+S3~!DhgEtLOGr6vjbKoO)F@)@8MkH&&$uo7^lSg9^dJ88Lj_U-#^8~+ zd>c2t;#a|EsNwHW`7z)JfFC4)IHx7anq-cExA-LRHsD>VJZOYYU%0Z}Sf;jkk$3&} zKjrc^CkL|^z4F(1*YExWcfS0!^nj{63sx-XQwZE4Ukf~kz@c;19zUi8knpS3^DYs5 zd!|gS-sD|x{HNOveBsM}jnrHs@=G8oOV#&5a2Bs8U~ImDP-1-z+ZkvTP(|c{Gmb|t zIBI3lT3Muxaicfy6M<#=^*8j4#!C~^tGE6WUWQ1T;3L4kya;LSl_G_KKQYd z#l-*g554T0X5eu9llstUFSE~SW8zg@`xxq??F+FAzCDX6HuK2rmHMFbYRsWIKc`vH zAOUjFW)(4BeG&`aAcYY9m)?D4@b>f=WtIsaTUu=~rff~dMs)wLFJy#;panhalM z%YRUiGNx1m)5OGG!Nm%ynwDfN#H=>*+lzq0znXdCA&*3=?eC?}V+AY88rQMF4 z{I`Q-xHPRL!Nj!5XNQ zw@;TwT+ZFJvocY|Wu5tMRx+w^X2sJ9H5FtITHPU>{7`$Tlj2YsN84RLb9SwC_)`xR z_`UZRXO?E%PtVV%FMH9`_XoQq|m1yD2!7BYmK7uWo z_^{E<$303^L{WEn=}U={AfD+BVjZSZCuqQ^tubRIV#{mlx`$D%fm{n5MYw>nf{+jv{SN@I13rf8NWs1L zy#6P6%8UO`4jp?Y=NqRm$OYi-g8Z4HM+iHHw@z~{`tNi$>rGdU-2ubD8^eP^YlUqS`~OI@O8j91J59^=UlaY z+m!?1?qL}rit&r6yhDU+efDoztbYyfdgDLgMKAjm9{gVqal>2R9_H_?K^~R79(XRo zHxnXMC)qxS4R?I(O~^jbSAFve>rKTgWKLLXRm@DyMwJqYgjrQpwfl|_WUc@H_JwO! zHr4hcByq$xJhY8Uo1@d_-Z(Xqx>%D8dE~suYjA?PJ>}Ad2en~Y%kNSFBe;GfCUY8J zSl4SeTa-Td>5R=5Y_u%D^$v4PHT&^rEkE$xbG+rxiZ}e^JQ4@TV!(YQiuTH&CXdQq zxfelQPF2vyuPK8v&U^Ylq=kyPz-0*B36;oL!+n80?oR?_f;^g~LL5dxbNb%hl z%)I=M-hgWia4LIuGUSR2D93;V8Mmub(X6$Mm`rSCA6)4>oAH6<$!Ar3T11L%e6OPE z#`TEFB%V7nHWje7tVO_5NIB%yEMhYe+eNcV2;1$&r;!t4g$FS7(TUH=vi9LY2ECEc z%!C&r#4Qw`6;W5fmR7g$A>^Sl5f?!SB~VaGnDbEWwhYEV!V zCVRtT9Hz7(S^Zi~R&SAb@qs|{w#UmYQBO>C349G~6WBmm01l&k6ycq~Ux5Gkpxb-A zk)M3qFYw9y=NEj>FJfn^_$2cLC*mBzVZqKh+=jJ%N`frSTwE-u_kLIVD$t6q;ZX80 zfBO6Xbid<%MePMY_)8ccqd7f`sNm5OaEHQI11}_$rY`UWUM0W0O;PU=GYy~Kf$Spi zpMl>9KPzBZ=zJBP5S8-4^>29_h(viL`m06wR^aOi4)b1fknHjE_8UCljd_I!&Nyv9 zIie5?y+n&^4Zlz-nogEsA2{pmo9|BMKX6ZWvenVKeuP97>aCoVc;MjJ6fusSGV@|w zJ6qUHogTil?&ZO=Zg4>6;lg@wbcU<&MZp@ip*(b$s@bkf}Dn%BJvZ zZ>ey@;@2I1>WLHo;F}8F?79=N%8vO-eW!**T_ip@rNGF6@5?<5A{r8$4wL1~rkC-; ziO)CJ5ke1B=t-!|RFOgh*IFW)TbZf|B9yr{)HR7`}RmfrK=oe4Vyt@D6 zVT3yHD1qf~c#+?F@iZ-=okeWz!gN|Cqb}-Wb+p~INlbjuNUr#ZBTM5{Tv9z_JH>znV zc&||}|A#!1Vw{Mis=gB1EOhsD9;DCkGFbHY9bL-l)`g(@xWQgHon0-9f=NYvjHV?h zbf&nzimj}wYaK>iNnP`Zn7E5X-94VPAca-%hX`a_p8)=!klNC}MR|bF)>EFi5Kxt! zS>%>iG0EQZNB?5izdi4Je+ln0&=N>^mjczdAx%>v8{xY}-t))rfn&GAxqr9Mp}z9= z!dLw2;7O?uz!eFo;u{rxp};o+UqeU@T}_Q#G5dl$XVa2tB*vm+n99%t!8Z)sNqo zRnD#YazBVZXu!jnOP*e;6bm6elxDhn4i>zsO1;f|>M-{_>{x5)R$B^});*bn*lztB zKx9b})lMf}*l5VH)0dR@e=?)h-JTxmW`@sPxR!fYuBCR&M9YVo=}f`fttn9@DBYH} zQsh_4G%W=z=n$qQNXNU08`z1~-*ERqrN$w}7MfJB#fzFXZxEjpM~l7fdJu{&Z{V6I z)OT(tYd<1K1GH;&CruAr5rped?m@V9$3;@Y1Z4r$ti71m(jGXPp{}`%PiFFvx#HSK z)O8LaQCs5qN4J&APQF69-#4}#4MpM>+CCDZ45LAZs#z&K$GEc<2@vvVc>NVG;QibNI(d{=@esJale%uv_R)Yp~w3w0dQosSQWP zLj{VM(iJRg?RVD2h`3x0b`W6~v5ESuDEnEHc8#&kbf<@GOLfxDNuttcQFopCUR6w~ zGkFP75mRa-rh?C&E5~2?4%Y7Ss=u|zgyTw(>jH<+x8A~5G^2y^D8kKyLGYa=OlfT& z0%yZSCW`OXaLpqjG-MNOd4;Tf2vcZEG<$ZcD9QG(u*NnoAsiqk?x^$up`!;X7c$E* z+iAi@;*O^khZ0OP4HKK9SwS@n<~Fs{QRS_D7fM|rlziq4q{D7?2pJkxSCFyWry;%& z&B`Gxb|8jO1!fS=pllE*v~}{6FfQN{dUIrr!xGP*ndV|*Yb)w|bGXhjKAF{I^%mmU zQ$eBEItEm{{S(X@a6ig*zz>r6Hv$hZ_cUbYI}UKWUmEuOANXZd6>E2Vz9N7Y2+qaV zD*7Ve*#rT%kvrRw1zT6`v9ybX2*&Gy4+^r`uTVmmLAir~vv?jk%{^b(vh0J}HfKi& zpv@PCkCWkID%fPl}l^x`eTR8=L#_i>H6Vzr+f6=+H!HT zZK4m{lW}&%5873rQWBnXdqiZ|o<0F0t8TNYhd%qL%)aff6COV2@MD(jz#RgqkG&FQ zTaXo%0N{^%S2UV|3YwHdgngJ}NPH+KZ63m>g`lQ;5R<%8Z!0n*rquSR6?}&8T*9~K zV0Lx7=Ar~_iu$i+{q7TZJq06nHhT8e{cA1&E1=J1OZCnG4--lXZv*k_yR)c^^JvDD zvBQ1}A(UZiRzE(dQn1Ahu``!2)wLmltf9qaV&j$oaO+o$n8BH11BJLxt0P|qb&>j{ zCK9!`J54H~as|a_CG0dc(g6sH`k21Rm<~Xkdzd$Y8C9~~45s1{ljI(J%w$tVz-MLg zy$ZIvvA+*MG>?iL2X00+G9t)pVg8w4S_37i@xlW69w`eZhg@n#j0 z^;^`mHnEjeF~w$1Sw8qG5Yt4Yj&g#qpgutD07W*Bb9=$dejTwMQ9*TRCUhy-co}#` z{;l3hSb(l$3QBOv^$^`8dYP*>){OM`$osP4xos8rL*R9Yd>qv@-zn7y&+|p7zKB4S zw)j}z?*UEr3no>CkKUVc%Sp=}*INXk(X}&nt#`}One{Zwx{HslF4YaC!_qQJ;QIBGgC>4oPd=??*^Xl5q4RC{L z0tv=r;tuMrN3)wTdWy5MM?@oBH%2ujc+dUlKmvSTEA!;ufYbMGlAIGR`3T z=sI&~I)fB8(5xz^vQAh)M#rzlwmHCOlo9GERfe4@i4u{hjZbUr9ykrTFjvG>R`XKU zAqOWgSgIx!VUikcet*6@Ct=Z5?ejN}8zEJz>f^AO4(?wE;7*Bp0d~=+>mF-;Pur^R z&5EsTvTqXzj@pZXze32EA}kw9am9<+o@!RotbR-i%MUV{(?c+&CbqOf);dmj4-$Mb zi|;LH7?-{i6Lf>4IfJQ|2ov@B@T87gj4=*r@U8ha*1oaE~*WJlT$viAdo4c-#9^ zo^h*k;*d})3(mv2l}zq?w7vY%2a3|_jE}DLO*QmKy%&+e(xPY<=Rnni@)qrJ!G0BF zVST3)BN8V;f!0}s{+qX-%_Li;v!NOxI2%SVg%-ZMAko6x)HR={uCXwk{)s43Xyco6 zXnHN>XM=upgrm`~{V{Thj5`Ut#6Ga+VZb0<11LVN;L{o6*$F~nJKG-&Vx(E~2okkJ zyHT5fO}L!U(;OkHQ71$;h89L~*wTik_3QB6S+O&hhH_r0PiiO@#u*4W!Xq69%Pg@&j})peK28cm62&rfq@v9(L;y364Mi(A<0C0uu2eQySQ zLHfB_59+#zsU#o|6Y|=GHTX-@Uhs<7;0#1AynlIG*a&(IMoF5u!+Fwmq$GL3V;r$9D#jR1s1m#5s*`Qc~Zo ziLI?OtvdL0mOC)s27Ja|+9^RYn*+QnN|rNmiw|Vbs^L$et=e3YXyN0y=KW-i>#(yc zBT3eF_Qn+3>gq@E$!(8sSvC31VQgZWP|Z9bjVd=GSpj%W>0Inev@XK*Mf>f zGZ!XsfCTnWTRBhGxE9x4#FRFqu=Jp)i*``dOWxMU5q^c^3lDOB1CgJd`ZgagcIV4~ zmB_Zpyby&q(h|}i&k}eZ$a4vN2s4j$U$5G3n=)yV5L9{p2zV3f?nf+Qy;0;2n za`6VlIPBaG5z)aW_JF>89&u1L>?#fzK1}mFJODN%#D#sBM^%EeffG|`D;xneVyo{^ z-}|Td&WzZZ_3_tH>1Q|8b!Np{iywtPclpLwIEnHkz~@WCO8)2T3A^tY*ImGtR}Ta< z)GXGlc@(j|(4HDG#pb@8jNMisZ#Al__U1O2T&;hRt-Xmaz@||G(8EXGudIWnMl-8O{*e>O%u<5O4H3JXcKu*_tdF7H z`LfsaS<}f}1%xHw`M|dmNVKj8DQ?~JReN08_`0r31fs1!0^Ugge4=6^foCZ4L%>V8 zO7lL^L;@hwA24npga14D^)GICK760Cw_cmgmCNGL-Yinu+_eEc5Pi{hQ%#G2$mlh; zzi~AaA^Lk?K@j72qPsUWO?D(5cYe-bU zNS#nMGeU6|B$_#oPiDxP$1%kQrc_toodH+Cme$AALHF--LfwUn@Vz-Pg$5?hRb$n{ zIOQu9&|>hg_ZSDUDI#e|#dteJ;IhZ)h)3ckq*e938L{P!eT;6-ks>0Zp9DFL@?yD4 zXzPKJYkmH+_V7zT0qz9BdF%q4sQw+oUz2Humf_le4qRSI>lfNy^zvWF+6tL#pg^fI zPp`ds9q=tE-%lu2EI*!VLMCbfeg&Is3s(-z1D^xl0{k(^r$GDPbp`kuL0&;{C=Wl@ z_i)vwH|OBbKd3BMn@3;tl(m^^NknWdVWcd?Wg?(7D}Xp8PPV^cIgnHGP5(jX2z5F4 z>QFxw!PpFmdzv+Fk$CwJK_0>Pt_k$-lW-{FkU|Szzk%NQ8K5&_;q%}NAj$2yBjSKH zg!iMooUk(&Tz4K5w~=@{5a{-si*c>Pf_Sl|P0i{jFqIXCnJ@=^p1M_0-=0MZS)kmO zzM{(rP^nENo;~f8^(T;RUN4b8Le@GW61B&sfx^PB6QTl&Pb#XeD0b#-SnLPbh;ck1 zg1REIqv9kd;XtrponSJ>7juG@aSmSau1&EK%0;`gf!t-<9HN>$jHZP2)31QN72msA zU8^CswmRn8Dz;cx*HB#d5Vk6q((9#ANN}B-aIK|K#@buNwHgx5oW+(mhA`pza2K0M zlxn8i+`Y z>P@}&=63<#4?K}=QO3u|{TS)*HLluN3j@>v{tkEp@TX4ow9P*wMUE0mj$cLq@Lo-W zeF>TftTmL^{Y6~(z$etc^tn;`oTo(Dh*13H#xo=oC>WPVa>s{R4K?Ni!0mu84g21~8G~_2x z-2yt=%B2u;*0|Oz zShr(`pXB--GdaITie^DZz7G`~dU8X96QYEs(8k0~^}QK!-P%6NTxE+!MfLP~yTG)BZ! z8mj5_Ztbq6=H~p@ zkHi)m``>kWi)$YO1XEteXC)Dnj;uD>*YLSmH7kiJuYq{Y8YRR!iRbqW^N6jk3MNzE ztL0n|V_4mkT$;TPy4mZk5`0=lY%hQ!g+YWONY!5)-MqKK(by*KZh%#kvk1=$(Gi=# zfV69`JIgzqOWT{u3Rz=LU29ovrOq@DO^nag-Xoxjrv&;YM7|53eVeAMH%m1C$UtRx z_uyuj;-+S;W8!;9u+h^{^Y6j0BJvZM;vG^bJR+{MuC94ReRl!ZSyGXqmtF&q+{BpL z6Rq6%pM|rpRypxA+lOva@%tCip7;H~%;wqqSU7nLiCB{PIXpcKb^+yS5$QJp-$LNK zxKeWTUog`=fO*ySw7LlIsua4v=`^~lofO+a3Y|rQhRhETFgJ%D>yBQvD{BH6qeRx? zy)SY_P#Hs}-l}E=*;8avJeNgxuApiJlMY4Qw{FiAx=3M0Z1q-5>Erm`MSOP-X4fVU zPLzUF5PQ2^_aE*ay#-Y5fqV{_L-;Qsbuby8Y<#d)0x05x+@x@pjL6Qh-qvC<_Ie&e~0SR1LaKA4N;}EG{WKO|ExiP4TE|s zw!EQ8M_p%Ld{PQkHB&veK1tgG!U>%Fu-Mt(2K!`ntrMEA-Xc2x@HR!=p>xHQH&j|j z)pzH`R&GN$hVluNp96jnk>_G7PE5HWnv_)2QZ8Hvh!>Hdy6ck}F~v33{$Mm#ct5c5 zFOc2&^4AUqf9aarxNH4Y+_~`DW#Fp}_G<`>&eI6x*IzPr)yde?+5}+df4*tq%=`Z9 zum1l#Px`Lk)5DeZBcLw^{sHhz9-pk8SM3U!5TbrobazzX@+_3Ls*3~L=%{MUoaAz= zE92W671_tdTPc{GW*w#o>TZ*{ulY+h-hYq!&htgH)8nP%RS=tsh{5*`wO>C94BA$x zisJb_gg-*Kfa+HfB(gz$gzwH_3iSzK#8DkE>RQWT@vE$XN%2{ksJ3@m>vZh~%n0Ix z_eVhD?gY7vw&^RVLz>9sX$wX=uj;L;D~KsJ=tmVKZUNmuw3QXXCcCezCJ34BdU-Yw z?jdkQ%wggVf^M;!4rNQWW$+~Ql~5Wbw!Er7J*mF4Ai>d?Fr)*5v6R3tc>-JbFxdBs zDZC34JzulUYc$)q0bqo)A(%p2BCM&smN%* zA}l3Tko=m!J0g>{{@btp2NDLYH zsBbo@YFY`=k%4C^Y!Rz%X`KXWT*X%J#cv+MwU@EgmB}Tl8s*jJU4l5*6Hq507ZLL& ziDqvEyk?u%1!u!YVr!QM8Z8iBHi^1<*=`BA4X0(Gg^=R>d$8tXxuo^8L7zk*h^x}C z8dAE5ehlS3kOaw-$I!m!dw+?FjVXTK(`@xHVlX0j)$4%o0=^q~5?|s>j*3vQDKEY4 zy?k!vdOmjU)&XAQE7TPDIPmMh9|P-(lDPQzH~-Kt|G>HC(vJ|F%B%JjWX8bJ`8HzW zE58E@rL8Iw?@@&)Uc{!MGGz*3wUK*cHSDB-7&cq$U@NzEeqYL^=fCDd_+AU&nZeFX zD04Lqi4)CQM`iVQqg{@M+^|&|sApaci7638)eNYr@602%ixgYzD{Z1iv*uyc7ewNg zm{LQt&OC_6M3*5%BZ^NdsE@JbwVdn1AQE`nLzLiPP4Bvtafs;!PKuESfMz8{#h7SI z#$Bw(Co3RF(Y4QvZutN|@YHwk zXZL;`pS*DMS8^KVA>e<2{tifAAaDufyG8WJotmecVcCnT_7!7Rlxop&D0ZeP@$yM~gkusdJW95?wxwb|fwa=ZUDO};jZYW$ z47uo;pUc-tokKVR_`na5%wkIG+jg!~jGDUctoqJUa4d?O5T$5V6v1!v#18Pes)o?f z7XO4O5@)Ke9Eh#BLfc^RNZiTk#7Fd0QJ++DZ>}%u1{lG2&!gEb_?f-vz-5c2ZAdf> z%5LRt-=)1%nLYGv8;F_5#4TJh7phxIft*Y*45Av!R;jQW0=;_^Tm2k9y(vUBYAch8 z)N?BnlNoX4RZOuCu7FQw z1(SmDC{f@OA;4XZ{Ve*v{{ppDxTX$h8{t8a2Y0#Ns~>{GW0UTF;mclwk1RM16lsAN z?wKe*L;!YP%i~a*`yi$UubZ~Y*ze8!(qEBE-?8$Zci4?c_gR!-*PVP7$J8hC@i zZzKM*Iee%Ld>im8f(}#^*zm9V8DEK}K$<2@pGA7FP_nIQ`(CqmZpRNge+NOqGl-6< zi^UY{`vcgj#Wj!S^_HpF(nk2_tdtAUOy>ybnD#^CTZ^|Tc*!}mubc^(LW`_7mrJkj z2sx=i)2c|+*@0g%63wmp)@Rp=mzTx&DwuL(`-9E<+SeFC6pYu-gSV;cz0@~8{$acH zqKlZqJf_gVwZQdeiS)t%6cpaM{(~9*BcO|7%Maq(b@jd3iSq5LR(of}UHZ#I+B;`_ z{bM&v>BPJy7cZLH9Z>Keq33@O_{W%nN@3|iah+9l&BN-u3;52WU{Xw>DYkkskd2W7 z4lx5_czN!R@3`zQ!kHFC24awA`^k6l8z1-{8r^CRBl@zo3&5Lz*BRgZgf}(t5+Qso z@K1p6AdqeWIhom2N8>BfR6Razg3exHF*O&dW<@cD*7)_YUtxFD}(DJ&z^ z+nw2+gC?fK%1#68tyUX!)Ck&cF*`-PlDUMXRB`vOw4| zbsSZ3of-ALISIH0kMya=eH5h6a|hu-->S-hQ$mIapVT9!%$40}s)#AHH1{?UTi@1e z^%0ttF@1K?{_nhz!t#o4Ha;f4^R1fJPfDS1{|;v)cM8;o^Tgxov0Hu5Gtu<0W@*K3 ze)#Qn;p-DI#in8s)nrE07X~!$(q}Cli0m*F9hpbszmaJ6Ax&3rSJyl)rqIS#)^?A) zi0?j9a;xt~d*=**?>uyio`2KV;Jf#_)4xl6>(7JA+Y!AU_;!#OTUi%du8U8L0UKh| z-0SV*C07T^#Mc}fIl?0U;tD>m{o7Z6&ucln;AynOP#Q6S9sypc=ywtTo?li>j+*#*eYmn&*Fg;u~r9%3rRLjVACXz^@TVx4fDe1Nv2n z{5S!FGg6}caa3V_)xL;k4b0Yx1kR?*DQFlr8Ll|#jBj^R738>U_=v!H)E#r2$5gsf zIP^@?i*sU1tI8(odsQ)&=J-pP49vU~mMfzp8r=lq2$1W6X9ZF~Z}8pOycBh^w#lH- z(p$}%MfEXNtsvm@6C7mMB4@p1ST#U?3gp zp}JVrM`GgEj);$7Yc0HcExz|u^}XY``eBLY&u*P=TwW;>LP@?IWh?JE={pbh>+g^H z^}D~yRBt^mg@uxsxTQXs!zUGNrI%mJqZqct=rt#FRGb(&SQOv<%$!^K%Q;Pk3Q2sgq@7bg0s2vePo*FGH^fi>`8L9TLFuA=2T(=Chw|CJ zDqDX4!UpgbgjFgh;d7t6qL1Zr>|6f+Z;(H`)J+z9&3gi3<1BBV^dY)m!0 z91GaMzE!$um@G#^IGvgr*$uPhW%>V-!3?4AX**HkHWJ#8lbGkX_&^ew|wb%E@uG8Um z_X&NkZ1WB4d1T4=)~Ow;YVWnzde{4sP2~9Oi?8PNSch|I|Np}m`XumsC|^S;j9O?m zT%za?19#z+ax2(>L^S?iGgWBJ7}{DJsJ{ZU+P#Y@cUVj0YPh<(EXIOulmbwZY8eHS zn!w9$#uhhVQj;QH5u49ecdA6om5&QM8VMiA*`RmePH&10Q`j&|&~&^e&n=Fxs?9g6 z7MsLm`knqqY9xjd7<5*3#*$A+E;1rwN-*bPN2;MGq&$`=p}WYyEG99IEi}bgi~00j`oG59}4Mu^SsEHJa$9G`vQF z*28$|38K6gjXv3Fhac(FaC`wkgkA;wkqAF;8`xRg{VOE4{_YrRKL(t_<*ru%88k8u zgew*p20j5i0l?%jWXu1&IFF-FyYWB$K20N?6w(u@b=1&>Qpr6@B#8TeCCD3bG|{X0 zcZ9!i&VwxScTD4=jab^Vqg>IuhdVYt$+qr8B>fP($Y-cP(kpT2mPc6Iev&Wmx|yes zZ(%gwfQ$V6yRAqW1O5j1QwQ`^fOXA=9|GP-6`?ahUFziD?ePD@mTC$t?=ZAC31~I_ zk2=kk(jY3PQnUeZBAZ6k3R34XrBMQmbrpyeQXnQdshA#(avLSl(2oQ;Y%yC?rHjp> zg)^0Ey|n>%it|7swfUyF(ec{DLuv>ox&%LuNQ9VDoOW?cn2!Fmw&=*Bf2Rhu3FlOc#tHC3jq)wxN6G2*NjW;C8h}{AE5wdg z26rlZ0+DMaXgT40qp4+D9Na)Ob;gc8owbvDJFulnl@qT4{#KOV6ycl2J-;0Kz3-ng z+09=9eHLXk!UohfX=$`k{DwRx^&sdI#Gu^snLS@=d&`ghA){jn6vfL6h13Mr75I1v zjXIY;4406(C)WL#$~#MNi`(jmate7_&W=&oZ2AqJN?VtA;E3 z_HfP0ovdj)ManNZMa(L`z5+&+?LCKC+kTSWXV>uf(H-nOyM_}Z-Q>f6cLATBz^|ix z5h03!@9)FmFt*fmhiMv{|A=V(KW7SUjgU&f#pTej1}$U}6IONtL>;5{R2wtk%-UZ> zEu|b~gI7_B9Fxe4_=3%^LA+y_#DsbFxn!EJJLENAd5Go9?;m;ZPm%e_ zR7ND(PHuG2+wjBDFRuMt{^a?K`6w#eE${ma{9?q!sA2>}42sQyEE8b|=%wn&l`aSR z_#Px#5>Y&{7_lgVwWeZ)Ll6h`N|8^}FJeUTO+;725Iv1UENdFT_d=SIlbEvPcAZP4 zRY?0qu3E8|t=)&X|G?G!%X6@flst^|G^cms932ZCb1yJf#2 zF5>p@ChE3SGopxCBKy5jL7oVN4Sj~d7h9c|sAf}aF{L)&BogH27aW{tVxpPUNnHgR zf|gPuexZ6Xh8JNHIkkmtqPicGm;k#B4FiYoSo8x>jdG%;;e}Es)2gADB5~S8qomqW zM*PN;wR5X08s(Z1QITLuHPRm3$Wd$+Gyo+8~qouL5YJ^6)EJy-TPi^t?Xz1HlnbeAL zOwgr;GwoV9-Jsz(ONjg|2mTxI4vn~*X!IUC@bwq%z}L^n*-N69Km0G0j{nOPg_Du` zG(?B~HvHXhQlxtP>Voa{@Bb6V^8vmMQBSbx1SVhVQ}jBJ*Q=ufUkSAHJ-R@PC`J@d zB8(`J8Y1ge-PBHwc5!^Pd-^K_w?KQw(vXBY!dvb*r`wl5-gW)##|E4V_Pa&7t{I} zW)>4CSi}_32(?9DykMzMW3hJ2d>ZAlmf4iyy5Nedv>}ovNan*6pBk1#*BLFIS)+wB z%^Hq2&BkKXY8s9iop>20cc+lV#xkhKm3U|X5GfntjEaD6!j`U&^2Y;QYT!b@TLCrz zYk?~i{#xJ*cH+g!+|@6Veh7*}frz3J%(=&5)%Rb-$8etA_TE1Q@hMm(5m;0C@tswgJTf`!(@YVF+>QM?6SzsMR!a*h1!3$H;$dH1FF5qL|- zuf2479Up({&Fnn0b{2E~_q0s`UjhCoQhm~MV3n1?uL19838}I&KBJ!J^B=Z!i_{?B zL*+diS9-KHg@{NA2}&0)O&@DmNuo-{$%Ql#`e>NIWQJ>M!z8ANbR{-ij(DSv7pYW% zJjCgpprr&Fb}gXZsvuzo2ZF<8PKsH?6yxwvZDL$nN~)H!VzOu4@BOJc1Roj$Ddj>? zWGdJ~qoZOCre^kgRS_>#)M87?i_g_4h)GOnm~V06qd07)Ws$RaQ=~u>O4jC24BPRY zA!hGMG>Y{|E^x!w2sG#E!mN}*mH&SsW`U=HzXR?;IcbYT$sO^Ce-rc;FoG&XK`*=# z5sjPP^7|xPv)H0Vuviga;4<-&?~1ENw(#$yVnk3-h%gt|H^w<3#W3JYJ8xxO`w6aJ z{qmB^GM_xQjlX{U9ULF&io@C8JB$AUrZ^_sKL)-Ia>3W%6sQo~#?UiREv&qOAq7V#UM^t&SsTV&G70Ouk!0&} z!h8l>vY_}XY!}t{0`Fo8q44jvz0wP*h%%CEXfNCMT2t)NkCWv1WBvM=I5zjk~NlsvMO=#pTiX&0$_@GR>Fvro*Qy(cO zZrc!=a6k(m4O4j8(b~f}nm|jPBEdwNi`|&S0%;dDu0&?3AqN$v)$OJqv?DMPV?|6U z2Ay6={p=!B?}bQWf^cl5B3Asy{yZd;cgJd~mZC zPW^jhnA`>Y5|*!Gh{OXFf?Hw7N#Leew((cL=YJFW7Gp;V^NlF#p=`zQK^$Y}CN94C z!2fSdMA>zA4f_UGa8=*lnxYy;VE;fLAA91hoEqzxb}~dX21I^Or4LFt;j`Z}VxG5Z z0c}>~F5stuOE1bie)oZ(|6$(+Q-y3==xh;6)+f}=6tNe3;dhUGY_2J8=oNaoRIvF5 z=Va6xJn$kU$YTpFz#x*CQk`rU4TCtEvw(zsmoozRO)^nWi1LzLOTO zX|GnG4J3)5J+2xiM4Xb_d7W`An5y11_e1@*km6}HGf)#j6u36zq7tgO8t3G=Mia}i zQ|-_&5R;S4aCFY?X@C;o3ye>Yy7Jec?N#5GGJaJ6* zKH%N<$~V02hku7iDk)%Bt{pk)}=|HDFaueb?{??r^x z`<|QUwaYy^TMdGZJm1efbpv4Ad4gB3@Byu9h+Cq8L-n5P2?@ zq6RG`Ahob>pw>tHNRnOSa~I{vK;DORxE%)s4b|Z@LRYU_%aGK^h8>7MDJDIlrNKVL z&(905h>0#JZQ?ExMMdT&iHo>WPDQ+e+At-KSftLUQG?Bt)4C{%4byI}77RyuK-Oh- ze6%<~wDU&`c$t5|_-`XB_0-YwcgxNI|1B~+6vP~?soTB-t*Y{x_xu@A&c}KoYcD^+ zq38NGi^vCnHv^mb4+o88eznF=DXPl!^9!oirX3LrP z?}B*-HuQNUeDDmR@0nQwnipucka3li64eofucK{Iw3vb@fg~rKY)6e|l9L*ZcZm&? zF0CWhaYrp;8U z&dVo=h}9W_sU@1qd3mN;B4TJJSf`$aIdSx4Y*Sp@upmz)Qu0JJAco40$ zFt`8!fB;EEK~zRy%-TcG^}P#63;zELS@!RijTbV6)*~>{q936MoEYikYkO{>82R%w zPQFJ@nlA&t2l_dXyh0$#+kjtHJ&K;W zXwmW*pP>dMs$+0E=U>$>j&h9-15yqE)J9h!CQ)!qO{Ekjl@m;eEv6Trt5OQDVc2B1 zjZ8Zldio>fq(gfw3tz@(WN`c!HQ_TfDm1EwY9tuN=2bOJ7?T{ILnh)*1LetC>XFEC zXs8-yNWUBqbXs1_D>&v*_39%tskH~L?`n&U6o)n-5{jQ4SmbI0w6o&3;P`JND?UQ; zegKLHqZhYhi9LOQ`i5SPvaD-A$@gFW zby9wjPdxKR_7AL>^|#->z9sw;@7VDGE$MOo^3l5}MHNKEt-*+8Yxfb}bLo9tv+`w1 zQNXgMK|cH9t2j5&`W+Ye+qlAGS)0p}rxKboMlKSXUjQw#qS!0#b^6{Q4Z1l|q&S758EaP^8^tnWC^zdZN4>G1o- zu(BgjK52I~4D+9_`4TUkUeD)Veucw)70hWE7#fq4+`RUA-oE_-R=1u3f%fbOpL*_f zJbr9D#W47;I75oyOT-&CKg!#;Kgi%z6FrTC93AdiG*Z^pIK-${Xm~)RU}PhZ z1X0x}fnaOoP*-f2P%UM|qz6IAfK~}w7okygtJVeagR6}>D9U&bwM^6TiimhcEk|Zv zN%OM(;x`Odogc?d(g{rJryZCCXkez4c=6wg_Ycp%JNo_0zubuC#+^?nU@ff(w^#hN z7^GqIaj*S#e`P0L{*snLW|h?5O!a3l0NkG)37!Akc=4T#OW*rZI#EIlQB(pU0oo$3X?0n-PVryK37_zh8`(?JK31 zH<^9*CGb*R!K_| zOf6e=*Uqii>drJLC(>Iy4R+c*4|bk%u`fP{?<%Ph$D<`R{^|70<3JsrA87s4Ctngg z;;xeLcg7cUsFj#wP+L_)WkI=jM5SLi!}VG-%r%j6;On;((J0G0)?kWp`iE&MMMOy1 zzh++hKhDNsY%xWZsnSuuqHsfVqc2hf(6+qA-Crr^1L z@;S)p#Wb`#jboatB)~}^ocgEY73!n9HKxFd5{zllmlo2+z~Jb?SaFtbbl1^z;rCz> zb)3z?Wa3E35sW&3J&D{5|S{XMFAI71waA0Ut_N9zlY51&d46 z!@Dzl1LRTP95Ceo#D*ga-{i`7EmithocYk=Mg5Q)NcDrt!R3SGyR#$UOAVROlW1}H zWPdxotuEy>)t1NB1D+q+#2|hTJFUTCmCS{O%8^n;_x~TwS$kCVd1vh0-hW%n+gX=l z6_(t;Dcf3%%X_mxHYs#P>$&tvi4J2CPN%CD9(zd=MP>)!`W-veFWQsWCr6jtrF(0` zAw2_pX4>0A+oZWWLR-+rcs=dNPhy6dr}Z_JuS|lzeHL09p25ndEmjk^-VZ?<1_>e- z&nv$cr(^#+_^II=q~YKW?_a4G(&{k*@^*cs)!pKM_tr(DOdqaod}aD!>mFCGepbZI ze)0>>95xIyZ7#mu17^FT>`leQf4q@jSaxYJv5xC&jg>>6iar`f?gq@JkItl$PW<|ReT$9n z^MP(*GuC=p)}qVztd4)nnZCr5z| ztsp?1aWr7N+qO8{&l0Y=p^Q!J68`W71Qkc_1@J`1FtZfAiW-C?X!gK{z+y#jlt9q8 z%Pg;RyRA=bj46Ll)Y4vR=hDsl!BryVL?T5~F$uR{OZMIwyv=;VKk{upB6SFjJX8cf zU*2pyp2vAeY^=4&x@<71)e*l`_YY3?t+yVa+H2mEAdb8>q-e=_2L54qrbwpW8d`GO z8vgh6GHhHJuV`IaL@Xs`Ys>Ck22O8`0^cEN;VMHYlMe5oG^&>ePC5AplP>z^(z|Wf z)?E{_{~V}zpKc#`+dW1CRZhO(P#F6?zx#6#)d3MkOvnci_Lv+gQCcDgoF~dM-->}x z*=Sd+m$Fgc?leYv##5h^JXz6Y7{@GByI528PA2HSpwVJJ0Bm1_iW3U^apCT7(#PNK z=-Ee$HpX_+~0QWu?qOku;ooq%;ldme4u*4GG>)K`>nE=cpv|*o=zgPFgwKsXwKgUa*$6> zQ2Awfj(kOk|50kqJ&0W_Uy&jI*OoC9ZFe-UNg2chUQ6nlyXkLXI42$Lk`bx^!*k$u zND@|>*h(+^OEOekYJo|qb!xGt47pkrWZH|xGS>r%slzzXnzEl@Hjj-@LBJ-Y3MA%0 zM%eh*bsp`u!f#m~2sKkKnXPzivPNio2~ssQ<7iDO>N=KK@pwXSKH6OTG^veW5`wJ8 z1|$O2g~n&&iy6rCc7JWAY(-HAQ0FM2sr}TYo0jK;LLFMl=Kw>3WCZ&`tb8~}H6{N{ z|A@lbl@=V^F$EGHp$g&>)L_*_SIjPU;ypD4&W;T*=^!AXt43PTIP8ygPeJ1)f! zj_7P{<>k`KH7@oe(}~Pv9*sgsLyu-d56UpTG-sTX{yeUCb3W)xe437(MLr7$nqm)( zrVEWS*5vMSRc~HlELD1jJ)EVO+zDP5SQTM^&aHQTSP^o$V@_o(%YKRiR<`({15nbY zAn^Q)ik)gm1WYW;!qD^e?l>Mn6XBWbbr)J3u7u@OwAJu(BT}0U_eNF6Yh#eGRr?>1 z8cx;?V^(URl}9`KPUPi&RAp1;bCf6Q2A{0*qdvG?7FFErJ*S+4g8&@*F5zZNFA)IZMADqH0(6!r zNf)Op+}!G>X(|fl0C%xS3f=)VH3naYMnXJtrm&q=q?tv--?*q-m{O`L!I@h*3Cy9^ z2vz_tC!y$98~r+NB_V?=>xp9;^Nn|ECWgyym%}L%0^7J$y_UFyo-OcxKqg!GTqK7x zl#C6AF^Mt#H9w`^D@;Lrnh6t9m4LL^IOb%|V`Rq4U2H))-mrrQrKwRnReqz79N|K& z1zHn}Hhn~6t5;mx5dYK$%LPl9)r71QCet!G^?1C>vF zZ*<>pJ3*X{1w(ThcjpHW_X@R@uP7%u4Tj6Am~HqW9$T0{{^2HX*QH24S8x1$;Fc1; zuk9&`oKU1mO=nbVe~_K#R*zb3p9QpM(dXXv?}(|%+R>69FD!JXjG_>K+|FND@_~$5 zC4!S#v!+l=`+TG_5%#^&)f=;lsF7+Jc_@DUxM<0?;}7}EYc}|KVRP|+te~^RKSmnD zcrcTn?C8=GqRV{H-ZQ$l{N;^NNuW{~DI#Q!@)>g*Myw#?aSJGA%?^Gsw671_j3{PG zLFXGGPp#>nLI378eXiD6<;d-x8<5RMA42hBy`Leu#n4&`l{p-#!=gTqjYecXlvzY*jwzZ4Fnb)n zEHFCn{lz}969|#K7r?6pWi%D0sXfZyvmw=1Tua&Oz0yI((-Yw859am=M15u4I}^q4 zYZZ4cBpYA~eO4;SvyX(xLdBbPLRvJ8m-1`R>ZG^NixMQ2-7$mR5MW=>PbJjFK!mIj zC=$PBM}%BZ7H@B1ai*Sq*bNEP5dCdcV1*XQ*(#$|iSQL)jJRrd|}Y0=@3O3IO%na%RuUsl76 z45iDTo@?qd4`gMTktgJ2RgAjrq`D<=Yj{|}0BzXIP3XV2|3lS^3@QrR?a(B%4gPJz zUj|fJe)FodP2R86eN0LGmDnXnu6i&)g;om*w%g<;7|*=_owoZra2KXfXY1{_9^}yi<4TnBI(kTo0%uAND}W}?dQ44;8+or z0kUVNR&_Bybv1z;Nd&6M5rTA{Xy>(;;#T{Ev4#Rl?1_UvS^@MM63+uVpQ-<(s((M| z`5Or&epw)Gfkgb;hJvA$WsivTCB7kGb^1&aZuQB=8Sn-TReuuvtIe;-x&u}9Hn=0~ z@(!j4W$>s2=rX;Gu{Q{8g%l@1=cs-rDA!h~ue<*W$eqCdNxIM6y)#mvmq*SaZ$$Rp zu_1x>(vEP)I2}DJGWSUNfx5hZ=g!;L3JiU?@P+=8{1ot3z&FDp6mb9F(8|0=f=tqX zh9XB!+H)o;%RNs~U#haOsa3yS2`6zPPp|QH-;Xun`%tajFzGPtnetM)unlR3-1>NAt!-kZWS|71ZFBHo zGc)@J!0p}WQ8TPtWU-&&>Y+o&IW_Fk!g1!gOiZMQ1#&>1hC0Iz&P}K6$(KP2VGifk zBEUiM7GDpi2UO8?35G#};@mMY2(#QK)OSb0%db;JsfZy~h|0W;u0Kvz?2KUm)7I&p z-)0ynBb>9!m)K)tG`XW2St?~Zh_uvCq#mAnD=6T{9l`$a6-yR(XOM-w{o(OdU281Z z-y!~SL90LqlDaTzV7UHAOe|jX+5Y7l4Nt0PBfFh@Z;P48d0QJajRM>U0O$)9-e*mk zniZTGT?ylujA~})S{Qd17!zfd!+8d{7A}tUdP{B#-0a$-bJGO@T{`RQ8~)Wgll6u} zu`0cLmOd3ZfK@x(He?J5@#JP*oV4uRvE-rh-iOriy=Ue0HC*a_9^ta8Us4s0j_M5 zr^AA+m|}U|;}@W`uhgD_*=Q@w#EdI+WjChK6ud)lR{F+{c|AJY7KC8S+ih?9EU_@H z^qP%5*0x*u2k(>7pL_wni-w{hD|vVQ6L}ySSZ*Y(*AKUb!L;Jh~2wSC)|6upF+Vojak|X_*mp+a74{`kPVAE z!y!=&{~G<@O3#?ZA~V-Y`!T<$;j4D`U<0oq83gNzm$Q=#DI5H}d?SevQhXX(`c#Q~ zp>-+Z!tgnW9n+@7us97>1uZ^JIo%;+5^COT*S~;cDWa^0tk`f|M5aepn|uq7hhf)m zBCCVZVzmlz!ziJwAQcEzLMlqrTdIro6*Bfd`~texWm%^av8P-O0xuC(rea?e9f29Va?H{Up5efG6RPnI5l@QSFYV8LY- zDl==bP74}`eJd~!rjla~5yP&bO2ppnMgU4cM&y4(9(#)tTs-T27xm>UoHcQKef(dK z@GQh2MpKLqGc2(Mt$2p=nx6@1btY@t&e;IE4S3#?FFSWnj1Rj!yd-ETF;Qn!nJY?7 zu0*N&(;}xv3yy|4(Fy{Oo>5=vFaESmxcvgAlPxF0u@!tRi#|9#MACmWjPgFSnyiti zid<a+{Q%`>#Sfx13l+bkiE#HdBkA|>dsSOlmVFVzO|jiUSLllUh3$O1dc@#q zAlojiU-3-U#oE^jBk;I;4$O*j5twzD0lfjpV{3?m-%W`#W1M1B2yOypRqd+4bw)$C zT;Q)|uEDz${}WL{vRDLzT40){S|Xuf=cq4ULi?iCElW~8V;WL&FZLD}SVB)ez!VS+ zzN3ka=Znht>Tk6UetyZwi-&5DZi;Si0xCtnhF@r%hHe~)kePjEB>lQ$R{hPg5Z=xs z)zLbPJOsYP>HG9;R4EH6GSomd77o7?g1pMyZ4HL3#~S?LHWXmR6Hm-Y?X^uo)~B$m z(v*VNCf$p8c-Q_1VnoX1)7ZIeaKB737|M4ZrzC_oDU(tlBOLj!^5@03G6%#+J2N{P z55rcZ5z*GG`+2nWBLIhuZr#~8-lYEuP+$Pw4kyL>aNQjrz(K(Cz-2V0`t>CWi81{! ze0k9)CNgu#N>+Y0BQ1xXS6at2qi7xq@LwtWqD~#Ve0I%IHs9VZ2g(HT`M4l_KHa6$ zDDx!y)s`L5O`Kd`>Pr3@2(j^GPBG?)FNYg`0}ODtkhDW+(=hQknyVtK5{5E$}6{WT3U@e#j@$f-c0#}zQelw?=uf<})`x$jl6bq3ecMSq zwk9b0g_O9X(HD_XB?I2_Rp%&qNvEs#_ z?fu~TRlbp{aIK|M5)t_AaG+GRAJus=wwyI(coK}TUyX;+N9^MPv?Bu08e5+`{Etkb z+_Lm04EQ~X( zpbnxC6#&F&acp-nG%mEZxzOU~;)%o6l=4Uu=JTdu*QwIBHU4B|mmm(*g7A zfwpMep8MpQ+s$9!TAs>ZgHPSoOIjjqGf_{_%&ATdF$`lCbh&-I3CjazNpZ%r@gyIi z;^^lIFEVMo^S@TNIBW#pNJJh3BS?w}W*h^B#vDCdn=`C+f2&vODgx{yp3)!mcz4kp zg!oN5Ul6-!)$Yx`S!Sf?7JjN)UE7nxfGCN+?yhymi;SoF{nA9gtx?Op!oWv@&bmjF))m5!Z!XIi^?KTnRedTQtF@_!|+wH}0L{8s}AhgXQ8Ix&*JT z1fBi(ri;M0AL;LdGH%D}=ke+#C=I?d2DI{_t6aO}$Me>WKixrd6PI*6aR{OXQy(30 z0EXH96aY~IZCr?0_S-I8DL@!Rb$RbI_+BI_`~&v`yr_fcZL+HaYdJ&$-njql5?^IG zG+bFQh|%}8mVBK@Mg`6Mw@PP_25+<$8?w`ka@u2_QE?{Bp1Y>jM}XpAj01DaVL&3`?|S$Tz%iHQiIC;-`B{_xnXEckP5krs!!4rQop=I7$?5bN zQ11SG*T&7m{`q!x>T!H^uwJ_(rQh)Zz{z~fH7>t7w164!q-eo=EW+r_2yIy?^?RJS zfR@?q??n&_3=n?7CE_X*mMPyXxtZff6;VU}lyLksyV5K?CD<#O;0#-uj(#FHW4Fp7S$^uYg16-TgZw3<8{8j*@e<|z}Q#p@VkA2^BW=&>}GWJkf8F#Ae#RGfy#&Mau^E$L32QI7&ws50hiZ+h9VGUbhGHzv$ z-hL8&p~eK)Va;kRo_lBqw(4I-tNCi+XrXj{SrCgqQ|TaxAgv(mVf*FVfxGCp>XWFF zOY6@Gb<6gkr!4p$-WYOu@{fEIRG8`9B6}~vF131}yaGNcMmLRlEzp~VuJzqFh6H)M z81Mw2&+s>|`<8rmh8Qn5>UV^_eb60-f5yx4c{#IwVch=+r3J+;Mutqx3r z=XnYB`b4N^4xcmv*LRt3y_MMot=(Q(2($7S92DfVm?tQM^|gBRJ=) z5AO22nPBI}-9BvKXO(uiZEfSosEb#H^8sZ4quTuBtKnD~V2*0_U65&a+e5cK<`YNJ zK%h5^8oa^%_!w!viu73;@W%fw@tcU-nYy{cE2;eDVUf>|#L>CoXP5UKLzY18O6F*} zJkCD_wtC|UN~bMK-QH$#f&}wo69kzZp7~X4K2>Q_zTx_K=089@(|^p+!bC#6cLXE} z_p@X2e?FpknB}#u@IH|-kE?CWcGuVD7Po^jvW}WV3r45#9fxdp{MQ!^^~g53QBh)` z?Q($Bwl2~FmXH!ouHQ!FygimC+8wPSRSM?=7rsjbCtXJ?bc6xmeqTi66CZz7VV*CG z>E>y6Qs-$gckDFnv^!)Hi}jt}LM0-s73b-zO(=y9YBd=Jt8^-Y^a2;!s5N?vWs~8B zN?1`^hPjp-js|92hSVz~xcoN?;dT;jX>b@u59zn2spVOjAvV7=!Vxm(nQNb?F+Y5( z2=F^6wmw4_1co-0fdGqABo@?ZCOIyx`M2w#_eGl;lPTTbTO}!`x#1#3TpV(YAH`Xc zB3i8rBJJ&7_}H0DVsCubK8?>5%wfM1b^63q`gvS6zfOC+RCVRNBgYvcNV>gC);v%7 zX2%YNoGpU{2@nMv{^)jp9#k#e3mL(~XiCQB7U8y&>N8cOW z>3lTVe7Ae% zP74sty?r5!CQDipNALF?OQ4Oi0j2!&?hK!cTRl2Bq$clx zuF`dzHR5|!g5lB%`?}vYO%``;P){jE@ehc9dI;ba!so2?2;j8!__d(6EPnJj%+2(V zd0YQ;sZCNT>qwp#aajNF5CS5Mfy$^5)Zi_y!1Xvjz@rqU9m$B|PO{&;l|};7t+oeB z{i^2+fV~b{mzdRlMzpH+$rN)ib@Gd-IF}VYu5z1e0loOYAD5j0@g{n6{SNgi1hH`p;nOGpsI5dPLMF;j%~mIgO!GbO`gGj}5Xrdgrw2^H_7* zTYb<6D8UQO;(-9995G5{I!1w8aU$lm?~6Pc!*TS8Z_aOc+c;r+ zErj7Hsc~wd9X!Dg!O^Wf5*As%=ifvH0Ime%88*_>K)_h&A#cb z=akljziIf~x8bt{8*3SoWKhRmqRuN3jsCKAY?)j;4lc>juy4Vg52b3(9q^aq&QbMOX^`})}6nMxT z`K-W7&L$aO z8~;B)ag#btN$64n?Dl9lq#WF5_te;v_qw;_T`|neuau~0Eu+%|d=g5w0(-L*au!8B zg@E2s8isJ}rMsKv5dOLj*0iAWj$~Ap(;$yJ96k9QL?mJHi<@nE5oD^VCZiK%$!B*v zgzT~5phrP*uTo@mS_qZdNN6)k@ZUWotZSOqy!tDQ=v$sL^qA5VnL-x|MIIZ@YHuq{ zXp+^=`hVJZC;iuwrjF@X()0Qiv#VI!#s0JOdPD8Vr;?fEF_57zd%-z3t68 z?5~T`614#FhD9d`KF*oZp=?jqkdUOMcJP1JldxF(riIYFA16Wn5=aK4KoB4j(y3}a z~?7(Krc(hzA&6kY+hu;62eWhHmBSA zu4A6J%WDW={{NrK-Qm{1&f^4xW{zb+-YMTs5)YePH`UR#`T!wXQ65| zwygjAVwdMxCZCfF@36NsM|tny1e${ZWv%z#DHcYL48+Qqv8nhY zSB!>Qw{4U1sz*{(g(n+3UW;bZAqkif7ItSTPHI!5boRP)7MyjhrJ&c}xf_ zr{_Zx;1j=lrcf0;FC7{5{quo{GV?PgbSUD3g}}T%9a(I3mo2iY^>st=`4}Tegn0d( zJ5$VMXB`Y*tY0`9Ytwz8%=Y3(4|16}$Ep1{rhL3>)YtBD=_i7unQvOW9wVK8m3AuB zp}_1*FT6i`<6JFla5^;!G;X<9>51m!z(jSpJd8SJ#T)J3OaX=SWLDa>q0rh`xmDUH zz?-_KILnV-fpD$q_&we?3{cJddrFh6C`}&pqM}{mk>lA^C2c~x>nS;FPuJ7hIL$Ra ztgidjUBR*i8)K_4J36Bz7vGwk3r)S5QrVU2?a8}Use#h0$K265|Dl^xuXTLtG86I3 zfCP6~RrkP&7&S7rAuO37J(ELdp(Q~yG0J+sRQ$`(@7m?YMbM;djEFKf_C!rqpFdO~ zLarAI`o5hd|I9EfBbd4g-%qvj7yQt-!Hm;)clR%cqL`xwy6R#=R* z?k5Rs1{*j~a``(W3jTk;^|ERT1UTYe`4TMV;hq0jc>p(n>V#^y3<~pxVTpeOTi|BW zhr2nWEu86RipJ7;;_B7$C+Y*2Dqn%SqPt>p#A}&=yKIWNYA-n|wtu-h2x6Max{@|U zZ*9BzU;?GRrw?rrYJd&r{q8<{4W|lX>UoE)It1FA+D1xbH!vHvI&87eL6|jamk{=~ z7NyO81$3c*6oR+YQROcuu0Id0H3S4_CZFB#ZFu5XpSzB~59=v8(6zfd#aIU|8Z5-i z1&U#jbO-(&n|N0)+cE}XA50&@$_U|Vsab}mZ|FO7a}3pXtAayP8%j|_(rA_uXBuTmfVb5ypYQp9a9!x zx?`!1P<&f-U-TshLC5<0o3|c*YYvsw`k+<_q#?@n(XBCE3rc9XByc8(%gD)9bDKT0 z%3`u1F&C7j*)YGYnzG8wNlC5sEB1165^#0^gL)tBB|Gg@&0plhkYAd$9; zM@71*Tnba>0Ub)H4MIQ!Np~=zR{kff)HcFpgiI~O#N8-pFcJ)t-TdZh z5Z`{Xe2nA#Onue#On*5l;bk-h2rqN8t(t-#IpqV}9w;ejk)^_fb9_!c{n2rfU_6PH(a!CMTLd!ggFDbuVU_Al}EO?-`>#MOEeg4MGl_ zxuy`QA_)q#qK@@mh?hK92hJh8OMZiWRX6^UNkTDPiV$RWH4j;b;s3Qj|7W~u7kIDv zRq-&b_e!@=kRXiykIfF!ZGmmSG&JNjO-;pcL$o>lW%#l^*5H6-u<#+`S|sU#7U{LC zIx4|R`R((Lqlwkq?rMbfN<|vTsVv^cu&uX2S9>B=h1fiG{(gKDyG8|V(Tj9IIOk@+Oof88&bgS%^e)a!0apHt)0lhvo_ zq(>Rm``Hq}{S8)JSzF07!7`sI^2qg4J;fbo&jBBY$99)>(#>yRpjX1p&izLtaA`x+ z{R!oVc>CKeI#vaXtot1BsO#o%YMfpV7a_}jwLT(;kkwY6N+OR=#;!GEg$Hn%0Dg8Y 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�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

Vd(n&BAX}|J5e_?H{MXX9#Wif@K_`Tx#l3=ti8? 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 From b2f126c4a0bad66d4b51028e8754a18e59ad84bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurycy=20Paw=C5=82owski-Wiero=C5=84ski?= Date: Sat, 25 Apr 2026 23:28:51 +0200 Subject: [PATCH 11/44] gh-148989: `_remote_debugging`: Remove dead code, unnecessary gc state read (#148990) dead code --- Modules/_remote_debugging/threads.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Modules/_remote_debugging/threads.c b/Modules/_remote_debugging/threads.c index 07f8148d7c941a..e303c667ea013a 100644 --- a/Modules/_remote_debugging/threads.c +++ b/Modules/_remote_debugging/threads.c @@ -314,19 +314,6 @@ unwind_stack_for_thread( long tid = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id); - // Read GC collecting state from the interpreter (before any skip checks) - uintptr_t interp_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.interp); - - // Read the GC runtime state from the interpreter state - uintptr_t gc_addr = interp_addr + unwinder->debug_offsets.interpreter_state.gc; - char gc_state[SIZEOF_GC_RUNTIME_STATE]; - if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, gc_addr, unwinder->debug_offsets.gc.size, gc_state) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read GC state"); - goto error; - } - STATS_INC(unwinder, memory_reads); - STATS_ADD(unwinder, memory_bytes_read, unwinder->debug_offsets.gc.size); - // Calculate thread status using flags (always) int status_flags = 0; From c5fcdb4a9bd04b88f363f0242cbf102a63825c37 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:02:51 -0700 Subject: [PATCH 12/44] gh-146311: Reject non-canonical padding bits in base32, 64, & 85 decoding (GH-146312) Add `canonical=False` keyword argument to `a2b_base64`, `a2b_base32`, `a2b_base85`, and `a2b_ascii85` (and their `base64` module wrappers). When `canonical=True`, non-canonical encodings are rejected per [RFC 4648 section 3.5](https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5). This is independent of `strict_mode`. For base85/ascii85, the check also rejects single-character final groups (never produced by a conforming encoder) and verifies partial group padding matches what the encoder would produce. Co-authored-by: Serhiy Storchaka via lots of great code review! --- Doc/library/base64.rst | 48 ++- Doc/library/binascii.rst | 37 ++- Doc/whatsnew/3.15.rst | 13 + .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 4 + Lib/base64.py | 42 ++- Lib/test/test_binascii.py | 291 +++++++++++++++++- ...-04-25-11-56-05.gh-issue-146311.iHWO0v.rst | 7 + Modules/binascii.c | 140 ++++++++- Modules/clinic/binascii.c.h | 124 +++++--- 12 files changed, 611 insertions(+), 98 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-25-11-56-05.gh-issue-146311.iHWO0v.rst diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst index 5e08d56fd66186..32da8294c5a58a 100644 --- a/Doc/library/base64.rst +++ b/Doc/library/base64.rst @@ -73,8 +73,8 @@ POST request. Added the *padded* and *wrapcol* parameters. -.. function:: b64decode(s, altchars=None, validate=False, *, padded=True) - b64decode(s, altchars=None, validate=True, *, ignorechars, padded=True) +.. function:: b64decode(s, altchars=None, validate=False, *, padded=True, canonical=False) + b64decode(s, altchars=None, validate=True, *, ignorechars, padded=True, canonical=False) Decode the Base64 encoded :term:`bytes-like object` or ASCII string *s* and return the decoded :class:`bytes`. @@ -112,10 +112,13 @@ POST request. If *validate* is true, these non-alphabet characters in the input result in a :exc:`binascii.Error`. + If *canonical* is true, non-zero padding bits are rejected. + See :func:`binascii.a2b_base64` for details. + For more information about the strict base64 check, see :func:`binascii.a2b_base64` .. versionchanged:: 3.15 - Added the *ignorechars* and *padded* parameters. + Added the *canonical*, *ignorechars*, and *padded* parameters. .. deprecated:: 3.15 Accepting the ``+`` and ``/`` characters with an alternative alphabet @@ -179,7 +182,7 @@ POST request. Added the *padded* and *wrapcol* parameters. -.. function:: b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b'') +.. function:: b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b'', canonical=False) Decode the Base32 encoded :term:`bytes-like object` or ASCII string *s* and return the decoded :class:`bytes`. @@ -205,12 +208,15 @@ POST request. *ignorechars* should be a :term:`bytes-like object` containing characters to ignore from the input. + If *canonical* is true, non-zero padding bits are rejected. + See :func:`binascii.a2b_base32` for details. + A :exc:`binascii.Error` is raised if *s* is incorrectly padded or if there are non-alphabet characters present in the input. .. versionchanged:: 3.15 - Added the *ignorechars* and *padded* parameters. + Added the *canonical*, *ignorechars*, and *padded* parameters. .. function:: b32hexencode(s, *, padded=True, wrapcol=0) @@ -224,7 +230,7 @@ POST request. Added the *padded* and *wrapcol* parameters. -.. function:: b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b'') +.. function:: b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b'', canonical=False) Similar to :func:`b32decode` but uses the Extended Hex Alphabet, as defined in :rfc:`4648`. @@ -237,7 +243,7 @@ POST request. .. versionadded:: 3.10 .. versionchanged:: 3.15 - Added the *ignorechars* and *padded* parameters. + Added the *canonical*, *ignorechars*, and *padded* parameters. .. function:: b16encode(s, *, wrapcol=0) @@ -317,7 +323,7 @@ Refer to the documentation of the individual functions for more information. .. versionadded:: 3.4 -.. function:: a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v') +.. function:: a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v', canonical=False) Decode the Ascii85 encoded :term:`bytes-like object` or ASCII string *b* and return the decoded :class:`bytes`. @@ -334,8 +340,16 @@ Refer to the documentation of the individual functions for more information. This should only contain whitespace characters, and by default contains all whitespace characters in ASCII. + If *canonical* is true, non-canonical encodings are rejected. + See :func:`binascii.a2b_ascii85` for details. + .. versionadded:: 3.4 + .. versionchanged:: next + Added the *canonical* parameter. + Single-character final groups are now always rejected as encoding + violations. + .. function:: b85encode(b, pad=False, *, wrapcol=0) @@ -355,7 +369,7 @@ Refer to the documentation of the individual functions for more information. Added the *wrapcol* parameter. -.. function:: b85decode(b, *, ignorechars=b'') +.. function:: b85decode(b, *, ignorechars=b'', canonical=False) Decode the base85-encoded :term:`bytes-like object` or ASCII string *b* and return the decoded :class:`bytes`. Padding is implicitly removed, if @@ -364,10 +378,15 @@ Refer to the documentation of the individual functions for more information. *ignorechars* should be a :term:`bytes-like object` containing characters to ignore from the input. + If *canonical* is true, non-canonical encodings are rejected. + See :func:`binascii.a2b_base85` for details. + .. versionadded:: 3.4 .. versionchanged:: 3.15 - Added the *ignorechars* parameter. + Added the *canonical* and *ignorechars* parameters. + Single-character final groups are now always rejected as encoding + violations. .. function:: z85encode(s, pad=False, *, wrapcol=0) @@ -392,7 +411,7 @@ Refer to the documentation of the individual functions for more information. Added the *wrapcol* parameter. -.. function:: z85decode(s, *, ignorechars=b'') +.. function:: z85decode(s, *, ignorechars=b'', canonical=False) Decode the Z85-encoded :term:`bytes-like object` or ASCII string *s* and return the decoded :class:`bytes`. See `Z85 specification @@ -401,10 +420,15 @@ Refer to the documentation of the individual functions for more information. *ignorechars* should be a :term:`bytes-like object` containing characters to ignore from the input. + If *canonical* is true, non-canonical encodings are rejected. + See :func:`binascii.a2b_base85` for details. + .. versionadded:: 3.13 .. versionchanged:: 3.15 - Added the *ignorechars* parameter. + Added the *canonical* and *ignorechars* parameters. + Single-character final groups are now always rejected as encoding + violations. .. _base64-legacy: diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst index 08a82cc4b5f6d5..8b4ba6ae9fb254 100644 --- a/Doc/library/binascii.rst +++ b/Doc/library/binascii.rst @@ -48,8 +48,8 @@ The :mod:`!binascii` module defines the following functions: Added the *backtick* parameter. -.. function:: a2b_base64(string, /, *, padded=True, alphabet=BASE64_ALPHABET, strict_mode=False) - a2b_base64(string, /, *, ignorechars, padded=True, alphabet=BASE64_ALPHABET, strict_mode=True) +.. function:: a2b_base64(string, /, *, padded=True, alphabet=BASE64_ALPHABET, strict_mode=False, canonical=False) + a2b_base64(string, /, *, ignorechars, padded=True, alphabet=BASE64_ALPHABET, strict_mode=True, canonical=False) Convert a block of base64 data back to binary and return the binary data. More than one line may be passed at a time. @@ -83,11 +83,15 @@ The :mod:`!binascii` module defines the following functions: * Contains no excess data after padding (including excess padding, newlines, etc.). * Does not start with a padding. + If *canonical* is true, non-zero padding bits in the last group are rejected + with :exc:`binascii.Error`, enforcing canonical encoding as defined in + :rfc:`4648` section 3.5. This check is independent of *strict_mode*. + .. versionchanged:: 3.11 Added the *strict_mode* parameter. .. versionchanged:: 3.15 - Added the *alphabet*, *ignorechars* and *padded* parameters. + Added the *alphabet*, *canonical*, *ignorechars*, and *padded* parameters. .. function:: b2a_base64(data, *, padded=True, alphabet=BASE64_ALPHABET, wrapcol=0, newline=True) @@ -113,7 +117,7 @@ The :mod:`!binascii` module defines the following functions: Added the *alphabet*, *padded* and *wrapcol* parameters. -.. function:: a2b_ascii85(string, /, *, foldspaces=False, adobe=False, ignorechars=b'') +.. function:: a2b_ascii85(string, /, *, foldspaces=False, adobe=False, ignorechars=b'', canonical=False) Convert Ascii85 data back to binary and return the binary data. @@ -122,7 +126,8 @@ The :mod:`!binascii` module defines the following functions: characters). Each group encodes 32 bits of binary data in the range from ``0`` to ``2 ** 32 - 1``, inclusive. The special character ``z`` is accepted as a short form of the group ``!!!!!``, which encodes four - consecutive null bytes. + consecutive null bytes. A single-character final group is always rejected + as an encoding violation. *foldspaces* is a flag that specifies whether the 'y' short sequence should be accepted as shorthand for 4 consecutive spaces (ASCII 0x20). @@ -135,6 +140,12 @@ The :mod:`!binascii` module defines the following functions: to ignore from the input. This should only contain whitespace characters. + If *canonical* is true, non-canonical encodings are rejected with + :exc:`binascii.Error`. Here "canonical" means the encoding that + :func:`b2a_ascii85` would produce: the ``z`` abbreviation must be used + for all-zero groups (rather than ``!!!!!``), and partial final groups + must use the same padding digits as the encoder. + Invalid Ascii85 data will raise :exc:`binascii.Error`. .. versionadded:: 3.15 @@ -163,7 +174,7 @@ The :mod:`!binascii` module defines the following functions: .. versionadded:: 3.15 -.. function:: a2b_base85(string, /, *, alphabet=BASE85_ALPHABET, ignorechars=b'') +.. function:: a2b_base85(string, /, *, alphabet=BASE85_ALPHABET, ignorechars=b'', canonical=False) Convert Base85 data back to binary and return the binary data. More than one line may be passed at a time. @@ -171,7 +182,8 @@ The :mod:`!binascii` module defines the following functions: Valid Base85 data contains characters from the Base85 alphabet in groups of five (except for the final group, which may have from two to five characters). Each group encodes 32 bits of binary data in the range from - ``0`` to ``2 ** 32 - 1``, inclusive. + ``0`` to ``2 ** 32 - 1``, inclusive. A single-character final group is + always rejected as an encoding violation. Optional *alphabet* must be a :class:`bytes` object of length 85 which specifies an alternative alphabet. @@ -179,6 +191,11 @@ The :mod:`!binascii` module defines the following functions: *ignorechars* should be a :term:`bytes-like object` containing characters to ignore from the input. + If *canonical* is true, non-canonical encodings are rejected with + :exc:`binascii.Error`. Here "canonical" means the encoding that + :func:`b2a_base85` would produce: partial final groups must use the + same padding digits as the encoder. + Invalid Base85 data will raise :exc:`binascii.Error`. .. versionadded:: 3.15 @@ -202,7 +219,7 @@ The :mod:`!binascii` module defines the following functions: .. versionadded:: 3.15 -.. function:: a2b_base32(string, /, *, padded=True, alphabet=BASE32_ALPHABET, ignorechars=b'') +.. function:: a2b_base32(string, /, *, padded=True, alphabet=BASE32_ALPHABET, ignorechars=b'', canonical=False) Convert base32 data back to binary and return the binary data. @@ -231,6 +248,10 @@ The :mod:`!binascii` module defines the following functions: presented before the end of the encoded data and the excess pad characters will be ignored. + If *canonical* is true, non-zero padding bits in the last group are rejected + with :exc:`binascii.Error`, enforcing canonical encoding as defined in + :rfc:`4648` section 3.5. + Invalid base32 data will raise :exc:`binascii.Error`. .. versionadded:: 3.15 diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index fc611cd5cd662d..4a14568ab88b6a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -729,6 +729,15 @@ base64 :func:`~base64.z85decode`. (Contributed by Serhiy Storchaka in :gh:`144001` and :gh:`146431`.) +* Added the *canonical* parameter in + :func:`~base64.b32decode`, :func:`~base64.b32hexdecode`, + :func:`~base64.b64decode`, :func:`~base64.urlsafe_b64decode`, + :func:`~base64.a85decode`, :func:`~base64.b85decode`, and + :func:`~base64.z85decode`, + to reject encodings with non-zero padding bits or other non-canonical + forms. + (Contributed by Gregory P. Smith in :gh:`146311`.) + binascii -------- @@ -762,6 +771,10 @@ binascii :func:`~binascii.unhexlify`, and :func:`~binascii.a2b_base64`. (Contributed by Serhiy Storchaka in :gh:`144001` and :gh:`146431`.) +* Added the *canonical* parameter in :func:`~binascii.a2b_base64`, + to reject encodings with non-zero padding bits. + (Contributed by Gregory P. Smith in :gh:`146311`.) + calendar -------- diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index beae65213a27b6..4fd42185d8a4a1 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1636,6 +1636,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(callable)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cancel)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(canonical)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(capath)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(capitals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(category)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index bb1c6dbaf03906..f2d43c22069b92 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -359,6 +359,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(callable) STRUCT_FOR_ID(callback) STRUCT_FOR_ID(cancel) + STRUCT_FOR_ID(canonical) STRUCT_FOR_ID(capath) STRUCT_FOR_ID(capitals) STRUCT_FOR_ID(category) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 64b029797ab9b3..6ee64a461d8568 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1634,6 +1634,7 @@ extern "C" { INIT_ID(callable), \ INIT_ID(callback), \ INIT_ID(cancel), \ + INIT_ID(canonical), \ INIT_ID(capath), \ INIT_ID(capitals), \ INIT_ID(category), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 461ee36dcebb6d..bcb117e1091674 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1216,6 +1216,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(canonical); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(capath); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/base64.py b/Lib/base64.py index 7f39c68070b5da..4b810e08569e5b 100644 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -68,7 +68,7 @@ def b64encode(s, altchars=None, *, padded=True, wrapcol=0): def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, - *, padded=True, ignorechars=_NOT_SPECIFIED): + *, padded=True, ignorechars=_NOT_SPECIFIED, canonical=False): """Decode the Base64 encoded bytes-like object or ASCII string s. Optional altchars must be a bytes-like object or ASCII string of length 2 @@ -110,11 +110,13 @@ def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, alphabet = binascii.BASE64_ALPHABET[:-2] + altchars return binascii.a2b_base64(s, strict_mode=validate, alphabet=alphabet, - padded=padded, ignorechars=ignorechars) + padded=padded, ignorechars=ignorechars, + canonical=canonical) if ignorechars is _NOT_SPECIFIED: ignorechars = b'' result = binascii.a2b_base64(s, strict_mode=validate, - padded=padded, ignorechars=ignorechars) + padded=padded, ignorechars=ignorechars, + canonical=canonical) if badchar is not None: import warnings if validate: @@ -230,7 +232,8 @@ def b32encode(s, *, padded=True, wrapcol=0): return binascii.b2a_base32(s, padded=padded, wrapcol=wrapcol) b32encode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32') -def b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b''): +def b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b'', + canonical=False): s = _bytes_from_decode_data(s) # Handle section 2.4 zero and one mapping. The flag map01 will be either # False, or the character to map the digit 1 (one) to. It should be @@ -240,7 +243,8 @@ def b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b''): s = s.translate(bytes.maketrans(b'01', b'O' + map01)) if casefold: s = s.upper() - return binascii.a2b_base32(s, padded=padded, ignorechars=ignorechars) + return binascii.a2b_base32(s, padded=padded, ignorechars=ignorechars, + canonical=canonical) b32decode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32', extra_args=_B32_DECODE_MAP01_DOCSTRING) @@ -249,13 +253,15 @@ def b32hexencode(s, *, padded=True, wrapcol=0): alphabet=binascii.BASE32HEX_ALPHABET) b32hexencode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32hex') -def b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b''): +def b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b'', + canonical=False): s = _bytes_from_decode_data(s) # base32hex does not have the 01 mapping if casefold: s = s.upper() return binascii.a2b_base32(s, alphabet=binascii.BASE32HEX_ALPHABET, - padded=padded, ignorechars=ignorechars) + padded=padded, ignorechars=ignorechars, + canonical=canonical) b32hexdecode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32hex', extra_args='') @@ -323,7 +329,8 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False): return binascii.b2a_ascii85(b, foldspaces=foldspaces, adobe=adobe, wrapcol=wrapcol, pad=pad) -def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): +def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v', + canonical=False): """Decode the Ascii85 encoded bytes-like object or ASCII string b. foldspaces is a flag that specifies whether the 'y' short sequence should be @@ -337,10 +344,13 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): input. This should only contain whitespace characters, and by default contains all whitespace characters in ASCII. + If canonical is true, non-canonical encodings are rejected. + The result is returned as a bytes object. """ return binascii.a2b_ascii85(b, foldspaces=foldspaces, - adobe=adobe, ignorechars=ignorechars) + adobe=adobe, ignorechars=ignorechars, + canonical=canonical) def b85encode(b, pad=False, *, wrapcol=0): """Encode bytes-like object b in base85 format and return a bytes object. @@ -353,12 +363,15 @@ def b85encode(b, pad=False, *, wrapcol=0): """ return binascii.b2a_base85(b, wrapcol=wrapcol, pad=pad) -def b85decode(b, *, ignorechars=b''): +def b85decode(b, *, ignorechars=b'', canonical=False): """Decode the base85-encoded bytes-like object or ASCII string b + If canonical is true, non-canonical encodings are rejected. + The result is returned as a bytes object. """ - return binascii.a2b_base85(b, ignorechars=ignorechars) + return binascii.a2b_base85(b, ignorechars=ignorechars, + canonical=canonical) def z85encode(s, pad=False, *, wrapcol=0): """Encode bytes-like object b in z85 format and return a bytes object. @@ -372,12 +385,15 @@ def z85encode(s, pad=False, *, wrapcol=0): return binascii.b2a_base85(s, wrapcol=wrapcol, pad=pad, alphabet=binascii.Z85_ALPHABET) -def z85decode(s, *, ignorechars=b''): +def z85decode(s, *, ignorechars=b'', canonical=False): """Decode the z85-encoded bytes-like object or ASCII string b + If canonical is true, non-canonical encodings are rejected. + The result is returned as a bytes object. """ - return binascii.a2b_base85(s, alphabet=binascii.Z85_ALPHABET, ignorechars=ignorechars) + return binascii.a2b_base85(s, alphabet=binascii.Z85_ALPHABET, + ignorechars=ignorechars, canonical=canonical) # Legacy interface. This code could be cleaned up since I don't believe # binascii has any line length limitations. It just doesn't seem worth it diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py index 81cdacb96241e2..6991e2ef6815e3 100644 --- a/Lib/test/test_binascii.py +++ b/Lib/test/test_binascii.py @@ -383,6 +383,49 @@ def assertInvalidLength(data, strict_mode=True): assertInvalidLength(b'A\tB\nC ??DE', # only 5 valid characters strict_mode=False) + def test_base64_canonical(self): + # https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5 + # Decoders MAY reject encoded data if the pad bits are not zero. + + # Without canonical=True, non-zero padding bits are accepted + self.assertEqual(binascii.a2b_base64(self.type2test(b'AB==')), b'\x00') + self.assertEqual(binascii.a2b_base64(self.type2test(b'AB=='), + strict_mode=True), b'\x00') + + # 2 data chars + "==": last char has 4 padding bits + # 'A' = 0, 'B' = 1 -> leftover 0001 (non-zero) + with self.assertRaises(binascii.Error): + binascii.a2b_base64(self.type2test(b'AB=='), canonical=True) + # 'A' = 0, 'P' = 15 -> leftover 1111 (non-zero) + with self.assertRaises(binascii.Error): + binascii.a2b_base64(self.type2test(b'AP=='), canonical=True) + + # 3 data chars + "=": last char has 2 padding bits + # 'A' = 0, 'A' = 0, 'B' = 1 -> leftover 01 (non-zero) + with self.assertRaises(binascii.Error): + binascii.a2b_base64(self.type2test(b'AAB='), canonical=True) + # 'A' = 0, 'A' = 0, 'D' = 3 -> leftover 11 (non-zero) + with self.assertRaises(binascii.Error): + binascii.a2b_base64(self.type2test(b'AAD='), canonical=True) + + # Verify that zero padding bits are accepted + binascii.a2b_base64(self.type2test(b'AA=='), canonical=True) + binascii.a2b_base64(self.type2test(b'AAA='), canonical=True) + + # Full quads with no padding have no leftover bits -- always valid + binascii.a2b_base64(self.type2test(b'AAAA'), canonical=True) + + @hypothesis.given(payload=hypothesis.strategies.binary()) + @hypothesis.example(b'') + @hypothesis.example(b'\x00') + @hypothesis.example(b'\xff\xff') + @hypothesis.example(b'abc') + def test_base64_canonical_roundtrip(self, payload): + # The encoder must always produce canonical output. + encoded = binascii.b2a_base64(payload, newline=False) + decoded = binascii.a2b_base64(encoded, canonical=True) + self.assertEqual(decoded, payload) + def test_base64_alphabet(self): alphabet = (b'!"#$%&\'()*+,-012345689@' b'ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr') @@ -443,20 +486,22 @@ def test_ascii85_valid(self): res += b self.assertEqual(res, rawdata) - # Test decoding inputs with length 1 mod 5 - params = [ - (b"a", False, False, b"", b""), - (b"xbw", False, False, b"wx", b""), - (b"<~c~>", False, True, b"", b""), - (b"{d ~>", False, True, b" {", b""), - (b"ye", True, False, b"", b" "), - (b"z\x01y\x00f", True, False, b"\x00\x01", b"\x00\x00\x00\x00 "), - (b"<~FCfN8yg~>", True, True, b"", b"test "), - (b"FE;\x03#8zFCf\x02N8yh~>", True, True, b"\x02\x03", b"tset\x00\x00\x00\x00test "), + # Inputs with length 1 mod 5 end with a 1-char group, which is + # an encoding violation per the PLRM spec. + error_params = [ + (b"a", False, False, b""), + (b"xbw", False, False, b"wx"), + (b"<~c~>", False, True, b""), + (b"{d ~>", False, True, b" {"), + (b"ye", True, False, b""), + (b"z\x01y\x00f", True, False, b"\x00\x01"), + (b"<~FCfN8yg~>", True, True, b""), + (b"FE;\x03#8zFCf\x02N8yh~>", True, True, b"\x02\x03"), ] - for a, foldspaces, adobe, ignorechars, b in params: + for a, foldspaces, adobe, ignorechars in error_params: kwargs = {"foldspaces": foldspaces, "adobe": adobe, "ignorechars": ignorechars} - self.assertEqual(binascii.a2b_ascii85(self.type2test(a), **kwargs), b) + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(self.type2test(a), **kwargs) def test_ascii85_invalid(self): # Test Ascii85 with invalid characters interleaved @@ -670,16 +715,18 @@ def test_base85_valid(self): self.assertEqual(res, self.rawdata) # Test decoding inputs with different length - self.assertEqual(binascii.a2b_base85(self.type2test(b'a')), b'') - self.assertEqual(binascii.a2b_base85(self.type2test(b'a')), b'') + # 1-char groups are rejected (encoding violation) + with self.assertRaises(binascii.Error): + binascii.a2b_base85(self.type2test(b'a')) self.assertEqual(binascii.a2b_base85(self.type2test(b'ab')), b'q') self.assertEqual(binascii.a2b_base85(self.type2test(b'abc')), b'qa') self.assertEqual(binascii.a2b_base85(self.type2test(b'abcd')), b'qa\x9e') self.assertEqual(binascii.a2b_base85(self.type2test(b'abcde')), b'qa\x9e\xb6') - self.assertEqual(binascii.a2b_base85(self.type2test(b'abcdef')), - b'qa\x9e\xb6') + # 6-char input = full 5-char group + trailing 1-char group (rejected) + with self.assertRaises(binascii.Error): + binascii.a2b_base85(self.type2test(b'abcdef')) self.assertEqual(binascii.a2b_base85(self.type2test(b'abcdefg')), b'qa\x9e\xb6\x81') @@ -767,6 +814,169 @@ def test_base85_alphabet(self): with self.assertRaises(TypeError): binascii.a2b_base64(data, alphabet=bytearray(alphabet)) + def test_base85_canonical(self): + # Non-canonical encodings are accepted without canonical=True + self.assertEqual(binascii.a2b_base85(b'VF'), b'a') + + # 1-char partial groups are always rejected (encoding violation: + # no conforming encoder produces them) + with self.assertRaises(binascii.Error): + binascii.a2b_base85(b'V') + with self.assertRaises(binascii.Error): + binascii.a2b_base85(b'0') + + # Verify round-trip: encode then decode with canonical=True works + for data in [b'a', b'ab', b'abc', b'abcd', b'abcde', + b'\x00', b'\xff', b'\x00\x00', b'\xff\xff\xff']: + encoded = binascii.b2a_base85(data) + decoded = binascii.a2b_base85(encoded, canonical=True) + self.assertEqual(decoded, data) + + # Test non-canonical rejection for each partial group size + # (2-char/1-byte, 3-char/2-byte, 4-char/3-byte). + # Incrementing the last digit by 1 produces a non-canonical + # encoding. For 4-char groups (n_pad=1) a +1 can change the + # output byte, so we use b'ab\x00' whose canonical form allows + # a +1 that still decodes to the same 3 bytes. + for data in [b'a', b'ab', b'ab\x00']: + canonical_enc = binascii.b2a_base85(data) + non_canonical = (canonical_enc[:-1] + + bytes([canonical_enc[-1] + 1])) + # Same decoded output without canonical check + self.assertEqual(binascii.a2b_base85(non_canonical), data) + # Rejected with canonical=True + with self.assertRaises(binascii.Error): + binascii.a2b_base85(non_canonical, canonical=True) + + # Boundary bytes: \x00 and \xff for each partial group size + for data in [b'\x00', b'\x00\x00', b'\x00\x00\x00', + b'\xff', b'\xff\xff', b'\xff\xff\xff']: + canonical_enc = binascii.b2a_base85(data) + binascii.a2b_base85(canonical_enc, canonical=True) + + # Full 5-char groups are always canonical (no padding bits) + self.assertEqual( + binascii.a2b_base85(b'VPa!s', canonical=True), b'abcd') + + # Empty input is valid + self.assertEqual(binascii.a2b_base85(b'', canonical=True), b'') + + @hypothesis.given(payload=hypothesis.strategies.binary()) + @hypothesis.example(b'') + @hypothesis.example(b'\x00') + @hypothesis.example(b'\xff\xff') + @hypothesis.example(b'abc') + def test_base85_canonical_roundtrip(self, payload): + encoded = binascii.b2a_base85(payload) + decoded = binascii.a2b_base85(encoded, canonical=True) + self.assertEqual(decoded, payload) + + @hypothesis.given(payload=hypothesis.strategies.binary(min_size=1, max_size=3)) + @hypothesis.example(b'\x00') + @hypothesis.example(b'\xff') + @hypothesis.example(b'ab\x00') + def test_base85_canonical_unique(self, payload): + # For a partial group, sweeping all 85 last-digit values should + # yield exactly one encoding that both decodes to the original + # payload AND passes canonical=True. + hypothesis.assume(len(payload) % 4 != 0) + canonical_enc = binascii.b2a_base85(payload) + table = binascii.BASE85_ALPHABET + accepted = [] + for digit in table: + candidate = canonical_enc[:-1] + bytes([digit]) + try: + result = binascii.a2b_base85(candidate, canonical=True) + if result == payload: + accepted.append(candidate) + except binascii.Error: + pass + self.assertEqual(accepted, [canonical_enc]) + + def test_ascii85_canonical(self): + # Non-canonical encodings are accepted without canonical=True + self.assertEqual(binascii.a2b_ascii85(b'@0'), b'a') + + # 1-char partial groups are always rejected (PLRM encoding violation) + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(b'@') + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(b'!') + + # Verify round-trip: encode then decode with canonical=True works + for data in [b'a', b'ab', b'abc', b'abcd', b'abcde', + b'\x00', b'\xff', b'\x00\x00', b'\xff\xff\xff']: + encoded = binascii.b2a_ascii85(data) + decoded = binascii.a2b_ascii85(encoded, canonical=True) + self.assertEqual(decoded, data) + + # Test non-canonical rejection for each partial group size. + # See test_base85_canonical for why b'ab\x00' is used for 3 bytes. + for data in [b'a', b'ab', b'ab\x00']: + canonical_enc = binascii.b2a_ascii85(data) + non_canonical = (canonical_enc[:-1] + + bytes([canonical_enc[-1] + 1])) + self.assertEqual(binascii.a2b_ascii85(non_canonical), data) + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(non_canonical, canonical=True) + + # Full 5-char groups are always canonical + self.assertEqual( + binascii.a2b_ascii85(b'@:E_W', canonical=True), b'abcd') + + # 'z' is the canonical form for all-zero groups per the PLRM. + # '!!!!!' decodes identically but is non-canonical. + self.assertEqual(binascii.a2b_ascii85(b'!!!!!'), b'\x00' * 4) + self.assertEqual(binascii.a2b_ascii85(b'z'), b'\x00' * 4) + self.assertEqual( + binascii.a2b_ascii85(b'z', canonical=True), b'\x00' * 4) + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(b'!!!!!', canonical=True) + # Multiple groups: z + !!!!! should fail + with self.assertRaises(binascii.Error): + binascii.a2b_ascii85(b'z!!!!!', canonical=True) + # Multiple z groups are fine + self.assertEqual( + binascii.a2b_ascii85(b'zz', canonical=True), b'\x00' * 8) + + # Empty input is valid + self.assertEqual(binascii.a2b_ascii85(b'', canonical=True), b'') + + # Adobe-wrapped with canonical + self.assertEqual( + binascii.a2b_ascii85(b'<~@:E_W~>', canonical=True, adobe=True), + b'abcd') + + @hypothesis.given(payload=hypothesis.strategies.binary()) + @hypothesis.example(b'') + @hypothesis.example(b'\x00') + @hypothesis.example(b'\x00\x00\x00\x00') # triggers z abbreviation + @hypothesis.example(b'\xff\xff') + @hypothesis.example(b'abc') + def test_ascii85_canonical_roundtrip(self, payload): + encoded = binascii.b2a_ascii85(payload) + decoded = binascii.a2b_ascii85(encoded, canonical=True) + self.assertEqual(decoded, payload) + + @hypothesis.given(payload=hypothesis.strategies.binary(min_size=1, max_size=3)) + @hypothesis.example(b'\x00') + @hypothesis.example(b'\xff') + @hypothesis.example(b'ab\x00') + def test_ascii85_canonical_unique(self, payload): + hypothesis.assume(len(payload) % 4 != 0) + canonical_enc = binascii.b2a_ascii85(payload) + # Ascii85 alphabet: '!' (33) through 'u' (117) + accepted = [] + for digit in range(33, 118): + candidate = canonical_enc[:-1] + bytes([digit]) + try: + result = binascii.a2b_ascii85(candidate, canonical=True) + if result == payload: + accepted.append(candidate) + except binascii.Error: + pass + self.assertEqual(accepted, [canonical_enc]) + def test_base32_valid(self): # Test base32 with valid data lines = [] @@ -935,6 +1145,55 @@ def assertInvalidLength(data, *args, length=None, **kwargs): assertInvalidLength(b" ABC=====", ignorechars=b' ') assertInvalidLength(b" ABCDEF==", ignorechars=b' ') + def test_base32_canonical(self): + # https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5 + # Decoders MAY reject encoded data if the pad bits are not zero. + + # Without canonical=True, non-zero padding bits are accepted + self.assertEqual(binascii.a2b_base32(self.type2test(b'AB======')), + b'\x00') + + # 2 data chars + "======": last char has 2 padding bits + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AB======'), canonical=True) + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AD======'), canonical=True) + + # 4 data chars + "====": last char has 4 padding bits + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AAAB===='), canonical=True) + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AAAP===='), canonical=True) + + # 5 data chars + "===": last char has 1 padding bit + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AAAAB==='), canonical=True) + + # 7 data chars + "=": last char has 3 padding bits + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AAAAAAB='), canonical=True) + with self.assertRaises(binascii.Error): + binascii.a2b_base32(self.type2test(b'AAAAAAH='), canonical=True) + + # Verify that zero padding bits are accepted + binascii.a2b_base32(self.type2test(b'AA======'), canonical=True) + binascii.a2b_base32(self.type2test(b'AAAA===='), canonical=True) + binascii.a2b_base32(self.type2test(b'AAAAA==='), canonical=True) + binascii.a2b_base32(self.type2test(b'AAAAAAA='), canonical=True) + + # Full octet with no padding -- always valid + binascii.a2b_base32(self.type2test(b'AAAAAAAA'), canonical=True) + + @hypothesis.given(payload=hypothesis.strategies.binary()) + @hypothesis.example(b'') + @hypothesis.example(b'\x00') + @hypothesis.example(b'\xff\xff') + @hypothesis.example(b'abc') + def test_base32_canonical_roundtrip(self, payload): + encoded = binascii.b2a_base32(payload) + decoded = binascii.a2b_base32(encoded, canonical=True) + self.assertEqual(decoded, payload) + def test_a2b_base32_padded(self): a2b_base32 = binascii.a2b_base32 t = self.type2test diff --git a/Misc/NEWS.d/next/Library/2026-04-25-11-56-05.gh-issue-146311.iHWO0v.rst b/Misc/NEWS.d/next/Library/2026-04-25-11-56-05.gh-issue-146311.iHWO0v.rst new file mode 100644 index 00000000000000..4f4a8365b6cf5e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-25-11-56-05.gh-issue-146311.iHWO0v.rst @@ -0,0 +1,7 @@ +Add a *canonical* keyword-only parameter to the base16, base32, base64, +base85, ascii85, and Z85 decoders in :mod:`base64` and :mod:`binascii`. +When true, encodings with non-zero padding bits (base16/32/64) or +non-canonical encodings (base85/ascii85) are rejected. Single-character +final groups in :func:`binascii.a2b_ascii85` and :func:`binascii.a2b_base85` +are now always rejected as encoding violations, regardless of *canonical*; +previously they were silently ignored and produced no output bytes. diff --git a/Modules/binascii.c b/Modules/binascii.c index b80bfbfffe430c..7e6e9655f8d498 100644 --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -244,6 +244,9 @@ static const _Py_ALIGNED_DEF(64, unsigned char) table_b2a_base85_a85[] = #define BASE85_A85_Z 0x00000000 #define BASE85_A85_Y 0x20202020 +/* 85**0 through 85**4, used for canonical encoding checks. */ +static const uint32_t pow85[] = {1, 85, 7225, 614125, 52200625}; + static const _Py_ALIGNED_DEF(64, unsigned char) table_a2b_base32[] = { -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, @@ -729,6 +732,8 @@ binascii.a2b_base64 ignorechars: Py_buffer = NULL A byte string containing characters to ignore from the input when strict_mode is true. + canonical: bool = False + When set to true, reject non-zero padding bits per RFC 4648 section 3.5. Decode a line of base64 data. [clinic start generated code]*/ @@ -736,8 +741,8 @@ Decode a line of base64 data. static PyObject * binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, int padded, PyBytesObject *alphabet, - Py_buffer *ignorechars) -/*[clinic end generated code: output=525d840a299ff132 input=74a53dd3b23474b3]*/ + Py_buffer *ignorechars, int canonical) +/*[clinic end generated code: output=77c46dcbf4239527 input=c99096d071deeec8]*/ { assert(data->len >= 0); @@ -909,6 +914,16 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, goto error_end; } + /* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5 + * Decoders MAY reject non-zero padding bits. */ + if (canonical && leftchar != 0) { + state = get_binascii_state(module); + if (state) { + PyErr_SetString(state->Error, "Non-zero padding bits"); + } + goto error_end; + } + Py_XDECREF(table_obj); return PyBytesWriter_FinishWithPointer(writer, bin_data); @@ -1037,14 +1052,16 @@ binascii.a2b_ascii85 Expect data to be wrapped in '<~' and '~>' as in Adobe Ascii85. ignorechars: Py_buffer = b'' A byte string containing characters to ignore from the input. + canonical: bool = False + When set to true, reject non-canonical encodings. Decode Ascii85 data. [clinic start generated code]*/ static PyObject * binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, - int adobe, Py_buffer *ignorechars) -/*[clinic end generated code: output=599aa3e41095a651 input=f39abd11eab4bac0]*/ + int adobe, Py_buffer *ignorechars, int canonical) +/*[clinic end generated code: output=09b35f1eac531357 input=dd050604ed30199e]*/ { const unsigned char *ascii_data = data->buf; Py_ssize_t ascii_len = data->len; @@ -1107,6 +1124,7 @@ binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, uint32_t leftchar = 0; int group_pos = 0; + int from_z = 0; /* true when current group came from 'z' shorthand */ for (; ascii_len > 0 || group_pos != 0; ascii_len--, ascii_data++) { /* Shift (in radix-85) data or padding into our buffer. */ unsigned char this_digit; @@ -1142,6 +1160,7 @@ binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, goto error; } leftchar = this_ch == 'y' ? BASE85_A85_Y : BASE85_A85_Z; + from_z = (this_ch == 'z'); group_pos = 5; } else if (!ignorechar(this_ch, ignorechars, ignorecache)) { @@ -1159,11 +1178,62 @@ binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, } /* Write current chunk. */ - Py_ssize_t chunk_len = ascii_len < 1 ? 3 + ascii_len : 4; - for (Py_ssize_t i = 0; i < chunk_len; i++) { + int chunk_len = ascii_len < 1 ? 3 + (int)ascii_len : 4; + + /* A final partial 5-tuple containing only one character is an + * encoding violation per the PLRM spec; reject unconditionally. */ + if (chunk_len == 0) { + state = get_binascii_state(module); + if (state != NULL) { + PyErr_SetString(state->Error, + "Incomplete Ascii85 group"); + } + goto error; + } + + for (int i = 0; i < chunk_len; i++) { *bin_data++ = (leftchar >> (24 - 8 * i)) & 0xff; } + if (canonical) { + /* The PLRM spec requires all-zero groups to use the 'z' + * abbreviation. Reject '!!!!!' (five zero digits). */ + if (chunk_len == 4 && leftchar == 0 && !from_z) { + state = get_binascii_state(module); + if (state != NULL) { + PyErr_SetString(state->Error, + "Non-canonical encoding, " + "use 'z' for all-zero groups"); + } + goto error; + } + /* Reject non-canonical partial groups. + * + * A partial group of N chars (2-4) encodes N-1 bytes. + * The decoder pads missing chars with digit 84 (the max). + * The encoder produces the unique N chars for those bytes + * by zero-padding the bytes to a uint32 and taking the + * leading N base-85 digits. Two encodings are equivalent + * iff they yield the same quotient when divided by + * 85**(5-N). */ + if (chunk_len < 4) { + int n_pad = 4 - chunk_len; + uint32_t canonical_top = + (leftchar >> (n_pad * 8)) << (n_pad * 8); + if (canonical_top / pow85[n_pad] + != leftchar / pow85[n_pad]) + { + state = get_binascii_state(module); + if (state != NULL) { + PyErr_SetString(state->Error, + "Non-zero padding bits"); + } + goto error; + } + } + } + + from_z = 0; group_pos = 0; leftchar = 0; } @@ -1315,14 +1385,17 @@ binascii.a2b_base85 alphabet: PyBytesObject(c_default="NULL") = BASE85_ALPHABET ignorechars: Py_buffer = b'' A byte string containing characters to ignore from the input. + canonical: bool = False + When set to true, reject non-canonical encodings. Decode a line of Base85 data. [clinic start generated code]*/ static PyObject * binascii_a2b_base85_impl(PyObject *module, Py_buffer *data, - PyBytesObject *alphabet, Py_buffer *ignorechars) -/*[clinic end generated code: output=6a8d6eae798818d7 input=04d72a319712bdf3]*/ + PyBytesObject *alphabet, Py_buffer *ignorechars, + int canonical) +/*[clinic end generated code: output=90dfef0c6b51e5f3 input=2819dc8aeffee5a2]*/ { const unsigned char *ascii_data = data->buf; Py_ssize_t ascii_len = data->len; @@ -1403,11 +1476,41 @@ binascii_a2b_base85_impl(PyObject *module, Py_buffer *data, } /* Write current chunk. */ - Py_ssize_t chunk_len = ascii_len < 1 ? 3 + ascii_len : 4; - for (Py_ssize_t i = 0; i < chunk_len; i++) { + int chunk_len = ascii_len < 1 ? 3 + (int)ascii_len : 4; + + /* A 1-char final group is an encoding violation (no conforming + * encoder produces it); reject unconditionally. */ + if (chunk_len == 0) { + state = get_binascii_state(module); + if (state != NULL) { + PyErr_SetString(state->Error, + "Incomplete Base85 group"); + } + goto error; + } + + for (int i = 0; i < chunk_len; i++) { *bin_data++ = (leftchar >> (24 - 8 * i)) & 0xff; } + /* Reject non-canonical encodings in the final group. + * See the comment in a2b_ascii85 for the full explanation. */ + if (canonical && chunk_len < 4) { + int n_pad = 4 - chunk_len; + uint32_t canonical_top = + (leftchar >> (n_pad * 8)) << (n_pad * 8); + if (canonical_top / pow85[n_pad] + != leftchar / pow85[n_pad]) + { + state = get_binascii_state(module); + if (state != NULL) { + PyErr_SetString(state->Error, + "Non-zero padding bits"); + } + goto error; + } + } + group_pos = 0; leftchar = 0; } @@ -1535,14 +1638,17 @@ binascii.a2b_base32 alphabet: PyBytesObject(c_default="NULL") = BASE32_ALPHABET ignorechars: Py_buffer = b'' A byte string containing characters to ignore from the input. + canonical: bool = False + When set to true, reject non-zero padding bits per RFC 4648 section 3.5. Decode a line of base32 data. [clinic start generated code]*/ static PyObject * binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded, - PyBytesObject *alphabet, Py_buffer *ignorechars) -/*[clinic end generated code: output=7dbbaa816d956b1c input=07a3721acdf9b688]*/ + PyBytesObject *alphabet, Py_buffer *ignorechars, + int canonical) +/*[clinic end generated code: output=bc70f2bb6001fb55 input=5bfe6d1ea2f30e3b]*/ { const unsigned char *ascii_data = data->buf; Py_ssize_t ascii_len = data->len; @@ -1723,6 +1829,16 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded, goto error; } + /* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5 + * Decoders MAY reject non-zero padding bits. */ + if (canonical && leftchar != 0) { + state = get_binascii_state(module); + if (state) { + PyErr_SetString(state->Error, "Non-zero padding bits"); + } + goto error; + } + Py_XDECREF(table_obj); return PyBytesWriter_FinishWithPointer(writer, bin_data); diff --git a/Modules/clinic/binascii.c.h b/Modules/clinic/binascii.c.h index 0a2d33c428d10a..ed695758ef998c 100644 --- a/Modules/clinic/binascii.c.h +++ b/Modules/clinic/binascii.c.h @@ -119,7 +119,7 @@ binascii_b2a_uu(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj PyDoc_STRVAR(binascii_a2b_base64__doc__, "a2b_base64($module, data, /, *, strict_mode=,\n" " padded=True, alphabet=BASE64_ALPHABET,\n" -" ignorechars=)\n" +" ignorechars=, canonical=False)\n" "--\n" "\n" "Decode a line of base64 data.\n" @@ -132,7 +132,9 @@ PyDoc_STRVAR(binascii_a2b_base64__doc__, " When set to false, padding in input is not required.\n" " ignorechars\n" " A byte string containing characters to ignore from the input when\n" -" strict_mode is true."); +" strict_mode is true.\n" +" canonical\n" +" When set to true, reject non-zero padding bits per RFC 4648 section 3.5."); #define BINASCII_A2B_BASE64_METHODDEF \ {"a2b_base64", _PyCFunction_CAST(binascii_a2b_base64), METH_FASTCALL|METH_KEYWORDS, binascii_a2b_base64__doc__}, @@ -140,7 +142,7 @@ PyDoc_STRVAR(binascii_a2b_base64__doc__, static PyObject * binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, int padded, PyBytesObject *alphabet, - Py_buffer *ignorechars); + Py_buffer *ignorechars, int canonical); static PyObject * binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -148,7 +150,7 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #define NUM_KEYWORDS 5 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -157,7 +159,7 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(strict_mode), &_Py_ID(padded), &_Py_ID(alphabet), &_Py_ID(ignorechars), }, + .ob_item = { &_Py_ID(strict_mode), &_Py_ID(padded), &_Py_ID(alphabet), &_Py_ID(ignorechars), &_Py_ID(canonical), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -166,20 +168,21 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "strict_mode", "padded", "alphabet", "ignorechars", NULL}; + static const char * const _keywords[] = {"", "strict_mode", "padded", "alphabet", "ignorechars", "canonical", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "a2b_base64", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[5]; + PyObject *argsbuf[6]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; int strict_mode = -1; int padded = 1; PyBytesObject *alphabet = NULL; Py_buffer ignorechars = {NULL, NULL}; + int canonical = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -220,11 +223,20 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P goto skip_optional_kwonly; } } - if (PyObject_GetBuffer(args[4], &ignorechars, PyBUF_SIMPLE) != 0) { + if (args[4]) { + if (PyObject_GetBuffer(args[4], &ignorechars, PyBUF_SIMPLE) != 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + canonical = PyObject_IsTrue(args[5]); + if (canonical < 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_a2b_base64_impl(module, &data, strict_mode, padded, alphabet, &ignorechars); + return_value = binascii_a2b_base64_impl(module, &data, strict_mode, padded, alphabet, &ignorechars, canonical); exit: /* Cleanup for data */ @@ -352,7 +364,7 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(binascii_a2b_ascii85__doc__, "a2b_ascii85($module, data, /, *, foldspaces=False, adobe=False,\n" -" ignorechars=b\'\')\n" +" ignorechars=b\'\', canonical=False)\n" "--\n" "\n" "Decode Ascii85 data.\n" @@ -362,14 +374,16 @@ PyDoc_STRVAR(binascii_a2b_ascii85__doc__, " adobe\n" " Expect data to be wrapped in \'<~\' and \'~>\' as in Adobe Ascii85.\n" " ignorechars\n" -" A byte string containing characters to ignore from the input."); +" A byte string containing characters to ignore from the input.\n" +" canonical\n" +" When set to true, reject non-canonical encodings."); #define BINASCII_A2B_ASCII85_METHODDEF \ {"a2b_ascii85", _PyCFunction_CAST(binascii_a2b_ascii85), METH_FASTCALL|METH_KEYWORDS, binascii_a2b_ascii85__doc__}, static PyObject * binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, - int adobe, Py_buffer *ignorechars); + int adobe, Py_buffer *ignorechars, int canonical); static PyObject * binascii_a2b_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -377,7 +391,7 @@ binascii_a2b_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 3 + #define NUM_KEYWORDS 4 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -386,7 +400,7 @@ binascii_a2b_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(foldspaces), &_Py_ID(adobe), &_Py_ID(ignorechars), }, + .ob_item = { &_Py_ID(foldspaces), &_Py_ID(adobe), &_Py_ID(ignorechars), &_Py_ID(canonical), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -395,19 +409,20 @@ binascii_a2b_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "foldspaces", "adobe", "ignorechars", NULL}; + static const char * const _keywords[] = {"", "foldspaces", "adobe", "ignorechars", "canonical", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "a2b_ascii85", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; int foldspaces = 0; int adobe = 0; Py_buffer ignorechars = {.buf = "", .obj = NULL, .len = 0}; + int canonical = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -438,11 +453,20 @@ binascii_a2b_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, goto skip_optional_kwonly; } } - if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) { + if (args[3]) { + if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + canonical = PyObject_IsTrue(args[4]); + if (canonical < 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_a2b_ascii85_impl(module, &data, foldspaces, adobe, &ignorechars); + return_value = binascii_a2b_ascii85_impl(module, &data, foldspaces, adobe, &ignorechars, canonical); exit: /* Cleanup for data */ @@ -573,20 +597,23 @@ binascii_b2a_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyDoc_STRVAR(binascii_a2b_base85__doc__, "a2b_base85($module, data, /, *, alphabet=BASE85_ALPHABET,\n" -" ignorechars=b\'\')\n" +" ignorechars=b\'\', canonical=False)\n" "--\n" "\n" "Decode a line of Base85 data.\n" "\n" " ignorechars\n" -" A byte string containing characters to ignore from the input."); +" A byte string containing characters to ignore from the input.\n" +" canonical\n" +" When set to true, reject non-canonical encodings."); #define BINASCII_A2B_BASE85_METHODDEF \ {"a2b_base85", _PyCFunction_CAST(binascii_a2b_base85), METH_FASTCALL|METH_KEYWORDS, binascii_a2b_base85__doc__}, static PyObject * binascii_a2b_base85_impl(PyObject *module, Py_buffer *data, - PyBytesObject *alphabet, Py_buffer *ignorechars); + PyBytesObject *alphabet, Py_buffer *ignorechars, + int canonical); static PyObject * binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -594,7 +621,7 @@ binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P 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 @@ -603,7 +630,7 @@ binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(alphabet), &_Py_ID(ignorechars), }, + .ob_item = { &_Py_ID(alphabet), &_Py_ID(ignorechars), &_Py_ID(canonical), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -612,18 +639,19 @@ binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "alphabet", "ignorechars", NULL}; + static const char * const _keywords[] = {"", "alphabet", "ignorechars", "canonical", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "a2b_base85", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[3]; + PyObject *argsbuf[4]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; PyBytesObject *alphabet = NULL; Py_buffer ignorechars = {.buf = "", .obj = NULL, .len = 0}; + int canonical = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -646,11 +674,20 @@ binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P goto skip_optional_kwonly; } } - if (PyObject_GetBuffer(args[2], &ignorechars, PyBUF_SIMPLE) != 0) { + if (args[2]) { + if (PyObject_GetBuffer(args[2], &ignorechars, PyBUF_SIMPLE) != 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + canonical = PyObject_IsTrue(args[3]); + if (canonical < 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_a2b_base85_impl(module, &data, alphabet, &ignorechars); + return_value = binascii_a2b_base85_impl(module, &data, alphabet, &ignorechars, canonical); exit: /* Cleanup for data */ @@ -768,7 +805,7 @@ binascii_b2a_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyDoc_STRVAR(binascii_a2b_base32__doc__, "a2b_base32($module, data, /, *, padded=True, alphabet=BASE32_ALPHABET,\n" -" ignorechars=b\'\')\n" +" ignorechars=b\'\', canonical=False)\n" "--\n" "\n" "Decode a line of base32 data.\n" @@ -776,14 +813,17 @@ PyDoc_STRVAR(binascii_a2b_base32__doc__, " padded\n" " When set to false, padding in input is not required.\n" " ignorechars\n" -" A byte string containing characters to ignore from the input."); +" A byte string containing characters to ignore from the input.\n" +" canonical\n" +" When set to true, reject non-zero padding bits per RFC 4648 section 3.5."); #define BINASCII_A2B_BASE32_METHODDEF \ {"a2b_base32", _PyCFunction_CAST(binascii_a2b_base32), METH_FASTCALL|METH_KEYWORDS, binascii_a2b_base32__doc__}, static PyObject * binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded, - PyBytesObject *alphabet, Py_buffer *ignorechars); + PyBytesObject *alphabet, Py_buffer *ignorechars, + int canonical); static PyObject * binascii_a2b_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -791,7 +831,7 @@ binascii_a2b_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 3 + #define NUM_KEYWORDS 4 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -800,7 +840,7 @@ binascii_a2b_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(padded), &_Py_ID(alphabet), &_Py_ID(ignorechars), }, + .ob_item = { &_Py_ID(padded), &_Py_ID(alphabet), &_Py_ID(ignorechars), &_Py_ID(canonical), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -809,19 +849,20 @@ binascii_a2b_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "padded", "alphabet", "ignorechars", NULL}; + static const char * const _keywords[] = {"", "padded", "alphabet", "ignorechars", "canonical", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "a2b_base32", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; int padded = 1; PyBytesObject *alphabet = NULL; Py_buffer ignorechars = {.buf = "", .obj = NULL, .len = 0}; + int canonical = 0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -853,11 +894,20 @@ binascii_a2b_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P goto skip_optional_kwonly; } } - if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) { + if (args[3]) { + if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + canonical = PyObject_IsTrue(args[4]); + if (canonical < 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_a2b_base32_impl(module, &data, padded, alphabet, &ignorechars); + return_value = binascii_a2b_base32_impl(module, &data, padded, alphabet, &ignorechars, canonical); exit: /* Cleanup for data */ @@ -1634,4 +1684,4 @@ binascii_b2a_qp(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj return return_value; } -/*[clinic end generated code: output=2acab1ceb0058b1a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b41544f39b0ef681 input=a9049054013a1b77]*/ From e1384cfd25b4fba5e0f8f3e6b536930e2e6cf5cf Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:01:33 -0700 Subject: [PATCH 13/44] gh-141473: Speed up subprocess test_communicate_timeout_large_input long tail (#149003) gh-141473: Speed up test_communicate_timeout_large_input Replace the slow reader's 30s sleep with a parent-driven wake over a loopback socket so post-timeout communicate() doesn't block waiting for the child to wake on its own. Worst-case runtime: ~30s -> <1s. --- Lib/test/test_subprocess.py | 56 +++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 0c5679611848ea..3237a9cb49876d 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -22,6 +22,7 @@ import sysconfig import select import shutil +import socket import threading import gc import textwrap @@ -1044,19 +1045,49 @@ def test_communicate_timeout_large_input(self): # On Windows, stdin writing must also honor the timeout rather than # blocking indefinitely when the pipe buffer fills. - # Input larger than typical pipe buffer (4-64KB on Windows) - input_data = b"x" * (128 * 1024) + input_data = b"x" * (128 * 1024) # > typical pipe buffer + + # Cross-platform wake mechanism: the slow reader connects to a + # loopback TCP socket and blocks in select() on it (capped at 9s + # as a safety net we don't expect to hit). After phase 1 raises + # TimeoutExpired, the parent sends a byte to release the child so + # it drains stdin. A socket (rather than a raw pipe) is required + # because Windows select() only supports sockets, not arbitrary + # file descriptors. + server = socket.create_server(('127.0.0.1', 0), backlog=1) + server.settimeout(10) # bound the accept() if the child fails to start + port = server.getsockname()[1] + # The child sends one byte (low byte of its PID) first so the parent + # can detect the rare case of an unrelated process on the same host + # connecting to our ephemeral port before our child does. A single + # byte gives 1/256 collision odds, which is plenty for flake-prevention. + slow_reader = ( + "import os, socket, sys, select; " + f"s = socket.create_connection(('127.0.0.1', {port}), timeout=9); " + "s.sendall(bytes([os.getpid() & 0xff])); " + "select.select([s], [], [], 9); " + "sys.stdout.buffer.write(sys.stdin.buffer.read())" + ) p = subprocess.Popen( - [sys.executable, "-c", - "import sys, time; " - "time.sleep(30); " # Don't read stdin for a long time - "sys.stdout.buffer.write(sys.stdin.buffer.read())"], + [sys.executable, "-c", slow_reader], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + conn = None try: + conn, _ = server.accept() + server.close() + server = None + + conn.settimeout(5) + peer_byte = conn.recv(1) + conn.settimeout(None) + self.assertEqual(peer_byte, bytes([p.pid & 0xff]), + f"loopback handshake byte {peer_byte!r} != " + f"low byte of child PID {p.pid} ({p.pid & 0xff:#x})") + timeout = 0.2 start = time.monotonic() try: @@ -1065,7 +1096,7 @@ def test_communicate_timeout_large_input(self): elapsed = time.monotonic() - start self.fail( f"TimeoutExpired not raised. communicate() completed in " - f"{elapsed:.2f}s, but subprocess sleeps for 30s. " + f"{elapsed:.2f}s, but slow reader stalls for up to 9s. " "Stdin writing blocked without enforcing timeout.") except subprocess.TimeoutExpired: elapsed = time.monotonic() - start @@ -1073,11 +1104,16 @@ def test_communicate_timeout_large_input(self): # Timeout should occur close to the specified timeout value, # not after waiting for the subprocess to finish sleeping. # Allow generous margin for slow CI, but must be well under - # the subprocess sleep time. + # the slow-reader's stall cap. self.assertLess(elapsed, 5.0, f"TimeoutExpired raised after {elapsed:.2f}s; expected ~{timeout}s. " "Stdin writing blocked without checking timeout.") + # Release the slow reader so it stops blocking and drains stdin. + conn.sendall(b'go') + conn.close() + conn = None + # After timeout, continue communication. The remaining input # should be sent and we should receive all data back. stdout, stderr = p.communicate() @@ -1087,6 +1123,10 @@ def test_communicate_timeout_large_input(self): f"Expected {len(input_data)} bytes output but got {len(stdout)}") self.assertEqual(stdout, input_data) finally: + if conn is not None: + conn.close() + if server is not None: + server.close() p.kill() p.wait() From 0a39730ecd45430e14cfeb244980b6eaa3571695 Mon Sep 17 00:00:00 2001 From: Daniel Hollas Date: Sun, 26 Apr 2026 06:57:38 +0100 Subject: [PATCH 14/44] gh-137855: Lazy import `inspect` module in dataclasses (#144387) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/dataclasses.py | 46 ++++++++++++------- Lib/test/test__colorize.py | 2 +- Lib/test/test_dataclasses/__init__.py | 26 ++++++++++- ...-02-12-18-05-16.gh-issue-137855.2_PTbg.rst | 1 + 4 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 988edfed6f4dcb..df192763c5bd15 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1,12 +1,12 @@ import sys import types -import inspect import keyword import itertools import annotationlib import abc from reprlib import recursive_repr lazy import copy +lazy import inspect lazy import re @@ -988,6 +988,28 @@ def _hash_exception(cls, fields, func_builder): # See https://bugs.python.org/issue32929#msg312829 for an if-statement # version of this table. +# A non-data descriptor to autogenerate class docstring +# from the signature of its __init__ method on demand. +# The primary reason is to be able to lazy import `inspect` module. +class _AutoDocstring: + + def __get__(self, _obj, cls): + try: + # In some cases fetching a signature is not possible. + # But, we surely should not fail in this case. + text_sig = str(inspect.signature( + cls, + annotation_format=annotationlib.Format.FORWARDREF, + )).replace(' -> None', '') + except TypeError, ValueError: + text_sig = '' + + doc = cls.__name__ + text_sig + setattr(cls, '__doc__', doc) + return doc + +_auto_docstring = _AutoDocstring() + def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot): @@ -1215,23 +1237,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, if hash_action: cls.__hash__ = hash_action(cls, field_list, func_builder) - # Generate the methods and add them to the class. This needs to be done - # before the __doc__ logic below, since inspect will look at the __init__ - # signature. + # Generate the methods and add them to the class. func_builder.add_fns_to_class(cls) if not getattr(cls, '__doc__'): - # Create a class doc-string. - try: - # In some cases fetching a signature is not possible. - # But, we surely should not fail in this case. - text_sig = str(inspect.signature( - cls, - annotation_format=annotationlib.Format.FORWARDREF, - )).replace(' -> None', '') - except (TypeError, ValueError): - text_sig = '' - cls.__doc__ = (cls.__name__ + text_sig) + # Create a class doc-string lazily via descriptor protocol + # to avoid importing `inspect` module. + cls.__doc__ = _auto_docstring if match_args: # I could probably compute this once. @@ -1391,8 +1403,10 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): # make an update, since all closures for a class will share a # given cell. for member in newcls.__dict__.values(): + # If this is a wrapped function, unwrap it. - member = inspect.unwrap(member) + if not isinstance(member, type) and hasattr(member, '__wrapped__'): + member = inspect.unwrap(member) if isinstance(member, types.FunctionType): if _update_func_cell_for__class__(member, cls, newcls): diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index 0353ff7530b92a..48fa52bfd5672c 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -28,7 +28,7 @@ class TestImportTime(unittest.TestCase): @cpython_only def test_lazy_import(self): import_helper.ensure_lazy_imports( - "_colorize", {"copy", "re"} + "_colorize", {"copy", "re", "inspect"} ) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 6ff82b8810abed..5eec9e23cd414b 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -27,11 +27,21 @@ import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. from test import support -from test.support import import_helper +from test.support import cpython_only, import_helper # Just any custom exception we can catch. class CustomError(Exception): pass + +class TestImportTime(unittest.TestCase): + + @cpython_only + def test_lazy_import(self): + import_helper.ensure_lazy_imports( + "dataclasses", {"inspect", "re", "copy"} + ) + + class TestCase(unittest.TestCase): def test_no_fields(self): @dataclass @@ -2309,6 +2319,20 @@ class C: self.assertDocStrEqual(C.__doc__, "C()") + def test_docstring_slotted(self): + @dataclass(slots=True) + class C: + x: int + + self.assertDocStrEqual(C.__doc__, "C(x:int)") + + def test_docstring_recursive(self): + @dataclass() + class C: + x: list[C] + + self.assertDocStrEqual(C.__doc__, "C(x:list[test.test_dataclasses.TestDocString.test_docstring_recursive..C])") + def test_docstring_one_field(self): @dataclass class C: diff --git a/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst b/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst new file mode 100644 index 00000000000000..586c7d3495ae26 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-12-18-05-16.gh-issue-137855.2_PTbg.rst @@ -0,0 +1 @@ +Reduce the import time of :mod:`dataclasses` module by ~20%. From 6d4ca16f47586533c9223fe5729f6b7e221fa9f9 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 26 Apr 2026 10:15:54 +0100 Subject: [PATCH 15/44] gh-148981: Add color parameter to `ast.dump` (#148982) And turn on color for the `ast` module CLI. --- Doc/library/ast.rst | 13 ++++- Doc/whatsnew/3.15.rst | 14 +++++ Lib/_colorize.py | 15 ++++++ Lib/ast.py | 54 +++++++++++++------ Lib/test/test_ast/test_ast.py | 11 ++++ ...-04-25-12-50-46.gh-issue-148981.YMM4Y9.rst | 1 + 6 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-25-12-50-46.gh-issue-148981.YMM4Y9.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 9b4e7ae18348f1..e23506768a7721 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -2480,7 +2480,7 @@ and classes for traversing abstract syntax trees: node = YourTransformer().visit(node) -.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=False) +.. function:: dump(node, annotate_fields=True, include_attributes=False, *, color=False, indent=None, show_empty=False) Return a formatted dump of the tree in *node*. This is mainly useful for debugging purposes. If *annotate_fields* is true (by default), @@ -2490,6 +2490,10 @@ and classes for traversing abstract syntax trees: numbers and column offsets are not dumped by default. If this is wanted, *include_attributes* can be set to true. + If *color* is ``True``, the returned string is syntax highlighted using + ANSI escape sequences. + If ``False`` (the default), colored output is always disabled. + If *indent* is a non-negative integer or string, then the tree will be pretty-printed with that indent level. An indent level of 0, negative, or ``""`` will only insert newlines. ``None`` (the default) @@ -2527,6 +2531,9 @@ and classes for traversing abstract syntax trees: .. versionchanged:: 3.15 Omit optional ``Load()`` values by default. + .. versionchanged:: next + Added the *color* parameter. + .. _ast-compiler-flags: @@ -2584,6 +2591,10 @@ Command-line usage .. versionadded:: 3.9 +.. versionchanged:: next + The output is now syntax highlighted by default. This can be + :ref:`controlled using environment variables `. + The :mod:`!ast` module can be executed as a script from the command line. It is as simple as: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 4a14568ab88b6a..0afa47c334012a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -704,6 +704,20 @@ array (Contributed by Sergey B Kirpichev in :gh:`146238`.) +ast +--- + +* Add *color* parameter to :func:`~ast.dump`. + If ``True``, the returned string is syntax highlighted using ANSI escape + sequences. + If ``False`` (the default), colored output is always disabled. + (Contributed by Stan Ulbrych in :gh:`148981`.) + +* The :ref:`command-line ` output is now syntax highlighted by default. + This can be :ref:`controlled using environment variables `. + (Contributed by Stan Ulbrych in :gh:`148981`.) + + base64 ------ diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 852ad38f08618e..f9ee2caa9d091c 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -189,6 +189,17 @@ class Argparse(ThemeSection): message: str = ANSIColors.MAGENTA +@dataclass(frozen=True, kw_only=True) +class Ast(ThemeSection): + node: str = ANSIColors.CYAN + field: str = ANSIColors.BLUE + attribute: str = ANSIColors.GREY + string: str = ANSIColors.GREEN + number: str = ANSIColors.YELLOW + keyword: str = ANSIColors.BOLD_BLUE + reset: str = ANSIColors.RESET + + @dataclass(frozen=True, kw_only=True) class Difflib(ThemeSection): """A 'git diff'-like theme for `difflib.unified_diff`.""" @@ -405,6 +416,7 @@ class Theme: below. """ argparse: Argparse = field(default_factory=Argparse) + ast: Ast = field(default_factory=Ast) difflib: Difflib = field(default_factory=Difflib) fancycompleter: FancyCompleter = field(default_factory=FancyCompleter) http_server: HttpServer = field(default_factory=HttpServer) @@ -418,6 +430,7 @@ def copy_with( self, *, argparse: Argparse | None = None, + ast: Ast | None = None, difflib: Difflib | None = None, fancycompleter: FancyCompleter | None = None, http_server: HttpServer | None = None, @@ -434,6 +447,7 @@ def copy_with( """ return type(self)( argparse=argparse or self.argparse, + ast=ast or self.ast, difflib=difflib or self.difflib, fancycompleter=fancycompleter or self.fancycompleter, http_server=http_server or self.http_server, @@ -454,6 +468,7 @@ def no_colors(cls) -> Self: """ return cls( argparse=Argparse.no_colors(), + ast=Ast.no_colors(), difflib=Difflib.no_colors(), fancycompleter=FancyCompleter.no_colors(), http_server=HttpServer.no_colors(), diff --git a/Lib/ast.py b/Lib/ast.py index d9743ba7ab40b1..ba4ee0197b85d2 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -21,6 +21,7 @@ :license: Python License. """ from _ast import * +lazy from _colorize import can_colorize, get_theme def parse(source, filename='', mode='exec', *, @@ -117,21 +118,32 @@ def _convert_literal(node): def dump( node, annotate_fields=True, include_attributes=False, *, - indent=None, show_empty=False, + color=False, indent=None, show_empty=False, ): """ Return a formatted dump of the tree in node. This is mainly useful for - debugging purposes. If annotate_fields is true (by default), - the returned string will show the names and the values for fields. - If annotate_fields is false, the result string will be more compact by - omitting unambiguous field names. Attributes such as line - numbers and column offsets are not dumped by default. If this is wanted, - include_attributes can be set to true. If indent is a non-negative - integer or string, then the tree will be pretty-printed with that indent - level. None (the default) selects the single line representation. + debugging purposes. + + If annotate_fields is true (by default), the returned string will show the + names and the values for fields. If annotate_fields is false, the result + string will be more compact by omitting unambiguous field names. + + Attributes such as line numbers and column offsets are not dumped by default. + If this is wanted, include_attributes can be set to true. + + If color is true, the returned string is syntax highlighted using ANSI + escape sequences. If color is false (the default), colored output is always + disabled. + + If indent is a non-negative integer or string, then the tree will be + pretty-printed with that indent level. If indent is None (the default), + the tree is dumped on a single line. + If show_empty is False, then empty lists and fields that are None will be omitted from the output for better readability. """ + t = get_theme(force_color=color, force_no_color=not color).ast + def _format(node, level=0): if indent is not None: level += 1 @@ -166,7 +178,9 @@ def _format(node, level=0): field_type = cls._field_types.get(name, object) if field_type is expr_context: if not keywords: - args_buffer.append(repr(value)) + args_buffer.append( + f'{t.node}{type(value).__name__}' + f'{t.reset}()') continue if not keywords: args.extend(args_buffer) @@ -174,7 +188,7 @@ def _format(node, level=0): value, simple = _format(value, level) allsimple = allsimple and simple if keywords: - args.append('%s=%s' % (name, value)) + args.append(f'{t.field}{name}{t.reset}={value}') else: args.append(value) if include_attributes and node._attributes: @@ -187,14 +201,21 @@ def _format(node, level=0): continue value, simple = _format(value, level) allsimple = allsimple and simple - args.append('%s=%s' % (name, value)) + args.append(f'{t.attribute}{name}{t.reset}={value}') + cls_name = f'{t.node}{cls.__name__}{t.reset}' if allsimple and len(args) <= 3: - return '%s(%s)' % (node.__class__.__name__, ', '.join(args)), not args - return '%s(%s%s)' % (node.__class__.__name__, prefix, sep.join(args)), False + return f'{cls_name}({", ".join(args)})', not args + return f'{cls_name}({prefix}{sep.join(args)})', False elif isinstance(node, list): if not node: return '[]', True return '[%s%s]' % (prefix, sep.join(_format(x, level)[0] for x in node)), False + if isinstance(node, bool) or node is None or node is Ellipsis: + return f'{t.keyword}{node!r}{t.reset}', True + if isinstance(node, (int, float, complex)): + return f'{t.number}{node!r}{t.reset}', True + if isinstance(node, (str, bytes)): + return f'{t.string}{node!r}{t.reset}', True return repr(node), True if not isinstance(node, AST): @@ -642,7 +663,7 @@ def main(args=None): import argparse import sys - parser = argparse.ArgumentParser(color=True) + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('infile', nargs='?', default='-', help='the file to parse; defaults to stdin') parser.add_argument('-m', '--mode', default='exec', @@ -661,7 +682,7 @@ def main(args=None): '(for example, 3.10)') parser.add_argument('-O', '--optimize', type=int, default=-1, metavar='LEVEL', - help='optimization level for parser (default -1)') + help='optimization level for parser') parser.add_argument('--show-empty', default=False, action='store_true', help='show empty lists and fields in dump output') args = parser.parse_args(args) @@ -688,6 +709,7 @@ def main(args=None): tree = parse(source, name, args.mode, type_comments=args.no_type_comments, feature_version=feature_version, optimize=args.optimize) print(dump(tree, include_attributes=args.include_attributes, + color=can_colorize(file=sys.stdout), indent=args.indent, show_empty=args.show_empty)) if __name__ == '__main__': diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index f29f98beb2d048..75d553e6f7778f 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -1705,6 +1705,16 @@ def check_text(code, empty, full, **kwargs): full="Module(body=[Import(names=[alias(name='_ast', asname='ast')], is_lazy=0), ImportFrom(module='module', names=[alias(name='sub')], level=0, is_lazy=0)], type_ignores=[])", ) + def test_dump_with_color(self): + node = ast.parse("x = 1") + self.assertNotIn("\x1b[", ast.dump(node)) + self.assertNotIn("\x1b[", ast.dump(node, color=False)) + self.assertIn("\x1b[", ast.dump(node, color=True)) + + node = ast.Constant(value="\x1b[31m") + self.assertEqual(ast.dump(node), "Constant(value='\\x1b[31m')") + self.assertIn("'\\x1b[31m'", ast.dump(node, color=True)) + def test_copy_location(self): src = ast.parse('1 + 1', mode='eval') src.body.right = ast.copy_location(ast.Constant(2), src.body.right) @@ -3415,6 +3425,7 @@ def test_subinterpreter(self): self.assertEqual(res, 0) +@support.force_not_colorized_test_class class CommandLineTests(unittest.TestCase): def setUp(self): self.filename = tempfile.mktemp() diff --git a/Misc/NEWS.d/next/Library/2026-04-25-12-50-46.gh-issue-148981.YMM4Y9.rst b/Misc/NEWS.d/next/Library/2026-04-25-12-50-46.gh-issue-148981.YMM4Y9.rst new file mode 100644 index 00000000000000..e36c7745f4080a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-25-12-50-46.gh-issue-148981.YMM4Y9.rst @@ -0,0 +1 @@ +Add *color* parameter to :func:`ast.dump`. From 5d416324c56cd6f262fa123f41b97b48631bea79 Mon Sep 17 00:00:00 2001 From: zSirius <107359899+zSirius@users.noreply.github.com> Date: Sun, 26 Apr 2026 20:15:24 +0800 Subject: [PATCH 16/44] =?UTF-8?q?gh-146455:=20Fix=20O(N=C2=B2)=20in=20add?= =?UTF-8?q?=5Fconst()=20after=20constant=20folding=20moved=20to=20CFG=20(#?= =?UTF-8?q?146456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The add_const() function in flowgraph.c uses a linear search over the consts list to find the index of a constant. After gh-126835 moved constant folding from the AST optimizer to the CFG optimizer, this function is now called N times for N inner tuple elements during fold_tuple_of_constants(), resulting in O(N²) total time. Fix by maintaining an auxiliary _Py_hashtable_t that maps object pointers to their indices in the consts list, providing O(1) lookup. For a file with 100,000 constant 2-tuples: - Before: 10.38s (add_const occupies 83.76% of CPU time) - After: 1.48s --- ...3-26-08-49-35.gh-issue-146455.f54083a9.rst | 1 + Python/flowgraph.c | 131 ++++++++++++------ 2 files changed, 89 insertions(+), 43 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst new file mode 100644 index 00000000000000..4d7537f2529da6 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-26-08-49-35.gh-issue-146455.f54083a9.rst @@ -0,0 +1 @@ +Fix O(N²) compile-time regression in constant folding after it was moved from AST to CFG optimizer. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 202e3bacf2e1bf..2cb2d32a410613 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -6,6 +6,7 @@ #include "pycore_intrinsics.h" #include "pycore_pymem.h" // _PyMem_IsPtrFreed() #include "pycore_long.h" // _PY_IS_SMALL_INT() +#include "pycore_hashtable.h" // _Py_hashtable_t #include "pycore_opcode_utils.h" #include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc @@ -1333,30 +1334,38 @@ get_const_value(int opcode, int oparg, PyObject *co_consts) // Steals a reference to newconst. static int -add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache) +add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache, + _Py_hashtable_t *consts_index) { if (_PyCompile_ConstCacheMergeOne(const_cache, &newconst) < 0) { Py_DECREF(newconst); return -1; } - Py_ssize_t index; - for (index = 0; index < PyList_GET_SIZE(consts); index++) { - if (PyList_GET_ITEM(consts, index) == newconst) { - break; - } + _Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts_index, (void *)newconst); + if (entry != NULL) { + Py_DECREF(newconst); + return (int)(uintptr_t)entry->value; } - if (index == PyList_GET_SIZE(consts)) { - if ((size_t)index >= (size_t)INT_MAX - 1) { - PyErr_SetString(PyExc_OverflowError, "too many constants"); - Py_DECREF(newconst); - return -1; - } - if (PyList_Append(consts, newconst)) { - Py_DECREF(newconst); - return -1; - } + + Py_ssize_t index = PyList_GET_SIZE(consts); + if ((size_t)index >= (size_t)INT_MAX - 1) { + PyErr_SetString(PyExc_OverflowError, "too many constants"); + Py_DECREF(newconst); + return -1; + } + if (PyList_Append(consts, newconst)) { + Py_DECREF(newconst); + return -1; + } + + if (_Py_hashtable_set(consts_index, (void *)newconst, (void *)(uintptr_t)index) < 0) { + PyList_SetSlice(consts, index, index + 1, NULL); + Py_DECREF(newconst); + PyErr_NoMemory(); + return -1; } + Py_DECREF(newconst); return (int)index; } @@ -1432,7 +1441,8 @@ maybe_instr_make_load_smallint(cfg_instr *instr, PyObject *newconst, /* Steals reference to "newconst" */ static int instr_make_load_const(cfg_instr *instr, PyObject *newconst, - PyObject *consts, PyObject *const_cache) + PyObject *consts, PyObject *const_cache, + _Py_hashtable_t *consts_index) { int res = maybe_instr_make_load_smallint(instr, newconst, consts, const_cache); if (res < 0) { @@ -1442,7 +1452,7 @@ instr_make_load_const(cfg_instr *instr, PyObject *newconst, if (res > 0) { return SUCCESS; } - int oparg = add_const(newconst, consts, const_cache); + int oparg = add_const(newconst, consts, const_cache, consts_index); RETURN_IF_ERROR(oparg); INSTR_SET_OP1(instr, LOAD_CONST, oparg); return SUCCESS; @@ -1455,7 +1465,8 @@ instr_make_load_const(cfg_instr *instr, PyObject *newconst, Called with codestr pointing to the first LOAD_CONST. */ static int -fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const_cache) +fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, + PyObject *const_cache, _Py_hashtable_t *consts_index) { /* Pre-conditions */ assert(PyDict_CheckExact(const_cache)); @@ -1492,7 +1503,7 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const } nop_out(const_instrs, seq_size); - return instr_make_load_const(instr, const_tuple, consts, const_cache); + return instr_make_load_const(instr, const_tuple, consts, const_cache, consts_index); } /* Replace: @@ -1510,7 +1521,8 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const */ static int fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, - PyObject *consts, PyObject *const_cache) + PyObject *consts, PyObject *const_cache, + _Py_hashtable_t *consts_index) { assert(PyDict_CheckExact(const_cache)); assert(PyList_CheckExact(consts)); @@ -1562,7 +1574,7 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, nop_out(&instr, 1); } assert(consts_found == 0); - return instr_make_load_const(intrinsic, newconst, consts, const_cache); + return instr_make_load_const(intrinsic, newconst, consts, const_cache, consts_index); } if (expect_append) { @@ -1598,7 +1610,8 @@ Optimize lists and sets for: */ static int optimize_lists_and_sets(basicblock *bb, int i, int nextop, - PyObject *consts, PyObject *const_cache) + PyObject *consts, PyObject *const_cache, + _Py_hashtable_t *consts_index) { assert(PyDict_CheckExact(const_cache)); assert(PyList_CheckExact(consts)); @@ -1648,7 +1661,7 @@ optimize_lists_and_sets(basicblock *bb, int i, int nextop, Py_SETREF(const_result, frozenset); } - int index = add_const(const_result, consts, const_cache); + int index = add_const(const_result, consts, const_cache, consts_index); RETURN_IF_ERROR(index); nop_out(const_instrs, seq_size); @@ -1845,7 +1858,8 @@ eval_const_binop(PyObject *left, int op, PyObject *right) } static int -fold_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache) +fold_const_binop(basicblock *bb, int i, PyObject *consts, + PyObject *const_cache, _Py_hashtable_t *consts_index) { #define BINOP_OPERAND_COUNT 2 assert(PyDict_CheckExact(const_cache)); @@ -1887,7 +1901,7 @@ fold_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache) } nop_out(operands_instrs, BINOP_OPERAND_COUNT); - return instr_make_load_const(binop, newconst, consts, const_cache); + return instr_make_load_const(binop, newconst, consts, const_cache, consts_index); } static PyObject * @@ -1933,7 +1947,8 @@ eval_const_unaryop(PyObject *operand, int opcode, int oparg) } static int -fold_const_unaryop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache) +fold_const_unaryop(basicblock *bb, int i, PyObject *consts, + PyObject *const_cache, _Py_hashtable_t *consts_index) { #define UNARYOP_OPERAND_COUNT 1 assert(PyDict_CheckExact(const_cache)); @@ -1970,7 +1985,7 @@ fold_const_unaryop(basicblock *bb, int i, PyObject *consts, PyObject *const_cach assert(PyBool_Check(newconst)); } nop_out(&operand_instr, UNARYOP_OPERAND_COUNT); - return instr_make_load_const(unaryop, newconst, consts, const_cache); + return instr_make_load_const(unaryop, newconst, consts, const_cache, consts_index); } #define VISITED (-1) @@ -2165,7 +2180,8 @@ apply_static_swaps(basicblock *block, int i) } static int -basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject *consts) +basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, + PyObject *consts, _Py_hashtable_t *consts_index) { assert(PyDict_CheckExact(const_cache)); assert(PyList_CheckExact(consts)); @@ -2283,7 +2299,7 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject * return ERROR; } cnt = PyBool_FromLong(is_true); - int index = add_const(cnt, consts, const_cache); + int index = add_const(cnt, consts, const_cache, consts_index); if (index < 0) { return ERROR; } @@ -2297,15 +2313,17 @@ basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, PyObject * } static int -optimize_load_const(PyObject *const_cache, cfg_builder *g, PyObject *consts) { +optimize_load_const(PyObject *const_cache, cfg_builder *g, PyObject *consts, + _Py_hashtable_t *consts_index) { for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(basicblock_optimize_load_const(const_cache, b, consts)); + RETURN_IF_ERROR(basicblock_optimize_load_const(const_cache, b, consts, consts_index)); } return SUCCESS; } static int -optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) +optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts, + _Py_hashtable_t *consts_index) { assert(PyDict_CheckExact(const_cache)); assert(PyList_CheckExact(consts)); @@ -2345,11 +2363,11 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) continue; } } - RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache)); + RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache, consts_index)); break; case BUILD_LIST: case BUILD_SET: - RETURN_IF_ERROR(optimize_lists_and_sets(bb, i, nextop, consts, const_cache)); + RETURN_IF_ERROR(optimize_lists_and_sets(bb, i, nextop, consts, const_cache, consts_index)); break; case POP_JUMP_IF_NOT_NONE: case POP_JUMP_IF_NONE: @@ -2484,7 +2502,7 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) _Py_FALLTHROUGH; case UNARY_INVERT: case UNARY_NEGATIVE: - RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache)); + RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index)); break; case CALL_INTRINSIC_1: if (oparg == INTRINSIC_LIST_TO_TUPLE) { @@ -2492,15 +2510,15 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) INSTR_SET_OP0(inst, NOP); } else { - RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache)); + RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache, consts_index)); } } else if (oparg == INTRINSIC_UNARY_POSITIVE) { - RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache)); + RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index)); } break; case BINARY_OP: - RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache)); + RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache, consts_index)); break; } } @@ -2545,16 +2563,17 @@ remove_redundant_nops_and_jumps(cfg_builder *g) NOPs. Later those NOPs are removed. */ static int -optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, int firstlineno) +optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, + _Py_hashtable_t *consts_index, int firstlineno) { assert(PyDict_CheckExact(const_cache)); RETURN_IF_ERROR(check_cfg(g)); RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock)); RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); - RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts)); + RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts, consts_index)); for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { - RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts)); + RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts, consts_index)); } RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock)); RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); @@ -3674,7 +3693,33 @@ _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache, RETURN_IF_ERROR(label_exception_targets(g->g_entryblock)); /** Optimization **/ - RETURN_IF_ERROR(optimize_cfg(g, consts, const_cache, firstlineno)); + + _Py_hashtable_t *consts_index = _Py_hashtable_new( + _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct); + if (consts_index == NULL) { + PyErr_NoMemory(); + return ERROR; + } + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(consts); i++) { + PyObject *item = PyList_GET_ITEM(consts, i); + if (_Py_hashtable_get_entry(consts_index, (void *)item) != NULL) { + continue; + } + if (_Py_hashtable_set(consts_index, (void *)item, + (void *)(uintptr_t)i) < 0) { + _Py_hashtable_destroy(consts_index); + PyErr_NoMemory(); + return ERROR; + } + } + + int ret = optimize_cfg(g, consts, const_cache, consts_index, firstlineno); + + _Py_hashtable_destroy(consts_index); + + RETURN_IF_ERROR(ret); + RETURN_IF_ERROR(remove_unused_consts(g->g_entryblock, consts)); RETURN_IF_ERROR( add_checks_for_loads_of_uninitialized_variables( From 1e7dfbce930d6abd4e5453961c4f0fe4c6ade08e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 26 Apr 2026 22:14:33 +0300 Subject: [PATCH 17/44] gh-148991: Add colour to `tokenize` CLI output (#148992) Co-authored-by: Stan Ulbrych --- Doc/library/tokenize.rst | 12 ++-- Doc/whatsnew/3.15.rst | 9 +++ Lib/_colorize.py | 12 ++++ Lib/test/test_tokenize.py | 1 + Lib/tokenize.py | 62 ++++++++++++++++--- ...-04-25-18-09-16.gh-issue-148991.AZ64Et.rst | 1 + 6 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-25-18-09-16.gh-issue-148991.AZ64Et.rst diff --git a/Doc/library/tokenize.rst b/Doc/library/tokenize.rst index 3db4cf42c17f3d..72fbcaba160660 100644 --- a/Doc/library/tokenize.rst +++ b/Doc/library/tokenize.rst @@ -28,7 +28,7 @@ type can be determined by checking the ``exact_type`` property on the **undefined** when providing invalid Python code and it can change at any point. -Tokenizing Input +Tokenizing input ---------------- The primary entry point is a :term:`generator`: @@ -146,7 +146,7 @@ function it uses to do this is available: .. _tokenize-cli: -Command-Line Usage +Command-line usage ------------------ .. versionadded:: 3.3 @@ -173,8 +173,12 @@ The following options are accepted: If :file:`filename.py` is specified its contents are tokenized to stdout. Otherwise, tokenization is performed on stdin. +.. versionadded:: next + Output is in color by default and can be + :ref:`controlled using environment variables `. + Examples ------------------- +-------- Example of a script rewriter that transforms float literals into Decimal objects:: @@ -227,7 +231,7 @@ Example of tokenizing from the command line. The script:: will be tokenized to the following output where the first column is the range of the line/column coordinates where the token is found, the second column is -the name of the token, and the final column is the value of the token (if any) +the name of the token, and the final column is the value of the token (if any): .. code-block:: shell-session diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 0afa47c334012a..405d388af487e8 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1244,6 +1244,15 @@ tkinter (Contributed by Matthias Kievernagel and Serhiy Storchaka in :gh:`47655`.) +tokenize +-------- + +* The output of the :mod:`tokenize` :ref:`command-line interface + ` is colored by default. This can be controlled with + :ref:`environment variables `. + (Contributed by Hugo van Kemenade in :gh:`148991`.) + + .. _whatsnew315-tomllib-1-1-0: tomllib diff --git a/Lib/_colorize.py b/Lib/_colorize.py index f9ee2caa9d091c..379ca2529b6585 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -386,6 +386,14 @@ class Timeit(ThemeSection): reset: str = ANSIColors.RESET +@dataclass(frozen=True, kw_only=True) +class Tokenize(ThemeSection): + whitespace: str = ANSIColors.GREY + error: str = ANSIColors.BOLD_RED + position: str = ANSIColors.GREY + delimiter: str = ANSIColors.RESET + + @dataclass(frozen=True, kw_only=True) class Traceback(ThemeSection): type: str = ANSIColors.BOLD_MAGENTA @@ -423,6 +431,7 @@ class Theme: live_profiler: LiveProfiler = field(default_factory=LiveProfiler) syntax: Syntax = field(default_factory=Syntax) timeit: Timeit = field(default_factory=Timeit) + tokenize: Tokenize = field(default_factory=Tokenize) traceback: Traceback = field(default_factory=Traceback) unittest: Unittest = field(default_factory=Unittest) @@ -437,6 +446,7 @@ def copy_with( live_profiler: LiveProfiler | None = None, syntax: Syntax | None = None, timeit: Timeit | None = None, + tokenize: Tokenize | None = None, traceback: Traceback | None = None, unittest: Unittest | None = None, ) -> Self: @@ -454,6 +464,7 @@ def copy_with( live_profiler=live_profiler or self.live_profiler, syntax=syntax or self.syntax, timeit=timeit or self.timeit, + tokenize=tokenize or self.tokenize, traceback=traceback or self.traceback, unittest=unittest or self.unittest, ) @@ -475,6 +486,7 @@ def no_colors(cls) -> Self: live_profiler=LiveProfiler.no_colors(), syntax=Syntax.no_colors(), timeit=Timeit.no_colors(), + tokenize=Tokenize.no_colors(), traceback=Traceback.no_colors(), unittest=Unittest.no_colors(), ) diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index ca67e381958757..ab53a20cff5539 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -3326,6 +3326,7 @@ def test_newline_at_the_end_of_buffer(self): run_test_script(file_name) +@support.force_not_colorized_test_class class CommandLineTest(unittest.TestCase): def setUp(self): self.filename = tempfile.mktemp() diff --git a/Lib/tokenize.py b/Lib/tokenize.py index 11c134482db024..52cf3f0b7ccaa9 100644 --- a/Lib/tokenize.py +++ b/Lib/tokenize.py @@ -35,6 +35,7 @@ from token import * from token import EXACT_TOKEN_TYPES import _tokenize +lazy import _colorize cookie_re = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) @@ -505,6 +506,56 @@ def generate_tokens(readline): """ return _generate_tokens_from_c_tokenizer(readline, extra_tokens=True) + +def _get_token_colors(syntax, tokenize): + """Map token type numbers to theme colors.""" + return frozendict({ + COMMENT: syntax.comment, + DEDENT: tokenize.whitespace, + ENCODING: tokenize.whitespace, + ENDMARKER: tokenize.whitespace, + ERRORTOKEN: tokenize.error, + FSTRING_START: syntax.string, + FSTRING_MIDDLE: syntax.string, + FSTRING_END: syntax.string, + INDENT: tokenize.whitespace, + NAME: syntax.reset, + NEWLINE: tokenize.whitespace, + NL: tokenize.whitespace, + NUMBER: syntax.number, + OP: syntax.op, + SOFT_KEYWORD: syntax.soft_keyword, + STRING: syntax.string, + TSTRING_START: syntax.string, + TSTRING_MIDDLE: syntax.string, + TSTRING_END: syntax.string, + }) + + +def _format_tokens(tokens, *, color=False, exact=False): + theme = _colorize.get_theme(force_no_color=not color) + s = theme.syntax + t = theme.tokenize + token_colors = _get_token_colors(s, t) + for token in tokens: + token_range = ( + f"{t.position}{token.start[0]}" + f"{t.delimiter},{t.position}{token.start[1]}" + f"{t.delimiter}-" + f"{t.position}{token.end[0]}" + f"{t.delimiter},{t.position}{token.end[1]}" + f"{t.delimiter}:" + ) + token_color = token_colors.get(token.type, s.reset) + token_name = tok_name[token.exact_type if exact else token.type] + visible_range = f"{token.start[0]},{token.start[1]}-{token.end[0]},{token.end[1]}:" + yield ( + f"{token_range}{' ' * (20 - len(visible_range))}" + f"{token_color}{token_name:<15}" + f"{s.reset}{token.string!r:<15}" + ) + + def _main(args=None): import argparse @@ -524,7 +575,7 @@ def error(message, filename=None, location=None): sys.exit(1) # Parse the arguments and options - parser = argparse.ArgumentParser(color=True) + parser = argparse.ArgumentParser() parser.add_argument(dest='filename', nargs='?', metavar='filename.py', help='the file to tokenize; defaults to stdin') @@ -545,13 +596,8 @@ def error(message, filename=None, location=None): # Output the tokenization - for token in tokens: - token_type = token.type - if args.exact: - token_type = token.exact_type - token_range = "%d,%d-%d,%d:" % (token.start + token.end) - print("%-20s%-15s%-15r" % - (token_range, tok_name[token_type], token.string)) + for line in _format_tokens(tokens, color=True, exact=args.exact): + print(line) except IndentationError as err: line, column = err.args[1][1:3] error(err.args[0], filename, (line, column)) diff --git a/Misc/NEWS.d/next/Library/2026-04-25-18-09-16.gh-issue-148991.AZ64Et.rst b/Misc/NEWS.d/next/Library/2026-04-25-18-09-16.gh-issue-148991.AZ64Et.rst new file mode 100644 index 00000000000000..336ed42e51f1b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-25-18-09-16.gh-issue-148991.AZ64Et.rst @@ -0,0 +1 @@ +Add colour to :mod:`tokenize` CLI output. Patch by Hugo van Kemenade. From 2754e9a615de298b0c3a97e0bfd65a7506ebf657 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:40:20 -0700 Subject: [PATCH 18/44] gh-47798: Refactor the POSIX subprocess.Popen._communicate selector loop into helpers (GH-149032) No public API change. Lift the per-iteration select/read/write loop out of Popen._communicate (POSIX) into a module-level _communicate_io_posix(), with small _flush_stdin / _make_input_view / _translate_newlines helpers alongside it. Popen._communicate calls the helper and persists the returned input offset for resume-after-timeout. Retire the private Popen._remaining_time method in favor of module-level _deadline_remaining; all call sites (POSIX and Windows) updated. Defensive behavioural deltas: the stdin and stdout/stderr .close() calls in the I/O loop now swallow BrokenPipeError / OSError, matching __exit__ and the no-input path; previously these were bare. Adds test_communicate_timeout_resume_partial_write to cover _input_offset bookkeeping across TimeoutExpired/resume. --- Lib/subprocess.py | 217 +++++++++++++++++++++++++----------- Lib/test/test_subprocess.py | 33 ++++++ 2 files changed, 184 insertions(+), 66 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 7ac2289f535b6d..38b655f2f7b9d2 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -250,6 +250,82 @@ def __repr__(self): else: _PopenSelector = selectors.SelectSelector + def _communicate_io_posix(selector, stdin, input_view, input_offset, + output_buffers, endtime, *, close_on_eof=False): + """ + Low-level POSIX I/O multiplexing loop used by Popen._communicate. + + Handles the select loop for reading/writing but does not manage + stream lifecycle or raise timeout exceptions. + + Args: + selector: A _PopenSelector with streams already registered + stdin: Writable file object for input, or None + input_view: memoryview of input bytes, or None + input_offset: Starting offset into input_view (for resume support) + output_buffers: Dict {file_object: list} to append read chunks to + endtime: Deadline timestamp, or None for no timeout + close_on_eof: If True, close output streams immediately when they + EOF rather than leaving them open for the caller to close. + Used by Popen._communicate() to match its historical behavior + of releasing fds as soon as the child closes the corresponding + pipe. + + Returns: + (new_input_offset, completed) + - new_input_offset: How many bytes of input were written + - completed: True if all I/O finished, False if timed out + + Note: + - Closes output streams on EOF only if close_on_eof=True + - Does NOT raise TimeoutExpired (caller handles) + - Appends to output_buffers lists in place + """ + stdin_fd = stdin.fileno() if stdin else None + + while selector.get_map(): + remaining = _deadline_remaining(endtime) + if remaining is not None and remaining <= 0: + return (input_offset, False) # Timed out + + ready = selector.select(remaining) + + # Check timeout after select (may have woken spuriously) + if endtime is not None and _time() > endtime: + return (input_offset, False) # Timed out + + for key, events in ready: + if key.fd == stdin_fd: + chunk = input_view[input_offset:input_offset + _PIPE_BUF] + try: + input_offset += os.write(key.fd, chunk) + except BrokenPipeError: + selector.unregister(key.fd) + try: + stdin.close() + except BrokenPipeError: + pass + else: + if input_offset >= len(input_view): + selector.unregister(key.fd) + try: + stdin.close() + except BrokenPipeError: + pass + elif key.fileobj in output_buffers: + data = os.read(key.fd, 32768) + if not data: + selector.unregister(key.fileobj) + if close_on_eof: + try: + key.fileobj.close() + except OSError: + pass + else: + output_buffers[key.fileobj].append(data) + + return (input_offset, True) # Completed + if _mswindows: # On Windows we just need to close `Popen._handle` when we no longer need @@ -289,6 +365,45 @@ def _cleanup(): DEVNULL = -3 +def _deadline_remaining(endtime): + """Calculate remaining time until deadline.""" + if endtime is None: + return None + return endtime - _time() + + +def _flush_stdin(stdin): + """Flush stdin, ignoring BrokenPipeError and closed file ValueError.""" + try: + stdin.flush() + except BrokenPipeError: + pass # communicate() must ignore BrokenPipeError. + except ValueError: + # Ignore ValueError: I/O operation on closed file. + if not stdin.closed: + raise + + +def _make_input_view(input_data): + """Convert input data to a byte memoryview for writing. + + Handles the case where input_data is already a memoryview with + non-byte elements (e.g., int32 array) by casting to a byte view. + This ensures len(view) returns the byte count, not element count. + """ + if not input_data: + return None + if isinstance(input_data, memoryview): + return input_data.cast("b") # ensure byte view for correct len() + return memoryview(input_data) + + +def _translate_newlines(data, encoding, errors): + """Decode bytes to str and translate newlines to \n.""" + data = data.decode(encoding, errors) + return data.replace("\r\n", "\n").replace("\r", "\n") + + # XXX This function is only used by multiprocessing and the test suite, # but it's here so that it can be imported when Python is compiled without # threads. @@ -1149,8 +1264,8 @@ def universal_newlines(self, universal_newlines): self.text_mode = bool(universal_newlines) def _translate_newlines(self, data, encoding, errors): - data = data.decode(encoding, errors) - return data.replace("\r\n", "\n").replace("\r", "\n") + # Subclass-overridable hook; defers to the module-level helper. + return _translate_newlines(data, encoding, errors) def __enter__(self): return self @@ -1277,7 +1392,7 @@ def communicate(self, input=None, timeout=None): # See the detailed comment in .wait(). if timeout is not None: sigint_timeout = min(self._sigint_wait_secs, - self._remaining_time(endtime)) + _deadline_remaining(endtime)) else: sigint_timeout = self._sigint_wait_secs self._sigint_wait_secs = 0 # nothing else should wait. @@ -1290,7 +1405,7 @@ def communicate(self, input=None, timeout=None): finally: self._communication_started = True try: - self.wait(timeout=self._remaining_time(endtime)) + self.wait(timeout=_deadline_remaining(endtime)) except TimeoutExpired as exc: exc.timeout = timeout raise @@ -1304,14 +1419,6 @@ def poll(self): return self._internal_poll() - def _remaining_time(self, endtime): - """Convenience for _communicate when computing timeouts.""" - if endtime is None: - return None - else: - return endtime - _time() - - def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq, skip_check_and_raise=False): """Convenience for checking if a timeout has expired.""" @@ -1337,7 +1444,7 @@ def wait(self, timeout=None): # generated SIGINT and will exit rapidly. if timeout is not None: sigint_timeout = min(self._sigint_wait_secs, - self._remaining_time(endtime)) + _deadline_remaining(endtime)) else: sigint_timeout = self._sigint_wait_secs self._sigint_wait_secs = 0 # nothing else should wait. @@ -1704,7 +1811,7 @@ def _communicate(self, input, endtime, orig_timeout): # thread remains writing and the fd left open in case the user # calls communicate again. if hasattr(self, "_stdin_thread"): - self._stdin_thread.join(self._remaining_time(endtime)) + self._stdin_thread.join(_deadline_remaining(endtime)) if self._stdin_thread.is_alive(): raise TimeoutExpired(self.args, orig_timeout) @@ -1712,11 +1819,11 @@ def _communicate(self, input, endtime, orig_timeout): # threads remain reading and the fds left open in case the user # calls communicate again. if self.stdout is not None: - self.stdout_thread.join(self._remaining_time(endtime)) + self.stdout_thread.join(_deadline_remaining(endtime)) if self.stdout_thread.is_alive(): raise TimeoutExpired(self.args, orig_timeout) if self.stderr is not None: - self.stderr_thread.join(self._remaining_time(endtime)) + self.stderr_thread.join(_deadline_remaining(endtime)) if self.stderr_thread.is_alive(): raise TimeoutExpired(self.args, orig_timeout) @@ -2210,7 +2317,7 @@ def _wait(self, timeout): break finally: self._waitpid_lock.release() - remaining = self._remaining_time(endtime) + remaining = _deadline_remaining(endtime) if remaining <= 0: raise TimeoutExpired(self.args, timeout) delay = min(delay * 2, remaining, .05) @@ -2234,14 +2341,7 @@ def _communicate(self, input, endtime, orig_timeout): if self.stdin and not self._communication_started: # Flush stdio buffer. This might block, if the user has # been writing to .stdin in an uncontrolled fashion. - try: - self.stdin.flush() - except BrokenPipeError: - pass # communicate() must ignore BrokenPipeError. - except ValueError: - # ignore ValueError: I/O operation on closed file. - if not self.stdin.closed: - raise + _flush_stdin(self.stdin) if not input: try: self.stdin.close() @@ -2266,11 +2366,8 @@ def _communicate(self, input, endtime, orig_timeout): self._save_input(input) - if self._input: - if not isinstance(self._input, memoryview): - input_view = memoryview(self._input) - else: - input_view = self._input.cast("b") # byte input required + input_view = _make_input_view(self._input) + input_offset = self._input_offset if self._input else 0 with _PopenSelector() as selector: if self.stdin and not self.stdin.closed and self._input: @@ -2280,43 +2377,31 @@ def _communicate(self, input, endtime, orig_timeout): if self.stderr and not self.stderr.closed: selector.register(self.stderr, selectors.EVENT_READ) - while selector.get_map(): - timeout = self._remaining_time(endtime) - if timeout is not None and timeout <= 0: - self._check_timeout(endtime, orig_timeout, - stdout, stderr, - skip_check_and_raise=True) - raise RuntimeError( # Impossible :) - '_check_timeout(..., skip_check_and_raise=True) ' - 'failed to raise TimeoutExpired.') - - ready = selector.select(timeout) - self._check_timeout(endtime, orig_timeout, stdout, stderr) - - # XXX Rewrite these to use non-blocking I/O on the file - # objects; they are no longer using C stdio! - - for key, events in ready: - if key.fileobj is self.stdin: - chunk = input_view[self._input_offset : - self._input_offset + _PIPE_BUF] - try: - self._input_offset += os.write(key.fd, chunk) - except BrokenPipeError: - selector.unregister(key.fileobj) - key.fileobj.close() - else: - if self._input_offset >= len(input_view): - selector.unregister(key.fileobj) - key.fileobj.close() - elif key.fileobj in (self.stdout, self.stderr): - data = os.read(key.fd, 32768) - if not data: - selector.unregister(key.fileobj) - key.fileobj.close() - self._fileobj2output[key.fileobj].append(data) + stdin_to_write = (self.stdin if self.stdin and self._input + and not self.stdin.closed else None) + # Persist the returned offset on self so a subsequent + # communicate() after a TimeoutExpired resumes mid-input + # rather than re-sending bytes the child already consumed. + new_offset, completed = _communicate_io_posix( + selector, + stdin_to_write, + input_view, + input_offset, + self._fileobj2output, + endtime, + close_on_eof=True) + if self._input: + self._input_offset = new_offset + + if not completed: + self._check_timeout(endtime, orig_timeout, stdout, stderr, + skip_check_and_raise=True) + raise RuntimeError( # Impossible :) + '_check_timeout(..., skip_check_and_raise=True) ' + 'failed to raise TimeoutExpired.') + try: - self.wait(timeout=self._remaining_time(endtime)) + self.wait(timeout=_deadline_remaining(endtime)) except TimeoutExpired as exc: exc.timeout = orig_timeout raise diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 3237a9cb49876d..1a3db527d3d5b8 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1130,6 +1130,39 @@ def test_communicate_timeout_large_input(self): p.kill() p.wait() + def test_communicate_timeout_resume_partial_write(self): + """Resume writing input after a partial-write TimeoutExpired. + + Exercises the _input_offset bookkeeping across the + _communicate_io_posix factoring: a first communicate() must time out + mid-write, and a subsequent communicate() must finish delivering the + remaining bytes so the child receives the full input intact. + """ + # 1 MiB easily exceeds typical pipe buffers (~64 KiB) so writing + # blocks once the buffer fills before the child starts reading. + input_data = bytes(range(256)) * 4096 # 1 MiB, distinctive pattern + self.assertEqual(len(input_data), 1024 * 1024) + + p = subprocess.Popen( + [sys.executable, "-c", + "import sys, time; " + "time.sleep(0.5); " + "sys.stdout.buffer.write(sys.stdin.buffer.read())"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + try: + with self.assertRaises(subprocess.TimeoutExpired): + p.communicate(input_data, timeout=0.05) + + # Resume: no new input, generous timeout to avoid CI flakes. + stdout, stderr = p.communicate(timeout=support.LONG_TIMEOUT) + self.assertEqual(len(stdout), len(input_data)) + self.assertEqual(stdout, input_data) + finally: + p.kill() + p.wait() + # Test for the fd leak reported in http://bugs.python.org/issue2791. def test_communicate_pipe_fd_leak(self): for stdin_pipe in (False, True): From f27e91e37212f148b8fe72a3656a69b242625622 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sun, 26 Apr 2026 18:42:13 -0700 Subject: [PATCH 19/44] Document that multiprocessing treats local same-user processes as trusted (GH-149001) Clarify in the Authentication keys section that the authkey handshake covers Listener/Client (addressable endpoints) only, not the anonymous pipes behind Pipe() and Queue, and that isolation between same-user processes must be arranged at the OS level. --- Doc/library/multiprocessing.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst index 3ceb5e717c4825..187143d02cd7bf 100644 --- a/Doc/library/multiprocessing.rst +++ b/Doc/library/multiprocessing.rst @@ -2917,6 +2917,16 @@ between themselves. Suitable authentication keys can also be generated by using :func:`os.urandom`. +This authentication protects :class:`Listener` and :func:`Client` connections, +which are reachable by address. It is not applied to the anonymous pipes +created by :func:`~multiprocessing.Pipe` or used internally by +:class:`~multiprocessing.Queue`. +:mod:`multiprocessing` treats all local processes running as the same user as +trusted; on most operating systems such processes can access each other's pipe +file descriptors regardless. Applications that require isolation between +processes of the same user must arrange it at the operating-system level -- +for example, by running workers under a different user account or in a sandbox. + Logging ^^^^^^^ From 804c213c89366dd5ffa7feeb1bd4feccfee75b38 Mon Sep 17 00:00:00 2001 From: Micah Najacht Date: Mon, 27 Apr 2026 01:27:05 -0500 Subject: [PATCH 20/44] gh-82665 Mention that HTMLParser.handle_starttag value can be None (#134312) * Specify boolean attribute behavior in parser * Tweak wording and example Co-authored-by: Serhiy Storchaka Co-authored-by: Ezio Melotti * Fix backticks --------- Co-authored-by: Ezio Melotti Co-authored-by: Serhiy Storchaka --- Doc/library/html.parser.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Doc/library/html.parser.rst b/Doc/library/html.parser.rst index 341a8337ba2ceb..11f851d4f6c4b7 100644 --- a/Doc/library/html.parser.rst +++ b/Doc/library/html.parser.rst @@ -141,7 +141,7 @@ implementations do nothing (except for :meth:`~HTMLParser.handle_startendtag`): argument is a list of ``(name, value)`` pairs containing the attributes found inside the tag's ``<>`` brackets. The *name* will be translated to lower case, and quotes in the *value* have been removed, and character and entity references - have been replaced. + have been replaced. For empty attributes, *value* is ``None``. For instance, for the tag ````, this method would be called as ``handle_starttag('a', [('href', 'https://www.cwi.nl/')])``. @@ -317,6 +317,18 @@ without further parsing: Data : alert("hello! ☺"); End tag : script +Attribute names are converted to lowercase, quotes from attribute values removed, +and ``None`` is returned as *value* for empty attributes (such as ``checked``): + +.. doctest:: + + >>> parser.feed("") + Start tag: input + attr: ('type', 'checkbox') + attr: ('checked', None) + attr: ('required', '') + attr: ('disabled', 'disabled') + Parsing comments: .. doctest:: From 54a8921140ba98461ca915fb80a043271c275b51 Mon Sep 17 00:00:00 2001 From: Anonymous941 <36797492+Anonymous941@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:21:53 -0400 Subject: [PATCH 21/44] Fix typo in `ceval.c` error message (#148860) Fix the "multiple values for keyword argument" error message used when the function's `__qualname__` cannot be retrieved. --- Python/ceval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index 506ea591c385c0..0d7f8a62e246ae 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3409,7 +3409,7 @@ _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwarg _PyErr_Format( tstate, PyExc_TypeError, "%V got multiple values for keyword argument '%S'", - funcstr, "finction", dupkey); + funcstr, "function", dupkey); Py_XDECREF(funcstr); return; } From 62792c8f77222b86f8d2d985fc3a2b9fffc679e8 Mon Sep 17 00:00:00 2001 From: Manoj K M Date: Mon, 27 Apr 2026 12:52:20 +0530 Subject: [PATCH 22/44] gh-148868: Increase test coverage for `cmath.isinf` (#148869) --- Lib/test/test_cmath.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_cmath.py b/Lib/test/test_cmath.py index 389a3fa0e0a1eb..a986fd6b892bd2 100644 --- a/Lib/test/test_cmath.py +++ b/Lib/test/test_cmath.py @@ -516,6 +516,7 @@ def test_isinf(self): self.assertFalse(cmath.isinf(1j)) self.assertFalse(cmath.isinf(NAN)) self.assertTrue(cmath.isinf(INF)) + self.assertTrue(cmath.isinf(-INF)) self.assertTrue(cmath.isinf(complex(INF, 0))) self.assertTrue(cmath.isinf(complex(0, INF))) self.assertTrue(cmath.isinf(complex(INF, INF))) From f4a726da402fc0e18994f8b607302800db56f101 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 27 Apr 2026 15:12:34 +0200 Subject: [PATCH 23/44] GH-135357: Add socket.SO_PASSRIGHTS constant (#135355) Constant added to Linux 6.16. See the LWN article: https://lwn.net/Articles/1023085/ Co-authored-by: Brian Schubert Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Co-authored-by: Victor Stinner Co-authored-by: Peter Bierma --- Doc/library/socket.rst | 1 + .../2025-06-10-17-30-55.gh-issue-135357.sUXU1W.rst | 1 + Modules/socketmodule.c | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-06-10-17-30-55.gh-issue-135357.sUXU1W.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 71747d5f515a06..96bc9e7a0d61e3 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -486,6 +486,7 @@ The AF_* and SOCK_* constants are now :class:`AddressFamily` and .. versionchanged:: 3.15 ``IPV6_HDRINCL`` was added. + Added support for ``SO_PASSRIGHTS`` on Linux platforms when available. .. data:: AF_CAN diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-10-17-30-55.gh-issue-135357.sUXU1W.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-10-17-30-55.gh-issue-135357.sUXU1W.rst new file mode 100644 index 00000000000000..378bb59de7930a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-10-17-30-55.gh-issue-135357.sUXU1W.rst @@ -0,0 +1 @@ +Add support for :data:`!socket.SO_PASSRIGHTS` on Linux. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index f1a55db229e115..f5993fc8fdaab2 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -8276,6 +8276,9 @@ socket_exec(PyObject *m) #ifdef SO_BINDTODEVICE ADD_INT_MACRO(m, SO_BINDTODEVICE); #endif +#ifdef SO_PASSRIGHTS + ADD_INT_MACRO(m, SO_PASSRIGHTS); +#endif #ifdef SO_BINDTOIFINDEX ADD_INT_MACRO(m, SO_BINDTOIFINDEX); #endif From a386a52d8c093381792e39f93eb1df63d1584b9e Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 27 Apr 2026 17:30:35 +0200 Subject: [PATCH 24/44] Un-skip previously-broken `test_get_type_hints_modules_forwardref` (#149048) --- Lib/test/test_typing.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6c3d67fb6b7383..5d19e3706802dd 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -14,7 +14,7 @@ import re import sys import warnings -from unittest import TestCase, main, skip +from unittest import TestCase, main from unittest.mock import patch from copy import copy, deepcopy @@ -6796,11 +6796,7 @@ def test_get_type_hints_modules(self): self.assertEqual(gth(ann_module2), {}) self.assertEqual(gth(ann_module3), {}) - @skip("known bug") def test_get_type_hints_modules_forwardref(self): - # FIXME: This currently exposes a bug in typing. Cached forward references - # don't account for the case where there are multiple types of the same - # name coming from different modules in the same program. mgc_hints = {'default_a': Optional[mod_generics_cache.A], 'default_b': Optional[mod_generics_cache.B]} self.assertEqual(gth(mod_generics_cache), mgc_hints) From 276f474c9a8e8cf4ce878847466c55ac1de061c1 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 27 Apr 2026 17:34:09 +0100 Subject: [PATCH 25/44] GH-146073: Add fitness to executor dumps. (GH-148959) --- Include/internal/pycore_uop.h | 1 + Python/optimizer.c | 20 +++++++++++--------- Python/optimizer_analysis.c | 3 +++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Include/internal/pycore_uop.h b/Include/internal/pycore_uop.h index 7bc8947cfa9a9d..320508e8b7a95e 100644 --- a/Include/internal/pycore_uop.h +++ b/Include/internal/pycore_uop.h @@ -31,6 +31,7 @@ typedef struct _PyUOpInstruction{ uint64_t operand0; // A cache entry uint64_t operand1; #ifdef Py_STATS + int32_t fitness; uint64_t execution_count; #endif } _PyUOpInstruction; diff --git a/Python/optimizer.c b/Python/optimizer.c index a389c0f4072817..2ce4da0910f3c4 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -562,12 +562,13 @@ dynamic_exit_uop[MAX_UOP_ID + 1] = { static inline void add_to_trace( - _PyJitUopBuffer *trace, + _PyJitTracerState *tracer, uint16_t opcode, uint16_t oparg, uint64_t operand, uint32_t target) { + _PyJitUopBuffer *trace = &tracer->code_buffer; _PyUOpInstruction *inst = trace->next; inst->opcode = opcode; inst->format = UOP_FORMAT_TARGET; @@ -576,6 +577,7 @@ add_to_trace( inst->operand0 = operand; #ifdef Py_STATS inst->execution_count = 0; + inst->fitness = tracer->translator_state.fitness; #endif trace->next++; } @@ -583,7 +585,7 @@ add_to_trace( #ifdef Py_DEBUG #define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ - add_to_trace(trace, (OPCODE), (OPARG), (OPERAND), (TARGET)); \ + add_to_trace(tracer, (OPCODE), (OPARG), (OPERAND), (TARGET)); \ if (lltrace >= 2) { \ printf("%4d ADD_TO_TRACE: ", uop_buffer_length(trace)); \ _PyUOpPrint(uop_buffer_last(trace)); \ @@ -591,7 +593,7 @@ add_to_trace( } #else #define ADD_TO_TRACE(OPCODE, OPARG, OPERAND, TARGET) \ - add_to_trace(trace, (OPCODE), (OPARG), (OPERAND), (TARGET)) + add_to_trace(tracer, (OPCODE), (OPARG), (OPERAND), (TARGET)) #endif #define INSTR_IP(INSTR, CODE) \ @@ -1133,6 +1135,9 @@ _PyJit_TryInitializeTracing( /* Set up tracing buffer*/ _PyJitUopBuffer *trace = &tracer->code_buffer; uop_buffer_init(trace, &tracer->uop_array[0], UOP_MAX_TRACE_LENGTH); + _PyJitTracerTranslatorState *ts = &tracer->translator_state; + ts->fitness = tstate->interp->opt_config.fitness_initial; + ts->frame_depth = 0; ADD_TO_TRACE(_START_EXECUTOR, 0, (uintptr_t)start_instr, INSTR_IP(start_instr, code)); ADD_TO_TRACE(_MAKE_WARM, 0, 0, 0); @@ -1162,10 +1167,6 @@ _PyJit_TryInitializeTracing( assert(curr_instr->op.code == JUMP_BACKWARD_JIT || curr_instr->op.code == RESUME_CHECK_JIT || (exit != NULL)); tracer->initial_state.jump_backward_instr = curr_instr; - const _PyOptimizationConfig *cfg = &tstate->interp->opt_config; - _PyJitTracerTranslatorState *ts = &tracer->translator_state; - ts->fitness = cfg->fitness_initial; - ts->frame_depth = 0; DPRINTF(3, "Fitness init: chain_depth=%d, fitness=%d\n", chain_depth, ts->fitness); @@ -1300,6 +1301,7 @@ static void make_exit(_PyUOpInstruction *inst, int opcode, int target, bool is_c inst->target = target; inst->operand1 = is_control_flow; #ifdef Py_STATS + inst->fitness = 0; inst->execution_count = 0; #endif } @@ -2100,8 +2102,8 @@ write_row_for_uop(_PyExecutorObject *executor, uint32_t i, FILE *out) #ifdef Py_STATS const char *bg_color = get_background_color(inst, executor->trace[0].execution_count); const char *color = get_foreground_color(inst, executor->trace[0].execution_count); - fprintf(out, " %s  --  %" PRIu64 "\n", - i, color, bg_color, color, opname, inst->execution_count); + fprintf(out, " %s [%d] --  %" PRIu64 "\n", + i, color, bg_color, color, opname, inst->fitness, inst->execution_count); #else const char *color = (_PyUop_Uncached[inst->opcode] == _DEOPT) ? RED : BLACK; fprintf(out, " %s op0=%" PRIu64 "\n", i, color, opname, inst->operand0); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 095bcfc639bc65..9f6ce206ef4722 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -235,6 +235,9 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr, out->target = this_instr->target; out->operand0 = (operand0); out->operand1 = this_instr->operand1; +#ifdef Py_STATS + out->fitness = this_instr->fitness; +#endif ctx->out_buffer.next++; } From 3e5a3cb2bd222f97f793b01bc1c0f7bb62aefc31 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Apr 2026 21:30:48 +0300 Subject: [PATCH 26/44] gh-148529: Minor improvements of the struct module documentation (GH-148565) * Document that 's' and 'p' accept bytes and bytearray. * Fix some footnotes. * Clarify that "string" is a byte string. * Fix the module docstring. --- Doc/library/struct.rst | 44 +++++++++++++++++++++++------------------- Modules/_struct.c | 27 +++++++++++++------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index fa0fb19d852f86..f504f931f0fa20 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -227,32 +227,32 @@ platform-dependent. +--------+--------------------------+--------------------+----------------+------------+ | ``c`` | :c:expr:`char` | bytes of length 1 | 1 | | +--------+--------------------------+--------------------+----------------+------------+ -| ``b`` | :c:expr:`signed char` | integer | 1 | \(1), \(2) | +| ``b`` | :c:expr:`signed char` | int | 1 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``B`` | :c:expr:`unsigned char` | integer | 1 | \(2) | +| ``B`` | :c:expr:`unsigned char` | int | 1 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ | ``?`` | :c:expr:`_Bool` | bool | 1 | \(1) | +--------+--------------------------+--------------------+----------------+------------+ -| ``h`` | :c:expr:`short` | integer | 2 | \(2) | +| ``h`` | :c:expr:`short` | int | 2 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``H`` | :c:expr:`unsigned short` | integer | 2 | \(2) | +| ``H`` | :c:expr:`unsigned short` | int | 2 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``i`` | :c:expr:`int` | integer | 4 | \(2) | +| ``i`` | :c:expr:`int` | int | 4 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``I`` | :c:expr:`unsigned int` | integer | 4 | \(2) | +| ``I`` | :c:expr:`unsigned int` | int | 4 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``l`` | :c:expr:`long` | integer | 4 | \(2) | +| ``l`` | :c:expr:`long` | int | 4 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``L`` | :c:expr:`unsigned long` | integer | 4 | \(2) | +| ``L`` | :c:expr:`unsigned long` | int | 4 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``q`` | :c:expr:`long long` | integer | 8 | \(2) | +| ``q`` | :c:expr:`long long` | int | 8 | \(2) | +--------+--------------------------+--------------------+----------------+------------+ -| ``Q`` | :c:expr:`unsigned long | integer | 8 | \(2) | +| ``Q`` | :c:expr:`unsigned long | int | 8 | \(2) | | | long` | | | | +--------+--------------------------+--------------------+----------------+------------+ -| ``n`` | :c:type:`ssize_t` | integer | | \(3) | +| ``n`` | :c:type:`ssize_t` | int | | \(2), \(3) | +--------+--------------------------+--------------------+----------------+------------+ -| ``N`` | :c:type:`size_t` | integer | | \(3) | +| ``N`` | :c:type:`size_t` | int | | \(2), \(3) | +--------+--------------------------+--------------------+----------------+------------+ | ``e`` | :c:expr:`_Float16` | float | 2 | \(4), \(6) | +--------+--------------------------+--------------------+----------------+------------+ @@ -268,7 +268,7 @@ platform-dependent. +--------+--------------------------+--------------------+----------------+------------+ | ``p`` | :c:expr:`char[]` | bytes | | \(8) | +--------+--------------------------+--------------------+----------------+------------+ -| ``P`` | :c:expr:`void \*` | integer | | \(5) | +| ``P`` | :c:expr:`void \*` | int | | \(2), \(5) | +--------+--------------------------+--------------------+----------------+------------+ .. versionchanged:: 3.3 @@ -342,27 +342,31 @@ Notes: The ``'p'`` format character encodes a "Pascal string", meaning a short variable-length string stored in a *fixed number of bytes*, given by the count. The first byte stored is the length of the string, or 255, whichever is - smaller. The bytes of the string follow. If the string passed in to + smaller. The bytes of the string follow. If the byte string passed in to :func:`pack` is too long (longer than the count minus 1), only the leading - ``count-1`` bytes of the string are stored. If the string is shorter than + ``count-1`` bytes of the string are stored. If the byte string is shorter than ``count-1``, it is padded with null bytes so that exactly count bytes in all are used. Note that for :func:`unpack`, the ``'p'`` format character consumes - ``count`` bytes, but that the string returned can never contain more than 255 + ``count`` bytes, but that the :class:`!bytes` object returned can never contain more than 255 bytes. + When packing, arguments of types :class:`bytes` and :class:`bytearray` + are accepted. (9) For the ``'s'`` format character, the count is interpreted as the length of the - bytes, not a repeat count like for the other format characters; for example, + byte string, not a repeat count like for the other format characters; for example, ``'10s'`` means a single 10-byte string mapping to or from a single Python byte string, while ``'10c'`` means 10 separate one byte character elements (e.g., ``cccccccccc``) mapping to or from ten different Python byte objects. (See :ref:`struct-examples` for a concrete demonstration of the difference.) - If a count is not given, it defaults to 1. For packing, the string is + If a count is not given, it defaults to 1. For packing, the byte string is truncated or padded with null bytes as appropriate to make it fit. For - unpacking, the resulting bytes object always has exactly the specified number - of bytes. As a special case, ``'0s'`` means a single, empty string (while + unpacking, the resulting :class:`!bytes` object always has exactly the specified number + of bytes. As a special case, ``'0s'`` means a single, empty byte string (while ``'0c'`` means 0 characters). + When packing, arguments of types :class:`bytes` and :class:`bytearray` + are accepted. (10) For the ``'F'`` and ``'D'`` format characters, the packed representation uses diff --git a/Modules/_struct.c b/Modules/_struct.c index c235e27a415543..47119cb3961e3c 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1,7 +1,7 @@ /* struct module -- pack values into and (out of) bytes objects */ /* New version supporting byte order, alignment and size options, - character strings, and unsigned numbers */ + byte strings, and unsigned numbers */ #ifndef Py_BUILD_CORE_BUILTIN # define Py_BUILD_CORE_MODULE 1 @@ -2297,7 +2297,7 @@ Struct_iter_unpack_impl(PyStructObject *self, PyObject *buffer) * * Takes a struct object, a tuple of arguments, and offset in that tuple of * argument for where to start processing the arguments for packing, and a - * character buffer for writing the packed string. The caller must insure + * character buffer for writing the packed data. The caller must ensure * that the buffer may contain the required length for packing the arguments. * 0 is returned on success, 1 is returned if there is an error. * @@ -2774,8 +2774,8 @@ static struct PyMethodDef module_functions[] = { PyDoc_STRVAR(module_doc, "Functions to convert between Python values and C structs.\n\ -Python bytes objects are used to hold the data representing the C struct\n\ -and also as format strings (explained below) to describe the layout of data\n\ +Python bytes objects are used to hold the data representing the C struct.\n\ +The format string (explained below) describes the layout of data\n\ in the C struct.\n\ \n\ The optional first format char indicates byte order, size and alignment:\n\ @@ -2785,19 +2785,18 @@ The optional first format char indicates byte order, size and alignment:\n\ >: big-endian, std. size & alignment\n\ !: same as >\n\ \n\ -The remaining chars indicate types of args and must match exactly;\n\ +The remaining characters indicate types of args and must match exactly;\n\ these can be preceded by a decimal repeat count:\n\ - x: pad byte (no data); c:char; b:signed byte; B:unsigned byte;\n\ - ?:_Bool; h:short; H:unsigned short; i:int; I:unsigned int;\n\ - l:long; L:unsigned long; f:float; d:double; e:half-float.\n\ - F:float complex; D:double complex.\n\ + x: pad byte (no data); c: char; b: signed byte; B: unsigned byte;\n\ + ?: _Bool; h: short; H: unsigned short; i: int; I: unsigned int;\n\ + l: long; L: unsigned long; q: long long; Q: unsigned long long;\n\ + f: float; d: double; e: half-float;\n\ + F: float complex; D: double complex.\n\ Special cases (preceding decimal count indicates length):\n\ - s:string (array of char); p: pascal string (with count byte).\n\ + s: byte string (array of char); p: Pascal string (with count byte).\n\ Special cases (only available in native format):\n\ - n:ssize_t; N:size_t;\n\ - P:an integer type that is wide enough to hold a pointer.\n\ -Special case (not in native mode unless 'long long' in platform C):\n\ - q:long long; Q:unsigned long long\n\ + n: ssize_t; N: size_t;\n\ + P: an integer type that is wide enough to hold a pointer.\n\ Whitespace between formats is ignored.\n\ \n\ The variable struct.error is an exception raised on errors.\n"); From fc829e88753858c8ac669594bf0093f44948c0f4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 27 Apr 2026 21:43:15 +0300 Subject: [PATCH 27/44] gh-146581: Fix vulnerability in shutil.unpack_archive() for ZIP files on Windows (GH-146591) Use ZipFile.extractall() to sanitize file names and extract files. Files with invalid names (e.g. absolute paths) are now skipped. Files containing ".." in the name are no longer skipped. --- Lib/shutil.py | 24 +------ Lib/test/test_shutil.py | 67 ++++++++++++++++++- Lib/zipfile/__init__.py | 21 ++++-- ...-03-29-12-51-33.gh-issue-146581.4vZfB0.rst | 5 ++ 4 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst diff --git a/Lib/shutil.py b/Lib/shutil.py index 44ccdbb503d4fb..45cbe4c855b462 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1317,27 +1317,9 @@ def _unpack_zipfile(filename, extract_dir): if not zipfile.is_zipfile(filename): raise ReadError("%s is not a zip file" % filename) - zip = zipfile.ZipFile(filename) - try: - for info in zip.infolist(): - name = info.filename - - # don't extract absolute paths or ones with .. in them - if name.startswith('/') or '..' in name: - continue - - targetpath = os.path.join(extract_dir, *name.split('/')) - if not targetpath: - continue - - _ensure_directory(targetpath) - if not name.endswith('/'): - # file - with zip.open(name, 'r') as source, \ - open(targetpath, 'wb') as target: - copyfileobj(source, target) - finally: - zip.close() + with zipfile.ZipFile(filename) as zip: + zip._ignore_invalid_names = True + zip.extractall(extract_dir) def _unpack_tarfile(filename, extract_dir, *, filter=None): """Unpack tar/tar.gz/tar.bz2/tar.xz/tar.zst `filename` to `extract_dir` diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index a4bd113bc7f1fc..13a3487382dfcf 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2136,8 +2136,6 @@ def test_make_zipfile_rootdir_nodir(self): def check_unpack_archive(self, format, **kwargs): self.check_unpack_archive_with_converter( format, lambda path: path, **kwargs) - self.check_unpack_archive_with_converter( - format, FakePath, **kwargs) self.check_unpack_archive_with_converter(format, FakePath, **kwargs) def check_unpack_archive_with_converter(self, format, converter, **kwargs): @@ -2194,6 +2192,71 @@ def test_unpack_archive_zip(self): with self.assertRaises(TypeError): self.check_unpack_archive('zip', filter='data') + def test_unpack_archive_zip_badpaths(self): + srcdir = self.mkdtemp() + zipname = os.path.join(srcdir, 'test.zip') + abspath = os.path.join(srcdir, 'abspath') + with zipfile.ZipFile(zipname, 'w') as zf: + zf.writestr(abspath, 'badfile') + zf.writestr(os.sep + abspath, 'badfile') + zf.writestr('/abspath', 'badfile') + zf.writestr('C:/abspath', 'badfile') + zf.writestr('D:\\abspath', 'badfile') + zf.writestr('E:abspath', 'badfile') + zf.writestr('F:/G:/abspath', 'badfile') + zf.writestr('//server/share/abspath', 'badfile') + zf.writestr('\\\\server2\\share\\abspath', 'badfile') + zf.writestr('../relpath', 'badfile') + zf.writestr(os.pardir + os.sep + 'relpath2', 'badfile') + zf.writestr('good/file', 'goodfile') + zf.writestr('good..file', 'goodfile') + + dstdir = os.path.join(self.mkdtemp(), 'dst') + unpack_archive(zipname, dstdir) + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'good', 'file'))) + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'good..file'))) + self.assertFalse(os.path.exists(abspath)) + self.assertFalse(os.path.exists(os.path.join(dstdir, 'abspath'))) + self.assertFalse(os.path.exists(os.path.join(dstdir, 'G_'))) + self.assertFalse(os.path.exists(os.path.join(dstdir, 'server'))) + if os.name != 'nt': + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'C:', 'abspath'))) + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'D:\\abspath'))) + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'E:abspath'))) + self.assertTrue(os.path.isfile(os.path.join(dstdir, 'F:', 'G:', 'abspath'))) + self.assertTrue(os.path.isfile(os.path.join(dstdir, '\\\\server2\\share\\abspath'))) + if os.pardir == '..': + self.assertFalse(os.path.exists(os.path.join(dstdir, '..', 'relpath'))) + self.assertFalse(os.path.exists(os.path.join(dstdir, 'relpath'))) + else: + self.assertTrue(os.path.isfile(os.path.join(dstdir, '..', 'relpath'))) + self.assertFalse(os.path.exists(os.path.join(dstdir, os.pardir, 'relpath2'))) + self.assertFalse(os.path.exists(os.path.join(dstdir, 'relpath2'))) + + dstdir2 = os.path.join(self.mkdtemp(), 'dst') + os.mkdir(dstdir2) + with os_helper.change_cwd(dstdir2): + unpack_archive(zipname, '') + self.assertTrue(os.path.isfile(os.path.join('good', 'file'))) + self.assertTrue(os.path.isfile('good..file')) + self.assertFalse(os.path.exists(abspath)) + self.assertFalse(os.path.exists('abspath')) + self.assertFalse(os.path.exists('C_')) + self.assertFalse(os.path.exists('server')) + if os.name != 'nt': + self.assertTrue(os.path.isfile(os.path.join('C:', 'abspath'))) + self.assertTrue(os.path.isfile('D:\\abspath')) + self.assertTrue(os.path.isfile('E:abspath')) + self.assertTrue(os.path.isfile(os.path.join('F:', 'G:', 'abspath'))) + self.assertTrue(os.path.isfile('\\\\server2\\share\\abspath')) + if os.pardir == '..': + self.assertFalse(os.path.exists(os.path.join('..', 'relpath'))) + self.assertFalse(os.path.exists('relpath')) + else: + self.assertTrue(os.path.isfile(os.path.join('..', 'relpath'))) + self.assertFalse(os.path.exists(os.path.join(os.pardir, 'relpath2'))) + self.assertFalse(os.path.exists('relpath2')) + def test_unpack_registry(self): formats = get_unpack_formats() diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 51e0ce9fa36d7e..1e0cc5f6234f28 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -1410,6 +1410,7 @@ class ZipFile: fp = None # Set here since __del__ checks it _windows_illegal_name_trans_table = None + _ignore_invalid_names = False def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, compresslevel=None, *, strict_timestamps=True, metadata_encoding=None): @@ -1890,21 +1891,31 @@ def _extract_member(self, member, targetpath, pwd): # build the destination pathname, replacing # forward slashes to platform specific separators. - arcname = member.filename.replace('/', os.path.sep) - - if os.path.altsep: + arcname = member.filename + if os.path.sep != '/': + arcname = arcname.replace('/', os.path.sep) + if os.path.altsep and os.path.altsep != '/': arcname = arcname.replace(os.path.altsep, os.path.sep) # interpret absolute pathname as relative, remove drive letter or # UNC path, redundant separators, "." and ".." components. - arcname = os.path.splitdrive(arcname)[1] + drive, root, arcname = os.path.splitroot(arcname) + if self._ignore_invalid_names and (drive or root): + return None + if self._ignore_invalid_names and os.path.pardir in arcname.split(os.path.sep): + return None invalid_path_parts = ('', os.path.curdir, os.path.pardir) arcname = os.path.sep.join(x for x in arcname.split(os.path.sep) if x not in invalid_path_parts) if os.path.sep == '\\': # filter illegal characters on Windows - arcname = self._sanitize_windows_name(arcname, os.path.sep) + arcname2 = self._sanitize_windows_name(arcname, os.path.sep) + if self._ignore_invalid_names and arcname2 != arcname: + return None + arcname = arcname2 if not arcname and not member.is_dir(): + if self._ignore_invalid_names: + return None raise ValueError("Empty filename.") targetpath = os.path.join(targetpath, arcname) diff --git a/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst b/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst new file mode 100644 index 00000000000000..98e65549d79016 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst @@ -0,0 +1,5 @@ +Fix vulnerability in :func:`shutil.unpack_archive` for ZIP files on Windows +which allowed to write files outside of the destination tree if the patch in +the archive contains a Windows drive prefix. Now such invalid paths will be +skipped. Files containing ".." in the name (like "foo..bar") are no longer +skipped. From 005555a3f0ae20ee8154eb4ee172e1e355144c8c Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Mon, 27 Apr 2026 21:22:35 +0100 Subject: [PATCH 28/44] gh-149017: Upgrade bundled Expat to 2.8.0 (#149020) --- ...-04-26-17-49-58.gh-issue-149017.EiVFPo.rst | 1 + Misc/sbom.spdx.json | 36 +- Modules/expat/expat.h | 16 +- Modules/expat/expat_config.h | 5 + Modules/expat/expat_external.h | 5 +- Modules/expat/internal.h | 4 +- Modules/expat/refresh.sh | 18 +- Modules/expat/xmlparse.c | 318 +++++++----------- Modules/expat/xmlrole.c | 2 +- Modules/expat/xmltok.c | 2 +- Modules/expat/xmltok_ns.c | 2 +- 11 files changed, 180 insertions(+), 229 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-04-26-17-49-58.gh-issue-149017.EiVFPo.rst diff --git a/Misc/NEWS.d/next/Security/2026-04-26-17-49-58.gh-issue-149017.EiVFPo.rst b/Misc/NEWS.d/next/Security/2026-04-26-17-49-58.gh-issue-149017.EiVFPo.rst new file mode 100644 index 00000000000000..6aa7efb68a1981 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-04-26-17-49-58.gh-issue-149017.EiVFPo.rst @@ -0,0 +1 @@ +Update bundled `libexpat `_ to version 2.8.0. diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index ed9c08016808bb..aaeffd58e799ed 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -48,11 +48,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "9dfd09a3be37618cbcea380c2374b2b8f0288f57" + "checksumValue": "5343adc95840915b022b1d4524d0acb66b369ba2" }, { "algorithm": "SHA256", - "checksumValue": "26805a0d1a7a6a5cd8ead9cf7f4da29f63f0547a9ad41e80dba4ed9fe1943140" + "checksumValue": "1ec3bad08b6864c2c479e1fd941038c2dcd24c6d9a16400f4da54912d95aa321" } ], "fileName": "Modules/expat/expat.h" @@ -62,11 +62,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "da0328279276800cc747ea7da23886a3f402ccb3" + "checksumValue": "d8f9211d52ff0384e229e4d4d56adae5db2d7f91" }, { "algorithm": "SHA256", - "checksumValue": "15a80e414e9e7c43edba64b1608a77c724387070138693f9e9bcca49c78a2df7" + "checksumValue": "b77f8192baf90aaa41f7023bc68fd1f22ab2552f98758271a1e090544537def5" } ], "fileName": "Modules/expat/expat_external.h" @@ -90,11 +90,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "6a4a232233ba1034c3f2b459159d502e9b2d413b" + "checksumValue": "2555e70b29c1efc0af40879daafd12f8b36aca2c" }, { "algorithm": "SHA256", - "checksumValue": "c803935722f0dbdeeede7f040028fb119135e96dfad949479f8a5304b885bdd6" + "checksumValue": "4feb1df53898a48ae0ae04b5d0352c90395c8e693e5c2675f8ced41903d6fa94" } ], "fileName": "Modules/expat/internal.h" @@ -174,11 +174,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "0c74fbd48dd515c58eeb65b7e71b29da94be4694" + "checksumValue": "cb0af01558ec7b6474d2bd0c9386380c82618e8f" }, { "algorithm": "SHA256", - "checksumValue": "861e7a50ce81f9f16b42d32a9caa4f817d962b274b2929b579511c6f76d348d4" + "checksumValue": "6745a6b8cdd7344d4bd8f27f605363ed746e57ff02d4ebce3eb1806579cd030f" } ], "fileName": "Modules/expat/xmlparse.c" @@ -188,11 +188,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "7cff4d7210f046144f5fa635113f9c26f30fe3d3" + "checksumValue": "c8769fcb93f00272a6e6ca560be633649c817ff7" }, { "algorithm": "SHA256", - "checksumValue": "eaa6c327f9db4a5cec768d0c01927fea212d3ef4d4f970ebc0a98b9f3602784c" + "checksumValue": "5b81f0eb0e144b611dbd1bc9e6037075a16bff94f823d57a81eb2a3e4999e91a" } ], "fileName": "Modules/expat/xmlrole.c" @@ -216,11 +216,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "48b7aa6503302d4157c61a8763629f3236c23502" + "checksumValue": "63e4766a09e63760c6518670509198f8d638f4ad" }, { "algorithm": "SHA256", - "checksumValue": "75da65603e99837fd3116f1453372efd556f9f97d8de73364594dd78b3c8ec54" + "checksumValue": "0ad3f915f2748dc91bf4e4b4a50cf40bf2c95769d0eca7e3b293a230d82bb779" } ], "fileName": "Modules/expat/xmltok.c" @@ -272,11 +272,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "705842f8a09b09cc021d82a71ab03344bfd07b0a" + "checksumValue": "41b8c8fc275882c76d4210b7d40a18e506b07147" }, { "algorithm": "SHA256", - "checksumValue": "f95a2b4b7efda40f5faf366537cb20a57dddbad9655859d2e304f5e75f6907cc" + "checksumValue": "b2188c7e5fa5b33e355cf6cf342dfb8f6e23859f2a6b1ddf79841d7f84f7b196" } ], "fileName": "Modules/expat/xmltok_ns.c" @@ -1730,14 +1730,14 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "9931f9860d18e6cf72d183eb8f309bfb96196c00e1d40caa978e95bc9aa978b6" + "checksumValue": "c7cec5f60ea3a42e7780781c6745255c19aa3dbfeeae58646b7132f88dc24780" } ], - "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_7_5/expat-2.7.5.tar.gz", + "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_8_0/expat-2.8.0.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.7.5:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.8.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], @@ -1745,7 +1745,7 @@ "name": "expat", "originator": "Organization: Expat development team", "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.7.5" + "versionInfo": "2.8.0" }, { "SPDXID": "SPDXRef-PACKAGE-hacl-star", diff --git a/Modules/expat/expat.h b/Modules/expat/expat.h index 18dbaebde293bc..79c609f19aa4cf 100644 --- a/Modules/expat/expat.h +++ b/Modules/expat/expat.h @@ -45,6 +45,7 @@ #ifndef Expat_INCLUDED # define Expat_INCLUDED 1 +# include // for uint8_t # include # include "expat_external.h" @@ -917,10 +918,21 @@ XML_SetParamEntityParsing(XML_Parser parser, function behavior. This must be called before parsing is started. Returns 1 if successful, 0 when called after parsing has started. Note: If parser == NULL, the function will do nothing and return 0. + DEPRECATED since Expat 2.8.0. */ XMLPARSEAPI(int) XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt); +/* Sets the hash salt to use for internal hash calculations. + Helps in preventing DoS attacks based on predicting hash function behavior. + This must be called before parsing is started. + Returns XML_TRUE if successful, XML_FALSE when called after parsing has + started or when parser is NULL. + Added in Expat 2.8.0. +*/ +XMLPARSEAPI(XML_Bool) +XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]); + /* If XML_Parse or XML_ParseBuffer have returned XML_STATUS_ERROR, then XML_GetErrorCode returns information about the error. */ @@ -1081,8 +1093,8 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); See https://semver.org */ # define XML_MAJOR_VERSION 2 -# define XML_MINOR_VERSION 7 -# define XML_MICRO_VERSION 5 +# define XML_MINOR_VERSION 8 +# define XML_MICRO_VERSION 0 # ifdef __cplusplus } diff --git a/Modules/expat/expat_config.h b/Modules/expat/expat_config.h index 09d3161dbc0fb2..70df73c8e00a5f 100644 --- a/Modules/expat/expat_config.h +++ b/Modules/expat/expat_config.h @@ -22,5 +22,10 @@ // bpo-30947: Python uses best available entropy sources to // call XML_SetHashSalt(), expat entropy sources are not needed #define XML_POOR_ENTROPY 1 +#undef HAVE_ARC4RANDOM +#undef HAVE_ARC4RANDOM_BUF +#undef HAVE_GETENTROPY +#undef HAVE_GETRANDOM +#undef HAVE_SYSCALL_GETRANDOM #endif /* EXPAT_CONFIG_H */ diff --git a/Modules/expat/expat_external.h b/Modules/expat/expat_external.h index cf4d445e68b00c..cc945c424e471f 100644 --- a/Modules/expat/expat_external.h +++ b/Modules/expat/expat_external.h @@ -12,9 +12,10 @@ Copyright (c) 2001-2002 Greg Stein Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2016 Cristian Rodríguez - Copyright (c) 2016-2026 Sebastian Pipping + Copyright (c) 2016-2025 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2018 Yury Gribov + Copyright (c) 2026 Matthew Fernandez Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -48,7 +49,7 @@ /* Expat tries very hard to make the API boundary very specifically defined. There are two macros defined to control this boundary; each of these can be defined before including this header to - achieve some different behavior, but doing so it not recommended or + achieve some different behavior, but doing so is not recommended or tested frequently. XMLCALL - The calling convention to use for all calls across the diff --git a/Modules/expat/internal.h b/Modules/expat/internal.h index 61266ebb7723d1..420d4217a569b1 100644 --- a/Modules/expat/internal.h +++ b/Modules/expat/internal.h @@ -28,7 +28,7 @@ Copyright (c) 2002-2003 Fred L. Drake, Jr. Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2003 Greg Stein - Copyright (c) 2016-2025 Sebastian Pipping + Copyright (c) 2016-2026 Sebastian Pipping Copyright (c) 2018 Yury Gribov Copyright (c) 2019 David Loffredo Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow @@ -113,6 +113,7 @@ #if defined(_WIN32) \ && (! defined(__USE_MINGW_ANSI_STDIO) \ || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0)) +# define EXPAT_FMT_LLX(midpart) "%" midpart "I64x" # define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" # if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" @@ -122,6 +123,7 @@ # define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" # endif #else +# define EXPAT_FMT_LLX(midpart) "%" midpart "llx" # define EXPAT_FMT_ULL(midpart) "%" midpart "llu" # if ! defined(ULONG_MAX) # error Compiler did not define ULONG_MAX for us diff --git a/Modules/expat/refresh.sh b/Modules/expat/refresh.sh index 779929fc6ed33c..774e0b89d94c0e 100755 --- a/Modules/expat/refresh.sh +++ b/Modules/expat/refresh.sh @@ -12,9 +12,9 @@ fi # Update this when updating to a new version after verifying that the changes # the update brings in are good. These values are used for verifying the SBOM, too. -expected_libexpat_tag="R_2_7_5" -expected_libexpat_version="2.7.5" -expected_libexpat_sha256="9931f9860d18e6cf72d183eb8f309bfb96196c00e1d40caa978e95bc9aa978b6" +expected_libexpat_tag="R_2_8_0" +expected_libexpat_version="2.8.0" +expected_libexpat_sha256="c7cec5f60ea3a42e7780781c6745255c19aa3dbfeeae58646b7132f88dc24780" expat_dir="$(realpath "$(dirname -- "${BASH_SOURCE[0]}")")" cd ${expat_dir} @@ -64,6 +64,18 @@ This may be due to source changes and will require updating this script" >&2 exit 1 fi +# Step 4: Skip the Windows rand_s entropy path in xmlparse.c when +# XML_POOR_ENTROPY is set. +sed -z -i 's|#if defined(_WIN32)\n# include "random_rand_s\.h"\n#endif /\* defined(_WIN32) \*/|#if defined(_WIN32) \&\& ! defined(XML_POOR_ENTROPY)\n# include "random_rand_s.h"\n#endif /* defined(_WIN32) \&\& ! defined(XML_POOR_ENTROPY) */|' xmlparse.c +sed -z -i 's|# ifdef _WIN32\n if (writeRandomBytes_rand_s|# if defined(_WIN32) \&\& ! defined(XML_POOR_ENTROPY)\n if (writeRandomBytes_rand_s|' xmlparse.c + +if ! grep -q '#if defined(_WIN32) && ! defined(XML_POOR_ENTROPY)' xmlparse.c; then + echo " +Error: rand_s gate not patched in xmlparse.c; +This may be due to source changes and will require updating this script" >&2 + exit 1 +fi + echo ' Updated! next steps: - Verify all is okay: diff --git a/Modules/expat/xmlparse.c b/Modules/expat/xmlparse.c index 0248b6651ffbff..e6842f3f0bf750 100644 --- a/Modules/expat/xmlparse.c +++ b/Modules/expat/xmlparse.c @@ -1,4 +1,4 @@ -/* 93c1caa66e2b0310459482516af05505b57c5cb7b96df777105308fc585c85d1 (2.7.5+) +/* a5d18f6a50f536615ac1c70304f87d94f99cc85a86b502188952440610ccf0f8 (2.8.0+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -41,10 +41,12 @@ Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow Copyright (c) 2024-2025 Berkay Eren Ürün Copyright (c) 2024 Hanno Böck - Copyright (c) 2025 Matthew Fernandez + Copyright (c) 2025-2026 Matthew Fernandez Copyright (c) 2025 Atrem Borovik Copyright (c) 2025 Alfonso Gregory Copyright (c) 2026 Rosen Penev + Copyright (c) 2026 Francesco Bertolaccini + Copyright (c) 2026 Christian Ng Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -84,28 +86,16 @@ # error XML_CONTEXT_BYTES must be defined, non-empty and >=0 (0 to disable, >=1 to enable; 1024 is a common default) #endif -#if defined(HAVE_SYSCALL_GETRANDOM) -# if ! defined(_GNU_SOURCE) -# define _GNU_SOURCE 1 /* syscall prototype */ -# endif -#endif - -#ifdef _WIN32 -/* force stdlib to define rand_s() */ -# if ! defined(_CRT_RAND_S) -# define _CRT_RAND_S -# endif -#endif - #include #include #include /* memset(), memcpy() */ #include #include /* INT_MAX, UINT_MAX */ #include /* fprintf */ -#include /* getenv, rand_s */ +#include /* getenv */ #include /* SIZE_MAX, uintptr_t */ #include /* isnan */ +#include #ifdef _WIN32 # define getpid GetCurrentProcessId @@ -125,26 +115,34 @@ #include "expat.h" #include "siphash.h" +#if defined(HAVE_ARC4RANDOM) +# include "random_arc4random.h" +#endif /* defined(HAVE_ARC4RANDOM) */ + +#if defined(HAVE_ARC4RANDOM_BUF) +# include "random_arc4random_buf.h" +#endif // defined(HAVE_ARC4RANDOM_BUF) + +#if defined(XML_DEV_URANDOM) +# include "random_dev_urandom.h" +#endif /* defined(XML_DEV_URANDOM) */ + +#if defined(HAVE_GETENTROPY) +# include "random_getentropy.h" +#endif // defined(HAVE_GETENTROPY) + #if defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) -# if defined(HAVE_GETRANDOM) -# include /* getrandom */ -# else -# include /* syscall */ -# include /* SYS_getrandom */ -# endif -# if ! defined(GRND_NONBLOCK) -# define GRND_NONBLOCK 0x0001 -# endif /* defined(GRND_NONBLOCK) */ -#endif /* defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) */ +# include "random_getrandom.h" +#endif /* defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) */ -#if defined(_WIN32) && ! defined(LOAD_LIBRARY_SEARCH_SYSTEM32) -# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 -#endif +#if defined(_WIN32) && ! defined(XML_POOR_ENTROPY) +# include "random_rand_s.h" +#endif /* defined(_WIN32) && ! defined(XML_POOR_ENTROPY) */ #if ! defined(HAVE_GETRANDOM) && ! defined(HAVE_SYSCALL_GETRANDOM) \ && ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) \ - && ! defined(XML_DEV_URANDOM) && ! defined(_WIN32) \ - && ! defined(XML_POOR_ENTROPY) + && ! defined(HAVE_GETENTROPY) && ! defined(XML_DEV_URANDOM) \ + && ! defined(_WIN32) && ! defined(XML_POOR_ENTROPY) # error You do not have support for any sources of high quality entropy \ enabled. For end user security, that is probably not what you want. \ \ @@ -153,10 +151,11 @@ * Linux >=3.17 + glibc (including <2.25) (syscall SYS_getrandom): HAVE_SYSCALL_GETRANDOM, \ * BSD / macOS >=10.7 / glibc >=2.36 (arc4random_buf): HAVE_ARC4RANDOM_BUF, \ * BSD / macOS (including <10.7) / glibc >=2.36 (arc4random): HAVE_ARC4RANDOM, \ + * BSD / macOS >=10.12 / glibc >=2.25 (getentropy): HAVE_GETENTROPY, \ * Linux (including <3.17) / BSD / macOS (including <10.7) / Solaris >=8 (/dev/urandom): XML_DEV_URANDOM, \ * Windows >=Vista (rand_s): _WIN32. \ \ - If insist on not using any of these, bypass this error by defining \ + If you insist on not using any of these, bypass this error by defining \ XML_POOR_ENTROPY; you have been warned. \ \ If you have reasons to patch this detection code away or need changes \ @@ -604,7 +603,7 @@ static ELEMENT_TYPE *getElementType(XML_Parser parser, const ENCODING *enc, static XML_Char *copyString(const XML_Char *s, XML_Parser parser); -static unsigned long generate_hash_secret_salt(XML_Parser parser); +static struct sipkey generate_hash_secret_salt(void); static XML_Bool startParsing(XML_Parser parser); static XML_Parser parserCreate(const XML_Char *encodingName, @@ -777,7 +776,8 @@ struct XML_ParserStruct { XML_Bool m_useForeignDTD; enum XML_ParamEntityParsing m_paramEntityParsing; #endif - unsigned long m_hash_secret_salt; + struct sipkey m_hash_secret_salt_128; + XML_Bool m_hash_secret_salt_set; #if XML_GE == 1 ACCOUNTING m_accounting; MALLOC_TRACKER m_alloc_tracker; @@ -1036,135 +1036,6 @@ static const XML_Char implicitContext[] ASCII_s, ASCII_p, ASCII_a, ASCII_c, ASCII_e, '\0'}; -/* To avoid warnings about unused functions: */ -#if ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) - -# if defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) - -/* Obtain entropy on Linux 3.17+ */ -static int -writeRandomBytes_getrandom_nonblock(void *target, size_t count) { - int success = 0; /* full count bytes written? */ - size_t bytesWrittenTotal = 0; - const unsigned int getrandomFlags = GRND_NONBLOCK; - - do { - void *const currentTarget = (void *)((char *)target + bytesWrittenTotal); - const size_t bytesToWrite = count - bytesWrittenTotal; - - assert(bytesToWrite <= INT_MAX); - - const int bytesWrittenMore = -# if defined(HAVE_GETRANDOM) - (int)getrandom(currentTarget, bytesToWrite, getrandomFlags); -# else - (int)syscall(SYS_getrandom, currentTarget, bytesToWrite, - getrandomFlags); -# endif - - if (bytesWrittenMore > 0) { - bytesWrittenTotal += bytesWrittenMore; - if (bytesWrittenTotal >= count) - success = 1; - } - } while (! success && (errno == EINTR)); - - return success; -} - -# endif /* defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) */ - -# if ! defined(_WIN32) && defined(XML_DEV_URANDOM) - -/* Extract entropy from /dev/urandom */ -static int -writeRandomBytes_dev_urandom(void *target, size_t count) { - int success = 0; /* full count bytes written? */ - size_t bytesWrittenTotal = 0; - - const int fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) { - return 0; - } - - do { - void *const currentTarget = (void *)((char *)target + bytesWrittenTotal); - const size_t bytesToWrite = count - bytesWrittenTotal; - - const ssize_t bytesWrittenMore = read(fd, currentTarget, bytesToWrite); - - if (bytesWrittenMore > 0) { - bytesWrittenTotal += bytesWrittenMore; - if (bytesWrittenTotal >= count) - success = 1; - } - } while (! success && (errno == EINTR)); - - close(fd); - return success; -} - -# endif /* ! defined(_WIN32) && defined(XML_DEV_URANDOM) */ - -#endif /* ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) */ - -#if defined(HAVE_ARC4RANDOM) && ! defined(HAVE_ARC4RANDOM_BUF) - -static void -writeRandomBytes_arc4random(void *target, size_t count) { - size_t bytesWrittenTotal = 0; - - while (bytesWrittenTotal < count) { - const uint32_t random32 = arc4random(); - size_t i = 0; - - for (; (i < sizeof(random32)) && (bytesWrittenTotal < count); - i++, bytesWrittenTotal++) { - const uint8_t random8 = (uint8_t)(random32 >> (i * 8)); - ((uint8_t *)target)[bytesWrittenTotal] = random8; - } - } -} - -#endif /* defined(HAVE_ARC4RANDOM) && ! defined(HAVE_ARC4RANDOM_BUF) */ - -#ifdef _WIN32 - -/* Provide declaration of rand_s() for MinGW-32 (not 64, which has it), - as it didn't declare it in its header prior to version 5.3.0 of its - runtime package (mingwrt, containing stdlib.h). The upstream fix - was introduced at https://osdn.net/projects/mingw/ticket/39658 . */ -# if defined(__MINGW32__) && defined(__MINGW32_VERSION) \ - && __MINGW32_VERSION < 5003000L && ! defined(__MINGW64_VERSION_MAJOR) -__declspec(dllimport) int rand_s(unsigned int *); -# endif - -/* Obtain entropy on Windows using the rand_s() function which - * generates cryptographically secure random numbers. Internally it - * uses RtlGenRandom API which is present in Windows XP and later. - */ -static int -writeRandomBytes_rand_s(void *target, size_t count) { - size_t bytesWrittenTotal = 0; - - while (bytesWrittenTotal < count) { - unsigned int random32 = 0; - size_t i = 0; - - if (rand_s(&random32)) - return 0; /* failure */ - - for (; (i < sizeof(random32)) && (bytesWrittenTotal < count); - i++, bytesWrittenTotal++) { - const uint8_t random8 = (uint8_t)(random32 >> (i * 8)); - ((uint8_t *)target)[bytesWrittenTotal] = random8; - } - } - return 1; /* success */ -} - -#endif /* _WIN32 */ - #if ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) static unsigned long @@ -1192,69 +1063,70 @@ gather_time_entropy(void) { #endif /* ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) */ -static unsigned long -ENTROPY_DEBUG(const char *label, unsigned long entropy) { +static struct sipkey +ENTROPY_DEBUG(const char *label, struct sipkey entropy_128) { if (getDebugLevel("EXPAT_ENTROPY_DEBUG", 0) >= 1u) { - fprintf(stderr, "expat: Entropy: %s --> 0x%0*lx (%lu bytes)\n", label, - (int)sizeof(entropy) * 2, entropy, (unsigned long)sizeof(entropy)); + fprintf(stderr, + "expat: Entropy: %s --> [0x" EXPAT_FMT_LLX( + "016") ", 0x" EXPAT_FMT_LLX("016") "] (16 bytes)\n", + label, (unsigned long long)entropy_128.k[0], + (unsigned long long)entropy_128.k[1]); } - return entropy; + return entropy_128; } -static unsigned long -generate_hash_secret_salt(XML_Parser parser) { - unsigned long entropy; - (void)parser; +static struct sipkey +generate_hash_secret_salt(void) { + struct sipkey entropy; /* "Failproof" high quality providers: */ #if defined(HAVE_ARC4RANDOM_BUF) - arc4random_buf(&entropy, sizeof(entropy)); + writeRandomBytes_arc4random_buf(&entropy, sizeof(entropy)); return ENTROPY_DEBUG("arc4random_buf", entropy); #elif defined(HAVE_ARC4RANDOM) - writeRandomBytes_arc4random((void *)&entropy, sizeof(entropy)); + writeRandomBytes_arc4random(&entropy, sizeof(entropy)); return ENTROPY_DEBUG("arc4random", entropy); #else /* Try high quality providers first .. */ -# ifdef _WIN32 - if (writeRandomBytes_rand_s((void *)&entropy, sizeof(entropy))) { +# if defined(_WIN32) && ! defined(XML_POOR_ENTROPY) + if (writeRandomBytes_rand_s(&entropy, sizeof(entropy))) { return ENTROPY_DEBUG("rand_s", entropy); } +# elif defined(HAVE_GETENTROPY) + if (writeRandomBytes_getentropy(&entropy, sizeof(entropy))) { + return ENTROPY_DEBUG("getentropy", entropy); + } + errno = 0; # elif defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM) - if (writeRandomBytes_getrandom_nonblock((void *)&entropy, sizeof(entropy))) { + if (writeRandomBytes_getrandom_nonblock(&entropy, sizeof(entropy))) { return ENTROPY_DEBUG("getrandom", entropy); } # endif # if ! defined(_WIN32) && defined(XML_DEV_URANDOM) - if (writeRandomBytes_dev_urandom((void *)&entropy, sizeof(entropy))) { + if (writeRandomBytes_dev_urandom(&entropy, sizeof(entropy))) { return ENTROPY_DEBUG("/dev/urandom", entropy); } # endif /* ! defined(_WIN32) && defined(XML_DEV_URANDOM) */ /* .. and self-made low quality for backup: */ - entropy = gather_time_entropy(); + entropy.k[0] = 0; + entropy.k[1] = gather_time_entropy(); # if ! defined(__wasi__) /* Process ID is 0 bits entropy if attacker has local access */ - entropy ^= getpid(); + entropy.k[1] ^= getpid(); # endif /* Factors are 2^31-1 and 2^61-1 (Mersenne primes M31 and M61) */ if (sizeof(unsigned long) == 4) { - return ENTROPY_DEBUG("fallback(4)", entropy * 2147483647); + entropy.k[1] *= 2147483647; + return ENTROPY_DEBUG("fallback(4)", entropy); } else { - return ENTROPY_DEBUG("fallback(8)", - entropy * (unsigned long)2305843009213693951ULL); + entropy.k[1] *= 2305843009213693951ULL; + return ENTROPY_DEBUG("fallback(8)", entropy); } #endif } -static unsigned long -get_hash_secret_salt(XML_Parser parser) { - const XML_Parser rootParser = getRootParserOf(parser, NULL); - assert(! rootParser->m_parentParser); - - return rootParser->m_hash_secret_salt; -} - static enum XML_Error callProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { @@ -1323,8 +1195,10 @@ callProcessor(XML_Parser parser, const char *start, const char *end, static XML_Bool /* only valid for root parser */ startParsing(XML_Parser parser) { /* hash functions must be initialized before setContext() is called */ - if (parser->m_hash_secret_salt == 0) - parser->m_hash_secret_salt = generate_hash_secret_salt(parser); + if (parser->m_hash_secret_salt_set != XML_TRUE) { + parser->m_hash_secret_salt_128 = generate_hash_secret_salt(); + parser->m_hash_secret_salt_set = XML_TRUE; + } if (parser->m_ns) { /* implicit context only set for root parser, since child parsers (i.e. external entity parsers) will inherit it @@ -1612,7 +1486,9 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_useForeignDTD = XML_FALSE; parser->m_paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER; #endif - parser->m_hash_secret_salt = 0; + parser->m_hash_secret_salt_128.k[0] = 0; + parser->m_hash_secret_salt_128.k[1] = 0; + parser->m_hash_secret_salt_set = XML_FALSE; #if XML_GE == 1 memset(&parser->m_accounting, 0, sizeof(ACCOUNTING)); @@ -1779,7 +1655,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, from hash tables associated with either parser without us having to worry which hash secrets each table has. */ - unsigned long oldhash_secret_salt; + struct sipkey oldhash_secret_salt_128; + XML_Bool oldhash_secret_salt_set; XML_Bool oldReparseDeferralEnabled; /* Validate the oldParser parameter before we pull everything out of it */ @@ -1825,7 +1702,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, from hash tables associated with either parser without us having to worry which hash secrets each table has. */ - oldhash_secret_salt = parser->m_hash_secret_salt; + oldhash_secret_salt_128 = parser->m_hash_secret_salt_128; + oldhash_secret_salt_set = parser->m_hash_secret_salt_set; oldReparseDeferralEnabled = parser->m_reparseDeferralEnabled; #ifdef XML_DTD @@ -1880,7 +1758,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, parser->m_externalEntityRefHandlerArg = oldExternalEntityRefHandlerArg; parser->m_defaultExpandInternalEntities = oldDefaultExpandInternalEntities; parser->m_ns_triplets = oldns_triplets; - parser->m_hash_secret_salt = oldhash_secret_salt; + parser->m_hash_secret_salt_128 = oldhash_secret_salt_128; + parser->m_hash_secret_salt_set = oldhash_secret_salt_set; parser->m_reparseDeferralEnabled = oldReparseDeferralEnabled; parser->m_parentParser = oldParser; #ifdef XML_DTD @@ -2324,6 +2203,7 @@ XML_SetParamEntityParsing(XML_Parser parser, #endif } +// DEPRECATED since Expat 2.8.0. int XMLCALL XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { if (parser == NULL) @@ -2335,10 +2215,46 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { /* block after XML_Parse()/XML_ParseBuffer() has been called */ if (parserBusy(rootParser)) return 0; - rootParser->m_hash_secret_salt = hash_salt; + + rootParser->m_hash_secret_salt_128.k[0] = 0; + rootParser->m_hash_secret_salt_128.k[1] = hash_salt; + + if (hash_salt != 0) { // to remain backwards compatible + rootParser->m_hash_secret_salt_set = XML_TRUE; + + if (sizeof(unsigned long) == 4) + ENTROPY_DEBUG("explicit(4)", rootParser->m_hash_secret_salt_128); + else + ENTROPY_DEBUG("explicit(8)", rootParser->m_hash_secret_salt_128); + } + return 1; } +XML_Bool XMLCALL +XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]) { + if (parser == NULL) + return XML_FALSE; + + if (entropy == NULL) + return XML_FALSE; + + const XML_Parser rootParser = getRootParserOf(parser, NULL); + assert(! rootParser->m_parentParser); + + /* block after XML_Parse()/XML_ParseBuffer() has been called */ + if (parserBusy(rootParser)) + return XML_FALSE; + + sip_tokey(&(rootParser->m_hash_secret_salt_128), entropy); + + rootParser->m_hash_secret_salt_set = XML_TRUE; + + ENTROPY_DEBUG("explicit(16)", rootParser->m_hash_secret_salt_128); + + return XML_TRUE; +} + enum XML_Status XMLCALL XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { if ((parser == NULL) || (len < 0) || ((s == NULL) && (len != 0))) { @@ -7842,8 +7758,10 @@ keylen(KEY s) { static void copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key) { - key->k[0] = 0; - key->k[1] = get_hash_secret_salt(parser); + const XML_Parser rootParser = getRootParserOf(parser, NULL); + assert(! rootParser->m_parentParser); + + *key = rootParser->m_hash_secret_salt_128; } static unsigned long FASTCALL diff --git a/Modules/expat/xmlrole.c b/Modules/expat/xmlrole.c index b1dfb456e5df87..d56bee82dd2d13 100644 --- a/Modules/expat/xmlrole.c +++ b/Modules/expat/xmlrole.c @@ -12,7 +12,7 @@ Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2002-2003 Fred L. Drake, Jr. Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2026 Sebastian Pipping + Copyright (c) 2016-2023 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2019 David Loffredo Copyright (c) 2021 Donghee Na diff --git a/Modules/expat/xmltok.c b/Modules/expat/xmltok.c index f6e5f742c928c8..32cd5f147e9322 100644 --- a/Modules/expat/xmltok.c +++ b/Modules/expat/xmltok.c @@ -12,7 +12,7 @@ Copyright (c) 2002 Greg Stein Copyright (c) 2002-2016 Karl Waclawek Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2026 Sebastian Pipping + Copyright (c) 2016-2024 Sebastian Pipping Copyright (c) 2016 Pascal Cuoq Copyright (c) 2016 Don Lewis Copyright (c) 2017 Rhodri James diff --git a/Modules/expat/xmltok_ns.c b/Modules/expat/xmltok_ns.c index 1cd60de1e4fe51..810ca2c6d0485e 100644 --- a/Modules/expat/xmltok_ns.c +++ b/Modules/expat/xmltok_ns.c @@ -11,7 +11,7 @@ Copyright (c) 2002 Greg Stein Copyright (c) 2002 Fred L. Drake, Jr. Copyright (c) 2002-2006 Karl Waclawek - Copyright (c) 2017-2026 Sebastian Pipping + Copyright (c) 2017-2021 Sebastian Pipping Copyright (c) 2025 Alfonso Gregory Licensed under the MIT license: From 0efd679a6ce3b57ec3c5cd07f795d36170629dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurycy=20Paw=C5=82owski-Wiero=C5=84ski?= Date: Tue, 28 Apr 2026 01:06:23 +0200 Subject: [PATCH 29/44] gh-148252: Avoid overflow in `_remote_debugging` binary reader bounds checks (#148972) --- .../2026-04-24-23-15-42.gh-issue-148252.8BLmzd.rst | 3 +++ Modules/_remote_debugging/binary_io.h | 1 + Modules/_remote_debugging/binary_io_reader.c | 6 +++--- Modules/_remote_debugging/binary_io_writer.c | 10 ++++++---- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-04-24-23-15-42.gh-issue-148252.8BLmzd.rst diff --git a/Misc/NEWS.d/next/Security/2026-04-24-23-15-42.gh-issue-148252.8BLmzd.rst b/Misc/NEWS.d/next/Security/2026-04-24-23-15-42.gh-issue-148252.8BLmzd.rst new file mode 100644 index 00000000000000..531ea2348ffdef --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-04-24-23-15-42.gh-issue-148252.8BLmzd.rst @@ -0,0 +1,3 @@ +Fixed string table and sample record bounds checks in :mod:`!_remote_debugging` +when decoding certain ``.pyb`` inputs on 32-bit builds. Patch by Maurycy +Pawłowski-Wieroński. diff --git a/Modules/_remote_debugging/binary_io.h b/Modules/_remote_debugging/binary_io.h index d90546078bf68c..18f989f672e103 100644 --- a/Modules/_remote_debugging/binary_io.h +++ b/Modules/_remote_debugging/binary_io.h @@ -61,6 +61,7 @@ extern "C" { #define HDR_SIZE_COMPRESSION 4 #define FILE_HEADER_SIZE (HDR_OFF_COMPRESSION + HDR_SIZE_COMPRESSION) #define FILE_HEADER_PLACEHOLDER_SIZE 64 +#define SAMPLE_HEADER_FIXED_SIZE (sizeof(uint64_t) + sizeof(uint32_t) + 1) static_assert(FILE_HEADER_SIZE <= FILE_HEADER_PLACEHOLDER_SIZE, "FILE_HEADER_SIZE exceeds FILE_HEADER_PLACEHOLDER_SIZE"); diff --git a/Modules/_remote_debugging/binary_io_reader.c b/Modules/_remote_debugging/binary_io_reader.c index aca93e9cb1a30e..6c32ef70ac3f65 100644 --- a/Modules/_remote_debugging/binary_io_reader.c +++ b/Modules/_remote_debugging/binary_io_reader.c @@ -258,7 +258,7 @@ reader_parse_string_table(BinaryReader *reader, const uint8_t *data, size_t file PyErr_SetString(PyExc_ValueError, "Malformed varint in string table"); return -1; } - if (offset + str_len > file_size) { + if (offset > file_size || str_len > file_size - offset) { PyErr_SetString(PyExc_ValueError, "String table overflow"); return -1; } @@ -976,8 +976,8 @@ binary_reader_replay(BinaryReader *reader, PyObject *collector, PyObject *progre } while (offset < reader->sample_data_size) { - /* Read thread_id (8 bytes) + interpreter_id (4 bytes) */ - if (offset + 13 > reader->sample_data_size) { + /* Read thread_id (8 bytes) + interpreter_id (4 bytes) + encoding byte */ + if (reader->sample_data_size - offset < SAMPLE_HEADER_FIXED_SIZE) { break; /* End of data */ } diff --git a/Modules/_remote_debugging/binary_io_writer.c b/Modules/_remote_debugging/binary_io_writer.c index c129c93efe23c5..0ac6c88d0373a7 100644 --- a/Modules/_remote_debugging/binary_io_writer.c +++ b/Modules/_remote_debugging/binary_io_writer.c @@ -23,7 +23,6 @@ * ============================================================================ */ /* Sample header sizes */ -#define SAMPLE_HEADER_FIXED_SIZE 13 /* thread_id(8) + interpreter_id(4) + encoding(1) */ #define SAMPLE_HEADER_MAX_SIZE 26 /* fixed + max_varint(10) + status(1) + margin */ #define MAX_VARINT_SIZE 10 /* Maximum bytes for a varint64 */ #define MAX_VARINT_SIZE_U32 5 /* Maximum bytes for a varint32 */ @@ -653,10 +652,13 @@ write_sample_with_encoding(BinaryWriter *writer, ThreadEntry *entry, memcpy(header_buf, &entry->thread_id, 8); memcpy(header_buf + 8, &entry->interpreter_id, 4); header_buf[12] = (uint8_t)encoding_type; - size_t varint_len = encode_varint_u64(header_buf + 13, timestamp_delta); - header_buf[13 + varint_len] = status; + size_t varint_len = encode_varint_u64( + header_buf + SAMPLE_HEADER_FIXED_SIZE, + timestamp_delta); + header_buf[SAMPLE_HEADER_FIXED_SIZE + varint_len] = status; - if (writer_write_bytes(writer, header_buf, 14 + varint_len) < 0) { + if (writer_write_bytes(writer, header_buf, + SAMPLE_HEADER_FIXED_SIZE + varint_len + 1) < 0) { return -1; } From 29a92abb6052cdbbecf97f24f576e29da88d12fc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 27 Apr 2026 19:28:30 -0700 Subject: [PATCH 30/44] gh-148829: Implement PEP 661 (#148831) Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com> Co-authored-by: Pieter Eendebak Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/c-api/concrete.rst | 1 + Doc/c-api/sentinel.rst | 35 ++++ Doc/data/refcounts.dat | 4 + Doc/library/functions.rst | 69 +++++- Doc/whatsnew/3.15.rst | 16 ++ Include/Python.h | 1 + Include/sentinelobject.h | 22 ++ Lib/test/pickletester.py | 1 + Lib/test/test_builtin.py | 98 +++++++++ Lib/test/test_capi/test_object.py | 22 ++ Lib/typing.py | 26 +-- Makefile.pre.in | 2 + ...-04-21-06-43-32.gh-issue-148829.GtIrYO.rst | 2 + Modules/_testcapi/object.c | 19 ++ Objects/clinic/sentinelobject.c.h | 34 +++ Objects/object.c | 1 + Objects/sentinelobject.c | 196 ++++++++++++++++++ Objects/unionobject.c | 1 + PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 2 + PCbuild/pythoncore.vcxproj.filters | 6 + Python/bltinmodule.c | 1 + Tools/c-analyzer/cpython/globals-to-fix.tsv | 1 + 24 files changed, 532 insertions(+), 32 deletions(-) create mode 100644 Doc/c-api/sentinel.rst create mode 100644 Include/sentinelobject.h create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-06-43-32.gh-issue-148829.GtIrYO.rst create mode 100644 Objects/clinic/sentinelobject.c.h create mode 100644 Objects/sentinelobject.c diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index 1746fe95eaaca9..3f38411a52de6b 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -112,6 +112,7 @@ Other Objects picklebuffer.rst weakref.rst capsule.rst + sentinel.rst frame.rst gen.rst coro.rst diff --git a/Doc/c-api/sentinel.rst b/Doc/c-api/sentinel.rst new file mode 100644 index 00000000000000..710ded56e2a6db --- /dev/null +++ b/Doc/c-api/sentinel.rst @@ -0,0 +1,35 @@ +.. highlight:: c + +.. _sentinelobjects: + +Sentinel objects +---------------- + +.. c:var:: PyTypeObject PySentinel_Type + + This instance of :c:type:`PyTypeObject` represents the Python + :class:`sentinel` type. This is the same object as :class:`sentinel`. + + .. versionadded:: next + +.. c:function:: int PySentinel_Check(PyObject *o) + + Return true if *o* is a :class:`sentinel` object. The :class:`sentinel` type + does not allow subclasses, so this check is exact. + + .. versionadded:: next + +.. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name) + + Return a new :class:`sentinel` object with :attr:`~sentinel.__name__` set to + *name* and :attr:`~sentinel.__module__` set to *module_name*. + *name* must not be ``NULL``. If *module_name* is ``NULL``, :attr:`~sentinel.__module__` + is set to ``None``. + Return ``NULL`` with an exception set on failure. + + For pickling to work, *module_name* must be the name of an importable + module, and the sentinel must be accessible from that module under a + path matching *name*. Pickle treats *name* as a global variable name + in *module_name* (see :meth:`object.__reduce__`). + + .. versionadded:: next diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 2a6e6b963134bb..663b79e45eec17 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -2037,6 +2037,10 @@ PySeqIter_Check:PyObject *:op:0: PySeqIter_New:PyObject*::+1: PySeqIter_New:PyObject*:seq:0: +PySentinel_New:PyObject*::+1: +PySentinel_New:const char*:name:: +PySentinel_New:const char*:module_name:: + PySequence_Check:int::: PySequence_Check:PyObject*:o:0: diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 119141d2e6daf3..aa99d198e436d5 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -19,13 +19,13 @@ are always available. They are listed here in alphabetical order. | | :func:`ascii` | | :func:`filter` | | :func:`map` | | **S** | | | | | :func:`float` | | :func:`max` | | |func-set|_ | | | **B** | | :func:`format` | | |func-memoryview|_ | | :func:`setattr` | -| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`slice` | -| | :func:`bool` | | | | | | :func:`sorted` | -| | :func:`breakpoint` | | **G** | | **N** | | :func:`staticmethod` | -| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | |func-str|_ | -| | |func-bytes|_ | | :func:`globals` | | | | :func:`sum` | -| | | | | | **O** | | :func:`super` | -| | **C** | | **H** | | :func:`object` | | | +| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`sentinel` | +| | :func:`bool` | | | | | | :func:`slice` | +| | :func:`breakpoint` | | **G** | | **N** | | :func:`sorted` | +| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | :func:`staticmethod` | +| | |func-bytes|_ | | :func:`globals` | | | | |func-str|_ | +| | | | | | **O** | | :func:`sum` | +| | **C** | | **H** | | :func:`object` | | :func:`super` | | | :func:`callable` | | :func:`hasattr` | | :func:`oct` | | **T** | | | :func:`chr` | | :func:`hash` | | :func:`open` | | |func-tuple|_ | | | :func:`classmethod` | | :func:`help` | | :func:`ord` | | :func:`type` | @@ -1827,6 +1827,61 @@ are always available. They are listed here in alphabetical order. :func:`setattr`. +.. class:: sentinel(name, /) + + Return a new unique sentinel object. *name* must be a :class:`str`, and is + used as the returned object's representation:: + + >>> MISSING = sentinel("MISSING") + >>> MISSING + MISSING + + Sentinel objects are truthy and compare equal only to themselves. They are + intended to be compared with the :keyword:`is` operator. + + Shallow and deep copies of a sentinel object return the object itself. + + Sentinels are conventionally assigned to a variable with a matching name. + Sentinels defined in this way can be used in :term:`type hints `:: + + MISSING = sentinel("MISSING") + + def next_value(default: int | MISSING = MISSING): + ... + + Sentinel objects support the :ref:`| ` operator for use in type expressions. + + :mod:`Pickling ` is supported for sentinel objects that are + placed in the global scope of a module under a name matching the sentinel's + name, and for sentinels placed in class scopes with a name matching the + :term:`qualified name` of the sentinel. Other sentinels, such as those + defined in a function scope, are not picklable. The identity of the sentinel is preserved + after pickling:: + + import pickle + + PICKLABLE = sentinel("PICKLABLE") + + assert pickle.loads(pickle.dumps(PICKLABLE)) is PICKLABLE + + class Cls: + PICKLABLE = sentinel("Cls.PICKLABLE") + + assert pickle.loads(pickle.dumps(Cls.PICKLABLE)) is Cls.PICKLABLE + + Sentinel objects have the following attributes: + + .. attribute:: __name__ + + The sentinel's name. + + .. attribute:: __module__ + + The name of the module where the sentinel was created. + + .. versionadded:: next + + .. class:: slice(stop, /) slice(start, stop, step=None, /) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 405d388af487e8..0a96a970ba2329 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -69,6 +69,8 @@ Summary -- Release highlights ` * :pep:`814`: :ref:`Add frozendict built-in type ` +* :pep:`661`: :ref:`Add sentinel built-in type + ` * :pep:`799`: :ref:`A dedicated profiling package for organizing Python profiling tools ` * :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler @@ -247,6 +249,20 @@ to accept also other mapping types such as :class:`~types.MappingProxyType`. (Contributed by Victor Stinner and Donghee Na in :gh:`141510`.) +.. _whatsnew315-sentinel: + +:pep:`661`: Add sentinel built-in type +-------------------------------------- + +A new :class:`sentinel` type is added to the :mod:`builtins` module for +creating unique sentinel values with a concise representation. Sentinel +objects preserve identity when copied, support use in type expressions with +the ``|`` operator, and can be pickled when they are importable by module and +name. + +(PEP by Tal Einat; contributed by Jelle Zijlstra in :gh:`148829`.) + + .. _whatsnew315-profiling-package: :pep:`799`: A dedicated profiling package diff --git a/Include/Python.h b/Include/Python.h index 8b76195b320998..1272e2464f91d1 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -117,6 +117,7 @@ __pragma(warning(disable: 4201)) #include "cpython/genobject.h" #include "descrobject.h" #include "genericaliasobject.h" +#include "sentinelobject.h" #include "warnings.h" #include "weakrefobject.h" #include "structseq.h" diff --git a/Include/sentinelobject.h b/Include/sentinelobject.h new file mode 100644 index 00000000000000..9d8577767b7485 --- /dev/null +++ b/Include/sentinelobject.h @@ -0,0 +1,22 @@ +/* Sentinel object interface */ + +#ifndef Py_SENTINELOBJECT_H +#define Py_SENTINELOBJECT_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_LIMITED_API +PyAPI_DATA(PyTypeObject) PySentinel_Type; + +#define PySentinel_Check(op) Py_IS_TYPE((op), &PySentinel_Type) + +PyAPI_FUNC(PyObject *) PySentinel_New( + const char *name, + const char *module_name); +#endif + +#ifdef __cplusplus +} +#endif +#endif /* !Py_SENTINELOBJECT_H */ diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 6366f12257f3b5..c2018c9785b9b3 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -3244,6 +3244,7 @@ def test_builtin_types(self): 'BuiltinImporter': (3, 3), 'str': (3, 4), # not interoperable with Python < 3.4 'frozendict': (3, 15), + 'sentinel': (3, 15), } for t in builtins.__dict__.values(): if isinstance(t, type) and not issubclass(t, BaseException): diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 844656eb0e2c2e..e323742665234c 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -4,6 +4,7 @@ import builtins import collections import contextlib +import copy import decimal import fractions import gc @@ -21,6 +22,7 @@ import typing import unittest import warnings +import weakref from contextlib import ExitStack from functools import partial from inspect import CO_COROUTINE @@ -52,6 +54,10 @@ # used as proof of globals being used A_GLOBAL_VALUE = 123 +A_SENTINEL = sentinel("A_SENTINEL") + +class SentinelContainer: + CLASS_SENTINEL = sentinel("SentinelContainer.CLASS_SENTINEL") class Squares: @@ -1903,6 +1909,98 @@ class C: __repr__ = None self.assertRaises(TypeError, repr, C()) + def test_sentinel(self): + missing = sentinel("MISSING") + other = sentinel("MISSING") + + self.assertIsInstance(missing, sentinel) + self.assertIs(type(missing), sentinel) + self.assertEqual(missing.__name__, "MISSING") + self.assertEqual(missing.__module__, __name__) + self.assertIsNot(missing, other) + self.assertEqual(repr(missing), "MISSING") + self.assertTrue(missing) + self.assertIs(copy.copy(missing), missing) + self.assertIs(copy.deepcopy(missing), missing) + self.assertEqual(missing, missing) + self.assertNotEqual(missing, other) + self.assertRaises(TypeError, sentinel) + self.assertRaises(TypeError, sentinel, "MISSING", "EXTRA") + self.assertRaises(TypeError, sentinel, name="MISSING") + with self.assertRaisesRegex(TypeError, "must be str"): + sentinel(1) + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_IMMUTABLETYPE) + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_HAVE_GC) + self.assertFalse(sentinel.__flags__ & support._TPFLAGS_BASETYPE) + with self.assertRaises(TypeError): + class SubSentinel(sentinel): + pass + with self.assertRaises(TypeError): + sentinel.attribute = "value" + with self.assertRaises(AttributeError): + missing.__name__ = "CHANGED" + with self.assertRaises(AttributeError): + missing.__module__ = "changed" + with self.assertRaises(AttributeError): + del missing.__name__ + with self.assertRaises(AttributeError): + del missing.__module__ + + def test_sentinel_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + self.assertIs( + pickle.loads(pickle.dumps(A_SENTINEL, protocol=proto)), + A_SENTINEL) + self.assertIs( + pickle.loads(pickle.dumps( + SentinelContainer.CLASS_SENTINEL, protocol=proto)), + SentinelContainer.CLASS_SENTINEL) + + missing = sentinel("MISSING") + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=proto): + with self.assertRaises(pickle.PicklingError): + pickle.dumps(missing, protocol=proto) + + def test_sentinel_str_subclass_name_cycle(self): + class Name(str): + pass + + name = Name("MISSING") + missing = sentinel(name) + self.assertIs(missing.__name__, name) + self.assertTrue(gc.is_tracked(missing)) + + name.missing = missing + ref = weakref.ref(name) + del name, missing + support.gc_collect() + self.assertIsNone(ref()) + + def test_sentinel_union(self): + missing = sentinel("MISSING") + + self.assertIsInstance(missing | int, typing.Union) + self.assertEqual((missing | int).__args__, (missing, int)) + self.assertIsInstance(int | missing, typing.Union) + self.assertEqual((int | missing).__args__, (int, missing)) + self.assertIs(missing | missing, missing) + self.assertEqual(repr(int | missing), "int | MISSING") + self.assertIsInstance(missing | None, typing.Union) + self.assertEqual((missing | None).__args__, (missing, type(None))) + self.assertIsInstance(None | missing, typing.Union) + self.assertEqual((None | missing).__args__, (type(None), missing)) + self.assertIsInstance(missing | list[int], typing.Union) + self.assertEqual((missing | list[int]).__args__, (missing, list[int])) + self.assertIsInstance(missing | (int | str), typing.Union) + self.assertEqual((missing | (int | str)).__args__, (missing, int, str)) + + with self.assertRaises(TypeError): + missing | 1 + with self.assertRaises(TypeError): + 1 | missing + def test_round(self): self.assertEqual(round(0.0), 0.0) self.assertEqual(type(round(0.0)), int) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 67572ab1ba268d..635deaa73f7efa 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -1,5 +1,6 @@ import enum import os +import pickle import sys import textwrap import unittest @@ -63,6 +64,27 @@ def test_get_constant_borrowed(self): self.check_get_constant(_testlimitedcapi.get_constant_borrowed) +class SentinelTest(unittest.TestCase): + + def test_pysentinel_new(self): + marker = _testcapi.pysentinel_new("CAPI_SENTINEL", __name__) + self.assertIs(type(marker), sentinel) + self.assertTrue(_testcapi.pysentinel_check(marker)) + self.assertFalse(_testcapi.pysentinel_check(object())) + self.assertEqual(marker.__name__, "CAPI_SENTINEL") + self.assertEqual(marker.__module__, __name__) + self.assertEqual(repr(marker), "CAPI_SENTINEL") + + no_module = _testcapi.pysentinel_new("NO_MODULE") + self.assertIs(type(no_module), sentinel) + self.assertEqual(no_module.__name__, "NO_MODULE") + self.assertIs(no_module.__module__, None) + + globals()["CAPI_SENTINEL"] = marker + self.addCleanup(globals().pop, "CAPI_SENTINEL", None) + self.assertIs(pickle.loads(pickle.dumps(marker)), marker) + + class PrintTest(unittest.TestCase): def testPyObjectPrintObject(self): diff --git a/Lib/typing.py b/Lib/typing.py index 46e7122b6c91c5..e7563a53878da5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3150,31 +3150,7 @@ def _namedtuple_mro_entries(bases): NamedTuple.__mro_entries__ = _namedtuple_mro_entries -class _SingletonMeta(type): - def __setattr__(cls, attr, value): - # TypeError is consistent with the behavior of NoneType - raise TypeError( - f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}" - ) - - -class _NoExtraItemsType(metaclass=_SingletonMeta): - """The type of the NoExtraItems singleton.""" - - __slots__ = () - - def __new__(cls): - return globals().get("NoExtraItems") or object.__new__(cls) - - def __repr__(self): - return 'typing.NoExtraItems' - - def __reduce__(self): - return 'NoExtraItems' - -NoExtraItems = _NoExtraItemsType() -del _NoExtraItemsType -del _SingletonMeta +NoExtraItems = sentinel("NoExtraItems") def _get_typeddict_qualifiers(annotation_type): diff --git a/Makefile.pre.in b/Makefile.pre.in index 8b46db33a2ac18..2ce53c6a816212 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -560,6 +560,7 @@ OBJECT_OBJS= \ Objects/obmalloc.o \ Objects/picklebufobject.o \ Objects/rangeobject.o \ + Objects/sentinelobject.o \ Objects/setobject.o \ Objects/sliceobject.o \ Objects/structseq.o \ @@ -1240,6 +1241,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/pytypedefs.h \ $(srcdir)/Include/rangeobject.h \ $(srcdir)/Include/refcount.h \ + $(srcdir)/Include/sentinelobject.h \ $(srcdir)/Include/setobject.h \ $(srcdir)/Include/sliceobject.h \ $(srcdir)/Include/structmember.h \ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-06-43-32.gh-issue-148829.GtIrYO.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-06-43-32.gh-issue-148829.GtIrYO.rst new file mode 100644 index 00000000000000..3d9b4faa6ca443 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-21-06-43-32.gh-issue-148829.GtIrYO.rst @@ -0,0 +1,2 @@ +Add :class:`sentinel`, implementing :pep:`661`. PEP by Tal Einat; patch by +Jelle Zijlstra. diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 9160005e00654f..6e5c8dcbb725fa 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -555,6 +555,23 @@ pyobject_dump(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static PyObject * +pysentinel_new(PyObject *self, PyObject *args) +{ + const char *name; + const char *module_name = NULL; + if (!PyArg_ParseTuple(args, "s|s", &name, &module_name)) { + return NULL; + } + return PySentinel_New(name, module_name); +} + +static PyObject * +pysentinel_check(PyObject *self, PyObject *obj) +{ + return PyBool_FromLong(PySentinel_Check(obj)); +} + static PyMethodDef test_methods[] = { {"call_pyobject_print", call_pyobject_print, METH_VARARGS}, @@ -585,6 +602,8 @@ static PyMethodDef test_methods[] = { {"clear_managed_dict", clear_managed_dict, METH_O, NULL}, {"is_uniquely_referenced", is_uniquely_referenced, METH_O}, {"pyobject_dump", pyobject_dump, METH_VARARGS}, + {"pysentinel_new", pysentinel_new, METH_VARARGS}, + {"pysentinel_check", pysentinel_check, METH_O}, {NULL}, }; diff --git a/Objects/clinic/sentinelobject.c.h b/Objects/clinic/sentinelobject.c.h new file mode 100644 index 00000000000000..51fd35a5979e31 --- /dev/null +++ b/Objects/clinic/sentinelobject.c.h @@ -0,0 +1,34 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +static PyObject * +sentinel_new_impl(PyTypeObject *type, PyObject *name); + +static PyObject * +sentinel_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = &PySentinel_Type; + PyObject *name; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("sentinel", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("sentinel", PyTuple_GET_SIZE(args), 1, 1)) { + goto exit; + } + if (!PyUnicode_Check(PyTuple_GET_ITEM(args, 0))) { + _PyArg_BadArgument("sentinel", "argument 1", "str", PyTuple_GET_ITEM(args, 0)); + goto exit; + } + name = PyTuple_GET_ITEM(args, 0); + return_value = sentinel_new_impl(type, name); + +exit: + return return_value; +} +/*[clinic end generated code: output=7f28fc0bf0259cba input=a9049054013a1b77]*/ diff --git a/Objects/object.c b/Objects/object.c index 4fa20470601eb3..e6a764435bc292 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2597,6 +2597,7 @@ static PyTypeObject* static_types[] = { &PyRange_Type, &PyReversed_Type, &PySTEntry_Type, + &PySentinel_Type, &PySeqIter_Type, &PySetIter_Type, &PySet_Type, diff --git a/Objects/sentinelobject.c b/Objects/sentinelobject.c new file mode 100644 index 00000000000000..e7e9f60e3edfbe --- /dev/null +++ b/Objects/sentinelobject.c @@ -0,0 +1,196 @@ +/* Sentinel object implementation */ + +#include "Python.h" +#include "descrobject.h" // PyMemberDef +#include "pycore_ceval.h" // _PyThreadState_GET() +#include "pycore_interpframe.h" // _PyFrame_IsIncomplete() +#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK() +#include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow() +#include "pycore_tuple.h" // _PyTuple_FromPair +#include "pycore_typeobject.h" // _Py_BaseObject_RichCompare() +#include "pycore_unionobject.h" // _Py_union_type_or() + +typedef struct { + PyObject_HEAD + PyObject *name; + PyObject *module; +} sentinelobject; + +#define sentinelobject_CAST(op) \ + (assert(PySentinel_Check(op)), _Py_CAST(sentinelobject *, (op))) + +/*[clinic input] +class sentinel "sentinelobject *" "&PySentinel_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=8b88f8268d3b5775]*/ + +#include "clinic/sentinelobject.c.h" + + +static PyObject * +caller(void) +{ + _PyInterpreterFrame *f = _PyThreadState_GET()->current_frame; + if (f == NULL || PyStackRef_IsNull(f->f_funcobj)) { + assert(!PyErr_Occurred()); + Py_RETURN_NONE; + } + PyFunctionObject *func = _PyFrame_GetFunction(f); + assert(PyFunction_Check(func)); + PyObject *r = PyFunction_GetModule((PyObject *)func); + if (!r) { + assert(!PyErr_Occurred()); + Py_RETURN_NONE; + } + return Py_NewRef(r); +} + +static PyObject * +sentinel_new_with_module(PyTypeObject *type, PyObject *name, PyObject *module) +{ + assert(PyUnicode_Check(name)); + + sentinelobject *self = PyObject_GC_New(sentinelobject, type); + if (self == NULL) { + return NULL; + } + self->name = Py_NewRef(name); + self->module = Py_NewRef(module); + _PyObject_GC_TRACK(self); + return (PyObject *)self; +} + +/*[clinic input] +@classmethod +sentinel.__new__ as sentinel_new + + name: object(subclass_of='&PyUnicode_Type') + / +[clinic start generated code]*/ + +static PyObject * +sentinel_new_impl(PyTypeObject *type, PyObject *name) +/*[clinic end generated code: output=4af55c6048bed30d input=3ab75704f39c119c]*/ +{ + PyObject *module = caller(); + PyObject *self = sentinel_new_with_module(type, name, module); + Py_DECREF(module); + return self; +} + +PyObject * +PySentinel_New(const char *name, const char *module_name) +{ + PyObject *name_obj = PyUnicode_FromString(name); + if (name_obj == NULL) { + return NULL; + } + PyObject *module_obj = module_name == NULL + ? Py_None + : PyUnicode_FromString(module_name); + if (module_obj == NULL) { + Py_DECREF(name_obj); + return NULL; + } + + PyObject *sentinel = sentinel_new_with_module( + &PySentinel_Type, name_obj, module_obj); + Py_DECREF(module_obj); + Py_DECREF(name_obj); + return sentinel; +} + +static int +sentinel_clear(PyObject *op) +{ + sentinelobject *self = sentinelobject_CAST(op); + Py_CLEAR(self->name); + Py_CLEAR(self->module); + return 0; +} + +static void +sentinel_dealloc(PyObject *op) +{ + _PyObject_GC_UNTRACK(op); + (void)sentinel_clear(op); + Py_TYPE(op)->tp_free(op); +} + +static int +sentinel_traverse(PyObject *op, visitproc visit, void *arg) +{ + sentinelobject *self = sentinelobject_CAST(op); + Py_VISIT(self->name); + Py_VISIT(self->module); + return 0; +} + +static PyObject * +sentinel_repr(PyObject *op) +{ + sentinelobject *self = sentinelobject_CAST(op); + return Py_NewRef(self->name); +} + +static PyObject * +sentinel_copy(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return Py_NewRef(self); +} + +static PyObject * +sentinel_deepcopy(PyObject *self, PyObject *Py_UNUSED(memo)) +{ + return Py_NewRef(self); +} + +static PyObject * +sentinel_reduce(PyObject *op, PyObject *Py_UNUSED(ignored)) +{ + sentinelobject *self = sentinelobject_CAST(op); + return Py_NewRef(self->name); +} + +static PyMethodDef sentinel_methods[] = { + {"__copy__", sentinel_copy, METH_NOARGS, NULL}, + {"__deepcopy__", sentinel_deepcopy, METH_O, NULL}, + {"__reduce__", sentinel_reduce, METH_NOARGS, NULL}, + {NULL, NULL} +}; + +static PyMemberDef sentinel_members[] = { + {"__name__", Py_T_OBJECT_EX, offsetof(sentinelobject, name), Py_READONLY}, + {"__module__", Py_T_OBJECT_EX, offsetof(sentinelobject, module), Py_READONLY}, + {NULL} +}; + +static PyNumberMethods sentinel_as_number = { + .nb_or = _Py_union_type_or, +}; + +PyDoc_STRVAR(sentinel_doc, +"sentinel(name, /)\n" +"--\n\n" +"Create a unique sentinel object with the given name."); + +PyTypeObject PySentinel_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "sentinel", + .tp_basicsize = sizeof(sentinelobject), + .tp_dealloc = sentinel_dealloc, + .tp_repr = sentinel_repr, + .tp_as_number = &sentinel_as_number, + .tp_hash = PyObject_GenericHash, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC, + .tp_doc = sentinel_doc, + .tp_traverse = sentinel_traverse, + .tp_clear = sentinel_clear, + .tp_richcompare = _Py_BaseObject_RichCompare, + .tp_methods = sentinel_methods, + .tp_members = sentinel_members, + .tp_new = sentinel_new, + .tp_free = PyObject_GC_Del, +}; diff --git a/Objects/unionobject.c b/Objects/unionobject.c index d33d581f049c5b..0f6b1e44bc2402 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -245,6 +245,7 @@ is_unionable(PyObject *obj) { if (obj == Py_None || PyType_Check(obj) || + PySentinel_Check(obj) || _PyGenericAlias_Check(obj) || _PyUnion_Check(obj) || Py_IS_TYPE(obj, &_PyTypeAlias_Type)) { diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 38236922a523db..953973a2ad32df 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -158,6 +158,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 73861dbb0c9e7e..13db4d93f54518 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -400,6 +400,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 07305add81d055..fb9217fee8bd73 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -384,6 +384,7 @@ + @@ -561,6 +562,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 629f063861de9a..1e1d085cd75511 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -222,6 +222,9 @@ Include + + Include + Include @@ -1274,6 +1277,9 @@ Objects + + Objects + Objects diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 16413d784cc87c..35b30a243318cc 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -3555,6 +3555,7 @@ _PyBuiltin_Init(PyInterpreterState *interp) SETBUILTIN("object", &PyBaseObject_Type); SETBUILTIN("range", &PyRange_Type); SETBUILTIN("reversed", &PyReversed_Type); + SETBUILTIN("sentinel", &PySentinel_Type); SETBUILTIN("set", &PySet_Type); SETBUILTIN("slice", &PySlice_Type); SETBUILTIN("staticmethod", &PyStaticMethod_Type); diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 74ca562824012b..db575d870be5c5 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -83,6 +83,7 @@ Objects/picklebufobject.c - PyPickleBuffer_Type - Objects/rangeobject.c - PyLongRangeIter_Type - Objects/rangeobject.c - PyRangeIter_Type - Objects/rangeobject.c - PyRange_Type - +Objects/sentinelobject.c - PySentinel_Type - Objects/setobject.c - PyFrozenSet_Type - Objects/setobject.c - PySetIter_Type - Objects/setobject.c - PySet_Type - From d2f506ae07e0bc097039634a28cf85b5d804ef72 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Mon, 27 Apr 2026 22:51:06 -0400 Subject: [PATCH 31/44] gh-137600: Promote `ast` node constructor deprecation warnings to errors (#137601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Jelle Zijlstra --- Doc/library/ast.rst | 5 +- Doc/whatsnew/3.15.rst | 10 + Lib/test/test_ast/test_ast.py | 98 +++--- ...-08-09-19-00-36.gh-issue-137600.p_p6OU.rst | 4 + Parser/asdl_c.py | 302 ++++++------------ Python/Python-ast.c | 302 ++++++------------ 6 files changed, 255 insertions(+), 466 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-08-09-19-00-36.gh-issue-137600.p_p6OU.rst diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index e23506768a7721..3c6e8745474316 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -35,6 +35,8 @@ The abstract grammar is currently defined as follows: :language: asdl +.. _ast_nodes: + Node classes ------------ @@ -164,8 +166,7 @@ Node classes Previous versions of Python allowed the creation of AST nodes that were missing required fields. Similarly, AST node constructors allowed arbitrary keyword arguments that were set as attributes of the AST node, even if they did not - match any of the fields of the AST node. This behavior is deprecated and will - be removed in Python 3.15. + match any of the fields of the AST node. These cases now raise a :exc:`TypeError`. .. note:: The descriptions of the specific node classes displayed here diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 0a96a970ba2329..65965d504c0976 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1620,6 +1620,16 @@ This was made possible by a refactoring of JIT data structures. Removed ======== +ast +--- + +* The constructors of :ref:`AST nodes ` now raise a :exc:`TypeError` + when a required argument is omitted or when a keyword argument that does not + map to a field on the AST node is passed. These cases had previously raised a + :exc:`DeprecationWarning` since Python 3.13. + (Contributed by Brian Schubert and Jelle Zijlstra in :gh:`137600` and :gh:`105858`.) + + collections.abc --------------- diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index 75d553e6f7778f..a7d5a51a2aa4d5 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -418,6 +418,17 @@ def test_field_attr_existence(self): if isinstance(x, ast.AST): self.assertIs(type(x._fields), tuple) + def test_dynamic_attr(self): + for name, item in ast.__dict__.items(): + # constructor has a different signature + if name == 'Index': + continue + if self._is_ast_node(name, item): + x = self._construct_ast_class(item) + # Custom attribute assignment is allowed + x.foo = 5 + self.assertEqual(x.foo, 5) + def _construct_ast_class(self, cls): kwargs = {} for name, typ in cls.__annotations__.items(): @@ -459,14 +470,9 @@ def test_field_attr_writable(self): self.assertEqual(x._fields, 666) def test_classattrs(self): - with self.assertWarns(DeprecationWarning): - x = ast.Constant() + x = ast.Constant(42) self.assertEqual(x._fields, ('value', 'kind')) - with self.assertRaises(AttributeError): - x.value - - x = ast.Constant(42) self.assertEqual(x.value, 42) with self.assertRaises(AttributeError): @@ -486,11 +492,8 @@ def test_classattrs(self): self.assertRaises(TypeError, ast.Constant, 1, None, 2) self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0) - # Arbitrary keyword arguments are supported (but deprecated) - with self.assertWarns(DeprecationWarning): - self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') - - with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"): + msg = "ast.Constant got multiple values for argument 'value'" + with self.assertRaisesRegex(TypeError, re.escape(msg)): ast.Constant(1, value=2) self.assertEqual(ast.Constant(42).value, 42) @@ -529,23 +532,24 @@ def test_module(self): self.assertEqual(x.body, body) def test_nodeclasses(self): - # Zero arguments constructor explicitly allowed (but deprecated) - with self.assertWarns(DeprecationWarning): - x = ast.BinOp() - self.assertEqual(x._fields, ('left', 'op', 'right')) - - # Random attribute allowed too - x.foobarbaz = 5 - self.assertEqual(x.foobarbaz, 5) + # Zero arguments constructor is not allowed + msg = "ast.BinOp.__init__ missing 3 required positional arguments: 'left', 'op', and 'right'" + self.assertRaisesRegex(TypeError, re.escape(msg), ast.BinOp) n1 = ast.Constant(1) n3 = ast.Constant(3) addop = ast.Add() x = ast.BinOp(n1, addop, n3) + self.assertEqual(x._fields, ('left', 'op', 'right')) self.assertEqual(x.left, n1) self.assertEqual(x.op, addop) self.assertEqual(x.right, n3) + # Arbitrary attributes are allowed + x.foobarbaz = 5 + self.assertEqual(x.foobarbaz, 5) + self.assertEqual(x._fields, ('left', 'op', 'right')) + x = ast.BinOp(1, 2, 3) self.assertEqual(x.left, 1) self.assertEqual(x.op, 2) @@ -569,10 +573,10 @@ def test_nodeclasses(self): self.assertEqual(x.right, 3) self.assertEqual(x.lineno, 0) - # Random kwargs also allowed (but deprecated) - with self.assertWarns(DeprecationWarning): - x = ast.BinOp(1, 2, 3, foobarbaz=42) - self.assertEqual(x.foobarbaz, 42) + # Arbitrary keyword arguments are not allowed + msg = "ast.BinOp.__init__ got an unexpected keyword argument 'foobarbaz'" + with self.assertRaisesRegex(TypeError, re.escape(msg)): + ast.BinOp(1, 2, 3, foobarbaz=42) def test_no_fields(self): # this used to fail because Sub._fields was None @@ -1377,14 +1381,14 @@ def test_replace_ignore_known_custom_instance_fields(self): self.assertRaises(AttributeError, getattr, repl, 'extra') def test_replace_reject_missing_field(self): - # case: warn if deleted field is not replaced + # case: raise if deleted field is not replaced node = ast.parse('x').body[0].value context = node.ctx del node.id self.assertRaises(AttributeError, getattr, node, 'id') self.assertIs(node.ctx, context) - msg = "Name.__replace__ missing 1 keyword argument: 'id'." + msg = "ast.Name.__init__ missing 1 required positional argument: 'id'" with self.assertRaisesRegex(TypeError, re.escape(msg)): copy.replace(node) # assert that there is no side-effect @@ -1421,7 +1425,7 @@ def test_replace_reject_known_custom_instance_fields_commits(self): # explicit rejection of known instance fields self.assertHasAttr(node, 'extra') - msg = "Name.__replace__ got an unexpected keyword argument 'extra'." + msg = "ast.Name.__init__ got an unexpected keyword argument 'extra'" with self.assertRaisesRegex(TypeError, re.escape(msg)): copy.replace(node, extra=1) # assert that there is no side-effect @@ -1435,7 +1439,7 @@ def test_replace_reject_unknown_instance_fields(self): # explicit rejection of unknown extra fields self.assertRaises(AttributeError, getattr, node, 'unknown') - msg = "Name.__replace__ got an unexpected keyword argument 'unknown'." + msg = "ast.Name.__init__ got an unexpected keyword argument 'unknown'" with self.assertRaisesRegex(TypeError, re.escape(msg)): copy.replace(node, unknown=1) # assert that there is no side-effect @@ -3200,11 +3204,10 @@ def test_FunctionDef(self): args = ast.arguments() self.assertEqual(args.args, []) self.assertEqual(args.posonlyargs, []) - with self.assertWarnsRegex(DeprecationWarning, - r"FunctionDef\.__init__ missing 1 required positional argument: 'name'"): - node = ast.FunctionDef(args=args) - self.assertNotHasAttr(node, "name") - self.assertEqual(node.decorator_list, []) + msg = "ast.FunctionDef.__init__ missing 1 required positional argument: 'name'" + with self.assertRaisesRegex(TypeError, re.escape(msg)): + ast.FunctionDef(args=args) + node = ast.FunctionDef(name='foo', args=args) self.assertEqual(node.name, 'foo') self.assertEqual(node.decorator_list, []) @@ -3222,9 +3225,8 @@ def test_expr_context(self): self.assertEqual(name3.id, "x") self.assertIsInstance(name3.ctx, ast.Del) - with self.assertWarnsRegex(DeprecationWarning, - r"Name\.__init__ missing 1 required positional argument: 'id'"): - name3 = ast.Name() + msg = "ast.Name.__init__ missing 1 required positional argument: 'id'" + self.assertRaisesRegex(TypeError, re.escape(msg), ast.Name) def test_custom_subclass_with_no_fields(self): class NoInit(ast.AST): @@ -3263,20 +3265,18 @@ class MyAttrs(ast.AST): self.assertEqual(obj.a, 1) self.assertEqual(obj.b, 2) - with self.assertWarnsRegex(DeprecationWarning, - r"MyAttrs.__init__ got an unexpected keyword argument 'c'."): - obj = MyAttrs(c=3) + msg = "MyAttrs.__init__ got an unexpected keyword argument 'c'" + with self.assertRaisesRegex(TypeError, re.escape(msg)): + MyAttrs(c=3) def test_fields_and_types_no_default(self): class FieldsAndTypesNoDefault(ast.AST): _fields = ('a',) _field_types = {'a': int} - with self.assertWarnsRegex(DeprecationWarning, - r"FieldsAndTypesNoDefault\.__init__ missing 1 required positional argument: 'a'\."): - obj = FieldsAndTypesNoDefault() - with self.assertRaises(AttributeError): - obj.a + msg = "FieldsAndTypesNoDefault.__init__ missing 1 required positional argument: 'a'" + self.assertRaisesRegex(TypeError, re.escape(msg), FieldsAndTypesNoDefault) + obj = FieldsAndTypesNoDefault(a=1) self.assertEqual(obj.a, 1) @@ -3287,13 +3287,8 @@ class MoreFieldsThanTypes(ast.AST): a: int | None = None b: int | None = None - with self.assertWarnsRegex( - DeprecationWarning, - r"Field 'b' is missing from MoreFieldsThanTypes\._field_types" - ): - obj = MoreFieldsThanTypes() - self.assertIs(obj.a, None) - self.assertIs(obj.b, None) + msg = r"Field 'b' is missing from .*\.MoreFieldsThanTypes\._field_types" + self.assertRaisesRegex(TypeError, msg, MoreFieldsThanTypes) obj = MoreFieldsThanTypes(a=1, b=2) self.assertEqual(obj.a, 1) @@ -3305,8 +3300,7 @@ class BadFields(ast.AST): _field_types = {'a': int} # This should not crash - with self.assertWarnsRegex(DeprecationWarning, r"Field b'\\xff\\xff.*' .*"): - obj = BadFields() + self.assertRaisesRegex(TypeError, r"Field b'\\xff\\xff.*' .*", BadFields) def test_complete_field_types(self): class _AllFieldTypes(ast.AST): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-09-19-00-36.gh-issue-137600.p_p6OU.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-09-19-00-36.gh-issue-137600.p_p6OU.rst new file mode 100644 index 00000000000000..d1d1e36215daa3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-09-19-00-36.gh-issue-137600.p_p6OU.rst @@ -0,0 +1,4 @@ +:mod:`ast`: The constructors of AST nodes now raise a :exc:`TypeError` when +a required argument is omitted or when a keyword argument that does not map to +a field on the AST node is passed. These cases had previously raised a +:exc:`DeprecationWarning` since Python 3.13. Patch by Brian Schubert. diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 71a164fbec5a06..df4454cf948693 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -873,6 +873,70 @@ def visitModule(self, mod): return 0; } +/* + * Format the names in the set 'missing' into a natural language list, + * sorted in the order in which they appear in 'fields'. + * + * Similar to format_missing() from 'Python/ceval.c'. + * + * Parameters + * + * missing Set of missing field names to render. + * fields Sequence of AST node field names (self._fields). + */ +static PyObject * +format_missing(PyObject *missing, PyObject *fields) +{ + Py_ssize_t num_fields, num_total, num_left; + num_fields = PySequence_Size(fields); + if (num_fields == -1) { + return NULL; + } + num_total = num_left = PySet_GET_SIZE(missing); + PyUnicodeWriter *writer = PyUnicodeWriter_Create(0); + if (writer == NULL) { + goto error; + } + // Iterate all AST node fields in order so that the missing positional + // arguments are rendered in the order in which __init__ expects them. + for (Py_ssize_t i = 0; i < num_fields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (name == NULL) { + goto error; + } + int contains = PySet_Contains(missing, name); + if (contains == -1) { + Py_DECREF(name); + goto error; + } + else if (contains == 1) { + const char* fmt = NULL; + if (num_left == 1) { + fmt = "'%U'"; + } + else if (num_total == 2) { + fmt = "'%U' and "; + } + else if (num_left == 2) { + fmt = "'%U', and "; + } + else { + fmt = "'%U', "; + } + num_left--; + if (PyUnicodeWriter_Format(writer, fmt, name) < 0) { + Py_DECREF(name); + goto error; + } + } + Py_DECREF(name); + } + return PyUnicodeWriter_Finish(writer); +error: + PyUnicodeWriter_Discard(writer); + return NULL; +} + static int ast_type_init(PyObject *self, PyObject *args, PyObject *kw) { @@ -942,8 +1006,8 @@ def visitModule(self, mod): } if (p == 0) { PyErr_Format(PyExc_TypeError, - "%.400s got multiple values for argument %R", - Py_TYPE(self)->tp_name, key); + "%T got multiple values for argument %R", + self, key); res = -1; goto cleanup; } @@ -963,16 +1027,11 @@ def visitModule(self, mod): goto cleanup; } else if (contains == 0) { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ got an unexpected keyword argument %R. " - "Support for arbitrary keyword arguments is deprecated " - "and will be removed in Python 3.15.", - Py_TYPE(self)->tp_name, key - ) < 0) { - res = -1; - goto cleanup; - } + PyErr_Format(PyExc_TypeError, + "%T.__init__ got an unexpected keyword argument %R", + self, key); + res = -1; + goto cleanup; } } res = PyObject_SetAttr(self, key, value); @@ -982,7 +1041,7 @@ def visitModule(self, mod): } } Py_ssize_t size = PySet_Size(remaining_fields); - PyObject *field_types = NULL, *remaining_list = NULL; + PyObject *field_types = NULL, *remaining_list = NULL, *missing_names = NULL; if (size > 0) { if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), &field_types) < 0) { @@ -999,6 +1058,10 @@ def visitModule(self, mod): if (!remaining_list) { goto set_remaining_cleanup; } + missing_names = PySet_New(NULL); + if (!missing_names) { + goto set_remaining_cleanup; + } for (Py_ssize_t i = 0; i < size; i++) { PyObject *name = PyList_GET_ITEM(remaining_list, i); PyObject *type = PyDict_GetItemWithError(field_types, name); @@ -1007,14 +1070,10 @@ def visitModule(self, mod): goto set_remaining_cleanup; } else { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "Field %R is missing from %.400s._field_types. " - "This will become an error in Python 3.15.", - name, Py_TYPE(self)->tp_name - ) < 0) { - goto set_remaining_cleanup; - } + PyErr_Format(PyExc_TypeError, + "Field %R is missing from %T._field_types", + name, self); + goto set_remaining_cleanup; } } else if (_PyUnion_Check(type)) { @@ -1042,16 +1101,25 @@ def visitModule(self, mod): } else { // simple field (e.g., identifier) - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ missing 1 required positional argument: %R. " - "This will become an error in Python 3.15.", - Py_TYPE(self)->tp_name, name - ) < 0) { + res = PySet_Add(missing_names, name); + if (res < 0) { goto set_remaining_cleanup; } } } + Py_ssize_t num_missing = PySet_GET_SIZE(missing_names); + if (num_missing > 0) { + PyObject *name_str = format_missing(missing_names, fields); + if (!name_str) { + goto set_remaining_cleanup; + } + PyErr_Format(PyExc_TypeError, + "%T.__init__ missing %d required positional argument%s: %U", + self, num_missing, num_missing == 1 ? "" : "s", name_str); + Py_DECREF(name_str); + goto set_remaining_cleanup; + } + Py_DECREF(missing_names); Py_DECREF(remaining_list); Py_DECREF(field_types); } @@ -1061,6 +1129,7 @@ def visitModule(self, mod): Py_XDECREF(remaining_fields); return res; set_remaining_cleanup: + Py_XDECREF(missing_names); Py_XDECREF(remaining_list); Py_XDECREF(field_types); res = -1; @@ -1144,182 +1213,6 @@ def visitModule(self, mod): return result; } -/* - * Perform the following validations: - * - * - All keyword arguments are known 'fields' or 'attributes'. - * - No field or attribute would be left unfilled after copy.replace(). - * - * On success, this returns 1. Otherwise, set a TypeError - * exception and returns -1 (no exception is set if some - * other internal errors occur). - * - * Parameters - * - * self The AST node instance. - * dict The AST node instance dictionary (self.__dict__). - * fields The list of fields (self._fields). - * attributes The list of attributes (self._attributes). - * kwargs Keyword arguments passed to ast_type_replace(). - * - * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. - * - * Note: this function can be removed in 3.15 since the verification - * will be done inside the constructor. - */ -static inline int -ast_type_replace_check(PyObject *self, - PyObject *dict, - PyObject *fields, - PyObject *attributes, - PyObject *kwargs) -{ - // While it is possible to make some fast paths that would avoid - // allocating objects on the stack, this would cost us readability. - // For instance, if 'fields' and 'attributes' are both empty, and - // 'kwargs' is not empty, we could raise a TypeError immediately. - PyObject *expecting = PySet_New(fields); - if (expecting == NULL) { - return -1; - } - if (attributes) { - if (_PySet_Update(expecting, attributes) < 0) { - Py_DECREF(expecting); - return -1; - } - } - // Any keyword argument that is neither a field nor attribute is rejected. - // We first need to check whether a keyword argument is accepted or not. - // If all keyword arguments are accepted, we compute the required fields - // and attributes. A field or attribute is not needed if: - // - // 1) it is given in 'kwargs', or - // 2) it already exists on 'self'. - if (kwargs) { - Py_ssize_t pos = 0; - PyObject *key, *value; - while (PyDict_Next(kwargs, &pos, &key, &value)) { - int rc = PySet_Discard(expecting, key); - if (rc < 0) { - Py_DECREF(expecting); - return -1; - } - if (rc == 0) { - PyErr_Format(PyExc_TypeError, - "%.400s.__replace__ got an unexpected keyword " - "argument %R.", Py_TYPE(self)->tp_name, key); - Py_DECREF(expecting); - return -1; - } - } - } - // check that the remaining fields or attributes would be filled - if (dict) { - Py_ssize_t pos = 0; - PyObject *key, *value; - while (PyDict_Next(dict, &pos, &key, &value)) { - // Mark fields or attributes that are found on the instance - // as non-mandatory. If they are not given in 'kwargs', they - // will be shallow-coied; otherwise, they would be replaced - // (not in this function). - if (PySet_Discard(expecting, key) < 0) { - Py_DECREF(expecting); - return -1; - } - } - if (attributes) { - // Some attributes may or may not be present at runtime. - // In particular, now that we checked whether 'kwargs' - // is correct or not, we allow any attribute to be missing. - // - // Note that fields must still be entirely determined when - // calling the constructor later. - PyObject *unused = PyObject_CallMethodOneArg(expecting, - &_Py_ID(difference_update), - attributes); - if (unused == NULL) { - Py_DECREF(expecting); - return -1; - } - Py_DECREF(unused); - } - } - - // Discard fields from 'expecting' that default to None - PyObject *field_types = NULL; - if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), - &_Py_ID(_field_types), - &field_types) < 0) - { - Py_DECREF(expecting); - return -1; - } - if (field_types != NULL) { - Py_ssize_t pos = 0; - PyObject *field_name, *field_type; - while (PyDict_Next(field_types, &pos, &field_name, &field_type)) { - if (_PyUnion_Check(field_type)) { - // optional field - if (PySet_Discard(expecting, field_name) < 0) { - Py_DECREF(expecting); - Py_DECREF(field_types); - return -1; - } - } - } - Py_DECREF(field_types); - } - - // Now 'expecting' contains the fields or attributes - // that would not be filled inside ast_type_replace(). - Py_ssize_t m = PySet_GET_SIZE(expecting); - if (m > 0) { - PyObject *names = PyList_New(m); - if (names == NULL) { - Py_DECREF(expecting); - return -1; - } - Py_ssize_t i = 0, pos = 0; - PyObject *item; - Py_hash_t hash; - while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { - PyObject *name = PyObject_Repr(item); - if (name == NULL) { - Py_DECREF(expecting); - Py_DECREF(names); - return -1; - } - // steal the reference 'name' - PyList_SET_ITEM(names, i++, name); - } - Py_DECREF(expecting); - if (PyList_Sort(names) < 0) { - Py_DECREF(names); - return -1; - } - PyObject *sep = PyUnicode_FromString(", "); - if (sep == NULL) { - Py_DECREF(names); - return -1; - } - PyObject *str_names = PyUnicode_Join(sep, names); - Py_DECREF(sep); - Py_DECREF(names); - if (str_names == NULL) { - return -1; - } - PyErr_Format(PyExc_TypeError, - "%.400s.__replace__ missing %ld keyword argument%s: %U.", - Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); - Py_DECREF(str_names); - return -1; - } - else { - Py_DECREF(expecting); - return 1; - } -} - /* * Python equivalent: * @@ -1409,9 +1302,6 @@ def visitModule(self, mod): if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { goto cleanup; } - if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { - goto cleanup; - } empty_tuple = PyTuple_New(0); if (empty_tuple == NULL) { goto cleanup; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index dad1530e343a38..6bcf57bdd6b4f4 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -5197,6 +5197,70 @@ ast_clear(PyObject *op) return 0; } +/* + * Format the names in the set 'missing' into a natural language list, + * sorted in the order in which they appear in 'fields'. + * + * Similar to format_missing() from 'Python/ceval.c'. + * + * Parameters + * + * missing Set of missing field names to render. + * fields Sequence of AST node field names (self._fields). + */ +static PyObject * +format_missing(PyObject *missing, PyObject *fields) +{ + Py_ssize_t num_fields, num_total, num_left; + num_fields = PySequence_Size(fields); + if (num_fields == -1) { + return NULL; + } + num_total = num_left = PySet_GET_SIZE(missing); + PyUnicodeWriter *writer = PyUnicodeWriter_Create(0); + if (writer == NULL) { + goto error; + } + // Iterate all AST node fields in order so that the missing positional + // arguments are rendered in the order in which __init__ expects them. + for (Py_ssize_t i = 0; i < num_fields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (name == NULL) { + goto error; + } + int contains = PySet_Contains(missing, name); + if (contains == -1) { + Py_DECREF(name); + goto error; + } + else if (contains == 1) { + const char* fmt = NULL; + if (num_left == 1) { + fmt = "'%U'"; + } + else if (num_total == 2) { + fmt = "'%U' and "; + } + else if (num_left == 2) { + fmt = "'%U', and "; + } + else { + fmt = "'%U', "; + } + num_left--; + if (PyUnicodeWriter_Format(writer, fmt, name) < 0) { + Py_DECREF(name); + goto error; + } + } + Py_DECREF(name); + } + return PyUnicodeWriter_Finish(writer); +error: + PyUnicodeWriter_Discard(writer); + return NULL; +} + static int ast_type_init(PyObject *self, PyObject *args, PyObject *kw) { @@ -5266,8 +5330,8 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) } if (p == 0) { PyErr_Format(PyExc_TypeError, - "%.400s got multiple values for argument %R", - Py_TYPE(self)->tp_name, key); + "%T got multiple values for argument %R", + self, key); res = -1; goto cleanup; } @@ -5287,16 +5351,11 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } else if (contains == 0) { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ got an unexpected keyword argument %R. " - "Support for arbitrary keyword arguments is deprecated " - "and will be removed in Python 3.15.", - Py_TYPE(self)->tp_name, key - ) < 0) { - res = -1; - goto cleanup; - } + PyErr_Format(PyExc_TypeError, + "%T.__init__ got an unexpected keyword argument %R", + self, key); + res = -1; + goto cleanup; } } res = PyObject_SetAttr(self, key, value); @@ -5306,7 +5365,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) } } Py_ssize_t size = PySet_Size(remaining_fields); - PyObject *field_types = NULL, *remaining_list = NULL; + PyObject *field_types = NULL, *remaining_list = NULL, *missing_names = NULL; if (size > 0) { if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), &field_types) < 0) { @@ -5323,6 +5382,10 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) if (!remaining_list) { goto set_remaining_cleanup; } + missing_names = PySet_New(NULL); + if (!missing_names) { + goto set_remaining_cleanup; + } for (Py_ssize_t i = 0; i < size; i++) { PyObject *name = PyList_GET_ITEM(remaining_list, i); PyObject *type = PyDict_GetItemWithError(field_types, name); @@ -5331,14 +5394,10 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto set_remaining_cleanup; } else { - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "Field %R is missing from %.400s._field_types. " - "This will become an error in Python 3.15.", - name, Py_TYPE(self)->tp_name - ) < 0) { - goto set_remaining_cleanup; - } + PyErr_Format(PyExc_TypeError, + "Field %R is missing from %T._field_types", + name, self); + goto set_remaining_cleanup; } } else if (_PyUnion_Check(type)) { @@ -5366,16 +5425,25 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) } else { // simple field (e.g., identifier) - if (PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, - "%.400s.__init__ missing 1 required positional argument: %R. " - "This will become an error in Python 3.15.", - Py_TYPE(self)->tp_name, name - ) < 0) { + res = PySet_Add(missing_names, name); + if (res < 0) { goto set_remaining_cleanup; } } } + Py_ssize_t num_missing = PySet_GET_SIZE(missing_names); + if (num_missing > 0) { + PyObject *name_str = format_missing(missing_names, fields); + if (!name_str) { + goto set_remaining_cleanup; + } + PyErr_Format(PyExc_TypeError, + "%T.__init__ missing %d required positional argument%s: %U", + self, num_missing, num_missing == 1 ? "" : "s", name_str); + Py_DECREF(name_str); + goto set_remaining_cleanup; + } + Py_DECREF(missing_names); Py_DECREF(remaining_list); Py_DECREF(field_types); } @@ -5385,6 +5453,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_XDECREF(remaining_fields); return res; set_remaining_cleanup: + Py_XDECREF(missing_names); Py_XDECREF(remaining_list); Py_XDECREF(field_types); res = -1; @@ -5468,182 +5537,6 @@ ast_type_reduce(PyObject *self, PyObject *unused) return result; } -/* - * Perform the following validations: - * - * - All keyword arguments are known 'fields' or 'attributes'. - * - No field or attribute would be left unfilled after copy.replace(). - * - * On success, this returns 1. Otherwise, set a TypeError - * exception and returns -1 (no exception is set if some - * other internal errors occur). - * - * Parameters - * - * self The AST node instance. - * dict The AST node instance dictionary (self.__dict__). - * fields The list of fields (self._fields). - * attributes The list of attributes (self._attributes). - * kwargs Keyword arguments passed to ast_type_replace(). - * - * The 'dict', 'fields', 'attributes' and 'kwargs' arguments can be NULL. - * - * Note: this function can be removed in 3.15 since the verification - * will be done inside the constructor. - */ -static inline int -ast_type_replace_check(PyObject *self, - PyObject *dict, - PyObject *fields, - PyObject *attributes, - PyObject *kwargs) -{ - // While it is possible to make some fast paths that would avoid - // allocating objects on the stack, this would cost us readability. - // For instance, if 'fields' and 'attributes' are both empty, and - // 'kwargs' is not empty, we could raise a TypeError immediately. - PyObject *expecting = PySet_New(fields); - if (expecting == NULL) { - return -1; - } - if (attributes) { - if (_PySet_Update(expecting, attributes) < 0) { - Py_DECREF(expecting); - return -1; - } - } - // Any keyword argument that is neither a field nor attribute is rejected. - // We first need to check whether a keyword argument is accepted or not. - // If all keyword arguments are accepted, we compute the required fields - // and attributes. A field or attribute is not needed if: - // - // 1) it is given in 'kwargs', or - // 2) it already exists on 'self'. - if (kwargs) { - Py_ssize_t pos = 0; - PyObject *key, *value; - while (PyDict_Next(kwargs, &pos, &key, &value)) { - int rc = PySet_Discard(expecting, key); - if (rc < 0) { - Py_DECREF(expecting); - return -1; - } - if (rc == 0) { - PyErr_Format(PyExc_TypeError, - "%.400s.__replace__ got an unexpected keyword " - "argument %R.", Py_TYPE(self)->tp_name, key); - Py_DECREF(expecting); - return -1; - } - } - } - // check that the remaining fields or attributes would be filled - if (dict) { - Py_ssize_t pos = 0; - PyObject *key, *value; - while (PyDict_Next(dict, &pos, &key, &value)) { - // Mark fields or attributes that are found on the instance - // as non-mandatory. If they are not given in 'kwargs', they - // will be shallow-coied; otherwise, they would be replaced - // (not in this function). - if (PySet_Discard(expecting, key) < 0) { - Py_DECREF(expecting); - return -1; - } - } - if (attributes) { - // Some attributes may or may not be present at runtime. - // In particular, now that we checked whether 'kwargs' - // is correct or not, we allow any attribute to be missing. - // - // Note that fields must still be entirely determined when - // calling the constructor later. - PyObject *unused = PyObject_CallMethodOneArg(expecting, - &_Py_ID(difference_update), - attributes); - if (unused == NULL) { - Py_DECREF(expecting); - return -1; - } - Py_DECREF(unused); - } - } - - // Discard fields from 'expecting' that default to None - PyObject *field_types = NULL; - if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), - &_Py_ID(_field_types), - &field_types) < 0) - { - Py_DECREF(expecting); - return -1; - } - if (field_types != NULL) { - Py_ssize_t pos = 0; - PyObject *field_name, *field_type; - while (PyDict_Next(field_types, &pos, &field_name, &field_type)) { - if (_PyUnion_Check(field_type)) { - // optional field - if (PySet_Discard(expecting, field_name) < 0) { - Py_DECREF(expecting); - Py_DECREF(field_types); - return -1; - } - } - } - Py_DECREF(field_types); - } - - // Now 'expecting' contains the fields or attributes - // that would not be filled inside ast_type_replace(). - Py_ssize_t m = PySet_GET_SIZE(expecting); - if (m > 0) { - PyObject *names = PyList_New(m); - if (names == NULL) { - Py_DECREF(expecting); - return -1; - } - Py_ssize_t i = 0, pos = 0; - PyObject *item; - Py_hash_t hash; - while (_PySet_NextEntry(expecting, &pos, &item, &hash)) { - PyObject *name = PyObject_Repr(item); - if (name == NULL) { - Py_DECREF(expecting); - Py_DECREF(names); - return -1; - } - // steal the reference 'name' - PyList_SET_ITEM(names, i++, name); - } - Py_DECREF(expecting); - if (PyList_Sort(names) < 0) { - Py_DECREF(names); - return -1; - } - PyObject *sep = PyUnicode_FromString(", "); - if (sep == NULL) { - Py_DECREF(names); - return -1; - } - PyObject *str_names = PyUnicode_Join(sep, names); - Py_DECREF(sep); - Py_DECREF(names); - if (str_names == NULL) { - return -1; - } - PyErr_Format(PyExc_TypeError, - "%.400s.__replace__ missing %ld keyword argument%s: %U.", - Py_TYPE(self)->tp_name, m, m == 1 ? "" : "s", str_names); - Py_DECREF(str_names); - return -1; - } - else { - Py_DECREF(expecting); - return 1; - } -} - /* * Python equivalent: * @@ -5733,9 +5626,6 @@ ast_type_replace(PyObject *self, PyObject *args, PyObject *kwargs) if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { goto cleanup; } - if (ast_type_replace_check(self, dict, fields, attributes, kwargs) < 0) { - goto cleanup; - } empty_tuple = PyTuple_New(0); if (empty_tuple == NULL) { goto cleanup; From c8799f137a90f8fdfff4439969c09d85ef4bb8b0 Mon Sep 17 00:00:00 2001 From: ByteFlow Date: Tue, 28 Apr 2026 13:08:23 +0800 Subject: [PATCH 32/44] gh-149035: Modernize legacy Python patterns in `Doc/tutorial/stdlib2.rst` (#149036) Co-authored-by: Copilot --- Doc/tutorial/stdlib2.rst | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Doc/tutorial/stdlib2.rst b/Doc/tutorial/stdlib2.rst index 678b71c9274c1c..6c68ba01081379 100644 --- a/Doc/tutorial/stdlib2.rst +++ b/Doc/tutorial/stdlib2.rst @@ -1,7 +1,7 @@ .. _tut-brieftourtwo: ********************************************** -Brief Tour of the Standard Library --- Part II +Brief tour of the standard library --- part II ********************************************** This second tour covers more advanced modules that support professional @@ -10,7 +10,7 @@ programming needs. These modules rarely occur in small scripts. .. _tut-output-formatting: -Output Formatting +Output formatting ================= The :mod:`reprlib` module provides a version of :func:`repr` customized for @@ -130,7 +130,7 @@ templates for XML files, plain text reports, and HTML web reports. .. _tut-binary-formats: -Working with Binary Data Record Layouts +Working with binary data record layouts ======================================= The :mod:`struct` module provides :func:`~struct.pack` and @@ -178,14 +178,13 @@ tasks in background while the main program continues to run:: class AsyncZip(threading.Thread): def __init__(self, infile, outfile): - threading.Thread.__init__(self) + super().__init__() self.infile = infile self.outfile = outfile def run(self): - f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) - f.write(self.infile) - f.close() + with zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED) as f: + f.write(self.infile) print('Finished background zip of:', self.infile) background = AsyncZip('mydata.txt', 'myarchive.zip') @@ -245,7 +244,7 @@ application. .. _tut-weak-references: -Weak References +Weak references =============== Python does automatic memory management (reference counting for most objects and @@ -286,7 +285,7 @@ applications include caching objects that are expensive to create:: .. _tut-list-tools: -Tools for Working with Lists +Tools for working with lists ============================ Many data structure needs can be met with the built-in list type. However, @@ -352,7 +351,7 @@ not want to run a full list sort:: .. _tut-decimal-fp: -Decimal Floating-Point Arithmetic +Decimal floating-point arithmetic ================================= The :mod:`decimal` module offers a :class:`~decimal.Decimal` datatype for From 29251396a9d4a91255c8f960d22c027887d40543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Tue, 28 Apr 2026 08:26:38 +0300 Subject: [PATCH 33/44] gh-125862: Keep ContextDecorator open across generator/coroutine execution (GH-136212) ContextDecorator and AsyncContextDecorator (and therefore @contextmanager and @asynccontextmanager used as decorators) now detect generator, coroutine, and asynchronous generator functions and emit a wrapper of the matching kind, so the context manager spans iteration or await rather than just the call that constructs the lazy object. Wrapped generators are explicitly closed when iteration ends. For asynchronous generator wrappers, values passed via asend() and exceptions via athrow() are not forwarded to the wrapped generator. AsyncContextDecorator now also accepts synchronous functions and generators, returning an asynchronous wrapper; ContextDecorator remains the recommended choice for those. inspect.isgeneratorfunction(), iscoroutinefunction(), and isasyncgenfunction() now return True for the decorated result when the input is of that kind. --------- Co-authored-by: Gregory P. Smith --- Doc/library/contextlib.rst | 37 ++++- Doc/whatsnew/3.15.rst | 9 ++ Lib/contextlib.py | 82 ++++++++-- Lib/test/test_contextlib.py | 148 ++++++++++++++++++ Lib/test/test_contextlib_async.py | 138 ++++++++++++++++ ...-07-02-17-01-17.gh-issue-125862.WgFYj3.rst | 4 + 6 files changed, 407 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-02-17-01-17.gh-issue-125862.WgFYj3.rst diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 5c6403879ab505..77bac8dcc3afbd 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -467,12 +467,40 @@ Functions and classes provided: statements. If this is not the case, then the original construct with the explicit :keyword:`!with` statement inside the function should be used. + When the decorated callable is a generator function, coroutine function, or + asynchronous generator function, the returned wrapper is of the same kind + and keeps the context manager open for the lifetime of the iteration or + await rather than only for the call that creates the generator or coroutine + object. Wrapped generators and asynchronous generators are explicitly + closed when iteration ends, as if by :func:`closing` or :func:`aclosing`. + + .. note:: + For asynchronous generators the wrapper re-yields each value with + ``async for``; values sent with :meth:`~agen.asend` and exceptions + thrown with :meth:`~agen.athrow` are not forwarded to the wrapped + generator. + .. versionadded:: 3.2 + .. versionchanged:: next + Decorating a generator function, coroutine function, or asynchronous + generator function now keeps the context manager open across iteration + or await. Previously the context manager exited as soon as the + generator or coroutine object was created. + .. class:: AsyncContextDecorator - Similar to :class:`ContextDecorator` but only for asynchronous functions. + Similar to :class:`ContextDecorator`, but the context manager is entered + and exited with :keyword:`async with`. Decorate coroutine functions and + asynchronous generator functions with this class; the returned wrapper is + of the same kind. + + .. note:: + Synchronous functions and generators are accepted, but the wrapper is + always asynchronous, so the decorated callable must then be awaited or + iterated with ``async for``. If that change of calling convention is + not intended, use :class:`ContextDecorator` instead. Example of ``AsyncContextDecorator``:: @@ -510,6 +538,13 @@ Functions and classes provided: .. versionadded:: 3.10 + .. versionchanged:: next + Decorating an asynchronous generator function now keeps the context + manager open across iteration. Previously the context manager exited + as soon as the generator object was created. Synchronous functions + and synchronous generator functions are also now accepted, with an + asynchronous wrapper returned. + .. class:: ExitStack() diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 65965d504c0976..ee49d043de2641 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -846,6 +846,15 @@ contextlib consistency with the :keyword:`with` and :keyword:`async with` statements. (Contributed by Serhiy Storchaka in :gh:`144386`.) +* :class:`~contextlib.ContextDecorator` and + :class:`~contextlib.AsyncContextDecorator` (and therefore + :func:`~contextlib.contextmanager` and :func:`~contextlib.asynccontextmanager` + used as decorators) now detect generator functions, coroutine functions, and + asynchronous generator functions and keep the context manager open across + iteration or await. Previously the context manager exited as soon as the + generator or coroutine object was created. + (Contributed by Alex Grönholm & Gregory P. Smith in :gh:`125862`.) + dataclasses ----------- diff --git a/Lib/contextlib.py b/Lib/contextlib.py index cac3e39eba8b52..efc02bfa9243da 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -1,10 +1,16 @@ """Utilities for with-statement contexts. See PEP 343.""" + import abc import os import sys import _collections_abc from collections import deque from functools import wraps +lazy from inspect import ( + isasyncgenfunction as _isasyncgenfunction, + iscoroutinefunction as _iscoroutinefunction, + isgeneratorfunction as _isgeneratorfunction, +) from types import GenericAlias __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", @@ -79,11 +85,37 @@ def _recreate_cm(self): return self def __call__(self, func): - @wraps(func) - def inner(*args, **kwds): - with self._recreate_cm(): - return func(*args, **kwds) - return inner + wrapper = wraps(func) + if _isasyncgenfunction(func): + + async def asyncgen_inner(*args, **kwds): + with self._recreate_cm(): + async with aclosing(func(*args, **kwds)) as gen: + async for value in gen: + yield value + + return wrapper(asyncgen_inner) + elif _iscoroutinefunction(func): + + async def async_inner(*args, **kwds): + with self._recreate_cm(): + return await func(*args, **kwds) + + return wrapper(async_inner) + elif _isgeneratorfunction(func): + + def gen_inner(*args, **kwds): + with self._recreate_cm(), closing(func(*args, **kwds)) as gen: + return (yield from gen) + + return wrapper(gen_inner) + else: + + def inner(*args, **kwds): + with self._recreate_cm(): + return func(*args, **kwds) + + return wrapper(inner) class AsyncContextDecorator(object): @@ -95,11 +127,41 @@ def _recreate_cm(self): return self def __call__(self, func): - @wraps(func) - async def inner(*args, **kwds): - async with self._recreate_cm(): - return await func(*args, **kwds) - return inner + wrapper = wraps(func) + if _isasyncgenfunction(func): + + async def asyncgen_inner(*args, **kwds): + async with ( + self._recreate_cm(), + aclosing(func(*args, **kwds)) as gen + ): + async for value in gen: + yield value + + return wrapper(asyncgen_inner) + elif _iscoroutinefunction(func): + + async def async_inner(*args, **kwds): + async with self._recreate_cm(): + return await func(*args, **kwds) + + return wrapper(async_inner) + elif _isgeneratorfunction(func): + + async def gen_inner(*args, **kwds): + async with self._recreate_cm(): + with closing(func(*args, **kwds)) as gen: + for value in gen: + yield value + + return wrapper(gen_inner) + else: + + async def inner(*args, **kwds): + async with self._recreate_cm(): + return func(*args, **kwds) + + return wrapper(inner) class _GeneratorContextManagerBase: diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 1fd8b3cb18c2d4..e291f814edbd93 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -680,6 +680,154 @@ def test(x): self.assertEqual(state, [1, 'something else', 999]) + def test_contextmanager_decorate_generator_function(self): + @contextmanager + def woohoo(y): + state.append(y) + yield + state.append(999) + + state = [] + @woohoo(1) + def test(x): + self.assertEqual(state, [1]) + state.append(x) + yield + state.append("second item") + return "result" + + gen = test("something") + for _ in gen: + self.assertEqual(state, [1, "something"]) + self.assertEqual(state, [1, "something", "second item", 999]) + + # The wrapped generator's return value is preserved. + state = [] + gen = test("something") + with self.assertRaises(StopIteration) as cm: + while True: + next(gen) + self.assertEqual(cm.exception.value, "result") + + + def test_contextmanager_decorate_generator_function_exception(self): + @contextmanager + def woohoo(): + state.append("enter") + try: + yield + finally: + state.append("exit") + + state = [] + @woohoo() + def test(): + state.append("body") + yield + raise ZeroDivisionError + + with self.assertRaises(ZeroDivisionError): + for _ in test(): + pass + self.assertEqual(state, ["enter", "body", "exit"]) + + + def test_contextmanager_decorate_generator_function_early_stop(self): + @contextmanager + def woohoo(): + state.append("enter") + try: + yield + finally: + state.append("exit") + + state = [] + @woohoo() + def test(): + try: + yield 1 + yield 2 + finally: + state.append("inner closed") + + gen = test() + self.assertEqual(next(gen), 1) + gen.close() + # The inner generator is closed before the context manager exits. + self.assertEqual(state, ["enter", "inner closed", "exit"]) + + + def test_contextmanager_decorate_generator_function_send_throw(self): + @contextmanager + def woohoo(): + yield + + @woohoo() + def test(): + received = yield "first" + state.append(("received", received)) + try: + yield "second" + except ValueError as exc: + state.append(("caught", type(exc))) + yield "after throw" + + # .send() and .throw() are forwarded to the wrapped generator. + state = [] + gen = test() + self.assertEqual(next(gen), "first") + self.assertEqual(gen.send("VALUE"), "second") + self.assertEqual(gen.throw(ValueError), "after throw") + gen.close() + self.assertEqual( + state, [("received", "VALUE"), ("caught", ValueError)] + ) + + + def test_contextmanager_decorate_coroutine_function(self): + @contextmanager + def woohoo(y): + state.append(y) + yield + state.append(999) + + state = [] + @woohoo(1) + async def test(x): + self.assertEqual(state, [1]) + state.append(x) + + coro = test("something") + with self.assertRaises(StopIteration): + coro.send(None) + + self.assertEqual(state, [1, "something", 999]) + + + def test_contextmanager_decorate_asyncgen_function(self): + @contextmanager + def woohoo(y): + state.append(y) + yield + state.append(999) + + state = [] + @woohoo(1) + async def test(x): + self.assertEqual(state, [1]) + state.append(x) + yield + state.append("second item") + + agen = test("something") + with self.assertRaises(StopIteration): + agen.asend(None).send(None) + with self.assertRaises(StopAsyncIteration): + agen.asend(None).send(None) + + self.assertEqual(state, [1, "something", "second item", 999]) + + class TestBaseExitStack: exit_stack = None diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 248d32d615225d..95bdfdb3d9d4a6 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -402,6 +402,144 @@ async def test(): await test() self.assertFalse(entered) + @_async_test + async def test_decorator_decorate_sync_function(self): + @asynccontextmanager + async def context(): + state.append(1) + yield + state.append(999) + + state = [] + @context() + def test(x): + self.assertEqual(state, [1]) + state.append(x) + + await test("something") + self.assertEqual(state, [1, "something", 999]) + + @_async_test + async def test_decorator_decorate_generator_function(self): + @asynccontextmanager + async def context(): + state.append(1) + yield + state.append(999) + + state = [] + @context() + def test(x): + self.assertEqual(state, [1]) + state.append(x) + yield + state.append("second item") + + async for _ in test("something"): + self.assertEqual(state, [1, "something"]) + self.assertEqual(state, [1, "something", "second item", 999]) + + @_async_test + async def test_decorator_decorate_asyncgen_function(self): + @asynccontextmanager + async def context(): + state.append(1) + yield + state.append(999) + + state = [] + @context() + async def test(x): + self.assertEqual(state, [1]) + state.append(x) + yield + state.append("second item") + + async for _ in test("something"): + self.assertEqual(state, [1, "something"]) + self.assertEqual(state, [1, "something", "second item", 999]) + + @_async_test + async def test_decorator_decorate_asyncgen_function_exception(self): + @asynccontextmanager + async def context(): + state.append("enter") + try: + yield + finally: + state.append("exit") + + state = [] + @context() + async def test(): + state.append("body") + yield + raise ZeroDivisionError + + with self.assertRaises(ZeroDivisionError): + async for _ in test(): + pass + self.assertEqual(state, ["enter", "body", "exit"]) + + @_async_test + async def test_decorator_decorate_asyncgen_function_early_stop(self): + @asynccontextmanager + async def context(): + state.append("enter") + try: + yield + finally: + state.append("exit") + + state = [] + @context() + async def test(): + try: + yield 1 + yield 2 + finally: + state.append("inner closed") + + agen = test() + async for value in agen: + self.assertEqual(value, 1) + break + await agen.aclose() + # The inner async generator is closed before the context + # manager exits. + self.assertEqual(state, ["enter", "inner closed", "exit"]) + + @_async_test + async def test_decorator_decorate_asyncgen_function_asend_athrow(self): + @asynccontextmanager + async def context(): + yield + + @context() + async def test(): + try: + received = yield "first" + state.append(("received", received)) + yield "second" + except ValueError: + state.append("inner saw ValueError") + raise + finally: + state.append("inner closed") + + # asend() values and athrow() exceptions are not forwarded to the + # wrapped generator (a documented limitation). + state = [] + agen = test() + self.assertEqual(await agen.__anext__(), "first") + self.assertEqual(await agen.asend("VALUE"), "second") + # The inner generator received None, not "VALUE". + self.assertEqual(state, [("received", None)]) + with self.assertRaises(ValueError): + await agen.athrow(ValueError) + # The inner generator was closed, not thrown into. + self.assertEqual(state, [("received", None), "inner closed"]) + @_async_test async def test_decorator_with_exception(self): entered = False diff --git a/Misc/NEWS.d/next/Library/2025-07-02-17-01-17.gh-issue-125862.WgFYj3.rst b/Misc/NEWS.d/next/Library/2025-07-02-17-01-17.gh-issue-125862.WgFYj3.rst new file mode 100644 index 00000000000000..1ccc91d55ec3ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-02-17-01-17.gh-issue-125862.WgFYj3.rst @@ -0,0 +1,4 @@ +The :func:`contextlib.contextmanager` and +:func:`contextlib.asynccontextmanager` decorators now work correctly with +generators, coroutine functions, and async generators when the wrapped +callables are used as decorators. From 9a57179d74c1a20e3188779696c60c8dd812e6fb Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:06:23 -0700 Subject: [PATCH 34/44] GH-83065: Fix import deadlock by implementing hierarchical module locking (GH-137196) Make _find_and_load() acquire the module locks for the full dotted-name chain (parent before child) when loading a nested module, so both threads contend on the same first lock and serialise instead of deadlocking. When acquiring a parent's lock would itself deadlock with another thread that is loading that parent (cross-package circular imports), the parent's lock is skipped and the partially-initialised parent is accepted -- the same policy _lock_unlock_module() already applies on the existing code path -- so concurrent circular imports that worked before continue to work. --- Doc/whatsnew/3.15.rst | 7 + Lib/importlib/_bootstrap.py | 66 ++++++++- .../test_importlib/test_threaded_import.py | 137 ++++++++++++++++++ ...6-04-28-05-59-17.gh-issue-83065.f0UPNE.rst | 7 + 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-05-59-17.gh-issue-83065.f0UPNE.rst diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index ee49d043de2641..6f0f7021de8e7a 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -677,6 +677,13 @@ Other language changes the existing support for unary minus. (Contributed by Bartosz Sławecki in :gh:`145239`.) +* The import system now acquires per-module locks in hierarchical order + (parent packages before their submodules). This fixes a long-standing + deadlock where one thread importing ``pkg.sub`` and another importing + ``pkg.sub.mod`` could each block the other when ``pkg/sub/__init__.py`` + imports ``pkg.sub.mod``. + (Contributed by Gregory P. Smith in :gh:`83065`.) + New modules =========== diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 45beb51659f5b7..06dc45d71d7ca4 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -424,6 +424,64 @@ def __exit__(self, *args, **kwargs): self._lock.release() +def _get_module_chain(name): + """Return the chain of dotted-name prefixes from root to leaf. + + For example: 'a.b.c' -> ['a', 'a.b', 'a.b.c'] + """ + parts = name.split('.') + return ['.'.join(parts[:i+1]) for i in range(len(parts))] + + +class _HierarchicalLockManager: + """Manages acquisition of multiple module locks in hierarchical order. + + This prevents deadlocks by ensuring all threads acquire locks in the + same order (parent modules before child modules). + """ + + def __init__(self, name): + self._name = name + self._module_chain = _get_module_chain(name) + self._locks = [] + + def __enter__(self): + try: + for module_name in self._module_chain: + # Only acquire lock if module is not already fully loaded + module = sys.modules.get(module_name) + if (module is None or + getattr(getattr(module, "__spec__", None), + "_initializing", False)): + lock = _get_module_lock(module_name) + try: + lock.acquire() + except _DeadlockError: + if module_name == self._name: + raise + # The parent is being initialised by a thread that + # is (transitively) waiting on a lock we hold. + # Apply the same policy as _lock_unlock_module(): + # accept a partially-initialised parent for circular + # imports rather than failing the whole chain. + continue + self._locks.append((module_name, lock)) + except: + # __exit__ is not called when __enter__ raises (e.g. _DeadlockError + # on the leaf lock, or KeyboardInterrupt), so release whatever we + # already hold to avoid permanently leaking held module locks. + for module_name, lock in reversed(self._locks): + lock.release() + self._locks.clear() + raise + return self + + def __exit__(self, *args, **kwargs): + for module_name, lock in reversed(self._locks): + lock.release() + self._locks.clear() + + # The following two functions are for consumption by Python/import.c. def _get_module_lock(name): @@ -1276,7 +1334,13 @@ def _find_and_load(name, import_): module = sys.modules.get(name, _NEEDS_LOADING) if (module is _NEEDS_LOADING or getattr(getattr(module, "__spec__", None), "_initializing", False)): - with _ModuleLockManager(name): + + if '.' in name: + lock_manager = _HierarchicalLockManager(name) + else: + lock_manager = _ModuleLockManager(name) + + with lock_manager: module = sys.modules.get(name, _NEEDS_LOADING) if module is _NEEDS_LOADING: return _find_and_load_unlocked(name, import_) diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py index 8b793ebf29bcae..6875fdca9c8528 100644 --- a/Lib/test/test_importlib/test_threaded_import.py +++ b/Lib/test/test_importlib/test_threaded_import.py @@ -324,6 +324,143 @@ def do_import(delay=0): # Neither thread should have errors about stale modules self.assertEqual(errors, [], f"Race condition detected: {errors}") + def test_hierarchical_import_deadlock(self): + # Regression test for bpo-38884 / gh-83065 + # Tests that concurrent imports at different hierarchy levels + # don't deadlock when parent imports child in __init__.py + + # Create package structure: + # package/__init__.py: from package import subpackage + # package/subpackage/__init__.py: from package.subpackage.module import * + # package/subpackage/module.py: class SomeClass: pass + + pkg_dir = os.path.join(TESTFN, 'hier_deadlock_pkg') + os.makedirs(pkg_dir) + self.addCleanup(shutil.rmtree, TESTFN) + + subpkg_dir = os.path.join(pkg_dir, 'subpackage') + os.makedirs(subpkg_dir) + + with open(os.path.join(pkg_dir, "__init__.py"), "w") as f: + f.write("from hier_deadlock_pkg import subpackage\n") + + with open(os.path.join(subpkg_dir, "__init__.py"), "w") as f: + f.write("from hier_deadlock_pkg.subpackage.module import *\n") + + with open(os.path.join(subpkg_dir, "module.py"), "w") as f: + f.write("class SomeClass:\n pass\n") + + sys.path.insert(0, TESTFN) + self.addCleanup(sys.path.remove, TESTFN) + self.addCleanup(forget, 'hier_deadlock_pkg') + self.addCleanup(forget, 'hier_deadlock_pkg.subpackage') + self.addCleanup(forget, 'hier_deadlock_pkg.subpackage.module') + + importlib.invalidate_caches() + + errors = [] + results = [] + barrier = threading.Barrier(2) + + def t1(): + barrier.wait() + try: + import hier_deadlock_pkg.subpackage + results.append('t1_success') + except Exception as e: + errors.append(('t1', type(e).__name__, str(e))) + + def t2(): + barrier.wait() + try: + import hier_deadlock_pkg.subpackage.module + results.append('t2_success') + except Exception as e: + errors.append(('t2', type(e).__name__, str(e))) + + # Run multiple times to increase chance of hitting race condition + for i in range(10): + for mod in ['hier_deadlock_pkg', 'hier_deadlock_pkg.subpackage', + 'hier_deadlock_pkg.subpackage.module']: + sys.modules.pop(mod, None) + + errors.clear() + results.clear() + barrier.reset() + + thread1 = threading.Thread(target=t1) + thread2 = threading.Thread(target=t2) + + thread1.start() + thread2.start() + + thread1.join(timeout=5) + thread2.join(timeout=5) + + if thread1.is_alive() or thread2.is_alive(): + self.fail(f"Threads deadlocked on iteration {i}") + + self.assertEqual( + errors, [], + f"Import(s) failed on iteration {i}: {errors}") + self.assertEqual( + sorted(results), ['t1_success', 't2_success'], + f"Not all imports succeeded on iteration {i}: {results}") + + def test_cross_package_circular_import(self): + # Two packages whose __init__.py each import a submodule of the + # other. Concurrent imports of submodules of each must not raise + # _DeadlockError; the import system accepts a partially-initialised + # parent in this case (see _lock_unlock_module). + os.makedirs(os.path.join(TESTFN, "circ_a")) + os.makedirs(os.path.join(TESTFN, "circ_b")) + self.addCleanup(shutil.rmtree, TESTFN) + with open(os.path.join(TESTFN, "circ_a", "__init__.py"), "w") as f: + f.write("import time; time.sleep(0.03)\nimport circ_b.other\n") + with open(os.path.join(TESTFN, "circ_b", "__init__.py"), "w") as f: + f.write("import time; time.sleep(0.03)\nimport circ_a.other\n") + for pkg in ("circ_a", "circ_b"): + for mod in ("sub.py", "other.py"): + with open(os.path.join(TESTFN, pkg, mod), "w") as f: + f.write("X = 1\n") + + sys.path.insert(0, TESTFN) + self.addCleanup(sys.path.remove, TESTFN) + for mod in ("circ_a", "circ_a.sub", "circ_a.other", + "circ_b", "circ_b.sub", "circ_b.other"): + self.addCleanup(forget, mod) + importlib.invalidate_caches() + + errors = [] + barrier = threading.Barrier(2) + + def do_import(name): + barrier.wait() + try: + importlib.import_module(name) + except Exception as e: + errors.append((name, type(e).__name__, str(e))) + + for i in range(10): + for mod in ("circ_a", "circ_a.sub", "circ_a.other", + "circ_b", "circ_b.sub", "circ_b.other"): + sys.modules.pop(mod, None) + errors.clear() + barrier.reset() + + thread1 = threading.Thread(target=do_import, args=("circ_a.sub",)) + thread2 = threading.Thread(target=do_import, args=("circ_b.sub",)) + thread1.start() + thread2.start() + thread1.join(timeout=5) + thread2.join(timeout=5) + + if thread1.is_alive() or thread2.is_alive(): + self.fail(f"Threads deadlocked on iteration {i}") + self.assertEqual( + errors, [], + f"Import(s) failed on iteration {i}: {errors}") + def setUpModule(): thread_info = threading_helper.threading_setup() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-05-59-17.gh-issue-83065.f0UPNE.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-05-59-17.gh-issue-83065.f0UPNE.rst new file mode 100644 index 00000000000000..81bfa45c069fd4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-05-59-17.gh-issue-83065.f0UPNE.rst @@ -0,0 +1,7 @@ +Fix a deadlock that could occur when one thread is importing a submodule +(for example ``import pkg.sub.mod``) while another thread is importing one +of its parent packages (for example ``import pkg.sub``) and that parent's +``__init__.py`` itself imports the submodule. The import system now +acquires module locks in hierarchical (parent-before-child) order so the +two threads serialise instead of raising +``_DeadlockError``. From be968c72100f35bc18a14310be954b56863630da Mon Sep 17 00:00:00 2001 From: Hai Zhu Date: Tue, 28 Apr 2026 20:41:16 +0800 Subject: [PATCH 35/44] gh-148571: [JIT] Preserve family-head recorder layouts for specialized opcode families (GH-148730) * Records the same objects for each member of family before execution * Records derived values when recording the trace * This makes sure that specialization, or deoptimization, does not cause invalid values to be recorded --- Include/internal/pycore_optimizer.h | 14 ++ Lib/test/test_capi/test_opt.py | 13 ++ Lib/test/test_generated_cases.py | 187 +++++++++++++++- ...-04-18-16-41-04.gh-issue-148571.Q6WB3A.rst | 1 + Python/optimizer.c | 51 ++++- Python/record_functions.c.h | 123 +++++++++- .../record_function_generator.py | 211 +++++++++++++++--- 7 files changed, 555 insertions(+), 45 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-18-16-41-04.gh-issue-148571.Q6WB3A.rst diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 7c2e0e95a80c3f..f356d60ae5c7a7 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -534,7 +534,21 @@ typedef struct { uint8_t count; uint8_t indices[MAX_RECORDED_VALUES]; } _PyOpcodeRecordEntry; + +typedef struct { + uint8_t count; + uint8_t transform_mask; + uint8_t slots[MAX_RECORDED_VALUES]; +} _PyOpcodeRecordSlotMap; + PyAPI_DATA(const _PyOpcodeRecordEntry) _PyOpcode_RecordEntries[256]; +PyAPI_DATA(const _PyOpcodeRecordSlotMap) _PyOpcode_RecordSlotMaps[256]; + +/* Convert a family-recorded value to the form a recorder uop expects. + * If no transform is needed, return the input value unchanged. + * Takes ownership of `value` and returns a new strong reference or NULL. + */ +PyAPI_FUNC(PyObject *) _PyOpcode_RecordTransformValue(int uop, PyObject *value); #endif #ifdef __cplusplus diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 39075fc64cf02b..b37c35495983c3 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5849,6 +5849,19 @@ def testfunc(n): self.assertNotIn("_LOAD_SUPER_ATTR_METHOD", uops) self.assertEqual(uops.count("_GUARD_NOS_TYPE_VERSION"), 2) + def test_settrace_then_polymorphic_call_does_not_crash(self): + script_helper.assert_python_ok("-c", textwrap.dedent(""" + import sys + sys.settrace(lambda *_: None) + sys.settrace(None) + + class C: + def __init__(self, x): + pass + + for i in 0, 1, 0, 1: + C(0) if i else str(0) + """)) def global_identity(x): return x diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 62cf0c0c6af0b2..748309b54593a1 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2074,19 +2074,33 @@ def tearDown(self) -> None: pass super().tearDown() - def generate_tables(self, input: str) -> str: - import io + def analyze_input(self, input: str): with open(self.temp_input_filename, "w+") as f: f.write(parser.BEGIN_MARKER) f.write(input) f.write(parser.END_MARKER) with handle_stderr(): - analysis = analyze_files([self.temp_input_filename]) + return analyze_files([self.temp_input_filename]) + + def generate_tables(self, input: str) -> str: + import io + analysis = self.analyze_input(input) buf = io.StringIO() out = CWriter(buf, 0, False) record_function_generator.generate_recorder_tables(analysis, out) return buf.getvalue() + def get_slot_map_section(self, output: str) -> str: + return output.split( + "const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = {\n", + 1, + )[1].split("};\n\n", 1)[0] + + def assert_slot_map_lines(self, output: str, *lines: str) -> None: + slot_map_section = self.get_slot_map_section(output) + for line in lines: + self.assertIn(line, slot_map_section) + def test_single_recording_uop_generates_count(self): input = """ tier2 op(_RECORD_TOS, (value -- value)) { @@ -2145,6 +2159,173 @@ def test_four_recording_uops_rejected(self): with self.assertRaisesRegex(ValueError, "exceeds MAX_RECORDED_VALUES"): self.generate_tables(input) + def test_family_member_needs_transform_only_when_shape_changes(self): + input = """ + tier2 op(_RECORD_TOS, (value -- value)) { + RECORD_VALUE(value); + } + tier2 op(_RECORD_TOS_TYPE, (value -- value)) { + RECORD_VALUE(Py_TYPE(value)); + } + op(_DO_STUFF, (value -- res)) { + res = value; + } + macro(OP_RAW) = _RECORD_TOS + _DO_STUFF; + macro(OP_RAW_SPECIALIZED) = _RECORD_TOS_TYPE + _DO_STUFF; + family(OP_RAW, INLINE_CACHE_ENTRIES_OP_RAW) = { OP_RAW_SPECIALIZED }; + + macro(OP_TYPED) = _RECORD_TOS_TYPE + _DO_STUFF; + macro(OP_TYPED_SPECIALIZED) = _RECORD_TOS_TYPE + _DO_STUFF; + family(OP_TYPED, INLINE_CACHE_ENTRIES_OP_TYPED) = { OP_TYPED_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assert_slot_map_lines( + output, + "[OP_RAW] = {1, 1, {0}}", + "[OP_RAW_SPECIALIZED] = {1, 0, {0}}", + "[OP_TYPED] = {1, 0, {0}}", + "[OP_TYPED_SPECIALIZED] = {1, 0, {0}}", + ) + + def test_family_member_maps_positional_recorders_to_family_slots(self): + input = """ + tier2 op(_RECORD_TOS, (sub -- sub)) { + RECORD_VALUE(sub); + } + tier2 op(_RECORD_NOS, (container, sub -- container, sub)) { + RECORD_VALUE(container); + } + op(_DO_STUFF, (container, sub -- res)) { + res = container; + } + macro(OP) = _RECORD_TOS + _RECORD_NOS + _DO_STUFF; + macro(OP_SPECIALIZED) = _RECORD_NOS + _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assert_slot_map_lines( + output, + "[OP] = {2, 0, {1, 0}}", + "[OP_SPECIALIZED] = {1, 0, {0}}", + ) + + def test_family_member_maps_non_positional_recorders_by_stack_shape(self): + input = """ + tier2 op(_RECORD_CALLABLE, (callable, self, args[oparg] -- callable, self, args[oparg])) { + RECORD_VALUE(callable); + } + tier2 op(_RECORD_BOUND_METHOD, (callable, self, args[oparg] -- callable, self, args[oparg])) { + RECORD_VALUE(callable); + } + op(_DO_STUFF, (callable, self, args[oparg] -- res)) { + res = callable; + } + macro(OP) = _RECORD_CALLABLE + _DO_STUFF; + macro(OP_SPECIALIZED) = _RECORD_BOUND_METHOD + _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assert_slot_map_lines( + output, + "[OP] = {1, 1, {0}}", + "[OP_SPECIALIZED] = {1, 0, {0}}", + ) + + def test_family_head_records_union_of_member_recorders(self): + input = """ + tier2 op(_RECORD_TOS, (value -- value)) { + RECORD_VALUE(value); + } + op(_DO_STUFF, (value -- res)) { + res = value; + } + macro(OP) = _DO_STUFF; + macro(OP_SPECIALIZED) = _RECORD_TOS + _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assertIn("[OP] = {1, {_RECORD_TOS_INDEX}}", output) + self.assertIn("[OP_SPECIALIZED] = {1, {_RECORD_TOS_INDEX}}", output) + self.assert_slot_map_lines(output, "[OP_SPECIALIZED] = {1, 0, {0}}") + + def test_family_detects_base_and_specialized_recording_difference(self): + input = """ + tier2 op(_RECORD_TOS, (value -- value)) { + RECORD_VALUE(value); + } + tier2 op(_RECORD_TOS_TYPE, (value -- value)) { + RECORD_VALUE(Py_TYPE(value)); + } + op(_DO_STUFF, (value -- res)) { + res = value; + } + macro(OP) = _RECORD_TOS + _DO_STUFF; + macro(OP_SPECIALIZED) = _RECORD_TOS_TYPE + _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + analysis = self.analyze_input(input) + output = self.generate_tables(input) + self.assertEqual( + record_function_generator.get_instruction_record_names( + analysis.instructions["OP"] + ), + ["_RECORD_TOS"], + ) + self.assertEqual( + record_function_generator.get_instruction_record_names( + analysis.instructions["OP_SPECIALIZED"] + ), + ["_RECORD_TOS_TYPE"], + ) + self.assertIn("[OP] = {1, {_RECORD_TOS_TYPE_INDEX}}", output) + self.assertIn("[OP_SPECIALIZED] = {1, {_RECORD_TOS_TYPE_INDEX}}", output) + self.assert_slot_map_lines( + output, + "[OP] = {1, 1, {0}}", + "[OP_SPECIALIZED] = {1, 0, {0}}", + ) + + def test_family_head_falls_back_for_missing_member_slots(self): + input = """ + tier2 op(_RECORD_TOS, (value -- value)) { + RECORD_VALUE(value); + } + op(_DO_STUFF, (value -- res)) { + res = value; + } + macro(OP) = _RECORD_TOS + _DO_STUFF; + macro(OP_SPECIALIZED) = _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assertIn("[OP] = {1, {_RECORD_TOS_INDEX}}", output) + self.assertIn("[OP_SPECIALIZED] = {1, {_RECORD_TOS_INDEX}}", output) + + def test_family_mixed_slots_only_transform_changed_recorders(self): + input = """ + tier2 op(_RECORD_TOS_TYPE, (left, right -- left, right)) { + RECORD_VALUE(Py_TYPE(right)); + } + tier2 op(_RECORD_NOS_TYPE, (left, right -- left, right)) { + RECORD_VALUE(Py_TYPE(left)); + } + tier2 op(_RECORD_NOS, (left, right -- left, right)) { + RECORD_VALUE(left); + } + op(_DO_STUFF, (left, right -- res)) { + res = left; + } + macro(OP) = _RECORD_TOS_TYPE + _RECORD_NOS_TYPE + _DO_STUFF; + macro(OP_SPECIALIZED) = _RECORD_NOS + _DO_STUFF; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED }; + """ + output = self.generate_tables(input) + self.assertIn("[OP] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}", output) + self.assert_slot_map_lines( + output, + "[OP] = {2, 2, {1, 0}}", + "[OP_SPECIALIZED] = {1, 0, {0}}", + ) class TestGeneratedAbstractCases(unittest.TestCase): def setUp(self) -> None: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-18-16-41-04.gh-issue-148571.Q6WB3A.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-18-16-41-04.gh-issue-148571.Q6WB3A.rst new file mode 100644 index 00000000000000..70eeada34320ac --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-18-16-41-04.gh-issue-148571.Q6WB3A.rst @@ -0,0 +1 @@ +Fix a crash in the JIT optimizer when specialized opcode families inherited incompatible recorded operand layouts. diff --git a/Python/optimizer.c b/Python/optimizer.c index 2ce4da0910f3c4..820a0771a2cb0f 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -660,6 +660,44 @@ is_terminator(const _PyUOpInstruction *uop) ); } +static PyObject * +record_trace_transform_to_type(PyObject *value) +{ + PyObject *tp = Py_NewRef((PyObject *)Py_TYPE(value)); + Py_DECREF(value); + return tp; +} + +/* _RECORD_NOS_GEN_FUNC and _RECORD_3OS_GEN_FUNC record the raw receiver. + * If it is a generator, return its function object; otherwise return NULL. + */ +static PyObject * +record_trace_transform_gen_func(PyObject *value) +{ + PyObject *func = NULL; + if (PyGen_Check(value)) { + _PyStackRef f = ((PyGenObject *)value)->gi_iframe.f_funcobj; + if (!PyStackRef_IsNull(f)) { + func = Py_NewRef(PyStackRef_AsPyObjectBorrow(f)); + } + } + Py_DECREF(value); + return func; +} + +/* _RECORD_BOUND_METHOD records the raw callable. + * Keep it only for bound methods; otherwise return NULL. + */ +static PyObject * +record_trace_transform_bound_method(PyObject *value) +{ + if (Py_TYPE(value) == &PyMethod_Type) { + return value; + } + Py_DECREF(value); + return NULL; +} + /* Returns 1 on success (added to trace), 0 on trace end. */ // gh-142543: inlining this function causes stack overflows @@ -833,6 +871,8 @@ _PyJit_translate_single_bytecode_to_trace( // One for possible _DEOPT, one because _CHECK_VALIDITY itself might _DEOPT trace->end -= 2; + const _PyOpcodeRecordSlotMap *record_slot_map = &_PyOpcode_RecordSlotMaps[opcode]; + assert(opcode != ENTER_EXECUTOR && opcode != EXTENDED_ARG); assert(!_PyErr_Occurred(tstate)); @@ -1029,8 +1069,15 @@ _PyJit_translate_single_bytecode_to_trace( } } else if (_PyUop_Flags[uop] & HAS_RECORDS_VALUE_FLAG) { - PyObject *recorded_value = tracer->prev_state.recorded_values[record_idx]; - tracer->prev_state.recorded_values[record_idx] = NULL; + assert(record_idx < record_slot_map->count); + uint8_t record_slot = record_slot_map->slots[record_idx]; + assert(record_slot < tracer->prev_state.recorded_count); + PyObject *recorded_value = tracer->prev_state.recorded_values[record_slot]; + tracer->prev_state.recorded_values[record_slot] = NULL; + if ((record_slot_map->transform_mask & (1u << record_idx)) && + recorded_value != NULL) { + recorded_value = _PyOpcode_RecordTransformValue(uop, recorded_value); + } record_idx++; operand = (uintptr_t)recorded_value; } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index dff13bfb45e5b0..504f6e1d9901c3 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -103,19 +103,45 @@ void _PyOpcode_RecordFunction_CODE(_PyInterpreterFrame *frame, _PyStackRef *stac #define _RECORD_3OS_GEN_FUNC_INDEX 3 #define _RECORD_NOS_GEN_FUNC_INDEX 4 #define _RECORD_CALLABLE_INDEX 5 -#define _RECORD_BOUND_METHOD_INDEX 6 -#define _RECORD_CALLABLE_KW_INDEX 7 -#define _RECORD_4OS_INDEX 8 -#define _RECORD_NOS_TYPE_INDEX 9 +#define _RECORD_CALLABLE_KW_INDEX 6 +#define _RECORD_4OS_INDEX 7 const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { + [TO_BOOL_BOOL] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [TO_BOOL_NONE] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_SUPER_ATTR_ATTR] = {1, {_RECORD_NOS_INDEX}}, + [TO_BOOL] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [TO_BOOL_INT] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [TO_BOOL_LIST] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [TO_BOOL_STR] = {1, {_RECORD_TOS_TYPE_INDEX}}, [TO_BOOL_ALWAYS_TRUE] = {1, {_RECORD_TOS_TYPE_INDEX}}, - [BINARY_OP_SUBSCR_GETITEM] = {1, {_RECORD_NOS_INDEX}}, + [BINARY_OP_MULTIPLY_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_ADD_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBTRACT_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_MULTIPLY_FLOAT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_ADD_FLOAT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBTRACT_FLOAT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_ADD_UNICODE] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_EXTEND] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_INPLACE_ADD_UNICODE] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_LIST_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_LIST_SLICE] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_STR_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_USTR_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_TUPLE_INT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_DICT] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [BINARY_OP_SUBSCR_GETITEM] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, + [SEND] = {1, {_RECORD_3OS_GEN_FUNC_INDEX}}, [SEND_GEN] = {1, {_RECORD_3OS_GEN_FUNC_INDEX}}, + [STORE_ATTR] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_SUPER_ATTR] = {1, {_RECORD_NOS_INDEX}}, [LOAD_SUPER_ATTR_METHOD] = {1, {_RECORD_NOS_INDEX}}, + [LOAD_ATTR] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_INSTANCE_VALUE] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_ATTR_MODULE] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_WITH_HINT] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_SLOT] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [LOAD_ATTR_CLASS] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_PROPERTY] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, {_RECORD_TOS_TYPE_INDEX}}, @@ -125,6 +151,11 @@ const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { [GET_ITER] = {1, {_RECORD_TOS_TYPE_INDEX}}, [GET_ITER_SELF] = {1, {_RECORD_TOS_TYPE_INDEX}}, [GET_ITER_VIRTUAL] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [FOR_ITER] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, + [FOR_ITER_VIRTUAL] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, + [FOR_ITER_LIST] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, + [FOR_ITER_TUPLE] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, + [FOR_ITER_RANGE] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, [FOR_ITER_GEN] = {1, {_RECORD_NOS_GEN_FUNC_INDEX}}, [LOAD_SPECIAL] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_METHOD_WITH_VALUES] = {1, {_RECORD_TOS_TYPE_INDEX}}, @@ -132,34 +163,104 @@ const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = { [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, {_RECORD_TOS_TYPE_INDEX}}, [LOAD_ATTR_METHOD_LAZY_DICT] = {1, {_RECORD_TOS_TYPE_INDEX}}, + [CALL] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_PY_GENERAL] = {1, {_RECORD_CALLABLE_INDEX}}, - [CALL_BOUND_METHOD_GENERAL] = {1, {_RECORD_BOUND_METHOD_INDEX}}, + [CALL_BOUND_METHOD_GENERAL] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_NON_PY_GENERAL] = {1, {_RECORD_CALLABLE_INDEX}}, - [CALL_BOUND_METHOD_EXACT_ARGS] = {1, {_RECORD_BOUND_METHOD_INDEX}}, + [CALL_BOUND_METHOD_EXACT_ARGS] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_PY_EXACT_ARGS] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_TYPE_1] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_STR_1] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_TUPLE_1] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_ALLOC_AND_ENTER_INIT] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_BUILTIN_CLASS] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_BUILTIN_O] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_BUILTIN_FAST] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_BUILTIN_FAST_WITH_KEYWORDS] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_LEN] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_ISINSTANCE] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_LIST_APPEND] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_METHOD_DESCRIPTOR_O] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_METHOD_DESCRIPTOR_NOARGS] = {1, {_RECORD_CALLABLE_INDEX}}, + [CALL_METHOD_DESCRIPTOR_FAST] = {1, {_RECORD_CALLABLE_INDEX}}, [CALL_KW_PY] = {1, {_RECORD_CALLABLE_KW_INDEX}}, [CALL_KW_BOUND_METHOD] = {1, {_RECORD_CALLABLE_KW_INDEX}}, + [CALL_KW] = {1, {_RECORD_CALLABLE_KW_INDEX}}, + [CALL_KW_NON_PY] = {1, {_RECORD_CALLABLE_KW_INDEX}}, + [CALL_FUNCTION_EX] = {1, {_RECORD_4OS_INDEX}}, [CALL_EX_PY] = {1, {_RECORD_4OS_INDEX}}, - [BINARY_OP] = {2, {_RECORD_TOS_TYPE_INDEX, _RECORD_NOS_TYPE_INDEX}}, + [CALL_EX_NON_PY_GENERAL] = {1, {_RECORD_4OS_INDEX}}, + [BINARY_OP] = {2, {_RECORD_NOS_INDEX, _RECORD_TOS_TYPE_INDEX}}, }; -const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[10] = { +const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = { + [TO_BOOL_ALWAYS_TRUE] = {1, 0, {0}}, + [BINARY_OP_SUBSCR_GETITEM] = {1, 0, {0}}, + [SEND_GEN] = {1, 0, {0}}, + [LOAD_SUPER_ATTR_METHOD] = {1, 0, {0}}, + [LOAD_ATTR_INSTANCE_VALUE] = {1, 0, {0}}, + [LOAD_ATTR_WITH_HINT] = {1, 0, {0}}, + [LOAD_ATTR_SLOT] = {1, 0, {0}}, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = {1, 0, {0}}, + [LOAD_ATTR_PROPERTY] = {1, 0, {0}}, + [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = {1, 0, {0}}, + [STORE_ATTR_INSTANCE_VALUE] = {1, 0, {0}}, + [STORE_ATTR_WITH_HINT] = {1, 0, {0}}, + [STORE_ATTR_SLOT] = {1, 0, {0}}, + [GET_ITER] = {1, 0, {0}}, + [GET_ITER_SELF] = {1, 0, {0}}, + [GET_ITER_VIRTUAL] = {1, 0, {0}}, + [FOR_ITER_GEN] = {1, 0, {0}}, + [LOAD_SPECIAL] = {1, 0, {0}}, + [LOAD_ATTR_METHOD_WITH_VALUES] = {1, 0, {0}}, + [LOAD_ATTR_METHOD_NO_DICT] = {1, 0, {0}}, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = {1, 0, {0}}, + [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = {1, 0, {0}}, + [LOAD_ATTR_METHOD_LAZY_DICT] = {1, 0, {0}}, + [CALL_PY_GENERAL] = {1, 0, {0}}, + [CALL_BOUND_METHOD_GENERAL] = {1, 1, {0}}, + [CALL_NON_PY_GENERAL] = {1, 0, {0}}, + [CALL_BOUND_METHOD_EXACT_ARGS] = {1, 1, {0}}, + [CALL_PY_EXACT_ARGS] = {1, 0, {0}}, + [CALL_ALLOC_AND_ENTER_INIT] = {1, 0, {0}}, + [CALL_BUILTIN_CLASS] = {1, 0, {0}}, + [CALL_BUILTIN_O] = {1, 0, {0}}, + [CALL_BUILTIN_FAST] = {1, 0, {0}}, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = {1, 0, {0}}, + [CALL_METHOD_DESCRIPTOR_O] = {1, 0, {0}}, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = {1, 0, {0}}, + [CALL_METHOD_DESCRIPTOR_NOARGS] = {1, 0, {0}}, + [CALL_KW_PY] = {1, 0, {0}}, + [CALL_KW_BOUND_METHOD] = {1, 0, {0}}, + [CALL_EX_PY] = {1, 0, {0}}, + [BINARY_OP] = {2, 2, {1, 0}}, +}; + +const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[8] = { [0] = NULL, [_RECORD_TOS_TYPE_INDEX] = _PyOpcode_RecordFunction_TOS_TYPE, [_RECORD_NOS_INDEX] = _PyOpcode_RecordFunction_NOS, [_RECORD_3OS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_3OS_GEN_FUNC, [_RECORD_NOS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_NOS_GEN_FUNC, [_RECORD_CALLABLE_INDEX] = _PyOpcode_RecordFunction_CALLABLE, - [_RECORD_BOUND_METHOD_INDEX] = _PyOpcode_RecordFunction_BOUND_METHOD, [_RECORD_CALLABLE_KW_INDEX] = _PyOpcode_RecordFunction_CALLABLE_KW, [_RECORD_4OS_INDEX] = _PyOpcode_RecordFunction_4OS, - [_RECORD_NOS_TYPE_INDEX] = _PyOpcode_RecordFunction_NOS_TYPE, }; + +PyObject * +_PyOpcode_RecordTransformValue(int uop, PyObject *value) +{ + switch (uop) { + case _RECORD_TOS_TYPE: + case _RECORD_NOS_TYPE: + return record_trace_transform_to_type(value); + case _RECORD_NOS_GEN_FUNC: + case _RECORD_3OS_GEN_FUNC: + return record_trace_transform_gen_func(value); + case _RECORD_BOUND_METHOD: + return record_trace_transform_bound_method(value); + default: + return value; + } +} diff --git a/Tools/cases_generator/record_function_generator.py b/Tools/cases_generator/record_function_generator.py index d7ae0ebf79fe62..6f518ffdcf2ac2 100644 --- a/Tools/cases_generator/record_function_generator.py +++ b/Tools/cases_generator/record_function_generator.py @@ -28,6 +28,21 @@ # Must match MAX_RECORDED_VALUES in Include/internal/pycore_optimizer.h. MAX_RECORDED_VALUES = 3 +# Map `_RECORD_*` uops to the helper that converts a raw family-recorded +# value to the form the specialized member consumes. +_RECORD_TRANSFORM_HELPERS: dict[str, str] = { + "_RECORD_TOS_TYPE": "record_trace_transform_to_type", + "_RECORD_NOS_TYPE": "record_trace_transform_to_type", + "_RECORD_NOS_GEN_FUNC": "record_trace_transform_gen_func", + "_RECORD_3OS_GEN_FUNC": "record_trace_transform_gen_func", + "_RECORD_BOUND_METHOD": "record_trace_transform_bound_method", +} + +# Recorder uops whose slot kind differs from the leading word of their name. +_RECORD_SLOT_KIND_OVERRIDES: dict[str, str] = { + "_RECORD_BOUND_METHOD": "CALLABLE", +} + class RecorderEmitter(Emitter): def __init__(self, out: CWriter): @@ -52,9 +67,83 @@ def record_value( return True +def get_record_slot_kind(record_name: str) -> str: + if record_name in _RECORD_SLOT_KIND_OVERRIDES: + return _RECORD_SLOT_KIND_OVERRIDES[record_name] + if not record_name.startswith("_RECORD_"): + return record_name + return record_name.removeprefix("_RECORD_").partition("_")[0] + + +def get_instruction_record_names(inst: Instruction) -> list[str]: + return [part.name for part in inst.parts if part.properties.records_value] + + +def get_family_record_names( + family_head: Instruction, + family_members: list[Instruction], + instruction_records: dict[str, list[str]], + record_slot_keys: dict[str, str], +) -> list[str]: + member_records = [instruction_records[m.name] for m in family_members] + all_member_names = {n for names in member_records for n in names} + records: list[str] = [] + slot_index: dict[str, int] = {} + + def add(name: str) -> None: + kind = record_slot_keys[name] + # Prefer the raw recorder if any member uses it; otherwise the given form. + raw = f"_RECORD_{kind}" + source = raw if raw in all_member_names else name + existing = slot_index.get(kind) + if existing is None: + slot_index[kind] = len(records) + records.append(source) + elif records[existing] != source: + raise ValueError( + f"Family {family_head.name} has incompatible recorders for " + f"slot {kind}: {records[existing]} and {source}" + ) + + for names in member_records: + for name in names: + add(name) + # Family head supplies any slots no member exercises. + for name in instruction_records[family_head.name]: + if record_slot_keys[name] not in slot_index: + slot_index[record_slot_keys[name]] = len(records) + records.append(name) + return records + + +def get_record_consumer_layout( + inst_name: str, + source_records: list[str], + own_records: list[str], + record_slot_keys: dict[str, str], +) -> tuple[list[int], int]: + used = [False] * len(source_records) + slot_map: list[int] = [] + transform_mask = 0 + for i, own in enumerate(own_records): + own_kind = record_slot_keys[own] + for j, src in enumerate(source_records): + if not used[j] and record_slot_keys[src] == own_kind: + used[j] = True + slot_map.append(j) + if src != own: + transform_mask |= 1 << i + break + else: + raise ValueError( + f"Instruction {inst_name} has no compatible family slot for " + f"{own} in {source_records}" + ) + return slot_map, transform_mask + def generate_recorder_functions(filenames: list[str], analysis: Analysis, out: CWriter) -> None: - write_header(__file__, filenames, outfile) - outfile.write( + write_header(__file__, filenames, out.out) + out.out.write( """ #ifdef TIER_ONE #error "This file is for Tier 2 only" @@ -63,13 +152,10 @@ def generate_recorder_functions(filenames: list[str], analysis: Analysis, out: C ) args = "_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, int oparg, PyObject **recorded_value" emitter = RecorderEmitter(out) - func_count = 0 nop = analysis.instructions["NOP"] - function_table: dict[str, int] = dict() - for name, uop in analysis.uops.items(): + for uop in analysis.uops.values(): if not uop.properties.records_value: continue - func_count += 1 out.emit(f"void _PyOpcode_RecordFunction{uop.name[7:]}({args}) {{\n") seen = {"unused"} for var in uop.stack.inputs: @@ -83,42 +169,109 @@ def generate_recorder_functions(filenames: list[str], analysis: Analysis, out: C out.emit("\n\n") def generate_recorder_tables(analysis: Analysis, out: CWriter) -> None: - record_function_indexes: dict[str, int] = dict() + instruction_records = { + inst.name: get_instruction_record_names(inst) + for inst in analysis.instructions.values() + } + record_uop_names = [ + name for name, uop in analysis.uops.items() if uop.properties.records_value + ] + record_slot_keys = {name: get_record_slot_kind(name) for name in record_uop_names} + family_record_table = { + family.name: get_family_record_names( + analysis.instructions[family.name], + family.members, + instruction_records, + record_slot_keys, + ) + for family in analysis.families.values() + } + record_table: dict[str, list[str]] = {} - index = 1 + record_consumer_table: dict[str, tuple[list[int], int]] = {} + record_function_indexes: dict[str, int] = {} for inst in analysis.instructions.values(): - if not inst.properties.records_value: + own_records = instruction_records[inst.name] + # TRACE_RECORD runs before execution, but specialization may rewrite + # the opcode before translation. Record the raw family shape (union + # of head + members) so any opcode in the family can be translated + # from the same recorded layout. + family = inst.family or analysis.families.get(inst.name) + records = family_record_table[family.name] if family is not None else own_records + if not records: continue - records: list[str] = [] - for part in inst.parts: - if not part.properties.records_value: - continue - if part.name not in record_function_indexes: - record_function_indexes[part.name] = index - index += 1 - records.append(part.name) - if records: - if len(records) > MAX_RECORDED_VALUES: - raise ValueError( - f"Instruction {inst.name} has {len(records)} recording ops, " - f"exceeds MAX_RECORDED_VALUES ({MAX_RECORDED_VALUES})" - ) - record_table[inst.name] = records - func_count = len(record_function_indexes) + if len(records) > MAX_RECORDED_VALUES: + raise ValueError( + f"Instruction {inst.name} has {len(records)} recording ops, " + f"exceeds MAX_RECORDED_VALUES ({MAX_RECORDED_VALUES})" + ) + record_table[inst.name] = records + for name in records: + if name not in record_function_indexes: + record_function_indexes[name] = len(record_function_indexes) + 1 + if own_records: + record_consumer_table[inst.name] = get_record_consumer_layout( + inst.name, records, own_records, record_slot_keys + ) for name, index in record_function_indexes.items(): out.emit(f"#define {name}_INDEX {index}\n") out.emit("\n") + out.emit("const _PyOpcodeRecordEntry _PyOpcode_RecordEntries[256] = {\n") - for inst_name, record_names in record_table.items(): - indices = ", ".join(f"{name}_INDEX" for name in record_names) - out.emit(f" [{inst_name}] = {{{len(record_names)}, {{{indices}}}}},\n") + for inst_name, records in record_table.items(): + indices = ", ".join(f"{name}_INDEX" for name in records) + out.emit(f" [{inst_name}] = {{{len(records)}, {{{indices}}}}},\n") + out.emit("};\n\n") + + out.emit("const _PyOpcodeRecordSlotMap _PyOpcode_RecordSlotMaps[256] = {\n") + for inst_name, (slots, mask) in record_consumer_table.items(): + slot_list = ", ".join(str(s) for s in slots) + out.emit( + f" [{inst_name}] = {{{len(slots)}, {mask}, {{{slot_list}}}}},\n" + ) out.emit("};\n\n") - out.emit(f"const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[{func_count+1}] = {{\n") + + out.emit( + f"const _Py_RecordFuncPtr _PyOpcode_RecordFunctions" + f"[{len(record_function_indexes) + 1}] = {{\n" + ) out.emit(" [0] = NULL,\n") for name in record_function_indexes: out.emit(f" [{name}_INDEX] = _PyOpcode_RecordFunction{name[7:]},\n") out.emit("};\n") + generate_record_transform_dispatcher(record_uop_names, out) + + +def generate_record_transform_dispatcher( + record_uop_names: list[str], out: CWriter +) -> None: + """Emit a switch that converts a family-recorded value for a recorder uop. + + Only `_RECORD_*` uops that need conversion get a case; the default + returns the input value unchanged. Helpers live in Python/optimizer.c. + """ + cases: dict[str, list[str]] = {} + for record_name in record_uop_names: + helper = _RECORD_TRANSFORM_HELPERS.get(record_name) + if helper is None: + continue + cases.setdefault(helper, []).append(record_name) + out.emit("\n") + out.emit( + "PyObject *\n" + "_PyOpcode_RecordTransformValue(int uop, PyObject *value)\n" + "{\n" + ) + out.emit(" switch (uop) {\n") + for helper, names in cases.items(): + for name in names: + out.emit(f" case {name}:\n") + out.emit(f" return {helper}(value);\n") + out.emit(" default:\n") + out.emit(" return value;\n") + out.emit(" }\n") + out.emit("}\n") arg_parser = argparse.ArgumentParser( From 2b6a13710f0fbf90a0d5f009d7188e934fab8d08 Mon Sep 17 00:00:00 2001 From: Neko Asakura Date: Tue, 28 Apr 2026 20:48:23 +0800 Subject: [PATCH 36/44] gh-148211: decompose `_SHUFFLE_3_LOAD_CONST_INLINE_BORROW` in JIT (GH-148816) --- Include/internal/pycore_uop_ids.h | 2041 ++++++++++++------------ Include/internal/pycore_uop_metadata.h | 21 - Lib/test/test_capi/test_opt.py | 2 +- Python/bytecodes.c | 7 - Python/executor_cases.c.h | 100 -- Python/optimizer_bytecodes.c | 5 +- Python/optimizer_cases.c.h | 20 +- 7 files changed, 1029 insertions(+), 1167 deletions(-) diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 6b96f9bc78e8fe..9e54cb5f06adb2 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -382,1038 +382,1033 @@ extern "C" { #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE 606 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW 607 -#define _SPILL_OR_RELOAD 608 -#define _START_EXECUTOR 609 -#define _STORE_ATTR 610 -#define _STORE_ATTR_INSTANCE_VALUE 611 -#define _STORE_ATTR_SLOT 612 -#define _STORE_ATTR_WITH_HINT 613 +#define _SPILL_OR_RELOAD 607 +#define _START_EXECUTOR 608 +#define _STORE_ATTR 609 +#define _STORE_ATTR_INSTANCE_VALUE 610 +#define _STORE_ATTR_SLOT 611 +#define _STORE_ATTR_WITH_HINT 612 #define _STORE_DEREF STORE_DEREF #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME -#define _STORE_SLICE 614 -#define _STORE_SUBSCR 615 -#define _STORE_SUBSCR_DICT 616 -#define _STORE_SUBSCR_DICT_KNOWN_HASH 617 -#define _STORE_SUBSCR_LIST_INT 618 -#define _SWAP 619 -#define _SWAP_2 620 -#define _SWAP_3 621 -#define _SWAP_FAST 622 -#define _SWAP_FAST_0 623 -#define _SWAP_FAST_1 624 -#define _SWAP_FAST_2 625 -#define _SWAP_FAST_3 626 -#define _SWAP_FAST_4 627 -#define _SWAP_FAST_5 628 -#define _SWAP_FAST_6 629 -#define _SWAP_FAST_7 630 -#define _TIER2_RESUME_CHECK 631 -#define _TO_BOOL 632 +#define _STORE_SLICE 613 +#define _STORE_SUBSCR 614 +#define _STORE_SUBSCR_DICT 615 +#define _STORE_SUBSCR_DICT_KNOWN_HASH 616 +#define _STORE_SUBSCR_LIST_INT 617 +#define _SWAP 618 +#define _SWAP_2 619 +#define _SWAP_3 620 +#define _SWAP_FAST 621 +#define _SWAP_FAST_0 622 +#define _SWAP_FAST_1 623 +#define _SWAP_FAST_2 624 +#define _SWAP_FAST_3 625 +#define _SWAP_FAST_4 626 +#define _SWAP_FAST_5 627 +#define _SWAP_FAST_6 628 +#define _SWAP_FAST_7 629 +#define _TIER2_RESUME_CHECK 630 +#define _TO_BOOL 631 #define _TO_BOOL_BOOL TO_BOOL_BOOL -#define _TO_BOOL_INT 633 -#define _TO_BOOL_LIST 634 +#define _TO_BOOL_INT 632 +#define _TO_BOOL_LIST 633 #define _TO_BOOL_NONE TO_BOOL_NONE -#define _TO_BOOL_STR 635 +#define _TO_BOOL_STR 634 #define _TRACE_RECORD TRACE_RECORD -#define _UNARY_INVERT 636 -#define _UNARY_NEGATIVE 637 -#define _UNARY_NEGATIVE_FLOAT_INPLACE 638 +#define _UNARY_INVERT 635 +#define _UNARY_NEGATIVE 636 +#define _UNARY_NEGATIVE_FLOAT_INPLACE 637 #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 639 -#define _UNPACK_SEQUENCE_LIST 640 -#define _UNPACK_SEQUENCE_TUPLE 641 -#define _UNPACK_SEQUENCE_TWO_TUPLE 642 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE 643 -#define _UNPACK_SEQUENCE_UNIQUE_TUPLE 644 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE 645 +#define _UNPACK_SEQUENCE 638 +#define _UNPACK_SEQUENCE_LIST 639 +#define _UNPACK_SEQUENCE_TUPLE 640 +#define _UNPACK_SEQUENCE_TWO_TUPLE 641 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE 642 +#define _UNPACK_SEQUENCE_UNIQUE_TUPLE 643 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE 644 #define _WITH_EXCEPT_START WITH_EXCEPT_START -#define _YIELD_VALUE 646 -#define MAX_UOP_ID 646 -#define _ALLOCATE_OBJECT_r00 647 -#define _BINARY_OP_r23 648 -#define _BINARY_OP_ADD_FLOAT_r03 649 -#define _BINARY_OP_ADD_FLOAT_r13 650 -#define _BINARY_OP_ADD_FLOAT_r23 651 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r03 652 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r13 653 -#define _BINARY_OP_ADD_FLOAT_INPLACE_r23 654 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r03 655 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r13 656 -#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r23 657 -#define _BINARY_OP_ADD_INT_r03 658 -#define _BINARY_OP_ADD_INT_r13 659 -#define _BINARY_OP_ADD_INT_r23 660 -#define _BINARY_OP_ADD_INT_INPLACE_r03 661 -#define _BINARY_OP_ADD_INT_INPLACE_r13 662 -#define _BINARY_OP_ADD_INT_INPLACE_r23 663 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r03 664 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r13 665 -#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r23 666 -#define _BINARY_OP_ADD_UNICODE_r03 667 -#define _BINARY_OP_ADD_UNICODE_r13 668 -#define _BINARY_OP_ADD_UNICODE_r23 669 -#define _BINARY_OP_EXTEND_r23 670 -#define _BINARY_OP_INPLACE_ADD_UNICODE_r21 671 -#define _BINARY_OP_MULTIPLY_FLOAT_r03 672 -#define _BINARY_OP_MULTIPLY_FLOAT_r13 673 -#define _BINARY_OP_MULTIPLY_FLOAT_r23 674 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r03 675 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r13 676 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r23 677 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r03 678 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r13 679 -#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r23 680 -#define _BINARY_OP_MULTIPLY_INT_r03 681 -#define _BINARY_OP_MULTIPLY_INT_r13 682 -#define _BINARY_OP_MULTIPLY_INT_r23 683 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r03 684 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r13 685 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_r23 686 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r03 687 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r13 688 -#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r23 689 -#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 690 -#define _BINARY_OP_SUBSCR_DICT_r23 691 -#define _BINARY_OP_SUBSCR_DICT_KNOWN_HASH_r23 692 -#define _BINARY_OP_SUBSCR_INIT_CALL_r01 693 -#define _BINARY_OP_SUBSCR_INIT_CALL_r11 694 -#define _BINARY_OP_SUBSCR_INIT_CALL_r21 695 -#define _BINARY_OP_SUBSCR_INIT_CALL_r31 696 -#define _BINARY_OP_SUBSCR_LIST_INT_r23 697 -#define _BINARY_OP_SUBSCR_LIST_SLICE_r23 698 -#define _BINARY_OP_SUBSCR_STR_INT_r23 699 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r03 700 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r13 701 -#define _BINARY_OP_SUBSCR_TUPLE_INT_r23 702 -#define _BINARY_OP_SUBSCR_USTR_INT_r23 703 -#define _BINARY_OP_SUBTRACT_FLOAT_r03 704 -#define _BINARY_OP_SUBTRACT_FLOAT_r13 705 -#define _BINARY_OP_SUBTRACT_FLOAT_r23 706 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r03 707 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r13 708 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r23 709 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r03 710 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r13 711 -#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r23 712 -#define _BINARY_OP_SUBTRACT_INT_r03 713 -#define _BINARY_OP_SUBTRACT_INT_r13 714 -#define _BINARY_OP_SUBTRACT_INT_r23 715 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r03 716 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r13 717 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_r23 718 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r03 719 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r13 720 -#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r23 721 -#define _BINARY_OP_TRUEDIV_FLOAT_r23 722 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r03 723 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r13 724 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r23 725 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r03 726 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r13 727 -#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r23 728 -#define _BINARY_SLICE_r31 729 -#define _BUILD_INTERPOLATION_r01 730 -#define _BUILD_LIST_r01 731 -#define _BUILD_MAP_r01 732 -#define _BUILD_SET_r01 733 -#define _BUILD_SLICE_r01 734 -#define _BUILD_STRING_r01 735 -#define _BUILD_TEMPLATE_r21 736 -#define _BUILD_TUPLE_r01 737 -#define _CALL_BUILTIN_CLASS_r00 738 -#define _CALL_BUILTIN_FAST_r00 739 -#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r00 740 -#define _CALL_BUILTIN_O_r03 741 -#define _CALL_FUNCTION_EX_NON_PY_GENERAL_r31 742 -#define _CALL_INTRINSIC_1_r12 743 -#define _CALL_INTRINSIC_2_r23 744 -#define _CALL_ISINSTANCE_r31 745 -#define _CALL_KW_NON_PY_r11 746 -#define _CALL_LEN_r33 747 -#define _CALL_LIST_APPEND_r03 748 -#define _CALL_LIST_APPEND_r13 749 -#define _CALL_LIST_APPEND_r23 750 -#define _CALL_LIST_APPEND_r33 751 -#define _CALL_METHOD_DESCRIPTOR_FAST_r00 752 -#define _CALL_METHOD_DESCRIPTOR_FAST_INLINE_r00 753 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 754 -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_INLINE_r00 755 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_r03 756 -#define _CALL_METHOD_DESCRIPTOR_NOARGS_INLINE_r03 757 -#define _CALL_METHOD_DESCRIPTOR_O_r03 758 -#define _CALL_METHOD_DESCRIPTOR_O_INLINE_r03 759 -#define _CALL_NON_PY_GENERAL_r01 760 -#define _CALL_STR_1_r32 761 -#define _CALL_TUPLE_1_r32 762 -#define _CALL_TYPE_1_r02 763 -#define _CALL_TYPE_1_r12 764 -#define _CALL_TYPE_1_r22 765 -#define _CALL_TYPE_1_r32 766 -#define _CHECK_ATTR_CLASS_r01 767 -#define _CHECK_ATTR_CLASS_r11 768 -#define _CHECK_ATTR_CLASS_r22 769 -#define _CHECK_ATTR_CLASS_r33 770 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 771 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 772 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 773 -#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 774 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 775 -#define _CHECK_EG_MATCH_r22 776 -#define _CHECK_EXC_MATCH_r22 777 -#define _CHECK_FUNCTION_EXACT_ARGS_r00 778 -#define _CHECK_FUNCTION_VERSION_r00 779 -#define _CHECK_FUNCTION_VERSION_INLINE_r00 780 -#define _CHECK_FUNCTION_VERSION_INLINE_r11 781 -#define _CHECK_FUNCTION_VERSION_INLINE_r22 782 -#define _CHECK_FUNCTION_VERSION_INLINE_r33 783 -#define _CHECK_FUNCTION_VERSION_KW_r11 784 -#define _CHECK_IS_NOT_PY_CALLABLE_r00 785 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r03 786 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r13 787 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r23 788 -#define _CHECK_IS_NOT_PY_CALLABLE_EX_r33 789 -#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 790 -#define _CHECK_IS_PY_CALLABLE_EX_r03 791 -#define _CHECK_IS_PY_CALLABLE_EX_r13 792 -#define _CHECK_IS_PY_CALLABLE_EX_r23 793 -#define _CHECK_IS_PY_CALLABLE_EX_r33 794 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 795 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 796 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 797 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 798 -#define _CHECK_METHOD_VERSION_r00 799 -#define _CHECK_METHOD_VERSION_KW_r11 800 -#define _CHECK_OBJECT_r00 801 -#define _CHECK_PEP_523_r00 802 -#define _CHECK_PEP_523_r11 803 -#define _CHECK_PEP_523_r22 804 -#define _CHECK_PEP_523_r33 805 -#define _CHECK_PERIODIC_r00 806 -#define _CHECK_PERIODIC_AT_END_r00 807 -#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 808 -#define _CHECK_RECURSION_LIMIT_r00 809 -#define _CHECK_RECURSION_LIMIT_r11 810 -#define _CHECK_RECURSION_LIMIT_r22 811 -#define _CHECK_RECURSION_LIMIT_r33 812 -#define _CHECK_RECURSION_REMAINING_r00 813 -#define _CHECK_RECURSION_REMAINING_r11 814 -#define _CHECK_RECURSION_REMAINING_r22 815 -#define _CHECK_RECURSION_REMAINING_r33 816 -#define _CHECK_STACK_SPACE_r00 817 -#define _CHECK_STACK_SPACE_OPERAND_r00 818 -#define _CHECK_STACK_SPACE_OPERAND_r11 819 -#define _CHECK_STACK_SPACE_OPERAND_r22 820 -#define _CHECK_STACK_SPACE_OPERAND_r33 821 -#define _CHECK_VALIDITY_r00 822 -#define _CHECK_VALIDITY_r11 823 -#define _CHECK_VALIDITY_r22 824 -#define _CHECK_VALIDITY_r33 825 -#define _COLD_DYNAMIC_EXIT_r00 826 -#define _COLD_EXIT_r00 827 -#define _COMPARE_OP_r21 828 -#define _COMPARE_OP_FLOAT_r03 829 -#define _COMPARE_OP_FLOAT_r13 830 -#define _COMPARE_OP_FLOAT_r23 831 -#define _COMPARE_OP_INT_r23 832 -#define _COMPARE_OP_STR_r23 833 -#define _CONTAINS_OP_r23 834 -#define _CONTAINS_OP_DICT_r23 835 -#define _CONTAINS_OP_SET_r23 836 -#define _CONVERT_VALUE_r11 837 -#define _COPY_r01 838 -#define _COPY_1_r02 839 -#define _COPY_1_r12 840 -#define _COPY_1_r23 841 -#define _COPY_2_r03 842 -#define _COPY_2_r13 843 -#define _COPY_2_r23 844 -#define _COPY_3_r03 845 -#define _COPY_3_r13 846 -#define _COPY_3_r23 847 -#define _COPY_3_r33 848 -#define _COPY_FREE_VARS_r00 849 -#define _COPY_FREE_VARS_r11 850 -#define _COPY_FREE_VARS_r22 851 -#define _COPY_FREE_VARS_r33 852 -#define _CREATE_INIT_FRAME_r01 853 -#define _DELETE_ATTR_r10 854 -#define _DELETE_DEREF_r00 855 -#define _DELETE_FAST_r00 856 -#define _DELETE_GLOBAL_r00 857 -#define _DELETE_NAME_r00 858 -#define _DELETE_SUBSCR_r20 859 -#define _DEOPT_r00 860 -#define _DEOPT_r10 861 -#define _DEOPT_r20 862 -#define _DEOPT_r30 863 -#define _DICT_MERGE_r11 864 -#define _DICT_UPDATE_r11 865 -#define _DO_CALL_r01 866 -#define _DO_CALL_FUNCTION_EX_r31 867 -#define _DO_CALL_KW_r11 868 -#define _DYNAMIC_EXIT_r00 869 -#define _DYNAMIC_EXIT_r10 870 -#define _DYNAMIC_EXIT_r20 871 -#define _DYNAMIC_EXIT_r30 872 -#define _END_FOR_r10 873 -#define _END_SEND_r31 874 -#define _ERROR_POP_N_r00 875 -#define _EXIT_INIT_CHECK_r10 876 -#define _EXIT_TRACE_r00 877 -#define _EXIT_TRACE_r10 878 -#define _EXIT_TRACE_r20 879 -#define _EXIT_TRACE_r30 880 -#define _EXPAND_METHOD_r00 881 -#define _EXPAND_METHOD_KW_r11 882 -#define _FATAL_ERROR_r00 883 -#define _FATAL_ERROR_r11 884 -#define _FATAL_ERROR_r22 885 -#define _FATAL_ERROR_r33 886 -#define _FORMAT_SIMPLE_r11 887 -#define _FORMAT_WITH_SPEC_r21 888 -#define _FOR_ITER_r23 889 -#define _FOR_ITER_GEN_FRAME_r03 890 -#define _FOR_ITER_GEN_FRAME_r13 891 -#define _FOR_ITER_GEN_FRAME_r23 892 -#define _FOR_ITER_TIER_TWO_r23 893 -#define _FOR_ITER_VIRTUAL_r23 894 -#define _FOR_ITER_VIRTUAL_TIER_TWO_r23 895 -#define _GET_AITER_r11 896 -#define _GET_ANEXT_r12 897 -#define _GET_AWAITABLE_r11 898 -#define _GET_ITER_r12 899 -#define _GET_ITER_TRAD_r12 900 -#define _GET_LEN_r12 901 -#define _GUARD_BINARY_OP_EXTEND_r22 902 -#define _GUARD_BINARY_OP_EXTEND_LHS_r02 903 -#define _GUARD_BINARY_OP_EXTEND_LHS_r12 904 -#define _GUARD_BINARY_OP_EXTEND_LHS_r22 905 -#define _GUARD_BINARY_OP_EXTEND_LHS_r33 906 -#define _GUARD_BINARY_OP_EXTEND_RHS_r02 907 -#define _GUARD_BINARY_OP_EXTEND_RHS_r12 908 -#define _GUARD_BINARY_OP_EXTEND_RHS_r22 909 -#define _GUARD_BINARY_OP_EXTEND_RHS_r33 910 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r02 911 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r12 912 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r22 913 -#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r33 914 -#define _GUARD_BIT_IS_SET_POP_r00 915 -#define _GUARD_BIT_IS_SET_POP_r10 916 -#define _GUARD_BIT_IS_SET_POP_r21 917 -#define _GUARD_BIT_IS_SET_POP_r32 918 -#define _GUARD_BIT_IS_SET_POP_4_r00 919 -#define _GUARD_BIT_IS_SET_POP_4_r10 920 -#define _GUARD_BIT_IS_SET_POP_4_r21 921 -#define _GUARD_BIT_IS_SET_POP_4_r32 922 -#define _GUARD_BIT_IS_SET_POP_5_r00 923 -#define _GUARD_BIT_IS_SET_POP_5_r10 924 -#define _GUARD_BIT_IS_SET_POP_5_r21 925 -#define _GUARD_BIT_IS_SET_POP_5_r32 926 -#define _GUARD_BIT_IS_SET_POP_6_r00 927 -#define _GUARD_BIT_IS_SET_POP_6_r10 928 -#define _GUARD_BIT_IS_SET_POP_6_r21 929 -#define _GUARD_BIT_IS_SET_POP_6_r32 930 -#define _GUARD_BIT_IS_SET_POP_7_r00 931 -#define _GUARD_BIT_IS_SET_POP_7_r10 932 -#define _GUARD_BIT_IS_SET_POP_7_r21 933 -#define _GUARD_BIT_IS_SET_POP_7_r32 934 -#define _GUARD_BIT_IS_UNSET_POP_r00 935 -#define _GUARD_BIT_IS_UNSET_POP_r10 936 -#define _GUARD_BIT_IS_UNSET_POP_r21 937 -#define _GUARD_BIT_IS_UNSET_POP_r32 938 -#define _GUARD_BIT_IS_UNSET_POP_4_r00 939 -#define _GUARD_BIT_IS_UNSET_POP_4_r10 940 -#define _GUARD_BIT_IS_UNSET_POP_4_r21 941 -#define _GUARD_BIT_IS_UNSET_POP_4_r32 942 -#define _GUARD_BIT_IS_UNSET_POP_5_r00 943 -#define _GUARD_BIT_IS_UNSET_POP_5_r10 944 -#define _GUARD_BIT_IS_UNSET_POP_5_r21 945 -#define _GUARD_BIT_IS_UNSET_POP_5_r32 946 -#define _GUARD_BIT_IS_UNSET_POP_6_r00 947 -#define _GUARD_BIT_IS_UNSET_POP_6_r10 948 -#define _GUARD_BIT_IS_UNSET_POP_6_r21 949 -#define _GUARD_BIT_IS_UNSET_POP_6_r32 950 -#define _GUARD_BIT_IS_UNSET_POP_7_r00 951 -#define _GUARD_BIT_IS_UNSET_POP_7_r10 952 -#define _GUARD_BIT_IS_UNSET_POP_7_r21 953 -#define _GUARD_BIT_IS_UNSET_POP_7_r32 954 -#define _GUARD_CALLABLE_BUILTIN_CLASS_r00 955 -#define _GUARD_CALLABLE_BUILTIN_FAST_r00 956 -#define _GUARD_CALLABLE_BUILTIN_FAST_WITH_KEYWORDS_r00 957 -#define _GUARD_CALLABLE_BUILTIN_O_r00 958 -#define _GUARD_CALLABLE_ISINSTANCE_r03 959 -#define _GUARD_CALLABLE_ISINSTANCE_r13 960 -#define _GUARD_CALLABLE_ISINSTANCE_r23 961 -#define _GUARD_CALLABLE_ISINSTANCE_r33 962 -#define _GUARD_CALLABLE_LEN_r03 963 -#define _GUARD_CALLABLE_LEN_r13 964 -#define _GUARD_CALLABLE_LEN_r23 965 -#define _GUARD_CALLABLE_LEN_r33 966 -#define _GUARD_CALLABLE_LIST_APPEND_r03 967 -#define _GUARD_CALLABLE_LIST_APPEND_r13 968 -#define _GUARD_CALLABLE_LIST_APPEND_r23 969 -#define _GUARD_CALLABLE_LIST_APPEND_r33 970 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_r00 971 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 972 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_NOARGS_r00 973 -#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_O_r00 974 -#define _GUARD_CALLABLE_STR_1_r03 975 -#define _GUARD_CALLABLE_STR_1_r13 976 -#define _GUARD_CALLABLE_STR_1_r23 977 -#define _GUARD_CALLABLE_STR_1_r33 978 -#define _GUARD_CALLABLE_TUPLE_1_r03 979 -#define _GUARD_CALLABLE_TUPLE_1_r13 980 -#define _GUARD_CALLABLE_TUPLE_1_r23 981 -#define _GUARD_CALLABLE_TUPLE_1_r33 982 -#define _GUARD_CALLABLE_TYPE_1_r03 983 -#define _GUARD_CALLABLE_TYPE_1_r13 984 -#define _GUARD_CALLABLE_TYPE_1_r23 985 -#define _GUARD_CALLABLE_TYPE_1_r33 986 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r00 987 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r11 988 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r22 989 -#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r33 990 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r00 991 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r11 992 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r22 993 -#define _GUARD_CODE_VERSION_RETURN_VALUE_r33 994 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r00 995 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r11 996 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r22 997 -#define _GUARD_CODE_VERSION_YIELD_VALUE_r33 998 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r00 999 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r11 1000 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r22 1001 -#define _GUARD_CODE_VERSION__PUSH_FRAME_r33 1002 -#define _GUARD_DORV_NO_DICT_r01 1003 -#define _GUARD_DORV_NO_DICT_r11 1004 -#define _GUARD_DORV_NO_DICT_r22 1005 -#define _GUARD_DORV_NO_DICT_r33 1006 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 1007 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 1008 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 1009 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 1010 -#define _GUARD_GLOBALS_VERSION_r00 1011 -#define _GUARD_GLOBALS_VERSION_r11 1012 -#define _GUARD_GLOBALS_VERSION_r22 1013 -#define _GUARD_GLOBALS_VERSION_r33 1014 -#define _GUARD_IP_RETURN_GENERATOR_r00 1015 -#define _GUARD_IP_RETURN_GENERATOR_r11 1016 -#define _GUARD_IP_RETURN_GENERATOR_r22 1017 -#define _GUARD_IP_RETURN_GENERATOR_r33 1018 -#define _GUARD_IP_RETURN_VALUE_r00 1019 -#define _GUARD_IP_RETURN_VALUE_r11 1020 -#define _GUARD_IP_RETURN_VALUE_r22 1021 -#define _GUARD_IP_RETURN_VALUE_r33 1022 -#define _GUARD_IP_YIELD_VALUE_r00 1023 -#define _GUARD_IP_YIELD_VALUE_r11 1024 -#define _GUARD_IP_YIELD_VALUE_r22 1025 -#define _GUARD_IP_YIELD_VALUE_r33 1026 -#define _GUARD_IP__PUSH_FRAME_r00 1027 -#define _GUARD_IP__PUSH_FRAME_r11 1028 -#define _GUARD_IP__PUSH_FRAME_r22 1029 -#define _GUARD_IP__PUSH_FRAME_r33 1030 -#define _GUARD_IS_FALSE_POP_r00 1031 -#define _GUARD_IS_FALSE_POP_r10 1032 -#define _GUARD_IS_FALSE_POP_r21 1033 -#define _GUARD_IS_FALSE_POP_r32 1034 -#define _GUARD_IS_NONE_POP_r00 1035 -#define _GUARD_IS_NONE_POP_r10 1036 -#define _GUARD_IS_NONE_POP_r21 1037 -#define _GUARD_IS_NONE_POP_r32 1038 -#define _GUARD_IS_NOT_NONE_POP_r10 1039 -#define _GUARD_IS_TRUE_POP_r00 1040 -#define _GUARD_IS_TRUE_POP_r10 1041 -#define _GUARD_IS_TRUE_POP_r21 1042 -#define _GUARD_IS_TRUE_POP_r32 1043 -#define _GUARD_ITERATOR_r01 1044 -#define _GUARD_ITERATOR_r11 1045 -#define _GUARD_ITERATOR_r22 1046 -#define _GUARD_ITERATOR_r33 1047 -#define _GUARD_ITER_VIRTUAL_r01 1048 -#define _GUARD_ITER_VIRTUAL_r11 1049 -#define _GUARD_ITER_VIRTUAL_r22 1050 -#define _GUARD_ITER_VIRTUAL_r33 1051 -#define _GUARD_KEYS_VERSION_r01 1052 -#define _GUARD_KEYS_VERSION_r11 1053 -#define _GUARD_KEYS_VERSION_r22 1054 -#define _GUARD_KEYS_VERSION_r33 1055 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r03 1056 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r13 1057 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r23 1058 -#define _GUARD_LOAD_SUPER_ATTR_METHOD_r33 1059 -#define _GUARD_NOS_ANY_DICT_r02 1060 -#define _GUARD_NOS_ANY_DICT_r12 1061 -#define _GUARD_NOS_ANY_DICT_r22 1062 -#define _GUARD_NOS_ANY_DICT_r33 1063 -#define _GUARD_NOS_COMPACT_ASCII_r02 1064 -#define _GUARD_NOS_COMPACT_ASCII_r12 1065 -#define _GUARD_NOS_COMPACT_ASCII_r22 1066 -#define _GUARD_NOS_COMPACT_ASCII_r33 1067 -#define _GUARD_NOS_DICT_r02 1068 -#define _GUARD_NOS_DICT_r12 1069 -#define _GUARD_NOS_DICT_r22 1070 -#define _GUARD_NOS_DICT_r33 1071 -#define _GUARD_NOS_FLOAT_r02 1072 -#define _GUARD_NOS_FLOAT_r12 1073 -#define _GUARD_NOS_FLOAT_r22 1074 -#define _GUARD_NOS_FLOAT_r33 1075 -#define _GUARD_NOS_INT_r02 1076 -#define _GUARD_NOS_INT_r12 1077 -#define _GUARD_NOS_INT_r22 1078 -#define _GUARD_NOS_INT_r33 1079 -#define _GUARD_NOS_ITER_VIRTUAL_r02 1080 -#define _GUARD_NOS_ITER_VIRTUAL_r12 1081 -#define _GUARD_NOS_ITER_VIRTUAL_r22 1082 -#define _GUARD_NOS_ITER_VIRTUAL_r33 1083 -#define _GUARD_NOS_LIST_r02 1084 -#define _GUARD_NOS_LIST_r12 1085 -#define _GUARD_NOS_LIST_r22 1086 -#define _GUARD_NOS_LIST_r33 1087 -#define _GUARD_NOS_NOT_NULL_r02 1088 -#define _GUARD_NOS_NOT_NULL_r12 1089 -#define _GUARD_NOS_NOT_NULL_r22 1090 -#define _GUARD_NOS_NOT_NULL_r33 1091 -#define _GUARD_NOS_NULL_r02 1092 -#define _GUARD_NOS_NULL_r12 1093 -#define _GUARD_NOS_NULL_r22 1094 -#define _GUARD_NOS_NULL_r33 1095 -#define _GUARD_NOS_OVERFLOWED_r02 1096 -#define _GUARD_NOS_OVERFLOWED_r12 1097 -#define _GUARD_NOS_OVERFLOWED_r22 1098 -#define _GUARD_NOS_OVERFLOWED_r33 1099 -#define _GUARD_NOS_TUPLE_r02 1100 -#define _GUARD_NOS_TUPLE_r12 1101 -#define _GUARD_NOS_TUPLE_r22 1102 -#define _GUARD_NOS_TUPLE_r33 1103 -#define _GUARD_NOS_TYPE_VERSION_r02 1104 -#define _GUARD_NOS_TYPE_VERSION_r12 1105 -#define _GUARD_NOS_TYPE_VERSION_r22 1106 -#define _GUARD_NOS_TYPE_VERSION_r33 1107 -#define _GUARD_NOS_UNICODE_r02 1108 -#define _GUARD_NOS_UNICODE_r12 1109 -#define _GUARD_NOS_UNICODE_r22 1110 -#define _GUARD_NOS_UNICODE_r33 1111 -#define _GUARD_NOT_EXHAUSTED_LIST_r02 1112 -#define _GUARD_NOT_EXHAUSTED_LIST_r12 1113 -#define _GUARD_NOT_EXHAUSTED_LIST_r22 1114 -#define _GUARD_NOT_EXHAUSTED_LIST_r33 1115 -#define _GUARD_NOT_EXHAUSTED_RANGE_r02 1116 -#define _GUARD_NOT_EXHAUSTED_RANGE_r12 1117 -#define _GUARD_NOT_EXHAUSTED_RANGE_r22 1118 -#define _GUARD_NOT_EXHAUSTED_RANGE_r33 1119 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 1120 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 1121 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 1122 -#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 1123 -#define _GUARD_THIRD_NULL_r03 1124 -#define _GUARD_THIRD_NULL_r13 1125 -#define _GUARD_THIRD_NULL_r23 1126 -#define _GUARD_THIRD_NULL_r33 1127 -#define _GUARD_TOS_ANY_DICT_r01 1128 -#define _GUARD_TOS_ANY_DICT_r11 1129 -#define _GUARD_TOS_ANY_DICT_r22 1130 -#define _GUARD_TOS_ANY_DICT_r33 1131 -#define _GUARD_TOS_ANY_SET_r01 1132 -#define _GUARD_TOS_ANY_SET_r11 1133 -#define _GUARD_TOS_ANY_SET_r22 1134 -#define _GUARD_TOS_ANY_SET_r33 1135 -#define _GUARD_TOS_DICT_r01 1136 -#define _GUARD_TOS_DICT_r11 1137 -#define _GUARD_TOS_DICT_r22 1138 -#define _GUARD_TOS_DICT_r33 1139 -#define _GUARD_TOS_FLOAT_r01 1140 -#define _GUARD_TOS_FLOAT_r11 1141 -#define _GUARD_TOS_FLOAT_r22 1142 -#define _GUARD_TOS_FLOAT_r33 1143 -#define _GUARD_TOS_FROZENDICT_r01 1144 -#define _GUARD_TOS_FROZENDICT_r11 1145 -#define _GUARD_TOS_FROZENDICT_r22 1146 -#define _GUARD_TOS_FROZENDICT_r33 1147 -#define _GUARD_TOS_FROZENSET_r01 1148 -#define _GUARD_TOS_FROZENSET_r11 1149 -#define _GUARD_TOS_FROZENSET_r22 1150 -#define _GUARD_TOS_FROZENSET_r33 1151 -#define _GUARD_TOS_INT_r01 1152 -#define _GUARD_TOS_INT_r11 1153 -#define _GUARD_TOS_INT_r22 1154 -#define _GUARD_TOS_INT_r33 1155 -#define _GUARD_TOS_LIST_r01 1156 -#define _GUARD_TOS_LIST_r11 1157 -#define _GUARD_TOS_LIST_r22 1158 -#define _GUARD_TOS_LIST_r33 1159 -#define _GUARD_TOS_OVERFLOWED_r01 1160 -#define _GUARD_TOS_OVERFLOWED_r11 1161 -#define _GUARD_TOS_OVERFLOWED_r22 1162 -#define _GUARD_TOS_OVERFLOWED_r33 1163 -#define _GUARD_TOS_SET_r01 1164 -#define _GUARD_TOS_SET_r11 1165 -#define _GUARD_TOS_SET_r22 1166 -#define _GUARD_TOS_SET_r33 1167 -#define _GUARD_TOS_SLICE_r01 1168 -#define _GUARD_TOS_SLICE_r11 1169 -#define _GUARD_TOS_SLICE_r22 1170 -#define _GUARD_TOS_SLICE_r33 1171 -#define _GUARD_TOS_TUPLE_r01 1172 -#define _GUARD_TOS_TUPLE_r11 1173 -#define _GUARD_TOS_TUPLE_r22 1174 -#define _GUARD_TOS_TUPLE_r33 1175 -#define _GUARD_TOS_UNICODE_r01 1176 -#define _GUARD_TOS_UNICODE_r11 1177 -#define _GUARD_TOS_UNICODE_r22 1178 -#define _GUARD_TOS_UNICODE_r33 1179 -#define _GUARD_TYPE_r01 1180 -#define _GUARD_TYPE_r11 1181 -#define _GUARD_TYPE_r22 1182 -#define _GUARD_TYPE_r33 1183 -#define _GUARD_TYPE_VERSION_r01 1184 -#define _GUARD_TYPE_VERSION_r11 1185 -#define _GUARD_TYPE_VERSION_r22 1186 -#define _GUARD_TYPE_VERSION_r33 1187 -#define _GUARD_TYPE_VERSION_LOCKED_r01 1188 -#define _GUARD_TYPE_VERSION_LOCKED_r11 1189 -#define _GUARD_TYPE_VERSION_LOCKED_r22 1190 -#define _GUARD_TYPE_VERSION_LOCKED_r33 1191 -#define _HANDLE_PENDING_AND_DEOPT_r00 1192 -#define _HANDLE_PENDING_AND_DEOPT_r10 1193 -#define _HANDLE_PENDING_AND_DEOPT_r20 1194 -#define _HANDLE_PENDING_AND_DEOPT_r30 1195 -#define _IMPORT_FROM_r12 1196 -#define _IMPORT_NAME_r21 1197 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 1198 -#define _INIT_CALL_PY_EXACT_ARGS_r01 1199 -#define _INIT_CALL_PY_EXACT_ARGS_0_r01 1200 -#define _INIT_CALL_PY_EXACT_ARGS_1_r01 1201 -#define _INIT_CALL_PY_EXACT_ARGS_2_r01 1202 -#define _INIT_CALL_PY_EXACT_ARGS_3_r01 1203 -#define _INIT_CALL_PY_EXACT_ARGS_4_r01 1204 -#define _INSERT_NULL_r10 1205 -#define _INSTRUMENTED_FOR_ITER_r23 1206 -#define _INSTRUMENTED_INSTRUCTION_r00 1207 -#define _INSTRUMENTED_JUMP_FORWARD_r00 1208 -#define _INSTRUMENTED_JUMP_FORWARD_r11 1209 -#define _INSTRUMENTED_JUMP_FORWARD_r22 1210 -#define _INSTRUMENTED_JUMP_FORWARD_r33 1211 -#define _INSTRUMENTED_LINE_r00 1212 -#define _INSTRUMENTED_NOT_TAKEN_r00 1213 -#define _INSTRUMENTED_NOT_TAKEN_r11 1214 -#define _INSTRUMENTED_NOT_TAKEN_r22 1215 -#define _INSTRUMENTED_NOT_TAKEN_r33 1216 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 1217 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 1218 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 1219 -#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 1220 -#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 1221 -#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 1222 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 1223 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 1224 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 1225 -#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 1226 -#define _IS_NONE_r11 1227 -#define _IS_OP_r03 1228 -#define _IS_OP_r13 1229 -#define _IS_OP_r23 1230 -#define _ITER_CHECK_LIST_r02 1231 -#define _ITER_CHECK_LIST_r12 1232 -#define _ITER_CHECK_LIST_r22 1233 -#define _ITER_CHECK_LIST_r33 1234 -#define _ITER_CHECK_RANGE_r02 1235 -#define _ITER_CHECK_RANGE_r12 1236 -#define _ITER_CHECK_RANGE_r22 1237 -#define _ITER_CHECK_RANGE_r33 1238 -#define _ITER_CHECK_TUPLE_r02 1239 -#define _ITER_CHECK_TUPLE_r12 1240 -#define _ITER_CHECK_TUPLE_r22 1241 -#define _ITER_CHECK_TUPLE_r33 1242 -#define _ITER_JUMP_LIST_r02 1243 -#define _ITER_JUMP_LIST_r12 1244 -#define _ITER_JUMP_LIST_r22 1245 -#define _ITER_JUMP_LIST_r33 1246 -#define _ITER_JUMP_RANGE_r02 1247 -#define _ITER_JUMP_RANGE_r12 1248 -#define _ITER_JUMP_RANGE_r22 1249 -#define _ITER_JUMP_RANGE_r33 1250 -#define _ITER_JUMP_TUPLE_r02 1251 -#define _ITER_JUMP_TUPLE_r12 1252 -#define _ITER_JUMP_TUPLE_r22 1253 -#define _ITER_JUMP_TUPLE_r33 1254 -#define _ITER_NEXT_LIST_r23 1255 -#define _ITER_NEXT_LIST_TIER_TWO_r23 1256 -#define _ITER_NEXT_RANGE_r03 1257 -#define _ITER_NEXT_RANGE_r13 1258 -#define _ITER_NEXT_RANGE_r23 1259 -#define _ITER_NEXT_TUPLE_r03 1260 -#define _ITER_NEXT_TUPLE_r13 1261 -#define _ITER_NEXT_TUPLE_r23 1262 -#define _JUMP_BACKWARD_NO_INTERRUPT_r00 1263 -#define _JUMP_BACKWARD_NO_INTERRUPT_r11 1264 -#define _JUMP_BACKWARD_NO_INTERRUPT_r22 1265 -#define _JUMP_BACKWARD_NO_INTERRUPT_r33 1266 -#define _JUMP_TO_TOP_r00 1267 -#define _LIST_APPEND_r10 1268 -#define _LIST_EXTEND_r11 1269 -#define _LOAD_ATTR_r10 1270 -#define _LOAD_ATTR_CLASS_r11 1271 -#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME_r11 1272 -#define _LOAD_ATTR_INSTANCE_VALUE_r02 1273 -#define _LOAD_ATTR_INSTANCE_VALUE_r12 1274 -#define _LOAD_ATTR_INSTANCE_VALUE_r23 1275 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1276 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1277 -#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1278 -#define _LOAD_ATTR_METHOD_NO_DICT_r02 1279 -#define _LOAD_ATTR_METHOD_NO_DICT_r12 1280 -#define _LOAD_ATTR_METHOD_NO_DICT_r23 1281 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1282 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1283 -#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1284 -#define _LOAD_ATTR_MODULE_r12 1285 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1286 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1287 -#define _LOAD_ATTR_PROPERTY_FRAME_r01 1288 -#define _LOAD_ATTR_PROPERTY_FRAME_r11 1289 -#define _LOAD_ATTR_PROPERTY_FRAME_r22 1290 -#define _LOAD_ATTR_PROPERTY_FRAME_r33 1291 -#define _LOAD_ATTR_SLOT_r02 1292 -#define _LOAD_ATTR_SLOT_r12 1293 -#define _LOAD_ATTR_SLOT_r23 1294 -#define _LOAD_ATTR_WITH_HINT_r12 1295 -#define _LOAD_BUILD_CLASS_r01 1296 -#define _LOAD_BYTECODE_r00 1297 -#define _LOAD_COMMON_CONSTANT_r01 1298 -#define _LOAD_COMMON_CONSTANT_r12 1299 -#define _LOAD_COMMON_CONSTANT_r23 1300 -#define _LOAD_CONST_r01 1301 -#define _LOAD_CONST_r12 1302 -#define _LOAD_CONST_r23 1303 -#define _LOAD_CONST_INLINE_r01 1304 -#define _LOAD_CONST_INLINE_r12 1305 -#define _LOAD_CONST_INLINE_r23 1306 -#define _LOAD_CONST_INLINE_BORROW_r01 1307 -#define _LOAD_CONST_INLINE_BORROW_r12 1308 -#define _LOAD_CONST_INLINE_BORROW_r23 1309 -#define _LOAD_DEREF_r01 1310 -#define _LOAD_FAST_r01 1311 -#define _LOAD_FAST_r12 1312 -#define _LOAD_FAST_r23 1313 -#define _LOAD_FAST_0_r01 1314 -#define _LOAD_FAST_0_r12 1315 -#define _LOAD_FAST_0_r23 1316 -#define _LOAD_FAST_1_r01 1317 -#define _LOAD_FAST_1_r12 1318 -#define _LOAD_FAST_1_r23 1319 -#define _LOAD_FAST_2_r01 1320 -#define _LOAD_FAST_2_r12 1321 -#define _LOAD_FAST_2_r23 1322 -#define _LOAD_FAST_3_r01 1323 -#define _LOAD_FAST_3_r12 1324 -#define _LOAD_FAST_3_r23 1325 -#define _LOAD_FAST_4_r01 1326 -#define _LOAD_FAST_4_r12 1327 -#define _LOAD_FAST_4_r23 1328 -#define _LOAD_FAST_5_r01 1329 -#define _LOAD_FAST_5_r12 1330 -#define _LOAD_FAST_5_r23 1331 -#define _LOAD_FAST_6_r01 1332 -#define _LOAD_FAST_6_r12 1333 -#define _LOAD_FAST_6_r23 1334 -#define _LOAD_FAST_7_r01 1335 -#define _LOAD_FAST_7_r12 1336 -#define _LOAD_FAST_7_r23 1337 -#define _LOAD_FAST_AND_CLEAR_r01 1338 -#define _LOAD_FAST_AND_CLEAR_r12 1339 -#define _LOAD_FAST_AND_CLEAR_r23 1340 -#define _LOAD_FAST_BORROW_r01 1341 -#define _LOAD_FAST_BORROW_r12 1342 -#define _LOAD_FAST_BORROW_r23 1343 -#define _LOAD_FAST_BORROW_0_r01 1344 -#define _LOAD_FAST_BORROW_0_r12 1345 -#define _LOAD_FAST_BORROW_0_r23 1346 -#define _LOAD_FAST_BORROW_1_r01 1347 -#define _LOAD_FAST_BORROW_1_r12 1348 -#define _LOAD_FAST_BORROW_1_r23 1349 -#define _LOAD_FAST_BORROW_2_r01 1350 -#define _LOAD_FAST_BORROW_2_r12 1351 -#define _LOAD_FAST_BORROW_2_r23 1352 -#define _LOAD_FAST_BORROW_3_r01 1353 -#define _LOAD_FAST_BORROW_3_r12 1354 -#define _LOAD_FAST_BORROW_3_r23 1355 -#define _LOAD_FAST_BORROW_4_r01 1356 -#define _LOAD_FAST_BORROW_4_r12 1357 -#define _LOAD_FAST_BORROW_4_r23 1358 -#define _LOAD_FAST_BORROW_5_r01 1359 -#define _LOAD_FAST_BORROW_5_r12 1360 -#define _LOAD_FAST_BORROW_5_r23 1361 -#define _LOAD_FAST_BORROW_6_r01 1362 -#define _LOAD_FAST_BORROW_6_r12 1363 -#define _LOAD_FAST_BORROW_6_r23 1364 -#define _LOAD_FAST_BORROW_7_r01 1365 -#define _LOAD_FAST_BORROW_7_r12 1366 -#define _LOAD_FAST_BORROW_7_r23 1367 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1368 -#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1369 -#define _LOAD_FAST_CHECK_r01 1370 -#define _LOAD_FAST_CHECK_r12 1371 -#define _LOAD_FAST_CHECK_r23 1372 -#define _LOAD_FAST_LOAD_FAST_r02 1373 -#define _LOAD_FAST_LOAD_FAST_r13 1374 -#define _LOAD_FROM_DICT_OR_DEREF_r11 1375 -#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1376 -#define _LOAD_GLOBAL_r00 1377 -#define _LOAD_GLOBAL_BUILTINS_r01 1378 -#define _LOAD_GLOBAL_MODULE_r01 1379 -#define _LOAD_LOCALS_r01 1380 -#define _LOAD_LOCALS_r12 1381 -#define _LOAD_LOCALS_r23 1382 -#define _LOAD_NAME_r01 1383 -#define _LOAD_SMALL_INT_r01 1384 -#define _LOAD_SMALL_INT_r12 1385 -#define _LOAD_SMALL_INT_r23 1386 -#define _LOAD_SMALL_INT_0_r01 1387 -#define _LOAD_SMALL_INT_0_r12 1388 -#define _LOAD_SMALL_INT_0_r23 1389 -#define _LOAD_SMALL_INT_1_r01 1390 -#define _LOAD_SMALL_INT_1_r12 1391 -#define _LOAD_SMALL_INT_1_r23 1392 -#define _LOAD_SMALL_INT_2_r01 1393 -#define _LOAD_SMALL_INT_2_r12 1394 -#define _LOAD_SMALL_INT_2_r23 1395 -#define _LOAD_SMALL_INT_3_r01 1396 -#define _LOAD_SMALL_INT_3_r12 1397 -#define _LOAD_SMALL_INT_3_r23 1398 -#define _LOAD_SPECIAL_r00 1399 -#define _LOAD_SUPER_ATTR_ATTR_r31 1400 -#define _LOAD_SUPER_ATTR_METHOD_r32 1401 -#define _LOCK_OBJECT_r01 1402 -#define _LOCK_OBJECT_r11 1403 -#define _LOCK_OBJECT_r22 1404 -#define _LOCK_OBJECT_r33 1405 -#define _MAKE_CALLARGS_A_TUPLE_r33 1406 -#define _MAKE_CELL_r00 1407 -#define _MAKE_FUNCTION_r12 1408 -#define _MAKE_HEAP_SAFE_r01 1409 -#define _MAKE_HEAP_SAFE_r11 1410 -#define _MAKE_HEAP_SAFE_r22 1411 -#define _MAKE_HEAP_SAFE_r33 1412 -#define _MAKE_WARM_r00 1413 -#define _MAKE_WARM_r11 1414 -#define _MAKE_WARM_r22 1415 -#define _MAKE_WARM_r33 1416 -#define _MAP_ADD_r20 1417 -#define _MATCH_CLASS_r33 1418 -#define _MATCH_KEYS_r23 1419 -#define _MATCH_MAPPING_r02 1420 -#define _MATCH_MAPPING_r12 1421 -#define _MATCH_MAPPING_r23 1422 -#define _MATCH_SEQUENCE_r02 1423 -#define _MATCH_SEQUENCE_r12 1424 -#define _MATCH_SEQUENCE_r23 1425 -#define _MAYBE_EXPAND_METHOD_r00 1426 -#define _MAYBE_EXPAND_METHOD_KW_r11 1427 -#define _MONITOR_CALL_r00 1428 -#define _MONITOR_CALL_KW_r11 1429 -#define _MONITOR_JUMP_BACKWARD_r00 1430 -#define _MONITOR_JUMP_BACKWARD_r11 1431 -#define _MONITOR_JUMP_BACKWARD_r22 1432 -#define _MONITOR_JUMP_BACKWARD_r33 1433 -#define _MONITOR_RESUME_r00 1434 -#define _NOP_r00 1435 -#define _NOP_r11 1436 -#define _NOP_r22 1437 -#define _NOP_r33 1438 -#define _POP_EXCEPT_r10 1439 -#define _POP_ITER_r20 1440 -#define _POP_JUMP_IF_FALSE_r00 1441 -#define _POP_JUMP_IF_FALSE_r10 1442 -#define _POP_JUMP_IF_FALSE_r21 1443 -#define _POP_JUMP_IF_FALSE_r32 1444 -#define _POP_JUMP_IF_TRUE_r00 1445 -#define _POP_JUMP_IF_TRUE_r10 1446 -#define _POP_JUMP_IF_TRUE_r21 1447 -#define _POP_JUMP_IF_TRUE_r32 1448 -#define _POP_TOP_r10 1449 -#define _POP_TOP_FLOAT_r00 1450 -#define _POP_TOP_FLOAT_r10 1451 -#define _POP_TOP_FLOAT_r21 1452 -#define _POP_TOP_FLOAT_r32 1453 -#define _POP_TOP_INT_r00 1454 -#define _POP_TOP_INT_r10 1455 -#define _POP_TOP_INT_r21 1456 -#define _POP_TOP_INT_r32 1457 -#define _POP_TOP_NOP_r00 1458 -#define _POP_TOP_NOP_r10 1459 -#define _POP_TOP_NOP_r21 1460 -#define _POP_TOP_NOP_r32 1461 -#define _POP_TOP_OPARG_r00 1462 -#define _POP_TOP_UNICODE_r00 1463 -#define _POP_TOP_UNICODE_r10 1464 -#define _POP_TOP_UNICODE_r21 1465 -#define _POP_TOP_UNICODE_r32 1466 -#define _PUSH_EXC_INFO_r02 1467 -#define _PUSH_EXC_INFO_r12 1468 -#define _PUSH_EXC_INFO_r23 1469 -#define _PUSH_FRAME_r10 1470 -#define _PUSH_NULL_r01 1471 -#define _PUSH_NULL_r12 1472 -#define _PUSH_NULL_r23 1473 -#define _PUSH_NULL_CONDITIONAL_r00 1474 -#define _PUSH_TAGGED_ZERO_r01 1475 -#define _PUSH_TAGGED_ZERO_r12 1476 -#define _PUSH_TAGGED_ZERO_r23 1477 -#define _PY_FRAME_EX_r31 1478 -#define _PY_FRAME_GENERAL_r01 1479 -#define _PY_FRAME_KW_r11 1480 -#define _REPLACE_WITH_TRUE_r02 1481 -#define _REPLACE_WITH_TRUE_r12 1482 -#define _REPLACE_WITH_TRUE_r23 1483 -#define _RESUME_CHECK_r00 1484 -#define _RESUME_CHECK_r11 1485 -#define _RESUME_CHECK_r22 1486 -#define _RESUME_CHECK_r33 1487 -#define _RETURN_GENERATOR_r01 1488 -#define _RETURN_VALUE_r11 1489 -#define _SAVE_RETURN_OFFSET_r00 1490 -#define _SAVE_RETURN_OFFSET_r11 1491 -#define _SAVE_RETURN_OFFSET_r22 1492 -#define _SAVE_RETURN_OFFSET_r33 1493 -#define _SEND_r33 1494 -#define _SEND_GEN_FRAME_r33 1495 -#define _SETUP_ANNOTATIONS_r00 1496 -#define _SET_ADD_r10 1497 -#define _SET_FUNCTION_ATTRIBUTE_r01 1498 -#define _SET_FUNCTION_ATTRIBUTE_r11 1499 -#define _SET_FUNCTION_ATTRIBUTE_r21 1500 -#define _SET_FUNCTION_ATTRIBUTE_r32 1501 -#define _SET_IP_r00 1502 -#define _SET_IP_r11 1503 -#define _SET_IP_r22 1504 -#define _SET_IP_r33 1505 -#define _SET_UPDATE_r11 1506 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 1507 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 1508 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 1509 -#define _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 1510 -#define _SPILL_OR_RELOAD_r01 1511 -#define _SPILL_OR_RELOAD_r02 1512 -#define _SPILL_OR_RELOAD_r03 1513 -#define _SPILL_OR_RELOAD_r10 1514 -#define _SPILL_OR_RELOAD_r12 1515 -#define _SPILL_OR_RELOAD_r13 1516 -#define _SPILL_OR_RELOAD_r20 1517 -#define _SPILL_OR_RELOAD_r21 1518 -#define _SPILL_OR_RELOAD_r23 1519 -#define _SPILL_OR_RELOAD_r30 1520 -#define _SPILL_OR_RELOAD_r31 1521 -#define _SPILL_OR_RELOAD_r32 1522 -#define _START_EXECUTOR_r00 1523 -#define _STORE_ATTR_r20 1524 -#define _STORE_ATTR_INSTANCE_VALUE_r21 1525 -#define _STORE_ATTR_SLOT_r21 1526 -#define _STORE_ATTR_WITH_HINT_r21 1527 -#define _STORE_DEREF_r10 1528 -#define _STORE_FAST_LOAD_FAST_r11 1529 -#define _STORE_FAST_STORE_FAST_r20 1530 -#define _STORE_GLOBAL_r10 1531 -#define _STORE_NAME_r10 1532 -#define _STORE_SLICE_r30 1533 -#define _STORE_SUBSCR_r30 1534 -#define _STORE_SUBSCR_DICT_r31 1535 -#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1536 -#define _STORE_SUBSCR_LIST_INT_r32 1537 -#define _SWAP_r11 1538 -#define _SWAP_2_r02 1539 -#define _SWAP_2_r12 1540 -#define _SWAP_2_r22 1541 -#define _SWAP_2_r33 1542 -#define _SWAP_3_r03 1543 -#define _SWAP_3_r13 1544 -#define _SWAP_3_r23 1545 -#define _SWAP_3_r33 1546 -#define _SWAP_FAST_r01 1547 -#define _SWAP_FAST_r11 1548 -#define _SWAP_FAST_r22 1549 -#define _SWAP_FAST_r33 1550 -#define _SWAP_FAST_0_r01 1551 -#define _SWAP_FAST_0_r11 1552 -#define _SWAP_FAST_0_r22 1553 -#define _SWAP_FAST_0_r33 1554 -#define _SWAP_FAST_1_r01 1555 -#define _SWAP_FAST_1_r11 1556 -#define _SWAP_FAST_1_r22 1557 -#define _SWAP_FAST_1_r33 1558 -#define _SWAP_FAST_2_r01 1559 -#define _SWAP_FAST_2_r11 1560 -#define _SWAP_FAST_2_r22 1561 -#define _SWAP_FAST_2_r33 1562 -#define _SWAP_FAST_3_r01 1563 -#define _SWAP_FAST_3_r11 1564 -#define _SWAP_FAST_3_r22 1565 -#define _SWAP_FAST_3_r33 1566 -#define _SWAP_FAST_4_r01 1567 -#define _SWAP_FAST_4_r11 1568 -#define _SWAP_FAST_4_r22 1569 -#define _SWAP_FAST_4_r33 1570 -#define _SWAP_FAST_5_r01 1571 -#define _SWAP_FAST_5_r11 1572 -#define _SWAP_FAST_5_r22 1573 -#define _SWAP_FAST_5_r33 1574 -#define _SWAP_FAST_6_r01 1575 -#define _SWAP_FAST_6_r11 1576 -#define _SWAP_FAST_6_r22 1577 -#define _SWAP_FAST_6_r33 1578 -#define _SWAP_FAST_7_r01 1579 -#define _SWAP_FAST_7_r11 1580 -#define _SWAP_FAST_7_r22 1581 -#define _SWAP_FAST_7_r33 1582 -#define _TIER2_RESUME_CHECK_r00 1583 -#define _TIER2_RESUME_CHECK_r11 1584 -#define _TIER2_RESUME_CHECK_r22 1585 -#define _TIER2_RESUME_CHECK_r33 1586 -#define _TO_BOOL_r11 1587 -#define _TO_BOOL_BOOL_r01 1588 -#define _TO_BOOL_BOOL_r11 1589 -#define _TO_BOOL_BOOL_r22 1590 -#define _TO_BOOL_BOOL_r33 1591 -#define _TO_BOOL_INT_r02 1592 -#define _TO_BOOL_INT_r12 1593 -#define _TO_BOOL_INT_r23 1594 -#define _TO_BOOL_LIST_r02 1595 -#define _TO_BOOL_LIST_r12 1596 -#define _TO_BOOL_LIST_r23 1597 -#define _TO_BOOL_NONE_r01 1598 -#define _TO_BOOL_NONE_r11 1599 -#define _TO_BOOL_NONE_r22 1600 -#define _TO_BOOL_NONE_r33 1601 -#define _TO_BOOL_STR_r02 1602 -#define _TO_BOOL_STR_r12 1603 -#define _TO_BOOL_STR_r23 1604 -#define _TRACE_RECORD_r00 1605 -#define _UNARY_INVERT_r12 1606 -#define _UNARY_NEGATIVE_r12 1607 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1608 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1609 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1610 -#define _UNARY_NOT_r01 1611 -#define _UNARY_NOT_r11 1612 -#define _UNARY_NOT_r22 1613 -#define _UNARY_NOT_r33 1614 -#define _UNPACK_EX_r10 1615 -#define _UNPACK_SEQUENCE_r10 1616 -#define _UNPACK_SEQUENCE_LIST_r10 1617 -#define _UNPACK_SEQUENCE_TUPLE_r10 1618 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1619 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1620 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1621 -#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1622 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1623 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1624 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1625 -#define _WITH_EXCEPT_START_r33 1626 -#define _YIELD_VALUE_r11 1627 -#define MAX_UOP_REGS_ID 1627 +#define _YIELD_VALUE 645 +#define MAX_UOP_ID 645 +#define _ALLOCATE_OBJECT_r00 646 +#define _BINARY_OP_r23 647 +#define _BINARY_OP_ADD_FLOAT_r03 648 +#define _BINARY_OP_ADD_FLOAT_r13 649 +#define _BINARY_OP_ADD_FLOAT_r23 650 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r03 651 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r13 652 +#define _BINARY_OP_ADD_FLOAT_INPLACE_r23 653 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r03 654 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r13 655 +#define _BINARY_OP_ADD_FLOAT_INPLACE_RIGHT_r23 656 +#define _BINARY_OP_ADD_INT_r03 657 +#define _BINARY_OP_ADD_INT_r13 658 +#define _BINARY_OP_ADD_INT_r23 659 +#define _BINARY_OP_ADD_INT_INPLACE_r03 660 +#define _BINARY_OP_ADD_INT_INPLACE_r13 661 +#define _BINARY_OP_ADD_INT_INPLACE_r23 662 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r03 663 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r13 664 +#define _BINARY_OP_ADD_INT_INPLACE_RIGHT_r23 665 +#define _BINARY_OP_ADD_UNICODE_r03 666 +#define _BINARY_OP_ADD_UNICODE_r13 667 +#define _BINARY_OP_ADD_UNICODE_r23 668 +#define _BINARY_OP_EXTEND_r23 669 +#define _BINARY_OP_INPLACE_ADD_UNICODE_r21 670 +#define _BINARY_OP_MULTIPLY_FLOAT_r03 671 +#define _BINARY_OP_MULTIPLY_FLOAT_r13 672 +#define _BINARY_OP_MULTIPLY_FLOAT_r23 673 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r03 674 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r13 675 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_r23 676 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r03 677 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r13 678 +#define _BINARY_OP_MULTIPLY_FLOAT_INPLACE_RIGHT_r23 679 +#define _BINARY_OP_MULTIPLY_INT_r03 680 +#define _BINARY_OP_MULTIPLY_INT_r13 681 +#define _BINARY_OP_MULTIPLY_INT_r23 682 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r03 683 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r13 684 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_r23 685 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r03 686 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r13 687 +#define _BINARY_OP_MULTIPLY_INT_INPLACE_RIGHT_r23 688 +#define _BINARY_OP_SUBSCR_CHECK_FUNC_r23 689 +#define _BINARY_OP_SUBSCR_DICT_r23 690 +#define _BINARY_OP_SUBSCR_DICT_KNOWN_HASH_r23 691 +#define _BINARY_OP_SUBSCR_INIT_CALL_r01 692 +#define _BINARY_OP_SUBSCR_INIT_CALL_r11 693 +#define _BINARY_OP_SUBSCR_INIT_CALL_r21 694 +#define _BINARY_OP_SUBSCR_INIT_CALL_r31 695 +#define _BINARY_OP_SUBSCR_LIST_INT_r23 696 +#define _BINARY_OP_SUBSCR_LIST_SLICE_r23 697 +#define _BINARY_OP_SUBSCR_STR_INT_r23 698 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r03 699 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r13 700 +#define _BINARY_OP_SUBSCR_TUPLE_INT_r23 701 +#define _BINARY_OP_SUBSCR_USTR_INT_r23 702 +#define _BINARY_OP_SUBTRACT_FLOAT_r03 703 +#define _BINARY_OP_SUBTRACT_FLOAT_r13 704 +#define _BINARY_OP_SUBTRACT_FLOAT_r23 705 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r03 706 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r13 707 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_r23 708 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r03 709 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r13 710 +#define _BINARY_OP_SUBTRACT_FLOAT_INPLACE_RIGHT_r23 711 +#define _BINARY_OP_SUBTRACT_INT_r03 712 +#define _BINARY_OP_SUBTRACT_INT_r13 713 +#define _BINARY_OP_SUBTRACT_INT_r23 714 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r03 715 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r13 716 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_r23 717 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r03 718 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r13 719 +#define _BINARY_OP_SUBTRACT_INT_INPLACE_RIGHT_r23 720 +#define _BINARY_OP_TRUEDIV_FLOAT_r23 721 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r03 722 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r13 723 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_r23 724 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r03 725 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r13 726 +#define _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT_r23 727 +#define _BINARY_SLICE_r31 728 +#define _BUILD_INTERPOLATION_r01 729 +#define _BUILD_LIST_r01 730 +#define _BUILD_MAP_r01 731 +#define _BUILD_SET_r01 732 +#define _BUILD_SLICE_r01 733 +#define _BUILD_STRING_r01 734 +#define _BUILD_TEMPLATE_r21 735 +#define _BUILD_TUPLE_r01 736 +#define _CALL_BUILTIN_CLASS_r00 737 +#define _CALL_BUILTIN_FAST_r00 738 +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS_r00 739 +#define _CALL_BUILTIN_O_r03 740 +#define _CALL_FUNCTION_EX_NON_PY_GENERAL_r31 741 +#define _CALL_INTRINSIC_1_r12 742 +#define _CALL_INTRINSIC_2_r23 743 +#define _CALL_ISINSTANCE_r31 744 +#define _CALL_KW_NON_PY_r11 745 +#define _CALL_LEN_r33 746 +#define _CALL_LIST_APPEND_r03 747 +#define _CALL_LIST_APPEND_r13 748 +#define _CALL_LIST_APPEND_r23 749 +#define _CALL_LIST_APPEND_r33 750 +#define _CALL_METHOD_DESCRIPTOR_FAST_r00 751 +#define _CALL_METHOD_DESCRIPTOR_FAST_INLINE_r00 752 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 753 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_INLINE_r00 754 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_r03 755 +#define _CALL_METHOD_DESCRIPTOR_NOARGS_INLINE_r03 756 +#define _CALL_METHOD_DESCRIPTOR_O_r03 757 +#define _CALL_METHOD_DESCRIPTOR_O_INLINE_r03 758 +#define _CALL_NON_PY_GENERAL_r01 759 +#define _CALL_STR_1_r32 760 +#define _CALL_TUPLE_1_r32 761 +#define _CALL_TYPE_1_r02 762 +#define _CALL_TYPE_1_r12 763 +#define _CALL_TYPE_1_r22 764 +#define _CALL_TYPE_1_r32 765 +#define _CHECK_ATTR_CLASS_r01 766 +#define _CHECK_ATTR_CLASS_r11 767 +#define _CHECK_ATTR_CLASS_r22 768 +#define _CHECK_ATTR_CLASS_r33 769 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r01 770 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r11 771 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r22 772 +#define _CHECK_ATTR_METHOD_LAZY_DICT_r33 773 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS_r00 774 +#define _CHECK_EG_MATCH_r22 775 +#define _CHECK_EXC_MATCH_r22 776 +#define _CHECK_FUNCTION_EXACT_ARGS_r00 777 +#define _CHECK_FUNCTION_VERSION_r00 778 +#define _CHECK_FUNCTION_VERSION_INLINE_r00 779 +#define _CHECK_FUNCTION_VERSION_INLINE_r11 780 +#define _CHECK_FUNCTION_VERSION_INLINE_r22 781 +#define _CHECK_FUNCTION_VERSION_INLINE_r33 782 +#define _CHECK_FUNCTION_VERSION_KW_r11 783 +#define _CHECK_IS_NOT_PY_CALLABLE_r00 784 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r03 785 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r13 786 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r23 787 +#define _CHECK_IS_NOT_PY_CALLABLE_EX_r33 788 +#define _CHECK_IS_NOT_PY_CALLABLE_KW_r11 789 +#define _CHECK_IS_PY_CALLABLE_EX_r03 790 +#define _CHECK_IS_PY_CALLABLE_EX_r13 791 +#define _CHECK_IS_PY_CALLABLE_EX_r23 792 +#define _CHECK_IS_PY_CALLABLE_EX_r33 793 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r01 794 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r11 795 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r22 796 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES_r33 797 +#define _CHECK_METHOD_VERSION_r00 798 +#define _CHECK_METHOD_VERSION_KW_r11 799 +#define _CHECK_OBJECT_r00 800 +#define _CHECK_PEP_523_r00 801 +#define _CHECK_PEP_523_r11 802 +#define _CHECK_PEP_523_r22 803 +#define _CHECK_PEP_523_r33 804 +#define _CHECK_PERIODIC_r00 805 +#define _CHECK_PERIODIC_AT_END_r00 806 +#define _CHECK_PERIODIC_IF_NOT_YIELD_FROM_r00 807 +#define _CHECK_RECURSION_LIMIT_r00 808 +#define _CHECK_RECURSION_LIMIT_r11 809 +#define _CHECK_RECURSION_LIMIT_r22 810 +#define _CHECK_RECURSION_LIMIT_r33 811 +#define _CHECK_RECURSION_REMAINING_r00 812 +#define _CHECK_RECURSION_REMAINING_r11 813 +#define _CHECK_RECURSION_REMAINING_r22 814 +#define _CHECK_RECURSION_REMAINING_r33 815 +#define _CHECK_STACK_SPACE_r00 816 +#define _CHECK_STACK_SPACE_OPERAND_r00 817 +#define _CHECK_STACK_SPACE_OPERAND_r11 818 +#define _CHECK_STACK_SPACE_OPERAND_r22 819 +#define _CHECK_STACK_SPACE_OPERAND_r33 820 +#define _CHECK_VALIDITY_r00 821 +#define _CHECK_VALIDITY_r11 822 +#define _CHECK_VALIDITY_r22 823 +#define _CHECK_VALIDITY_r33 824 +#define _COLD_DYNAMIC_EXIT_r00 825 +#define _COLD_EXIT_r00 826 +#define _COMPARE_OP_r21 827 +#define _COMPARE_OP_FLOAT_r03 828 +#define _COMPARE_OP_FLOAT_r13 829 +#define _COMPARE_OP_FLOAT_r23 830 +#define _COMPARE_OP_INT_r23 831 +#define _COMPARE_OP_STR_r23 832 +#define _CONTAINS_OP_r23 833 +#define _CONTAINS_OP_DICT_r23 834 +#define _CONTAINS_OP_SET_r23 835 +#define _CONVERT_VALUE_r11 836 +#define _COPY_r01 837 +#define _COPY_1_r02 838 +#define _COPY_1_r12 839 +#define _COPY_1_r23 840 +#define _COPY_2_r03 841 +#define _COPY_2_r13 842 +#define _COPY_2_r23 843 +#define _COPY_3_r03 844 +#define _COPY_3_r13 845 +#define _COPY_3_r23 846 +#define _COPY_3_r33 847 +#define _COPY_FREE_VARS_r00 848 +#define _COPY_FREE_VARS_r11 849 +#define _COPY_FREE_VARS_r22 850 +#define _COPY_FREE_VARS_r33 851 +#define _CREATE_INIT_FRAME_r01 852 +#define _DELETE_ATTR_r10 853 +#define _DELETE_DEREF_r00 854 +#define _DELETE_FAST_r00 855 +#define _DELETE_GLOBAL_r00 856 +#define _DELETE_NAME_r00 857 +#define _DELETE_SUBSCR_r20 858 +#define _DEOPT_r00 859 +#define _DEOPT_r10 860 +#define _DEOPT_r20 861 +#define _DEOPT_r30 862 +#define _DICT_MERGE_r11 863 +#define _DICT_UPDATE_r11 864 +#define _DO_CALL_r01 865 +#define _DO_CALL_FUNCTION_EX_r31 866 +#define _DO_CALL_KW_r11 867 +#define _DYNAMIC_EXIT_r00 868 +#define _DYNAMIC_EXIT_r10 869 +#define _DYNAMIC_EXIT_r20 870 +#define _DYNAMIC_EXIT_r30 871 +#define _END_FOR_r10 872 +#define _END_SEND_r31 873 +#define _ERROR_POP_N_r00 874 +#define _EXIT_INIT_CHECK_r10 875 +#define _EXIT_TRACE_r00 876 +#define _EXIT_TRACE_r10 877 +#define _EXIT_TRACE_r20 878 +#define _EXIT_TRACE_r30 879 +#define _EXPAND_METHOD_r00 880 +#define _EXPAND_METHOD_KW_r11 881 +#define _FATAL_ERROR_r00 882 +#define _FATAL_ERROR_r11 883 +#define _FATAL_ERROR_r22 884 +#define _FATAL_ERROR_r33 885 +#define _FORMAT_SIMPLE_r11 886 +#define _FORMAT_WITH_SPEC_r21 887 +#define _FOR_ITER_r23 888 +#define _FOR_ITER_GEN_FRAME_r03 889 +#define _FOR_ITER_GEN_FRAME_r13 890 +#define _FOR_ITER_GEN_FRAME_r23 891 +#define _FOR_ITER_TIER_TWO_r23 892 +#define _FOR_ITER_VIRTUAL_r23 893 +#define _FOR_ITER_VIRTUAL_TIER_TWO_r23 894 +#define _GET_AITER_r11 895 +#define _GET_ANEXT_r12 896 +#define _GET_AWAITABLE_r11 897 +#define _GET_ITER_r12 898 +#define _GET_ITER_TRAD_r12 899 +#define _GET_LEN_r12 900 +#define _GUARD_BINARY_OP_EXTEND_r22 901 +#define _GUARD_BINARY_OP_EXTEND_LHS_r02 902 +#define _GUARD_BINARY_OP_EXTEND_LHS_r12 903 +#define _GUARD_BINARY_OP_EXTEND_LHS_r22 904 +#define _GUARD_BINARY_OP_EXTEND_LHS_r33 905 +#define _GUARD_BINARY_OP_EXTEND_RHS_r02 906 +#define _GUARD_BINARY_OP_EXTEND_RHS_r12 907 +#define _GUARD_BINARY_OP_EXTEND_RHS_r22 908 +#define _GUARD_BINARY_OP_EXTEND_RHS_r33 909 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r02 910 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r12 911 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r22 912 +#define _GUARD_BINARY_OP_SUBSCR_TUPLE_INT_BOUNDS_r33 913 +#define _GUARD_BIT_IS_SET_POP_r00 914 +#define _GUARD_BIT_IS_SET_POP_r10 915 +#define _GUARD_BIT_IS_SET_POP_r21 916 +#define _GUARD_BIT_IS_SET_POP_r32 917 +#define _GUARD_BIT_IS_SET_POP_4_r00 918 +#define _GUARD_BIT_IS_SET_POP_4_r10 919 +#define _GUARD_BIT_IS_SET_POP_4_r21 920 +#define _GUARD_BIT_IS_SET_POP_4_r32 921 +#define _GUARD_BIT_IS_SET_POP_5_r00 922 +#define _GUARD_BIT_IS_SET_POP_5_r10 923 +#define _GUARD_BIT_IS_SET_POP_5_r21 924 +#define _GUARD_BIT_IS_SET_POP_5_r32 925 +#define _GUARD_BIT_IS_SET_POP_6_r00 926 +#define _GUARD_BIT_IS_SET_POP_6_r10 927 +#define _GUARD_BIT_IS_SET_POP_6_r21 928 +#define _GUARD_BIT_IS_SET_POP_6_r32 929 +#define _GUARD_BIT_IS_SET_POP_7_r00 930 +#define _GUARD_BIT_IS_SET_POP_7_r10 931 +#define _GUARD_BIT_IS_SET_POP_7_r21 932 +#define _GUARD_BIT_IS_SET_POP_7_r32 933 +#define _GUARD_BIT_IS_UNSET_POP_r00 934 +#define _GUARD_BIT_IS_UNSET_POP_r10 935 +#define _GUARD_BIT_IS_UNSET_POP_r21 936 +#define _GUARD_BIT_IS_UNSET_POP_r32 937 +#define _GUARD_BIT_IS_UNSET_POP_4_r00 938 +#define _GUARD_BIT_IS_UNSET_POP_4_r10 939 +#define _GUARD_BIT_IS_UNSET_POP_4_r21 940 +#define _GUARD_BIT_IS_UNSET_POP_4_r32 941 +#define _GUARD_BIT_IS_UNSET_POP_5_r00 942 +#define _GUARD_BIT_IS_UNSET_POP_5_r10 943 +#define _GUARD_BIT_IS_UNSET_POP_5_r21 944 +#define _GUARD_BIT_IS_UNSET_POP_5_r32 945 +#define _GUARD_BIT_IS_UNSET_POP_6_r00 946 +#define _GUARD_BIT_IS_UNSET_POP_6_r10 947 +#define _GUARD_BIT_IS_UNSET_POP_6_r21 948 +#define _GUARD_BIT_IS_UNSET_POP_6_r32 949 +#define _GUARD_BIT_IS_UNSET_POP_7_r00 950 +#define _GUARD_BIT_IS_UNSET_POP_7_r10 951 +#define _GUARD_BIT_IS_UNSET_POP_7_r21 952 +#define _GUARD_BIT_IS_UNSET_POP_7_r32 953 +#define _GUARD_CALLABLE_BUILTIN_CLASS_r00 954 +#define _GUARD_CALLABLE_BUILTIN_FAST_r00 955 +#define _GUARD_CALLABLE_BUILTIN_FAST_WITH_KEYWORDS_r00 956 +#define _GUARD_CALLABLE_BUILTIN_O_r00 957 +#define _GUARD_CALLABLE_ISINSTANCE_r03 958 +#define _GUARD_CALLABLE_ISINSTANCE_r13 959 +#define _GUARD_CALLABLE_ISINSTANCE_r23 960 +#define _GUARD_CALLABLE_ISINSTANCE_r33 961 +#define _GUARD_CALLABLE_LEN_r03 962 +#define _GUARD_CALLABLE_LEN_r13 963 +#define _GUARD_CALLABLE_LEN_r23 964 +#define _GUARD_CALLABLE_LEN_r33 965 +#define _GUARD_CALLABLE_LIST_APPEND_r03 966 +#define _GUARD_CALLABLE_LIST_APPEND_r13 967 +#define _GUARD_CALLABLE_LIST_APPEND_r23 968 +#define _GUARD_CALLABLE_LIST_APPEND_r33 969 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_r00 970 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS_r00 971 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_NOARGS_r00 972 +#define _GUARD_CALLABLE_METHOD_DESCRIPTOR_O_r00 973 +#define _GUARD_CALLABLE_STR_1_r03 974 +#define _GUARD_CALLABLE_STR_1_r13 975 +#define _GUARD_CALLABLE_STR_1_r23 976 +#define _GUARD_CALLABLE_STR_1_r33 977 +#define _GUARD_CALLABLE_TUPLE_1_r03 978 +#define _GUARD_CALLABLE_TUPLE_1_r13 979 +#define _GUARD_CALLABLE_TUPLE_1_r23 980 +#define _GUARD_CALLABLE_TUPLE_1_r33 981 +#define _GUARD_CALLABLE_TYPE_1_r03 982 +#define _GUARD_CALLABLE_TYPE_1_r13 983 +#define _GUARD_CALLABLE_TYPE_1_r23 984 +#define _GUARD_CALLABLE_TYPE_1_r33 985 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r00 986 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r11 987 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r22 988 +#define _GUARD_CODE_VERSION_RETURN_GENERATOR_r33 989 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r00 990 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r11 991 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r22 992 +#define _GUARD_CODE_VERSION_RETURN_VALUE_r33 993 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r00 994 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r11 995 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r22 996 +#define _GUARD_CODE_VERSION_YIELD_VALUE_r33 997 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r00 998 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r11 999 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r22 1000 +#define _GUARD_CODE_VERSION__PUSH_FRAME_r33 1001 +#define _GUARD_DORV_NO_DICT_r01 1002 +#define _GUARD_DORV_NO_DICT_r11 1003 +#define _GUARD_DORV_NO_DICT_r22 1004 +#define _GUARD_DORV_NO_DICT_r33 1005 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r01 1006 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r11 1007 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r22 1008 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT_r33 1009 +#define _GUARD_GLOBALS_VERSION_r00 1010 +#define _GUARD_GLOBALS_VERSION_r11 1011 +#define _GUARD_GLOBALS_VERSION_r22 1012 +#define _GUARD_GLOBALS_VERSION_r33 1013 +#define _GUARD_IP_RETURN_GENERATOR_r00 1014 +#define _GUARD_IP_RETURN_GENERATOR_r11 1015 +#define _GUARD_IP_RETURN_GENERATOR_r22 1016 +#define _GUARD_IP_RETURN_GENERATOR_r33 1017 +#define _GUARD_IP_RETURN_VALUE_r00 1018 +#define _GUARD_IP_RETURN_VALUE_r11 1019 +#define _GUARD_IP_RETURN_VALUE_r22 1020 +#define _GUARD_IP_RETURN_VALUE_r33 1021 +#define _GUARD_IP_YIELD_VALUE_r00 1022 +#define _GUARD_IP_YIELD_VALUE_r11 1023 +#define _GUARD_IP_YIELD_VALUE_r22 1024 +#define _GUARD_IP_YIELD_VALUE_r33 1025 +#define _GUARD_IP__PUSH_FRAME_r00 1026 +#define _GUARD_IP__PUSH_FRAME_r11 1027 +#define _GUARD_IP__PUSH_FRAME_r22 1028 +#define _GUARD_IP__PUSH_FRAME_r33 1029 +#define _GUARD_IS_FALSE_POP_r00 1030 +#define _GUARD_IS_FALSE_POP_r10 1031 +#define _GUARD_IS_FALSE_POP_r21 1032 +#define _GUARD_IS_FALSE_POP_r32 1033 +#define _GUARD_IS_NONE_POP_r00 1034 +#define _GUARD_IS_NONE_POP_r10 1035 +#define _GUARD_IS_NONE_POP_r21 1036 +#define _GUARD_IS_NONE_POP_r32 1037 +#define _GUARD_IS_NOT_NONE_POP_r10 1038 +#define _GUARD_IS_TRUE_POP_r00 1039 +#define _GUARD_IS_TRUE_POP_r10 1040 +#define _GUARD_IS_TRUE_POP_r21 1041 +#define _GUARD_IS_TRUE_POP_r32 1042 +#define _GUARD_ITERATOR_r01 1043 +#define _GUARD_ITERATOR_r11 1044 +#define _GUARD_ITERATOR_r22 1045 +#define _GUARD_ITERATOR_r33 1046 +#define _GUARD_ITER_VIRTUAL_r01 1047 +#define _GUARD_ITER_VIRTUAL_r11 1048 +#define _GUARD_ITER_VIRTUAL_r22 1049 +#define _GUARD_ITER_VIRTUAL_r33 1050 +#define _GUARD_KEYS_VERSION_r01 1051 +#define _GUARD_KEYS_VERSION_r11 1052 +#define _GUARD_KEYS_VERSION_r22 1053 +#define _GUARD_KEYS_VERSION_r33 1054 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r03 1055 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r13 1056 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r23 1057 +#define _GUARD_LOAD_SUPER_ATTR_METHOD_r33 1058 +#define _GUARD_NOS_ANY_DICT_r02 1059 +#define _GUARD_NOS_ANY_DICT_r12 1060 +#define _GUARD_NOS_ANY_DICT_r22 1061 +#define _GUARD_NOS_ANY_DICT_r33 1062 +#define _GUARD_NOS_COMPACT_ASCII_r02 1063 +#define _GUARD_NOS_COMPACT_ASCII_r12 1064 +#define _GUARD_NOS_COMPACT_ASCII_r22 1065 +#define _GUARD_NOS_COMPACT_ASCII_r33 1066 +#define _GUARD_NOS_DICT_r02 1067 +#define _GUARD_NOS_DICT_r12 1068 +#define _GUARD_NOS_DICT_r22 1069 +#define _GUARD_NOS_DICT_r33 1070 +#define _GUARD_NOS_FLOAT_r02 1071 +#define _GUARD_NOS_FLOAT_r12 1072 +#define _GUARD_NOS_FLOAT_r22 1073 +#define _GUARD_NOS_FLOAT_r33 1074 +#define _GUARD_NOS_INT_r02 1075 +#define _GUARD_NOS_INT_r12 1076 +#define _GUARD_NOS_INT_r22 1077 +#define _GUARD_NOS_INT_r33 1078 +#define _GUARD_NOS_ITER_VIRTUAL_r02 1079 +#define _GUARD_NOS_ITER_VIRTUAL_r12 1080 +#define _GUARD_NOS_ITER_VIRTUAL_r22 1081 +#define _GUARD_NOS_ITER_VIRTUAL_r33 1082 +#define _GUARD_NOS_LIST_r02 1083 +#define _GUARD_NOS_LIST_r12 1084 +#define _GUARD_NOS_LIST_r22 1085 +#define _GUARD_NOS_LIST_r33 1086 +#define _GUARD_NOS_NOT_NULL_r02 1087 +#define _GUARD_NOS_NOT_NULL_r12 1088 +#define _GUARD_NOS_NOT_NULL_r22 1089 +#define _GUARD_NOS_NOT_NULL_r33 1090 +#define _GUARD_NOS_NULL_r02 1091 +#define _GUARD_NOS_NULL_r12 1092 +#define _GUARD_NOS_NULL_r22 1093 +#define _GUARD_NOS_NULL_r33 1094 +#define _GUARD_NOS_OVERFLOWED_r02 1095 +#define _GUARD_NOS_OVERFLOWED_r12 1096 +#define _GUARD_NOS_OVERFLOWED_r22 1097 +#define _GUARD_NOS_OVERFLOWED_r33 1098 +#define _GUARD_NOS_TUPLE_r02 1099 +#define _GUARD_NOS_TUPLE_r12 1100 +#define _GUARD_NOS_TUPLE_r22 1101 +#define _GUARD_NOS_TUPLE_r33 1102 +#define _GUARD_NOS_TYPE_VERSION_r02 1103 +#define _GUARD_NOS_TYPE_VERSION_r12 1104 +#define _GUARD_NOS_TYPE_VERSION_r22 1105 +#define _GUARD_NOS_TYPE_VERSION_r33 1106 +#define _GUARD_NOS_UNICODE_r02 1107 +#define _GUARD_NOS_UNICODE_r12 1108 +#define _GUARD_NOS_UNICODE_r22 1109 +#define _GUARD_NOS_UNICODE_r33 1110 +#define _GUARD_NOT_EXHAUSTED_LIST_r02 1111 +#define _GUARD_NOT_EXHAUSTED_LIST_r12 1112 +#define _GUARD_NOT_EXHAUSTED_LIST_r22 1113 +#define _GUARD_NOT_EXHAUSTED_LIST_r33 1114 +#define _GUARD_NOT_EXHAUSTED_RANGE_r02 1115 +#define _GUARD_NOT_EXHAUSTED_RANGE_r12 1116 +#define _GUARD_NOT_EXHAUSTED_RANGE_r22 1117 +#define _GUARD_NOT_EXHAUSTED_RANGE_r33 1118 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r02 1119 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r12 1120 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r22 1121 +#define _GUARD_NOT_EXHAUSTED_TUPLE_r33 1122 +#define _GUARD_THIRD_NULL_r03 1123 +#define _GUARD_THIRD_NULL_r13 1124 +#define _GUARD_THIRD_NULL_r23 1125 +#define _GUARD_THIRD_NULL_r33 1126 +#define _GUARD_TOS_ANY_DICT_r01 1127 +#define _GUARD_TOS_ANY_DICT_r11 1128 +#define _GUARD_TOS_ANY_DICT_r22 1129 +#define _GUARD_TOS_ANY_DICT_r33 1130 +#define _GUARD_TOS_ANY_SET_r01 1131 +#define _GUARD_TOS_ANY_SET_r11 1132 +#define _GUARD_TOS_ANY_SET_r22 1133 +#define _GUARD_TOS_ANY_SET_r33 1134 +#define _GUARD_TOS_DICT_r01 1135 +#define _GUARD_TOS_DICT_r11 1136 +#define _GUARD_TOS_DICT_r22 1137 +#define _GUARD_TOS_DICT_r33 1138 +#define _GUARD_TOS_FLOAT_r01 1139 +#define _GUARD_TOS_FLOAT_r11 1140 +#define _GUARD_TOS_FLOAT_r22 1141 +#define _GUARD_TOS_FLOAT_r33 1142 +#define _GUARD_TOS_FROZENDICT_r01 1143 +#define _GUARD_TOS_FROZENDICT_r11 1144 +#define _GUARD_TOS_FROZENDICT_r22 1145 +#define _GUARD_TOS_FROZENDICT_r33 1146 +#define _GUARD_TOS_FROZENSET_r01 1147 +#define _GUARD_TOS_FROZENSET_r11 1148 +#define _GUARD_TOS_FROZENSET_r22 1149 +#define _GUARD_TOS_FROZENSET_r33 1150 +#define _GUARD_TOS_INT_r01 1151 +#define _GUARD_TOS_INT_r11 1152 +#define _GUARD_TOS_INT_r22 1153 +#define _GUARD_TOS_INT_r33 1154 +#define _GUARD_TOS_LIST_r01 1155 +#define _GUARD_TOS_LIST_r11 1156 +#define _GUARD_TOS_LIST_r22 1157 +#define _GUARD_TOS_LIST_r33 1158 +#define _GUARD_TOS_OVERFLOWED_r01 1159 +#define _GUARD_TOS_OVERFLOWED_r11 1160 +#define _GUARD_TOS_OVERFLOWED_r22 1161 +#define _GUARD_TOS_OVERFLOWED_r33 1162 +#define _GUARD_TOS_SET_r01 1163 +#define _GUARD_TOS_SET_r11 1164 +#define _GUARD_TOS_SET_r22 1165 +#define _GUARD_TOS_SET_r33 1166 +#define _GUARD_TOS_SLICE_r01 1167 +#define _GUARD_TOS_SLICE_r11 1168 +#define _GUARD_TOS_SLICE_r22 1169 +#define _GUARD_TOS_SLICE_r33 1170 +#define _GUARD_TOS_TUPLE_r01 1171 +#define _GUARD_TOS_TUPLE_r11 1172 +#define _GUARD_TOS_TUPLE_r22 1173 +#define _GUARD_TOS_TUPLE_r33 1174 +#define _GUARD_TOS_UNICODE_r01 1175 +#define _GUARD_TOS_UNICODE_r11 1176 +#define _GUARD_TOS_UNICODE_r22 1177 +#define _GUARD_TOS_UNICODE_r33 1178 +#define _GUARD_TYPE_r01 1179 +#define _GUARD_TYPE_r11 1180 +#define _GUARD_TYPE_r22 1181 +#define _GUARD_TYPE_r33 1182 +#define _GUARD_TYPE_VERSION_r01 1183 +#define _GUARD_TYPE_VERSION_r11 1184 +#define _GUARD_TYPE_VERSION_r22 1185 +#define _GUARD_TYPE_VERSION_r33 1186 +#define _GUARD_TYPE_VERSION_LOCKED_r01 1187 +#define _GUARD_TYPE_VERSION_LOCKED_r11 1188 +#define _GUARD_TYPE_VERSION_LOCKED_r22 1189 +#define _GUARD_TYPE_VERSION_LOCKED_r33 1190 +#define _HANDLE_PENDING_AND_DEOPT_r00 1191 +#define _HANDLE_PENDING_AND_DEOPT_r10 1192 +#define _HANDLE_PENDING_AND_DEOPT_r20 1193 +#define _HANDLE_PENDING_AND_DEOPT_r30 1194 +#define _IMPORT_FROM_r12 1195 +#define _IMPORT_NAME_r21 1196 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS_r00 1197 +#define _INIT_CALL_PY_EXACT_ARGS_r01 1198 +#define _INIT_CALL_PY_EXACT_ARGS_0_r01 1199 +#define _INIT_CALL_PY_EXACT_ARGS_1_r01 1200 +#define _INIT_CALL_PY_EXACT_ARGS_2_r01 1201 +#define _INIT_CALL_PY_EXACT_ARGS_3_r01 1202 +#define _INIT_CALL_PY_EXACT_ARGS_4_r01 1203 +#define _INSERT_NULL_r10 1204 +#define _INSTRUMENTED_FOR_ITER_r23 1205 +#define _INSTRUMENTED_INSTRUCTION_r00 1206 +#define _INSTRUMENTED_JUMP_FORWARD_r00 1207 +#define _INSTRUMENTED_JUMP_FORWARD_r11 1208 +#define _INSTRUMENTED_JUMP_FORWARD_r22 1209 +#define _INSTRUMENTED_JUMP_FORWARD_r33 1210 +#define _INSTRUMENTED_LINE_r00 1211 +#define _INSTRUMENTED_NOT_TAKEN_r00 1212 +#define _INSTRUMENTED_NOT_TAKEN_r11 1213 +#define _INSTRUMENTED_NOT_TAKEN_r22 1214 +#define _INSTRUMENTED_NOT_TAKEN_r33 1215 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r00 1216 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r10 1217 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r21 1218 +#define _INSTRUMENTED_POP_JUMP_IF_FALSE_r32 1219 +#define _INSTRUMENTED_POP_JUMP_IF_NONE_r10 1220 +#define _INSTRUMENTED_POP_JUMP_IF_NOT_NONE_r10 1221 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r00 1222 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r10 1223 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r21 1224 +#define _INSTRUMENTED_POP_JUMP_IF_TRUE_r32 1225 +#define _IS_NONE_r11 1226 +#define _IS_OP_r03 1227 +#define _IS_OP_r13 1228 +#define _IS_OP_r23 1229 +#define _ITER_CHECK_LIST_r02 1230 +#define _ITER_CHECK_LIST_r12 1231 +#define _ITER_CHECK_LIST_r22 1232 +#define _ITER_CHECK_LIST_r33 1233 +#define _ITER_CHECK_RANGE_r02 1234 +#define _ITER_CHECK_RANGE_r12 1235 +#define _ITER_CHECK_RANGE_r22 1236 +#define _ITER_CHECK_RANGE_r33 1237 +#define _ITER_CHECK_TUPLE_r02 1238 +#define _ITER_CHECK_TUPLE_r12 1239 +#define _ITER_CHECK_TUPLE_r22 1240 +#define _ITER_CHECK_TUPLE_r33 1241 +#define _ITER_JUMP_LIST_r02 1242 +#define _ITER_JUMP_LIST_r12 1243 +#define _ITER_JUMP_LIST_r22 1244 +#define _ITER_JUMP_LIST_r33 1245 +#define _ITER_JUMP_RANGE_r02 1246 +#define _ITER_JUMP_RANGE_r12 1247 +#define _ITER_JUMP_RANGE_r22 1248 +#define _ITER_JUMP_RANGE_r33 1249 +#define _ITER_JUMP_TUPLE_r02 1250 +#define _ITER_JUMP_TUPLE_r12 1251 +#define _ITER_JUMP_TUPLE_r22 1252 +#define _ITER_JUMP_TUPLE_r33 1253 +#define _ITER_NEXT_LIST_r23 1254 +#define _ITER_NEXT_LIST_TIER_TWO_r23 1255 +#define _ITER_NEXT_RANGE_r03 1256 +#define _ITER_NEXT_RANGE_r13 1257 +#define _ITER_NEXT_RANGE_r23 1258 +#define _ITER_NEXT_TUPLE_r03 1259 +#define _ITER_NEXT_TUPLE_r13 1260 +#define _ITER_NEXT_TUPLE_r23 1261 +#define _JUMP_BACKWARD_NO_INTERRUPT_r00 1262 +#define _JUMP_BACKWARD_NO_INTERRUPT_r11 1263 +#define _JUMP_BACKWARD_NO_INTERRUPT_r22 1264 +#define _JUMP_BACKWARD_NO_INTERRUPT_r33 1265 +#define _JUMP_TO_TOP_r00 1266 +#define _LIST_APPEND_r10 1267 +#define _LIST_EXTEND_r11 1268 +#define _LOAD_ATTR_r10 1269 +#define _LOAD_ATTR_CLASS_r11 1270 +#define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME_r11 1271 +#define _LOAD_ATTR_INSTANCE_VALUE_r02 1272 +#define _LOAD_ATTR_INSTANCE_VALUE_r12 1273 +#define _LOAD_ATTR_INSTANCE_VALUE_r23 1274 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r02 1275 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r12 1276 +#define _LOAD_ATTR_METHOD_LAZY_DICT_r23 1277 +#define _LOAD_ATTR_METHOD_NO_DICT_r02 1278 +#define _LOAD_ATTR_METHOD_NO_DICT_r12 1279 +#define _LOAD_ATTR_METHOD_NO_DICT_r23 1280 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r02 1281 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r12 1282 +#define _LOAD_ATTR_METHOD_WITH_VALUES_r23 1283 +#define _LOAD_ATTR_MODULE_r12 1284 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT_r11 1285 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES_r11 1286 +#define _LOAD_ATTR_PROPERTY_FRAME_r01 1287 +#define _LOAD_ATTR_PROPERTY_FRAME_r11 1288 +#define _LOAD_ATTR_PROPERTY_FRAME_r22 1289 +#define _LOAD_ATTR_PROPERTY_FRAME_r33 1290 +#define _LOAD_ATTR_SLOT_r02 1291 +#define _LOAD_ATTR_SLOT_r12 1292 +#define _LOAD_ATTR_SLOT_r23 1293 +#define _LOAD_ATTR_WITH_HINT_r12 1294 +#define _LOAD_BUILD_CLASS_r01 1295 +#define _LOAD_BYTECODE_r00 1296 +#define _LOAD_COMMON_CONSTANT_r01 1297 +#define _LOAD_COMMON_CONSTANT_r12 1298 +#define _LOAD_COMMON_CONSTANT_r23 1299 +#define _LOAD_CONST_r01 1300 +#define _LOAD_CONST_r12 1301 +#define _LOAD_CONST_r23 1302 +#define _LOAD_CONST_INLINE_r01 1303 +#define _LOAD_CONST_INLINE_r12 1304 +#define _LOAD_CONST_INLINE_r23 1305 +#define _LOAD_CONST_INLINE_BORROW_r01 1306 +#define _LOAD_CONST_INLINE_BORROW_r12 1307 +#define _LOAD_CONST_INLINE_BORROW_r23 1308 +#define _LOAD_DEREF_r01 1309 +#define _LOAD_FAST_r01 1310 +#define _LOAD_FAST_r12 1311 +#define _LOAD_FAST_r23 1312 +#define _LOAD_FAST_0_r01 1313 +#define _LOAD_FAST_0_r12 1314 +#define _LOAD_FAST_0_r23 1315 +#define _LOAD_FAST_1_r01 1316 +#define _LOAD_FAST_1_r12 1317 +#define _LOAD_FAST_1_r23 1318 +#define _LOAD_FAST_2_r01 1319 +#define _LOAD_FAST_2_r12 1320 +#define _LOAD_FAST_2_r23 1321 +#define _LOAD_FAST_3_r01 1322 +#define _LOAD_FAST_3_r12 1323 +#define _LOAD_FAST_3_r23 1324 +#define _LOAD_FAST_4_r01 1325 +#define _LOAD_FAST_4_r12 1326 +#define _LOAD_FAST_4_r23 1327 +#define _LOAD_FAST_5_r01 1328 +#define _LOAD_FAST_5_r12 1329 +#define _LOAD_FAST_5_r23 1330 +#define _LOAD_FAST_6_r01 1331 +#define _LOAD_FAST_6_r12 1332 +#define _LOAD_FAST_6_r23 1333 +#define _LOAD_FAST_7_r01 1334 +#define _LOAD_FAST_7_r12 1335 +#define _LOAD_FAST_7_r23 1336 +#define _LOAD_FAST_AND_CLEAR_r01 1337 +#define _LOAD_FAST_AND_CLEAR_r12 1338 +#define _LOAD_FAST_AND_CLEAR_r23 1339 +#define _LOAD_FAST_BORROW_r01 1340 +#define _LOAD_FAST_BORROW_r12 1341 +#define _LOAD_FAST_BORROW_r23 1342 +#define _LOAD_FAST_BORROW_0_r01 1343 +#define _LOAD_FAST_BORROW_0_r12 1344 +#define _LOAD_FAST_BORROW_0_r23 1345 +#define _LOAD_FAST_BORROW_1_r01 1346 +#define _LOAD_FAST_BORROW_1_r12 1347 +#define _LOAD_FAST_BORROW_1_r23 1348 +#define _LOAD_FAST_BORROW_2_r01 1349 +#define _LOAD_FAST_BORROW_2_r12 1350 +#define _LOAD_FAST_BORROW_2_r23 1351 +#define _LOAD_FAST_BORROW_3_r01 1352 +#define _LOAD_FAST_BORROW_3_r12 1353 +#define _LOAD_FAST_BORROW_3_r23 1354 +#define _LOAD_FAST_BORROW_4_r01 1355 +#define _LOAD_FAST_BORROW_4_r12 1356 +#define _LOAD_FAST_BORROW_4_r23 1357 +#define _LOAD_FAST_BORROW_5_r01 1358 +#define _LOAD_FAST_BORROW_5_r12 1359 +#define _LOAD_FAST_BORROW_5_r23 1360 +#define _LOAD_FAST_BORROW_6_r01 1361 +#define _LOAD_FAST_BORROW_6_r12 1362 +#define _LOAD_FAST_BORROW_6_r23 1363 +#define _LOAD_FAST_BORROW_7_r01 1364 +#define _LOAD_FAST_BORROW_7_r12 1365 +#define _LOAD_FAST_BORROW_7_r23 1366 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r02 1367 +#define _LOAD_FAST_BORROW_LOAD_FAST_BORROW_r13 1368 +#define _LOAD_FAST_CHECK_r01 1369 +#define _LOAD_FAST_CHECK_r12 1370 +#define _LOAD_FAST_CHECK_r23 1371 +#define _LOAD_FAST_LOAD_FAST_r02 1372 +#define _LOAD_FAST_LOAD_FAST_r13 1373 +#define _LOAD_FROM_DICT_OR_DEREF_r11 1374 +#define _LOAD_FROM_DICT_OR_GLOBALS_r11 1375 +#define _LOAD_GLOBAL_r00 1376 +#define _LOAD_GLOBAL_BUILTINS_r01 1377 +#define _LOAD_GLOBAL_MODULE_r01 1378 +#define _LOAD_LOCALS_r01 1379 +#define _LOAD_LOCALS_r12 1380 +#define _LOAD_LOCALS_r23 1381 +#define _LOAD_NAME_r01 1382 +#define _LOAD_SMALL_INT_r01 1383 +#define _LOAD_SMALL_INT_r12 1384 +#define _LOAD_SMALL_INT_r23 1385 +#define _LOAD_SMALL_INT_0_r01 1386 +#define _LOAD_SMALL_INT_0_r12 1387 +#define _LOAD_SMALL_INT_0_r23 1388 +#define _LOAD_SMALL_INT_1_r01 1389 +#define _LOAD_SMALL_INT_1_r12 1390 +#define _LOAD_SMALL_INT_1_r23 1391 +#define _LOAD_SMALL_INT_2_r01 1392 +#define _LOAD_SMALL_INT_2_r12 1393 +#define _LOAD_SMALL_INT_2_r23 1394 +#define _LOAD_SMALL_INT_3_r01 1395 +#define _LOAD_SMALL_INT_3_r12 1396 +#define _LOAD_SMALL_INT_3_r23 1397 +#define _LOAD_SPECIAL_r00 1398 +#define _LOAD_SUPER_ATTR_ATTR_r31 1399 +#define _LOAD_SUPER_ATTR_METHOD_r32 1400 +#define _LOCK_OBJECT_r01 1401 +#define _LOCK_OBJECT_r11 1402 +#define _LOCK_OBJECT_r22 1403 +#define _LOCK_OBJECT_r33 1404 +#define _MAKE_CALLARGS_A_TUPLE_r33 1405 +#define _MAKE_CELL_r00 1406 +#define _MAKE_FUNCTION_r12 1407 +#define _MAKE_HEAP_SAFE_r01 1408 +#define _MAKE_HEAP_SAFE_r11 1409 +#define _MAKE_HEAP_SAFE_r22 1410 +#define _MAKE_HEAP_SAFE_r33 1411 +#define _MAKE_WARM_r00 1412 +#define _MAKE_WARM_r11 1413 +#define _MAKE_WARM_r22 1414 +#define _MAKE_WARM_r33 1415 +#define _MAP_ADD_r20 1416 +#define _MATCH_CLASS_r33 1417 +#define _MATCH_KEYS_r23 1418 +#define _MATCH_MAPPING_r02 1419 +#define _MATCH_MAPPING_r12 1420 +#define _MATCH_MAPPING_r23 1421 +#define _MATCH_SEQUENCE_r02 1422 +#define _MATCH_SEQUENCE_r12 1423 +#define _MATCH_SEQUENCE_r23 1424 +#define _MAYBE_EXPAND_METHOD_r00 1425 +#define _MAYBE_EXPAND_METHOD_KW_r11 1426 +#define _MONITOR_CALL_r00 1427 +#define _MONITOR_CALL_KW_r11 1428 +#define _MONITOR_JUMP_BACKWARD_r00 1429 +#define _MONITOR_JUMP_BACKWARD_r11 1430 +#define _MONITOR_JUMP_BACKWARD_r22 1431 +#define _MONITOR_JUMP_BACKWARD_r33 1432 +#define _MONITOR_RESUME_r00 1433 +#define _NOP_r00 1434 +#define _NOP_r11 1435 +#define _NOP_r22 1436 +#define _NOP_r33 1437 +#define _POP_EXCEPT_r10 1438 +#define _POP_ITER_r20 1439 +#define _POP_JUMP_IF_FALSE_r00 1440 +#define _POP_JUMP_IF_FALSE_r10 1441 +#define _POP_JUMP_IF_FALSE_r21 1442 +#define _POP_JUMP_IF_FALSE_r32 1443 +#define _POP_JUMP_IF_TRUE_r00 1444 +#define _POP_JUMP_IF_TRUE_r10 1445 +#define _POP_JUMP_IF_TRUE_r21 1446 +#define _POP_JUMP_IF_TRUE_r32 1447 +#define _POP_TOP_r10 1448 +#define _POP_TOP_FLOAT_r00 1449 +#define _POP_TOP_FLOAT_r10 1450 +#define _POP_TOP_FLOAT_r21 1451 +#define _POP_TOP_FLOAT_r32 1452 +#define _POP_TOP_INT_r00 1453 +#define _POP_TOP_INT_r10 1454 +#define _POP_TOP_INT_r21 1455 +#define _POP_TOP_INT_r32 1456 +#define _POP_TOP_NOP_r00 1457 +#define _POP_TOP_NOP_r10 1458 +#define _POP_TOP_NOP_r21 1459 +#define _POP_TOP_NOP_r32 1460 +#define _POP_TOP_OPARG_r00 1461 +#define _POP_TOP_UNICODE_r00 1462 +#define _POP_TOP_UNICODE_r10 1463 +#define _POP_TOP_UNICODE_r21 1464 +#define _POP_TOP_UNICODE_r32 1465 +#define _PUSH_EXC_INFO_r02 1466 +#define _PUSH_EXC_INFO_r12 1467 +#define _PUSH_EXC_INFO_r23 1468 +#define _PUSH_FRAME_r10 1469 +#define _PUSH_NULL_r01 1470 +#define _PUSH_NULL_r12 1471 +#define _PUSH_NULL_r23 1472 +#define _PUSH_NULL_CONDITIONAL_r00 1473 +#define _PUSH_TAGGED_ZERO_r01 1474 +#define _PUSH_TAGGED_ZERO_r12 1475 +#define _PUSH_TAGGED_ZERO_r23 1476 +#define _PY_FRAME_EX_r31 1477 +#define _PY_FRAME_GENERAL_r01 1478 +#define _PY_FRAME_KW_r11 1479 +#define _REPLACE_WITH_TRUE_r02 1480 +#define _REPLACE_WITH_TRUE_r12 1481 +#define _REPLACE_WITH_TRUE_r23 1482 +#define _RESUME_CHECK_r00 1483 +#define _RESUME_CHECK_r11 1484 +#define _RESUME_CHECK_r22 1485 +#define _RESUME_CHECK_r33 1486 +#define _RETURN_GENERATOR_r01 1487 +#define _RETURN_VALUE_r11 1488 +#define _SAVE_RETURN_OFFSET_r00 1489 +#define _SAVE_RETURN_OFFSET_r11 1490 +#define _SAVE_RETURN_OFFSET_r22 1491 +#define _SAVE_RETURN_OFFSET_r33 1492 +#define _SEND_r33 1493 +#define _SEND_GEN_FRAME_r33 1494 +#define _SETUP_ANNOTATIONS_r00 1495 +#define _SET_ADD_r10 1496 +#define _SET_FUNCTION_ATTRIBUTE_r01 1497 +#define _SET_FUNCTION_ATTRIBUTE_r11 1498 +#define _SET_FUNCTION_ATTRIBUTE_r21 1499 +#define _SET_FUNCTION_ATTRIBUTE_r32 1500 +#define _SET_IP_r00 1501 +#define _SET_IP_r11 1502 +#define _SET_IP_r22 1503 +#define _SET_IP_r33 1504 +#define _SET_UPDATE_r11 1505 +#define _SPILL_OR_RELOAD_r01 1506 +#define _SPILL_OR_RELOAD_r02 1507 +#define _SPILL_OR_RELOAD_r03 1508 +#define _SPILL_OR_RELOAD_r10 1509 +#define _SPILL_OR_RELOAD_r12 1510 +#define _SPILL_OR_RELOAD_r13 1511 +#define _SPILL_OR_RELOAD_r20 1512 +#define _SPILL_OR_RELOAD_r21 1513 +#define _SPILL_OR_RELOAD_r23 1514 +#define _SPILL_OR_RELOAD_r30 1515 +#define _SPILL_OR_RELOAD_r31 1516 +#define _SPILL_OR_RELOAD_r32 1517 +#define _START_EXECUTOR_r00 1518 +#define _STORE_ATTR_r20 1519 +#define _STORE_ATTR_INSTANCE_VALUE_r21 1520 +#define _STORE_ATTR_SLOT_r21 1521 +#define _STORE_ATTR_WITH_HINT_r21 1522 +#define _STORE_DEREF_r10 1523 +#define _STORE_FAST_LOAD_FAST_r11 1524 +#define _STORE_FAST_STORE_FAST_r20 1525 +#define _STORE_GLOBAL_r10 1526 +#define _STORE_NAME_r10 1527 +#define _STORE_SLICE_r30 1528 +#define _STORE_SUBSCR_r30 1529 +#define _STORE_SUBSCR_DICT_r31 1530 +#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1531 +#define _STORE_SUBSCR_LIST_INT_r32 1532 +#define _SWAP_r11 1533 +#define _SWAP_2_r02 1534 +#define _SWAP_2_r12 1535 +#define _SWAP_2_r22 1536 +#define _SWAP_2_r33 1537 +#define _SWAP_3_r03 1538 +#define _SWAP_3_r13 1539 +#define _SWAP_3_r23 1540 +#define _SWAP_3_r33 1541 +#define _SWAP_FAST_r01 1542 +#define _SWAP_FAST_r11 1543 +#define _SWAP_FAST_r22 1544 +#define _SWAP_FAST_r33 1545 +#define _SWAP_FAST_0_r01 1546 +#define _SWAP_FAST_0_r11 1547 +#define _SWAP_FAST_0_r22 1548 +#define _SWAP_FAST_0_r33 1549 +#define _SWAP_FAST_1_r01 1550 +#define _SWAP_FAST_1_r11 1551 +#define _SWAP_FAST_1_r22 1552 +#define _SWAP_FAST_1_r33 1553 +#define _SWAP_FAST_2_r01 1554 +#define _SWAP_FAST_2_r11 1555 +#define _SWAP_FAST_2_r22 1556 +#define _SWAP_FAST_2_r33 1557 +#define _SWAP_FAST_3_r01 1558 +#define _SWAP_FAST_3_r11 1559 +#define _SWAP_FAST_3_r22 1560 +#define _SWAP_FAST_3_r33 1561 +#define _SWAP_FAST_4_r01 1562 +#define _SWAP_FAST_4_r11 1563 +#define _SWAP_FAST_4_r22 1564 +#define _SWAP_FAST_4_r33 1565 +#define _SWAP_FAST_5_r01 1566 +#define _SWAP_FAST_5_r11 1567 +#define _SWAP_FAST_5_r22 1568 +#define _SWAP_FAST_5_r33 1569 +#define _SWAP_FAST_6_r01 1570 +#define _SWAP_FAST_6_r11 1571 +#define _SWAP_FAST_6_r22 1572 +#define _SWAP_FAST_6_r33 1573 +#define _SWAP_FAST_7_r01 1574 +#define _SWAP_FAST_7_r11 1575 +#define _SWAP_FAST_7_r22 1576 +#define _SWAP_FAST_7_r33 1577 +#define _TIER2_RESUME_CHECK_r00 1578 +#define _TIER2_RESUME_CHECK_r11 1579 +#define _TIER2_RESUME_CHECK_r22 1580 +#define _TIER2_RESUME_CHECK_r33 1581 +#define _TO_BOOL_r11 1582 +#define _TO_BOOL_BOOL_r01 1583 +#define _TO_BOOL_BOOL_r11 1584 +#define _TO_BOOL_BOOL_r22 1585 +#define _TO_BOOL_BOOL_r33 1586 +#define _TO_BOOL_INT_r02 1587 +#define _TO_BOOL_INT_r12 1588 +#define _TO_BOOL_INT_r23 1589 +#define _TO_BOOL_LIST_r02 1590 +#define _TO_BOOL_LIST_r12 1591 +#define _TO_BOOL_LIST_r23 1592 +#define _TO_BOOL_NONE_r01 1593 +#define _TO_BOOL_NONE_r11 1594 +#define _TO_BOOL_NONE_r22 1595 +#define _TO_BOOL_NONE_r33 1596 +#define _TO_BOOL_STR_r02 1597 +#define _TO_BOOL_STR_r12 1598 +#define _TO_BOOL_STR_r23 1599 +#define _TRACE_RECORD_r00 1600 +#define _UNARY_INVERT_r12 1601 +#define _UNARY_NEGATIVE_r12 1602 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1603 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1604 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1605 +#define _UNARY_NOT_r01 1606 +#define _UNARY_NOT_r11 1607 +#define _UNARY_NOT_r22 1608 +#define _UNARY_NOT_r33 1609 +#define _UNPACK_EX_r10 1610 +#define _UNPACK_SEQUENCE_r10 1611 +#define _UNPACK_SEQUENCE_LIST_r10 1612 +#define _UNPACK_SEQUENCE_TUPLE_r10 1613 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1614 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1615 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1616 +#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1617 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1618 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1619 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1620 +#define _WITH_EXCEPT_START_r33 1621 +#define _YIELD_VALUE_r11 1622 +#define MAX_UOP_REGS_ID 1622 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 8465fd4345e9a5..17fd5d04838309 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -397,7 +397,6 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = 0, [_START_EXECUTOR] = HAS_DEOPT_FLAG, [_MAKE_WARM] = 0, [_FATAL_ERROR] = 0, @@ -3700,15 +3699,6 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, }, }, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = { - .best = { 0, 1, 2, 3 }, - .entries = { - { 3, 0, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03 }, - { 3, 1, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13 }, - { 3, 2, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23 }, - { 3, 3, _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33 }, - }, - }, [_START_EXECUTOR] = { .best = { 0, 0, 0, 0 }, .entries = { @@ -4705,10 +4695,6 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_LOAD_CONST_INLINE_BORROW_r01] = _LOAD_CONST_INLINE_BORROW, [_LOAD_CONST_INLINE_BORROW_r12] = _LOAD_CONST_INLINE_BORROW, [_LOAD_CONST_INLINE_BORROW_r23] = _LOAD_CONST_INLINE_BORROW, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = _SHUFFLE_3_LOAD_CONST_INLINE_BORROW, [_START_EXECUTOR_r00] = _START_EXECUTOR, [_MAKE_WARM_r00] = _MAKE_WARM, [_MAKE_WARM_r11] = _MAKE_WARM, @@ -5933,11 +5919,6 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_SET_IP_r33] = "_SET_IP_r33", [_SET_UPDATE] = "_SET_UPDATE", [_SET_UPDATE_r11] = "_SET_UPDATE_r11", - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW", - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03", - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13", - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23", - [_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33] = "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33", [_SPILL_OR_RELOAD] = "_SPILL_OR_RELOAD", [_SPILL_OR_RELOAD_r01] = "_SPILL_OR_RELOAD_r01", [_SPILL_OR_RELOAD_r02] = "_SPILL_OR_RELOAD_r02", @@ -6827,8 +6808,6 @@ int _PyUop_num_popped(int opcode, int oparg) return 0; case _LOAD_CONST_INLINE_BORROW: return 0; - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW: - return 3; case _START_EXECUTOR: return 0; case _MAKE_WARM: diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index b37c35495983c3..79643587a60002 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2963,7 +2963,7 @@ def testfunc(n): self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertNotIn("_CALL_LEN", uops) - self.assertEqual(count_ops(ex, "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW"), 4) + self.assertGreaterEqual(count_ops(ex, "_LOAD_CONST_INLINE_BORROW"), 8) def test_call_len_known_length_small_int(self): # Make sure that len(t) is optimized for a tuple of length 5. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 59db0eb399b121..a73df99f3f5578 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -6107,13 +6107,6 @@ dummy_func( value = PyStackRef_FromPyObjectBorrow(ptr); } - tier2 op(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, arg -- res, a, c)) { - res = PyStackRef_FromPyObjectBorrow(ptr); - a = arg; - c = callable; - INPUTS_DEAD(); - } - tier2 op(_START_EXECUTOR, (executor/4 --)) { #ifndef _Py_JIT assert(current_executor == (_PyExecutorObject*)executor); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 93d39bee1b9ff6..5a7129c1ffdea5 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -22337,106 +22337,6 @@ break; } - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r03: { - CHECK_CURRENT_CACHED_VALUES(0); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - _PyStackRef arg; - _PyStackRef callable; - _PyStackRef res; - _PyStackRef a; - _PyStackRef c; - arg = stack_pointer[-1]; - callable = stack_pointer[-3]; - PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); - res = PyStackRef_FromPyObjectBorrow(ptr); - a = arg; - c = callable; - _tos_cache2 = c; - _tos_cache1 = a; - _tos_cache0 = res; - SET_CURRENT_CACHED_VALUES(3); - stack_pointer += -3; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - break; - } - - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r13: { - CHECK_CURRENT_CACHED_VALUES(1); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - _PyStackRef arg; - _PyStackRef callable; - _PyStackRef res; - _PyStackRef a; - _PyStackRef c; - _PyStackRef _stack_item_0 = _tos_cache0; - arg = _stack_item_0; - callable = stack_pointer[-2]; - PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); - res = PyStackRef_FromPyObjectBorrow(ptr); - a = arg; - c = callable; - _tos_cache2 = c; - _tos_cache1 = a; - _tos_cache0 = res; - SET_CURRENT_CACHED_VALUES(3); - stack_pointer += -2; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - break; - } - - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r23: { - CHECK_CURRENT_CACHED_VALUES(2); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - _PyStackRef arg; - _PyStackRef callable; - _PyStackRef res; - _PyStackRef a; - _PyStackRef c; - _PyStackRef _stack_item_0 = _tos_cache0; - _PyStackRef _stack_item_1 = _tos_cache1; - arg = _stack_item_1; - callable = stack_pointer[-1]; - PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); - res = PyStackRef_FromPyObjectBorrow(ptr); - a = arg; - c = callable; - _tos_cache2 = c; - _tos_cache1 = a; - _tos_cache0 = res; - SET_CURRENT_CACHED_VALUES(3); - stack_pointer += -1; - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - break; - } - - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW_r33: { - CHECK_CURRENT_CACHED_VALUES(3); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - _PyStackRef arg; - _PyStackRef callable; - _PyStackRef res; - _PyStackRef a; - _PyStackRef c; - _PyStackRef _stack_item_0 = _tos_cache0; - _PyStackRef _stack_item_1 = _tos_cache1; - _PyStackRef _stack_item_2 = _tos_cache2; - arg = _stack_item_2; - callable = _stack_item_0; - PyObject *ptr = (PyObject *)CURRENT_OPERAND0_64(); - res = PyStackRef_FromPyObjectBorrow(ptr); - a = arg; - c = callable; - _tos_cache2 = c; - _tos_cache1 = a; - _tos_cache0 = res; - SET_CURRENT_CACHED_VALUES(3); - assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); - break; - } - case _START_EXECUTOR_r00: { CHECK_CURRENT_CACHED_VALUES(0); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index daebef4a04320b..ae9e19341441ea 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -2330,7 +2330,10 @@ dummy_func(void) { goto error; } if (_Py_IsImmortal(temp)) { - ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); + ADD_OP(_SWAP, 2, 0); + optimize_pop_top(ctx, this_instr, null); + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); + ADD_OP(_SWAP, 3, 0); } res = sym_new_const(ctx, temp); Py_DECREF(temp); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index c3c889e9de9a7e..d48f38a95f7b16 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -4514,11 +4514,13 @@ case _CALL_LEN: { JitOptRef arg; + JitOptRef null; JitOptRef callable; JitOptRef res; JitOptRef a; JitOptRef c; arg = stack_pointer[-1]; + null = stack_pointer[-2]; callable = stack_pointer[-3]; res = sym_new_type(ctx, &PyLong_Type); Py_ssize_t length = sym_tuple_length(arg); @@ -4544,7 +4546,10 @@ goto error; } if (_Py_IsImmortal(temp)) { - ADD_OP(_SHUFFLE_3_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); + ADD_OP(_SWAP, 2, 0); + optimize_pop_top(ctx, this_instr, null); + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); + ADD_OP(_SWAP, 3, 0); } res = sym_new_const(ctx, temp); CHECK_STACK_BOUNDS(-2); @@ -5497,19 +5502,6 @@ break; } - case _SHUFFLE_3_LOAD_CONST_INLINE_BORROW: { - JitOptRef res; - JitOptRef a; - JitOptRef c; - res = sym_new_not_null(ctx); - a = sym_new_not_null(ctx); - c = sym_new_not_null(ctx); - stack_pointer[-3] = res; - stack_pointer[-2] = a; - stack_pointer[-1] = c; - break; - } - case _START_EXECUTOR: { break; } From 993e204ad44fd95ac522a7903f83a4bbe5d3af9c Mon Sep 17 00:00:00 2001 From: ivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:42:21 +0100 Subject: [PATCH 37/44] gh-142927: Fix inverted flamegraph width (#148568) Fix inverted flamegraph width The inverted view used thread presence as a proxy for self time. This missed self samples on C-level wrapper frames like _run_code, where the node's thread always appears in its children too. Those samples were silently dropped, causing the chart to render narrower than full width. Now uses the explicit self field on each node instead of the thread heuristic. --- .../sampling/_flamegraph_assets/flamegraph.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index d7a8890d4a1ad9..3f884c4c690fd1 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -1063,11 +1063,7 @@ function populateStats(data) { funcname = funcname || 'unknown'; if (filename !== 'unknown' && funcname !== 'unknown' && node.value > 0) { - let childrenValue = 0; - if (node.children) { - childrenValue = node.children.reduce((sum, child) => sum + child.value, 0); - } - const directSamples = Math.max(0, node.value - childrenValue); + const directSamples = node.self || 0; const funcKey = `${filename}:${node.lineno || '?'}:${funcname}`; @@ -1345,14 +1341,13 @@ function processLeaf(invertedRoot, path, leafNode, isDifferential) { } function traverseInvert(path, currentNode, invertedRoot, isDifferential) { - const children = currentNode.children || []; - const childThreads = new Set(children.flatMap(c => c.threads || [])); - const selfThreads = (currentNode.threads || []).filter(t => !childThreads.has(t)); + const selfValue = currentNode.self || 0; - if (selfThreads.length > 0) { - processLeaf(invertedRoot, path, { ...currentNode, threads: selfThreads }, isDifferential); + if (selfValue > 0) { + processLeaf(invertedRoot, path, { ...currentNode, value: selfValue }, isDifferential); } + const children = currentNode.children || []; children.forEach(child => traverseInvert(path.concat([child]), child, invertedRoot, isDifferential)); } From 8a8d737be200aef4d3f4ffd576505d2ff660a3a8 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Tue, 28 Apr 2026 09:45:55 -0700 Subject: [PATCH 38/44] gh-108951: document addition of TaskGroup.cancel() (#149031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alex Grönholm Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/asyncio-task.rst | 4 ++-- Doc/whatsnew/3.15.rst | 11 +++++++++++ .../2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index f0fe91b363d95e..2e17d0dc70c7a5 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -394,8 +394,8 @@ Example:: The ``async with`` statement will wait for all tasks in the group to finish. While waiting, new tasks may still be added to the group (for example, by passing ``tg`` into one of the coroutines -and calling ``tg.create_task()`` in that coroutine). There is also opportunity -to short-circuit the entire task group with ``tg.cancel()``, based on some condition. +and calling ``tg.create_task()`` in that coroutine). There is also opportunity to +request termination of the entire task group with ``tg.cancel()``, based on some condition. Once the last task has finished and the ``async with`` block is exited, no new tasks may be added to the group. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 6f0f7021de8e7a..eb08f8c4ed69e7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -741,6 +741,17 @@ ast (Contributed by Stan Ulbrych in :gh:`148981`.) +asyncio +------- + +* Added :meth:`TaskGroup.cancel ` to allow early + termination of a task group, for instance, when the goal of the tasks has + been achieved or their services are no longer needed. + Previously this would involve unintuitive boilerplate such as an extra task + raising a custom exception which is then suppressed as it exits the task group. + (Contributed by John Belmonte in :gh:`127214`.) + + base64 ------ diff --git a/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst b/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst index 1696a2dd1728ed..0e0280c9b6b0e7 100644 --- a/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst +++ b/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst @@ -1 +1,2 @@ -Add :meth:`~asyncio.TaskGroup.cancel` which cancels unfinished tasks and exits the group without error. +:mod:`asyncio`: Add :meth:`TaskGroup.cancel ` which cancels +unfinished tasks and exits the group without raising :exc:`asyncio.CancelledError`. From 933e2dd6cfec50ca45fd75f47ce5190c58ce28be Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:30:16 +0300 Subject: [PATCH 39/44] Replace deprecated action with RtD app (#149111) --- .github/workflows/documentation-links.yml | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/documentation-links.yml diff --git a/.github/workflows/documentation-links.yml b/.github/workflows/documentation-links.yml deleted file mode 100644 index 19314dd0c889b0..00000000000000 --- a/.github/workflows/documentation-links.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Read the Docs PR preview -# Automatically edits a pull request's descriptions with a link -# to the documentation's preview on Read the Docs. - -on: - pull_request_target: - types: - - opened - paths: - - 'Doc/**' - - '.github/workflows/doc.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - documentation-links: - runs-on: ubuntu-latest - permissions: - pull-requests: write - timeout-minutes: 5 - - steps: - - uses: readthedocs/actions/preview@b8bba1484329bda1a3abe986df7ebc80a8950333 # v1.5 - with: - project-slug: "cpython-previews" - single-version: "true" From 40dc61a0e0672810c3934da8cc2eda68027c24ce Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 28 Apr 2026 21:45:38 +0300 Subject: [PATCH 40/44] Build docs from `pylock.toml` (#149058) --- .github/workflows/reusable-docs.yml | 2 +- Doc/Makefile | 2 +- Doc/make.bat | 2 +- Doc/pylock.toml | 256 ++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 Doc/pylock.toml diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 0453b6ab555048..7b524569f85c9e 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -56,7 +56,7 @@ jobs: with: python-version: '3' cache: 'pip' - cache-dependency-path: 'Doc/requirements.txt' + cache-dependency-path: 'Doc/pylock.toml' - name: 'Install build dependencies' run: make -C Doc/ venv diff --git a/Doc/Makefile b/Doc/Makefile index 7bdabd8bf168fe..60970d50833f14 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -13,7 +13,7 @@ JOBS = auto PAPER = SOURCES = DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py) -REQUIREMENTS = requirements.txt +REQUIREMENTS = pylock.toml SPHINXERRORHANDLING = --fail-on-warning # Internal variables. diff --git a/Doc/make.bat b/Doc/make.bat index 99f0d5c44f0098..64a42257c92571 100644 --- a/Doc/make.bat +++ b/Doc/make.bat @@ -13,7 +13,7 @@ if not defined SPHINXBUILD ( %PYTHON% -c "import sphinx" > nul 2> nul if errorlevel 1 ( echo Installing sphinx with %PYTHON% - %PYTHON% -m pip install -r requirements.txt + %PYTHON% -m pip install -r pylock.toml if errorlevel 1 exit /B ) set SPHINXBUILD=%PYTHON% -c "import sphinx.cmd.build, sys; sys.exit(sphinx.cmd.build.main())" diff --git a/Doc/pylock.toml b/Doc/pylock.toml new file mode 100644 index 00000000000000..f1febe21c239c3 --- /dev/null +++ b/Doc/pylock.toml @@ -0,0 +1,256 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile Doc/requirements.txt --exclude-newer P14D --exclude-newer-package linklint=PT0S --exclude-newer-package python-docs-theme=PT0S --no-cache --output-file Doc/pylock.toml --python-version 3.12 --universal +lock-version = "1.0" +created-by = "uv" + +[[packages]] +name = "alabaster" +version = "1.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", upload-time = 2024-07-26T18:15:03Z, size = 24210, hashes = { sha256 = "c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", upload-time = 2024-07-26T18:15:02Z, size = 13929, hashes = { sha256 = "fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b" } }] + +[[packages]] +name = "babel" +version = "2.18.0" +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", upload-time = 2026-02-01T12:30:56Z, size = 9959554, hashes = { sha256 = "b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", upload-time = 2026-02-01T12:30:53Z, size = 10196845, hashes = { sha256 = "e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35" } }] + +[[packages]] +name = "blurb" +version = "2.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/d7/82/8597d891f4b03f3eaefcb4213a811643d558350cac9a69864d127832cc4f/blurb-2.0.0.tar.gz", upload-time = 2025-01-15T12:48:53Z, size = 24666, hashes = { sha256 = "c78d8114294225a4f7a2eabba6e05d36a6a50e45ba9f5a41afabc198350038e0" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/b4/03/374bd9e31b58e8a8e5dc65cc3f68ca7cdd716c32b5e5dcb0e1b76bb75b4a/blurb-2.0.0-py3-none-any.whl", upload-time = 2025-01-15T12:48:49Z, size = 18924, hashes = { sha256 = "f6d0e858dbe94765f6a89b8228217ffdb9c19cff08fc8f2c3153954846d31aa1" } }] + +[[packages]] +name = "certifi" +version = "2026.2.25" +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", upload-time = 2026-02-25T02:54:17Z, size = 155029, hashes = { sha256 = "e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", upload-time = 2026-02-25T02:54:15Z, size = 153684, hashes = { sha256 = "027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa" } }] + +[[packages]] +name = "charset-normalizer" +version = "3.4.7" +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", upload-time = 2026-04-02T09:28:39Z, size = 144271, hashes = { sha256 = "ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", upload-time = 2026-04-02T09:26:24Z, size = 311328, hashes = { sha256 = "eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46" } }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2026-04-02T09:26:25Z, size = 208061, hashes = { sha256 = "6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2" } }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2026-04-02T09:26:26Z, size = 229031, hashes = { sha256 = "e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b" } }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2026-04-02T09:26:28Z, size = 225239, hashes = { sha256 = "edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a" } }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2026-04-02T09:26:29Z, size = 216589, hashes = { sha256 = "5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116" } }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", upload-time = 2026-04-02T09:26:30Z, size = 202733, hashes = { sha256 = "203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb" } }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2026-04-02T09:26:31Z, size = 212652, hashes = { sha256 = "298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1" } }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", upload-time = 2026-04-02T09:26:33Z, size = 211229, hashes = { sha256 = "708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15" } }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", upload-time = 2026-04-02T09:26:34Z, size = 203552, hashes = { sha256 = "0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5" } }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", upload-time = 2026-04-02T09:26:36Z, size = 230806, hashes = { sha256 = "4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d" } }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", upload-time = 2026-04-02T09:26:37Z, size = 212316, hashes = { sha256 = "aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7" } }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", upload-time = 2026-04-02T09:26:38Z, size = 227274, hashes = { sha256 = "fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464" } }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", upload-time = 2026-04-02T09:26:40Z, size = 218468, hashes = { sha256 = "bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49" } }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", upload-time = 2026-04-02T09:26:41Z, size = 148460, hashes = { sha256 = "2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c" } }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", upload-time = 2026-04-02T09:26:42Z, size = 159330, hashes = { sha256 = "5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6" } }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", upload-time = 2026-04-02T09:26:44Z, size = 147828, hashes = { sha256 = "56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d" } }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", upload-time = 2026-04-02T09:26:45Z, size = 309627, hashes = { sha256 = "f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063" } }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2026-04-02T09:26:46Z, size = 207008, hashes = { sha256 = "0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c" } }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2026-04-02T09:26:48Z, size = 228303, hashes = { sha256 = "a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66" } }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2026-04-02T09:26:49Z, size = 224282, hashes = { sha256 = "3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18" } }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2026-04-02T09:26:50Z, size = 215595, hashes = { sha256 = "e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd" } }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", upload-time = 2026-04-02T09:26:52Z, size = 201986, hashes = { sha256 = "f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215" } }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2026-04-02T09:26:53Z, size = 211711, hashes = { sha256 = "e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859" } }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", upload-time = 2026-04-02T09:26:54Z, size = 210036, hashes = { sha256 = "7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8" } }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", upload-time = 2026-04-02T09:26:56Z, size = 202998, hashes = { sha256 = "481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5" } }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", upload-time = 2026-04-02T09:26:57Z, size = 230056, hashes = { sha256 = "f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832" } }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", upload-time = 2026-04-02T09:26:58Z, size = 211537, hashes = { sha256 = "f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6" } }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", upload-time = 2026-04-02T09:27:00Z, size = 226176, hashes = { sha256 = "3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48" } }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", upload-time = 2026-04-02T09:27:02Z, size = 217723, hashes = { sha256 = "64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a" } }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", upload-time = 2026-04-02T09:27:03Z, size = 148085, hashes = { sha256 = "4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e" } }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", upload-time = 2026-04-02T09:27:04Z, size = 158819, hashes = { sha256 = "3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110" } }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", upload-time = 2026-04-02T09:27:05Z, size = 147915, hashes = { sha256 = "80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b" } }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", upload-time = 2026-04-02T09:27:07Z, size = 309234, hashes = { sha256 = "c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0" } }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2026-04-02T09:27:08Z, size = 208042, hashes = { sha256 = "1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a" } }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2026-04-02T09:27:09Z, size = 228706, hashes = { sha256 = "54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b" } }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2026-04-02T09:27:11Z, size = 224727, hashes = { sha256 = "715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41" } }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2026-04-02T09:27:12Z, size = 215882, hashes = { sha256 = "bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e" } }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", upload-time = 2026-04-02T09:27:13Z, size = 200860, hashes = { sha256 = "c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae" } }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2026-04-02T09:27:15Z, size = 211564, hashes = { sha256 = "3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18" } }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", upload-time = 2026-04-02T09:27:16Z, size = 211276, hashes = { sha256 = "e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b" } }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", upload-time = 2026-04-02T09:27:18Z, size = 201238, hashes = { sha256 = "a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356" } }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", upload-time = 2026-04-02T09:27:19Z, size = 230189, hashes = { sha256 = "2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab" } }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", upload-time = 2026-04-02T09:27:20Z, size = 211352, hashes = { sha256 = "e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46" } }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", upload-time = 2026-04-02T09:27:22Z, size = 227024, hashes = { sha256 = "d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44" } }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", upload-time = 2026-04-02T09:27:23Z, size = 217869, hashes = { sha256 = "7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72" } }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", upload-time = 2026-04-02T09:27:25Z, size = 148541, hashes = { sha256 = "5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10" } }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", upload-time = 2026-04-02T09:27:26Z, size = 159634, hashes = { sha256 = "92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f" } }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", upload-time = 2026-04-02T09:27:28Z, size = 148384, hashes = { sha256 = "67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246" } }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", upload-time = 2026-04-02T09:27:29Z, size = 330133, hashes = { sha256 = "effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24" } }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", upload-time = 2026-04-02T09:27:30Z, size = 216257, hashes = { sha256 = "fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79" } }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", upload-time = 2026-04-02T09:27:32Z, size = 234851, hashes = { sha256 = "733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960" } }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", upload-time = 2026-04-02T09:27:34Z, size = 233393, hashes = { sha256 = "a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4" } }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", upload-time = 2026-04-02T09:27:35Z, size = 223251, hashes = { sha256 = "6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e" } }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", upload-time = 2026-04-02T09:27:36Z, size = 206609, hashes = { sha256 = "a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1" } }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", upload-time = 2026-04-02T09:27:38Z, size = 220014, hashes = { sha256 = "3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44" } }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", upload-time = 2026-04-02T09:27:39Z, size = 218979, hashes = { sha256 = "8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e" } }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", upload-time = 2026-04-02T09:27:40Z, size = 209238, hashes = { sha256 = "cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3" } }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", upload-time = 2026-04-02T09:27:42Z, size = 236110, hashes = { sha256 = "0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0" } }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", upload-time = 2026-04-02T09:27:43Z, size = 219824, hashes = { sha256 = "752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e" } }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", upload-time = 2026-04-02T09:27:45Z, size = 233103, hashes = { sha256 = "8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb" } }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", upload-time = 2026-04-02T09:27:46Z, size = 225194, hashes = { sha256 = "ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe" } }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", upload-time = 2026-04-02T09:27:48Z, size = 159827, hashes = { sha256 = "c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0" } }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", upload-time = 2026-04-02T09:27:49Z, size = 174168, hashes = { sha256 = "03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c" } }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", upload-time = 2026-04-02T09:27:51Z, size = 153018, hashes = { sha256 = "c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d" } }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", upload-time = 2026-04-02T09:28:37Z, size = 61958, hashes = { sha256 = "3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d" } }, +] + +[[packages]] +name = "colorama" +version = "0.4.6" +marker = "sys_platform == 'win32'" +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", upload-time = 2022-10-25T02:36:22Z, size = 27697, hashes = { sha256 = "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", upload-time = 2022-10-25T02:36:20Z, size = 25335, hashes = { sha256 = "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" } }] + +[[packages]] +name = "docutils" +version = "0.21.2" +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", upload-time = 2024-04-23T18:57:18Z, size = 2204444, hashes = { sha256 = "3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", upload-time = 2024-04-23T18:57:14Z, size = 587408, hashes = { sha256 = "dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" } }] + +[[packages]] +name = "idna" +version = "3.11" +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", upload-time = 2025-10-12T14:55:20Z, size = 194582, hashes = { sha256 = "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", upload-time = 2025-10-12T14:55:18Z, size = 71008, hashes = { sha256 = "771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" } }] + +[[packages]] +name = "imagesize" +version = "1.5.0" +sdist = { url = "https://files.pythonhosted.org/packages/cf/59/4b0dd64676aa6fb4986a755790cb6fc558559cf0084effad516820208ec3/imagesize-1.5.0.tar.gz", upload-time = 2026-03-03T01:59:54Z, size = 1281127, hashes = { sha256 = "8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/1e/b1/a0662b03103c66cf77101a187f396ea91167cd9b7d5d3a2e465ad2c7ee9b/imagesize-1.5.0-py2.py3-none-any.whl", upload-time = 2026-03-03T01:59:52Z, size = 5763, hashes = { sha256 = "32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899" } }] + +[[packages]] +name = "jinja2" +version = "3.1.6" +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", upload-time = 2025-03-05T20:05:02Z, size = 245115, hashes = { sha256 = "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", upload-time = 2025-03-05T20:05:00Z, size = 134899, hashes = { sha256 = "85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" } }] + +[[packages]] +name = "linklint" +version = "0.4.1" +sdist = { url = "https://files.pythonhosted.org/packages/61/bc/9972ace8643a04a74210942717fd20c1c34d96079b59fd7790b4db56df7d/linklint-0.4.1.tar.gz", upload-time = 2026-03-27T10:48:40Z, size = 20588, hashes = { sha256 = "a5d291a0d8a7ab8b1f96f62bb7e1d9d2c79d8eceb934e2efc0235d6b2e77f19b" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/af/88/9c4865cdbd6f73fff668706072c421a329de79c3b69e0aa511679a2ff4f3/linklint-0.4.1-py3-none-any.whl", upload-time = 2026-03-27T10:48:38Z, size = 12186, hashes = { sha256 = "78ff4d23ff3d3c62837fa34f0dcb909593dea52a2a1f426307264f081a8b41b5" } }] + +[[packages]] +name = "markupsafe" +version = "2.1.5" +sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", upload-time = 2024-02-02T16:31:22Z, size = 19384, hashes = { sha256 = "d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b" } } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", upload-time = 2024-02-02T16:30:33Z, size = 18215, hashes = { sha256 = "8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1" } }, + { url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", upload-time = 2024-02-02T16:30:34Z, size = 14069, hashes = { sha256 = "3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4" } }, + { url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", upload-time = 2024-02-02T16:30:35Z, size = 29452, hashes = { sha256 = "ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee" } }, + { url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", upload-time = 2024-02-02T16:30:36Z, size = 28462, hashes = { sha256 = "f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5" } }, + { url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", upload-time = 2024-02-02T16:30:37Z, size = 27869, hashes = { sha256 = "ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b" } }, + { url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", upload-time = 2024-02-02T16:30:39Z, size = 33906, hashes = { sha256 = "d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a" } }, + { url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", upload-time = 2024-02-02T16:30:40Z, size = 32296, hashes = { sha256 = "bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f" } }, + { url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", upload-time = 2024-02-02T16:30:42Z, size = 33038, hashes = { sha256 = "58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169" } }, + { url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", upload-time = 2024-02-02T16:30:43Z, size = 16572, hashes = { sha256 = "8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad" } }, + { url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", upload-time = 2024-02-02T16:30:44Z, size = 17127, hashes = { sha256 = "823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb" } }, +] + +[[packages]] +name = "packaging" +version = "24.2" +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", upload-time = 2024-11-08T09:47:47Z, size = 163950, hashes = { sha256 = "c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", upload-time = 2024-11-08T09:47:44Z, size = 65451, hashes = { sha256 = "09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759" } }] + +[[packages]] +name = "pygments" +version = "2.20.0" +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", upload-time = 2026-03-29T13:29:33Z, size = 4955991, hashes = { sha256 = "6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", upload-time = 2026-03-29T13:29:30Z, size = 1231151, hashes = { sha256 = "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" } }] + +[[packages]] +name = "python-docs-theme" +version = "2026.4" +sdist = { url = "https://files.pythonhosted.org/packages/fd/59/dbb07775a15ddf9f7f8d5f6ef4cd4da5e8afd908cc27e6585bb132e6366a/python_docs_theme-2026.4.tar.gz", upload-time = 2026-04-19T18:35:13Z, size = 29782, hashes = { sha256 = "a815f80c5a09f734449eb2498fbcbad05340976a7a543e431f57de92218a9315" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/db/05/b9298eb9330c70a3d1465a6116ab01dad095538c2e574a2d704bb0002f4d/python_docs_theme-2026.4-py3-none-any.whl", upload-time = 2026-04-19T18:35:12Z, size = 73742, hashes = { sha256 = "f755d80ebe8d7aa4fad8ee964ff999635c72eebd24ab10928a0e9726363d65fc" } }] + +[[packages]] +name = "requests" +version = "2.33.1" +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", upload-time = 2026-03-30T16:09:15Z, size = 134120, hashes = { sha256 = "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", upload-time = 2026-03-30T16:09:13Z, size = 64947, hashes = { sha256 = "4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a" } }] + +[[packages]] +name = "roman-numerals" +version = "4.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", upload-time = 2025-12-17T18:25:34Z, size = 9077, hashes = { sha256 = "1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", upload-time = 2025-12-17T18:25:33Z, size = 7676, hashes = { sha256 = "647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7" } }] + +[[packages]] +name = "roman-numerals-py" +version = "4.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", upload-time = 2025-12-17T18:25:41Z, size = 4274, hashes = { sha256 = "f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", upload-time = 2025-12-17T18:25:40Z, size = 4547, hashes = { sha256 = "553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780" } }] + +[[packages]] +name = "snowballstemmer" +version = "2.2.0" +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", upload-time = 2021-11-16T18:38:38Z, size = 86699, hashes = { sha256 = "09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", upload-time = 2021-11-16T18:38:34Z, size = 93002, hashes = { sha256 = "c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" } }] + +[[packages]] +name = "sphinx" +version = "8.2.3" +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", upload-time = 2025-03-02T22:31:59Z, size = 8321876, hashes = { sha256 = "398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", upload-time = 2025-03-02T22:31:56Z, size = 3589741, hashes = { sha256 = "4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3" } }] + +[[packages]] +name = "sphinx-notfound-page" +version = "1.0.4" +sdist = { url = "https://files.pythonhosted.org/packages/73/7d/c545883c714319380325a52c9f80d093c97e718d812fd8090e42b1a08508/sphinx_notfound_page-1.0.4.tar.gz", upload-time = 2024-07-31T12:29:21Z, size = 519228, hashes = { sha256 = "2a52f49cd367b5c4e64072de1591cc367714098500abf4ecb9a3ecb4fec25aae" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/87/c4/877a5beffb8dcaf35e919c4c3cad56732c76370d106126394f4ca211ad7f/sphinx_notfound_page-1.0.4-py3-none-any.whl", upload-time = 2024-07-31T12:29:18Z, size = 8170, hashes = { sha256 = "f7c26ae0df3cf3d6f38f56b068762e6203d0ebb7e1c804de1059598d7dd8b9d8" } }] + +[[packages]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", upload-time = 2024-07-29T01:09:00Z, size = 20053, hashes = { sha256 = "2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", upload-time = 2024-07-29T01:08:58Z, size = 119300, hashes = { sha256 = "4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5" } }] + +[[packages]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", upload-time = 2024-07-29T01:09:23Z, size = 12967, hashes = { sha256 = "411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", upload-time = 2024-07-29T01:09:21Z, size = 82530, hashes = { sha256 = "aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2" } }] + +[[packages]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", upload-time = 2024-07-29T01:09:37Z, size = 22617, hashes = { sha256 = "c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", upload-time = 2024-07-29T01:09:36Z, size = 98705, hashes = { sha256 = "166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8" } }] + +[[packages]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", upload-time = 2019-01-21T16:10:16Z, size = 5787, hashes = { sha256 = "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", upload-time = 2019-01-21T16:10:14Z, size = 5071, hashes = { sha256 = "2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178" } }] + +[[packages]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", upload-time = 2024-07-29T01:09:56Z, size = 17165, hashes = { sha256 = "4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", upload-time = 2024-07-29T01:09:54Z, size = 88743, hashes = { sha256 = "b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb" } }] + +[[packages]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", upload-time = 2024-07-29T01:10:09Z, size = 16080, hashes = { sha256 = "e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", upload-time = 2024-07-29T01:10:08Z, size = 92072, hashes = { sha256 = "6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331" } }] + +[[packages]] +name = "sphinxext-opengraph" +version = "0.13.0" +sdist = { url = "https://files.pythonhosted.org/packages/f6/c0/eb6838e3bae624ce6c8b90b245d17e84252863150e95efdb88f92c8aa3fb/sphinxext_opengraph-0.13.0.tar.gz", upload-time = 2025-08-29T12:20:31Z, size = 1026875, hashes = { sha256 = "103335d08567ad8468faf1425f575e3b698e9621f9323949a6c8b96d9793e80b" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/bf/a4/66c1fd4f8fab88faf71cee04a945f9806ba0fef753f2cfc8be6353f64508/sphinxext_opengraph-0.13.0-py3-none-any.whl", upload-time = 2025-08-29T12:20:29Z, size = 1004152, hashes = { sha256 = "936c07828edc9ad9a7b07908b29596dc84ed0b3ceaa77acdf51282d232d4d80e" } }] + +[[packages]] +name = "urllib3" +version = "2.6.3" +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", upload-time = 2026-01-07T16:24:43Z, size = 435556, hashes = { sha256 = "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" } } +wheels = [{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", upload-time = 2026-01-07T16:24:42Z, size = 131584, hashes = { sha256 = "bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4" } }] From 2674c322c6cc5a81ce54d807c64cb0e221521786 Mon Sep 17 00:00:00 2001 From: Stelar <90330577+StelarDream@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:50:53 +0200 Subject: [PATCH 41/44] gh-149119: docs: mention that sentinel does not support subclassing (#149120) Add note about sentinel objects not supporting subclassing Clarify that sentinel objects do not support subclassing. --- Doc/library/functions.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index aa99d198e436d5..06fd5cdc7be2a6 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1839,6 +1839,8 @@ are always available. They are listed here in alphabetical order. Sentinel objects are truthy and compare equal only to themselves. They are intended to be compared with the :keyword:`is` operator. + ``sentinel`` does not support subclassing. + Shallow and deep copies of a sentinel object return the object itself. Sentinels are conventionally assigned to a variable with a matching name. From d71e3bc5a0f2f7af407eb32a8a5b36acf66070aa Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 28 Apr 2026 18:59:48 -0700 Subject: [PATCH 42/44] gh-148829: bump number of static types (#149121) --- Include/internal/pycore_interp_structs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 01adadd1485189..fb810c82a5aa63 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -531,7 +531,7 @@ struct _py_func_state { /* For now we hard-code this to a value for which we are confident all the static builtin types will fit (for all builds). */ -#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 202 +#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 203 #define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10 #define _Py_MAX_MANAGED_STATIC_TYPES \ (_Py_MAX_MANAGED_STATIC_BUILTIN_TYPES + _Py_MAX_MANAGED_STATIC_EXT_TYPES) From d4eee166598bd22f3710101f41c7c157be0a3f2f Mon Sep 17 00:00:00 2001 From: ivonastojanovic <80911834+ivonastojanovic@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:00:07 +0100 Subject: [PATCH 43/44] gh-142927: Show module names instead of file paths in flamegraph (#146040) --- .../_flamegraph_assets/flamegraph.css | 9 +- .../sampling/_flamegraph_assets/flamegraph.js | 72 +++++++++-- .../flamegraph_template.html | 6 + Lib/profiling/sampling/heatmap_collector.py | 121 +----------------- Lib/profiling/sampling/module_utils.py | 102 +++++++++++++++ Lib/profiling/sampling/stack_collector.py | 44 ++++++- .../test_sampling_profiler/test_collectors.py | 2 + 7 files changed, 215 insertions(+), 141 deletions(-) create mode 100644 Lib/profiling/sampling/module_utils.py diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css index c4da169d15de88..c93ee1e9dd470e 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.css @@ -315,6 +315,12 @@ body.resizing-sidebar { } /* View Mode Section */ +.view-mode-section { + display: flex; + flex-direction: column; + gap: 8px; +} + .view-mode-section .section-content { display: flex; flex-direction: column; @@ -1067,7 +1073,8 @@ body.resizing-sidebar { -------------------------------------------------------------------------- */ #toggle-invert .toggle-track.on, -#toggle-elided .toggle-track.on { +#toggle-elided .toggle-track.on, +#toggle-path-display .toggle-track.on { background: #8e44ad; border-color: #8e44ad; box-shadow: 0 0 8px rgba(142, 68, 173, 0.3); diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js index 3f884c4c690fd1..1611bf754424c1 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph.js @@ -6,6 +6,7 @@ let normalData = null; let invertedData = null; let currentThreadFilter = 'all'; let isInverted = false; +let useModuleNames = true; // Heat colors are now defined in CSS variables (--heat-1 through --heat-8) // and automatically switch with theme changes - no JS color arrays needed! @@ -64,6 +65,12 @@ function resolveStringIndices(node, table) { if (typeof resolved.funcname === 'number') { resolved.funcname = resolveString(resolved.funcname, table); } + if (typeof resolved.module === 'number') { + resolved.module = resolveString(resolved.module, table); + } + if (typeof resolved.label === 'number') { + resolved.label = resolveString(resolved.label, table); + } if (Array.isArray(resolved.source)) { resolved.source = resolved.source.map(index => @@ -78,6 +85,19 @@ function resolveStringIndices(node, table) { return resolved; } +// Escape HTML special characters +function escapeHtml(str) { + return str.replace(/&/g, "&").replace(//g, ">"); +} + +// Get display path based on user preference (module or full path) +function getDisplayName(moduleName, filename) { + if (useModuleNames) { + return moduleName || filename; + } + return filename; +} + function selectFlamegraphData() { const baseData = isShowingElided ? elidedFlamegraphData : normalData; @@ -228,6 +248,7 @@ function setupLogos() { function updateStatusBar(nodeData, rootValue) { const funcname = resolveString(nodeData.funcname) || resolveString(nodeData.name) || "--"; const filename = resolveString(nodeData.filename) || ""; + const moduleName = resolveString(nodeData.module) || ""; const lineno = nodeData.lineno; const timeMs = (nodeData.value / 1000).toFixed(2); const percent = rootValue > 0 ? ((nodeData.value / rootValue) * 100).toFixed(1) : "0.0"; @@ -249,8 +270,8 @@ function updateStatusBar(nodeData, rootValue) { const fileEl = document.getElementById('status-file'); if (fileEl && filename && filename !== "~") { - const basename = filename.split('/').pop(); - fileEl.textContent = lineno ? `${basename}:${lineno}` : basename; + const displayName = getDisplayName(moduleName, filename); + fileEl.textContent = lineno ? `${displayName}:${lineno}` : displayName; } const funcEl = document.getElementById('status-func'); @@ -301,6 +322,8 @@ function createPythonTooltip(data) { const funcname = resolveString(d.data.funcname) || resolveString(d.data.name); const filename = resolveString(d.data.filename) || ""; + const moduleName = resolveString(d.data.module) || ""; + const displayName = escapeHtml(useModuleNames ? (moduleName || filename) : filename); const isSpecialFrame = filename === "~"; // Build source section @@ -309,7 +332,7 @@ function createPythonTooltip(data) { const sourceLines = source .map((line) => { const isCurrent = line.startsWith("→"); - const escaped = line.replace(/&/g, "&").replace(//g, ">"); + const escaped = escapeHtml(line); return `

`; }) .join(""); @@ -369,7 +392,7 @@ function createPythonTooltip(data) { } const fileLocationHTML = isSpecialFrame ? "" : ` -
${filename}${d.data.lineno ? ":" + d.data.lineno : ""}
`; +
${displayName}${d.data.lineno ? ":" + d.data.lineno : ""}
`; // Differential stats section let diffSection = ""; @@ -586,6 +609,7 @@ function createFlamegraph(tooltip, rootValue, data) { .minFrameSize(1) .tooltip(tooltip) .inverted(true) + .getName(d => resolveString(useModuleNames ? d.data.label : d.data.name) || resolveString(d.data.name) || '') .setColorMapper(function (d) { if (d.depth === 0) return 'transparent'; @@ -628,25 +652,25 @@ function updateSearchHighlight(searchTerm, searchInput) { const name = resolveString(d.data.name) || ""; const funcname = resolveString(d.data.funcname) || ""; const filename = resolveString(d.data.filename) || ""; + const moduleName = resolveString(d.data.module) || ""; + const displayName = getDisplayName(moduleName, filename); const lineno = d.data.lineno; const term = searchTerm.toLowerCase(); - // Check if search term looks like file:line pattern + // Check if search term looks like path:line pattern const fileLineMatch = term.match(/^(.+):(\d+)$/); let matches = false; if (fileLineMatch) { - // Exact file:line matching const searchFile = fileLineMatch[1]; const searchLine = parseInt(fileLineMatch[2], 10); - const basename = filename.split('/').pop().toLowerCase(); - matches = basename.includes(searchFile) && lineno === searchLine; + matches = displayName.toLowerCase().includes(searchFile) && lineno === searchLine; } else { // Regular substring search matches = name.toLowerCase().includes(term) || funcname.toLowerCase().includes(term) || - filename.toLowerCase().includes(term); + displayName.toLowerCase().includes(term); } if (matches) { @@ -1047,6 +1071,7 @@ function populateStats(data) { let filename = resolveString(node.filename); let funcname = resolveString(node.funcname); + let moduleName = resolveString(node.module); if (!filename || !funcname) { const nameStr = resolveString(node.name); @@ -1061,6 +1086,7 @@ function populateStats(data) { filename = filename || 'unknown'; funcname = funcname || 'unknown'; + moduleName = moduleName || 'unknown'; if (filename !== 'unknown' && funcname !== 'unknown' && node.value > 0) { const directSamples = node.self || 0; @@ -1073,12 +1099,14 @@ function populateStats(data) { existing.directPercent = (existing.directSamples / totalSamples) * 100; if (directSamples > existing.maxSingleSamples) { existing.filename = filename; + existing.module = moduleName; existing.lineno = node.lineno || '?'; existing.maxSingleSamples = directSamples; } } else { functionMap.set(funcKey, { filename: filename, + module: moduleName, lineno: node.lineno || '?', funcname: funcname, directSamples, @@ -1113,6 +1141,7 @@ function populateStats(data) { const h = hotSpots[i]; const filename = h.filename || 'unknown'; const lineno = h.lineno ?? '?'; + const moduleName = h.module || 'unknown'; const isSpecialFrame = filename === '~' && (lineno === 0 || lineno === '?'); let funcDisplay = h.funcname || 'unknown'; @@ -1123,8 +1152,8 @@ function populateStats(data) { if (isSpecialFrame) { fileEl.textContent = '--'; } else { - const basename = filename !== 'unknown' ? filename.split('/').pop() : 'unknown'; - fileEl.textContent = `${basename}:${lineno}`; + const displayName = getDisplayName(moduleName, filename); + fileEl.textContent = `${displayName}:${lineno}`; } } if (percentEl) percentEl.textContent = `${h.directPercent.toFixed(1)}%`; @@ -1140,8 +1169,11 @@ function populateStats(data) { if (card) { if (i < hotSpots.length && hotSpots[i]) { const h = hotSpots[i]; - const basename = h.filename !== 'unknown' ? h.filename.split('/').pop() : ''; - const searchTerm = basename && h.lineno !== '?' ? `${basename}:${h.lineno}` : h.funcname; + const moduleName = h.module || 'unknown'; + const filename = h.filename || 'unknown'; + const displayName = getDisplayName(moduleName, filename); + const hasValidLocation = displayName !== 'unknown' && h.lineno !== '?'; + const searchTerm = hasValidLocation ? `${displayName}:${h.lineno}` : h.funcname; card.dataset.searchterm = searchTerm; card.onclick = () => searchForHotspot(searchTerm); card.style.cursor = 'pointer'; @@ -1273,10 +1305,12 @@ function accumulateInvertedNode(parent, stackFrame, leaf, isDifferential) { if (!parent.children[key]) { const newNode = { name: stackFrame.name, + label: stackFrame.label, value: 0, self: 0, children: {}, filename: stackFrame.filename, + module: stackFrame.module, lineno: stackFrame.lineno, funcname: stackFrame.funcname, source: stackFrame.source, @@ -1370,6 +1404,7 @@ function generateInvertedFlamegraph(data) { const invertedRoot = { name: data.name, + label: data.label, value: data.value, children: {}, stats: data.stats, @@ -1394,6 +1429,12 @@ function toggleInvert() { updateFlamegraphView(); } +function togglePathDisplay() { + useModuleNames = !useModuleNames; + updateToggleUI('toggle-path-display', useModuleNames); + updateFlamegraphView(); +} + // ============================================================================ // Initialization // ============================================================================ @@ -1441,6 +1482,11 @@ function initFlamegraph() { if (toggleInvertBtn) { toggleInvertBtn.addEventListener('click', toggleInvert); } + + const togglePathDisplayBtn = document.getElementById('toggle-path-display'); + if (togglePathDisplayBtn) { + togglePathDisplayBtn.addEventListener('click', togglePathDisplay); + } } // Keyboard shortcut: Enter/Space activates toggle switches diff --git a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html index 9a77178aeff7ec..f1c5bb0300679a 100644 --- a/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html +++ b/Lib/profiling/sampling/_flamegraph_assets/flamegraph_template.html @@ -117,6 +117,12 @@

View Mode

Elided +
+ File Paths +
+ Module Names +
+
Flamegraph
diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index ea1beec70d39f8..5c36d78f5535e7 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -20,6 +20,7 @@ from .collector import normalize_location, extract_lineno from .opcode_utils import get_opcode_info, format_opcode from .stack_collector import StackTraceCollector +from .module_utils import extract_module_name, get_python_path_info # ============================================================================ @@ -49,126 +50,6 @@ class TreeNode: children: Dict[str, 'TreeNode'] = field(default_factory=dict) -# ============================================================================ -# Module Path Analysis -# ============================================================================ - -def get_python_path_info(): - """Get information about Python installation paths for module extraction. - - Returns: - dict: Dictionary containing stdlib path, site-packages paths, and sys.path entries. - """ - info = { - 'stdlib': None, - 'site_packages': [], - 'sys_path': [] - } - - # Get standard library path from os module location - try: - if hasattr(os, '__file__') and os.__file__: - info['stdlib'] = Path(os.__file__).parent - except (AttributeError, OSError): - pass # Silently continue if we can't determine stdlib path - - # Get site-packages directories - site_packages = [] - try: - site_packages.extend(Path(p) for p in site.getsitepackages()) - except (AttributeError, OSError): - pass # Continue without site packages if unavailable - - # Get user site-packages - try: - user_site = site.getusersitepackages() - if user_site and Path(user_site).exists(): - site_packages.append(Path(user_site)) - except (AttributeError, OSError): - pass # Continue without user site packages - - info['site_packages'] = site_packages - info['sys_path'] = [Path(p) for p in sys.path if p] - - return info - - -def extract_module_name(filename, path_info): - """Extract Python module name and type from file path. - - Args: - filename: Path to the Python file - path_info: Dictionary from get_python_path_info() - - Returns: - tuple: (module_name, module_type) where module_type is one of: - 'stdlib', 'site-packages', 'project', or 'other' - """ - if not filename: - return ('unknown', 'other') - - try: - file_path = Path(filename) - except (ValueError, OSError): - return (str(filename), 'other') - - # Check if it's in stdlib - if path_info['stdlib'] and _is_subpath(file_path, path_info['stdlib']): - try: - rel_path = file_path.relative_to(path_info['stdlib']) - return (_path_to_module(rel_path), 'stdlib') - except ValueError: - pass - - # Check site-packages - for site_pkg in path_info['site_packages']: - if _is_subpath(file_path, site_pkg): - try: - rel_path = file_path.relative_to(site_pkg) - return (_path_to_module(rel_path), 'site-packages') - except ValueError: - continue - - # Check other sys.path entries (project files) - if not str(file_path).startswith(('<', '[')): # Skip special files - for path_entry in path_info['sys_path']: - if _is_subpath(file_path, path_entry): - try: - rel_path = file_path.relative_to(path_entry) - return (_path_to_module(rel_path), 'project') - except ValueError: - continue - - # Fallback: just use the filename - return (_path_to_module(file_path), 'other') - - -def _is_subpath(file_path, parent_path): - try: - file_path.relative_to(parent_path) - return True - except (ValueError, OSError): - return False - - -def _path_to_module(path): - if isinstance(path, str): - path = Path(path) - - # Remove .py extension - if path.suffix == '.py': - path = path.with_suffix('') - - # Convert path separators to dots - parts = path.parts - - # Handle __init__ files - they represent the package itself - if parts and parts[-1] == '__init__': - parts = parts[:-1] - - return '.'.join(parts) if parts else path.stem - - # ============================================================================ # Helper Classes # ============================================================================ diff --git a/Lib/profiling/sampling/module_utils.py b/Lib/profiling/sampling/module_utils.py new file mode 100644 index 00000000000000..dfde2b28ab29a4 --- /dev/null +++ b/Lib/profiling/sampling/module_utils.py @@ -0,0 +1,102 @@ +"""Utilities for extracting module names from file paths.""" + +import os +import site +import sys +from pathlib import Path + + +def get_python_path_info(): + """Get information about Python's search paths. + + Returns: + dict: Dictionary containing stdlib path, site-packages paths, and sys.path entries. + """ + info = { + 'stdlib': None, + 'site_packages': [], + 'sys_path': [] + } + + # Get standard library path from os module location + try: + if hasattr(os, '__file__') and os.__file__: + info['stdlib'] = Path(os.__file__).parent + except (AttributeError, OSError): + pass # Silently continue if we can't determine stdlib path + + # Get site-packages directories + site_packages = [] + try: + site_packages.extend(Path(p) for p in site.getsitepackages()) + except (AttributeError, OSError): + pass # Continue without site packages if unavailable + + # Get user site-packages + try: + user_site = site.getusersitepackages() + if user_site and Path(user_site).exists(): + site_packages.append(Path(user_site)) + except (AttributeError, OSError): + pass # Continue without user site packages + + info['site_packages'] = site_packages + info['sys_path'] = [Path(p) for p in sys.path if p] + + return info + + +def extract_module_name(filename, path_info): + """Extract Python module name and type from file path. + + Args: + filename: Path to the Python file + path_info: Dictionary from get_python_path_info() + + Returns: + tuple: (module_name, module_type) where module_type is one of: + 'stdlib', 'site-packages', 'project', or 'other' + """ + if not filename: + return ('unknown', 'other') + + try: + file_path = Path(filename) + except (ValueError, OSError): + return (str(filename), 'other') + + # Check if it's in stdlib + if path_info['stdlib'] and file_path.is_relative_to(path_info['stdlib']): + return (_path_to_module(file_path.relative_to(path_info['stdlib'])), 'stdlib') + + # Check site-packages + for site_pkg in path_info['site_packages']: + if file_path.is_relative_to(site_pkg): + return (_path_to_module(file_path.relative_to(site_pkg)), 'site-packages') + + # Check other sys.path entries (project files) + if not str(file_path).startswith(('<', '[')): # Skip special files + for path_entry in path_info['sys_path']: + if file_path.is_relative_to(path_entry): + return (_path_to_module(file_path.relative_to(path_entry)), 'project') + + # Fallback: just use the filename + return (_path_to_module(file_path), 'other') + + +def _path_to_module(path): + if isinstance(path, str): + path = Path(path) + + # Remove .py extension + if path.suffix == '.py': + path = path.with_suffix('') + + # Convert path separators to dots, stripping root/drive (e.g. "/" or "C:\") + parts = [p for p in path.parts if p != path.root and p != path.drive] + + # Handle __init__ files - they represent the package itself + if parts and parts[-1] == '__init__': + parts = parts[:-1] + + return '.'.join(parts) if parts else path.stem diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index 461ce95a25874b..04622a8c1e89ef 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -12,6 +12,7 @@ from .collector import Collector, extract_lineno from .opcode_utils import get_opcode_mapping from .string_table import StringTable +from .module_utils import extract_module_name, get_python_path_info class StackTraceCollector(Collector): @@ -72,6 +73,7 @@ def __init__(self, *args, **kwargs): self._sample_count = 0 # Track actual number of samples (not thread traces) self._func_intern = {} self._string_table = StringTable() + self._module_cache = {} self._all_threads = set() # Thread status statistics (similar to LiveStatsCollector) @@ -182,6 +184,24 @@ def _format_function_name(func): return f"{funcname} ({filename}:{lineno})" + @staticmethod + @functools.lru_cache(maxsize=None) + def _format_module_name(func, module_name): + filename, lineno, funcname = func + + # Special frames like and should not show file:line + if filename == "~" and lineno == 0: + return funcname + + return f"{funcname} ({module_name}:{lineno})" + + def _get_module_name(self, filename, path_info): + module_name = self._module_cache.get(filename) + if module_name is None: + module_name, _ = extract_module_name(filename, path_info) + self._module_cache[filename] = module_name + return module_name + def _convert_to_flamegraph_format(self): if self._total_samples == 0: return { @@ -192,7 +212,7 @@ def _convert_to_flamegraph_format(self): "strings": self._string_table.get_strings() } - def convert_children(children, min_samples): + def convert_children(children, min_samples, path_info): out = [] for func, node in children.items(): samples = node["samples"] @@ -202,14 +222,20 @@ def convert_children(children, min_samples): # Intern all string components for maximum efficiency filename_idx = self._string_table.intern(func[0]) funcname_idx = self._string_table.intern(func[2]) + module_name = self._get_module_name(func[0], path_info) + + module_idx = self._string_table.intern(module_name) name_idx = self._string_table.intern(self._format_function_name(func)) + label_idx = self._string_table.intern(self._format_module_name(func, module_name)) child_entry = { "name": name_idx, + "label": label_idx, "value": samples, "self": node.get("self", 0), "children": [], "filename": filename_idx, + "module": module_idx, "lineno": func[1], "funcname": funcname_idx, "threads": sorted(list(node.get("threads", set()))), @@ -228,7 +254,7 @@ def convert_children(children, min_samples): # Recurse child_entry["children"] = convert_children( - node["children"], min_samples + node["children"], min_samples, path_info ) out.append(child_entry) @@ -239,8 +265,9 @@ def convert_children(children, min_samples): # Filter out very small functions (less than 0.1% of total samples) total_samples = self._total_samples min_samples = max(1, int(total_samples * 0.001)) + path_info = get_python_path_info() - root_children = convert_children(self._root["children"], min_samples) + root_children = convert_children(self._root["children"], min_samples, path_info) if not root_children: return { "name": self._string_table.intern("No significant data"), @@ -282,10 +309,11 @@ def convert_children(children, min_samples): # If we only have one root child, make it the root to avoid redundant level if len(root_children) == 1: main_child = root_children[0] - # Update the name to indicate it's the program root + # Update name and label to indicate it's the program root old_name = self._string_table.get_string(main_child["name"]) - new_name = f"Program Root: {old_name}" - main_child["name"] = self._string_table.intern(new_name) + main_child["name"] = self._string_table.intern(f"Program Root: {old_name}") + old_label = self._string_table.get_string(main_child["label"]) + main_child["label"] = self._string_table.intern(f"Program Root: {old_label}") main_child["stats"] = { **self.stats, "thread_stats": thread_stats, @@ -296,8 +324,10 @@ def convert_children(children, min_samples): main_child["opcode_mapping"] = opcode_mapping return main_child + program_root_idx = self._string_table.intern("Program Root") return { - "name": self._string_table.intern("Program Root"), + "name": program_root_idx, + "label": program_root_idx, "value": total_samples, "children": root_children, "stats": { diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py index 503430ddf02163..240ec8a195c43b 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py @@ -436,6 +436,8 @@ def test_flamegraph_collector_basic(self): name = resolve_name(data, strings) self.assertTrue(name.startswith("Program Root: ")) self.assertIn("func2 (file.py:20)", name) + label = strings[data["label"]] + self.assertTrue(label.startswith("Program Root: ")) self.assertEqual(data["self"], 0) # non-leaf: no self time children = data.get("children", []) self.assertEqual(len(children), 1) From c3350d27f43aa32bff28eb34ba862c956b462532 Mon Sep 17 00:00:00 2001 From: Nick Begg Date: Wed, 29 Apr 2026 11:21:04 +0200 Subject: [PATCH 44/44] add blurb --- .../next/Tests/2026-04-29-11-20-43.gh-issue-149141.Ue1Qww.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2026-04-29-11-20-43.gh-issue-149141.Ue1Qww.rst diff --git a/Misc/NEWS.d/next/Tests/2026-04-29-11-20-43.gh-issue-149141.Ue1Qww.rst b/Misc/NEWS.d/next/Tests/2026-04-29-11-20-43.gh-issue-149141.Ue1Qww.rst new file mode 100644 index 00000000000000..9d57c46ee546cb --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-04-29-11-20-43.gh-issue-149141.Ue1Qww.rst @@ -0,0 +1,4 @@ +Test discovery would fail when the path to the source tree contains a +symlink. Paths that take an alternate route to the same place (eg just the +real path) would not be considered a subpath, and cause an assert. Instead, +always resolve to the real path before comparing.