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" 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/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/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/ast.rst b/Doc/library/ast.rst index 9b4e7ae18348f1..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 @@ -2480,7 +2481,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 +2491,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 +2532,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 +2592,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/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 4e60eee44290af..2e17d0dc70c7a5 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 +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. @@ -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/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/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/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/functions.rst b/Doc/library/functions.rst index 119141d2e6daf3..06fd5cdc7be2a6 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,63 @@ 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. + + ``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. + 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/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:: 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/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 ^^^^^^^ 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/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/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/Doc/library/tachyon-logo.png b/Doc/library/tachyon-logo.png deleted file mode 100644 index bf0901ec9f313e..00000000000000 Binary files a/Doc/library/tachyon-logo.png and /dev/null differ 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/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" } }] diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 1e53c0e0e6f971..aef5bbe151cfeb 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 ^^^^^^^^^^^^^^^^^^^ @@ -1461,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) @@ -1534,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/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/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/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 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 dbdd5de01700a3..eb08f8c4ed69e7 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 @@ -184,6 +186,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`.) @@ -235,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 @@ -260,7 +288,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 @@ -649,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 =========== @@ -692,6 +727,31 @@ 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`.) + + +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 ------ @@ -717,6 +777,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 -------- @@ -750,6 +819,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 -------- @@ -791,6 +864,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 ----------- @@ -1205,6 +1287,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 @@ -1556,6 +1647,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 --------------- @@ -1646,6 +1747,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/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/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 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_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) 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/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/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/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/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/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/_colorize.py b/Lib/_colorize.py index 852ad38f08618e..379ca2529b6585 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`.""" @@ -375,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 @@ -405,12 +424,14 @@ 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) 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) @@ -418,12 +439,14 @@ 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, 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: @@ -434,12 +457,14 @@ 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, 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, ) @@ -454,12 +479,14 @@ 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(), 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/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/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/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/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/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/dataclasses.py b/Lib/dataclasses.py index 9d5bed6b96fc49..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. @@ -1298,10 +1310,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 @@ -1383,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/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/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/profiling/sampling/_assets/tachyon-logo.png b/Lib/profiling/sampling/_assets/tachyon-logo.png index f87e006b14f215..03e67823d5afc9 100644 Binary files a/Lib/profiling/sampling/_assets/tachyon-logo.png and b/Lib/profiling/sampling/_assets/tachyon-logo.png differ 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 d7a8890d4a1ad9..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 `
${escaped}
`; }) .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,13 +1086,10 @@ function populateStats(data) { filename = filename || 'unknown'; funcname = funcname || 'unknown'; + moduleName = moduleName || '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}`; @@ -1077,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, @@ -1117,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'; @@ -1127,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)}%`; @@ -1144,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'; @@ -1277,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, @@ -1345,14 +1375,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)); } @@ -1375,6 +1404,7 @@ function generateInvertedFlamegraph(data) { const invertedRoot = { name: data.name, + label: data.label, value: data.value, children: {}, stats: data.stats, @@ -1399,6 +1429,12 @@ function toggleInvert() { updateFlamegraphView(); } +function togglePathDisplay() { + useModuleNames = !useModuleNames; + updateToggleUI('toggle-path-display', useModuleNames); + updateFlamegraphView(); +} + // ============================================================================ // Initialization // ============================================================================ @@ -1446,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/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/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/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__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_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index f29f98beb2d048..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 @@ -1705,6 +1709,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) @@ -3190,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, []) @@ -3212,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): @@ -3253,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) @@ -3277,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) @@ -3295,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): @@ -3415,6 +3419,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/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/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/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/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 39075fc64cf02b..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. @@ -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_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))) 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/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/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index e0cfe3df3e6357..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: @@ -5375,5 +5399,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/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/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/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/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) 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/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/test/test_subprocess.py b/Lib/test/test_subprocess.py index 0c5679611848ea..1a3db527d3d5b8 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() @@ -1086,6 +1122,43 @@ def test_communicate_timeout_large_input(self): self.assertEqual(len(stdout), len(input_data), 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() + + 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() 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/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) 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/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/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" 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/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/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. .. 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/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/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/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/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/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/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``. 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..0e0280c9b6b0e7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-24-07-18-40.gh-issue-108951.jyKygP.rst @@ -0,0 +1,2 @@ +:mod:`asyncio`: Add :meth:`TaskGroup.cancel ` which cancels +unfinished tasks and exits the group without raising :exc:`asyncio.CancelledError`. 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. 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. 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%. 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. 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/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`. 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. 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. 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/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/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. 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/_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; } 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; 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"); 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/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/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/_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/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]*/ 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: 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 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/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. 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/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; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index fec64e1ff9d25f..35b30a243318cc 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; @@ -3553,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/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/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; } 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]*/ 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/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/flowgraph.c b/Python/flowgraph.c index c234fa3d8c3876..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 @@ -1309,6 +1310,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) { @@ -1325,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; } @@ -1424,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) { @@ -1434,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; @@ -1447,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)); @@ -1484,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: @@ -1502,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)); @@ -1554,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) { @@ -1590,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)); @@ -1640,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); @@ -1837,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)); @@ -1879,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 * @@ -1925,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)); @@ -1962,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) @@ -2157,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)); @@ -2167,6 +2191,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) { @@ -2272,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; } @@ -2286,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)); @@ -2334,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: @@ -2473,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) { @@ -2481,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; } } @@ -2534,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)); @@ -3663,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( @@ -4064,6 +4120,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; diff --git a/Python/optimizer.c b/Python/optimizer.c index a389c0f4072817..820a0771a2cb0f 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) \ @@ -658,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 @@ -831,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)); @@ -1027,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; } @@ -1133,6 +1182,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 +1214,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 +1348,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 +2149,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++; } 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; } 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/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 - 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(