mirror of
https://github.com/python/cpython.git
synced 2026-05-04 09:31:02 +00:00
Merge branch 'main' into issue-149219
This commit is contained in:
commit
09df7afac8
150 changed files with 6564 additions and 2303 deletions
19
.github/workflows/posix-deps-apt.sh
vendored
19
.github/workflows/posix-deps-apt.sh
vendored
|
|
@ -26,9 +26,16 @@ apt-get -yq --no-install-recommends install \
|
|||
xvfb \
|
||||
zlib1g-dev
|
||||
|
||||
# Workaround missing libmpdec-dev on ubuntu 24.04:
|
||||
# https://launchpad.net/~ondrej/+archive/ubuntu/php
|
||||
# https://deb.sury.org/
|
||||
sudo add-apt-repository ppa:ondrej/php
|
||||
apt-get update
|
||||
apt-get -yq --no-install-recommends install libmpdec-dev
|
||||
# Workaround missing libmpdec-dev on ubuntu 24.04 by building mpdecimal
|
||||
# from source. ppa:ondrej/php (launchpad.net) are unreliable
|
||||
# (https://status.canonical.com) so fetch the tarball directly
|
||||
# from the upstream host.
|
||||
# https://www.bytereef.org/mpdecimal/
|
||||
MPDECIMAL_VERSION=4.0.1
|
||||
curl -fsSL "https://www.bytereef.org/software/mpdecimal/releases/mpdecimal-${MPDECIMAL_VERSION}.tar.gz" \
|
||||
| tar -xz -C /tmp
|
||||
(cd "/tmp/mpdecimal-${MPDECIMAL_VERSION}" \
|
||||
&& ./configure --prefix=/usr/local \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install)
|
||||
ldconfig
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Note that holding an :term:`attached thread state` is not required for these API
|
|||
or ``-2`` on failure to create a lock. Check ``errno`` for more information
|
||||
about the cause of a failure.
|
||||
|
||||
.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, unsigned int code_size, const char *entry_name)
|
||||
.. c:function:: int PyUnstable_WritePerfMapEntry(const void *code_addr, size_t code_size, const char *entry_name)
|
||||
|
||||
Write one single entry to the ``/tmp/perf-$pid.map`` file. This function is
|
||||
thread safe. Here is what an example entry looks like::
|
||||
|
|
|
|||
|
|
@ -38,3 +38,8 @@ Pending removal in Python 3.20
|
|||
- :mod:`zlib`
|
||||
|
||||
(Contributed by Hugo van Kemenade and Stan Ulbrych in :gh:`76007`.)
|
||||
|
||||
* :mod:`ast`:
|
||||
|
||||
* Creating instances of abstract AST nodes (such as :class:`ast.AST`
|
||||
or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20.
|
||||
|
|
|
|||
|
|
@ -1924,7 +1924,7 @@ correctly using identity tests:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
_sentinel = object()
|
||||
_sentinel = sentinel('_sentinel')
|
||||
|
||||
def pop(self, key, default=_sentinel):
|
||||
if key in self:
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ Glossary
|
|||
ABCs with the :mod:`abc` module.
|
||||
|
||||
annotate function
|
||||
A function that can be called to retrieve the :term:`annotations <annotation>`
|
||||
of an object. This function is accessible as the :attr:`~object.__annotate__`
|
||||
attribute of functions, classes, and modules. Annotate functions are a
|
||||
subset of :term:`evaluate functions <evaluate function>`.
|
||||
A callable that can be called to retrieve the :term:`annotations <annotation>` of
|
||||
an object. Annotate functions are usually :term:`functions <function>`,
|
||||
automatically generated as the :attr:`~object.__annotate__` attribute of functions,
|
||||
classes, and modules. Annotate functions are a subset of
|
||||
:term:`evaluate functions <evaluate function>`.
|
||||
|
||||
annotation
|
||||
A label associated with a variable, a class
|
||||
|
|
|
|||
|
|
@ -594,7 +594,7 @@ a pure Python equivalent:
|
|||
|
||||
def object_getattribute(obj, name):
|
||||
"Emulate PyObject_GenericGetAttr() in Objects/object.c"
|
||||
null = object()
|
||||
null = sentinel('null')
|
||||
objtype = type(obj)
|
||||
cls_var = find_name_in_mro(objtype, name, null)
|
||||
descr_get = getattr(type(cls_var), '__get__', null)
|
||||
|
|
@ -1635,12 +1635,12 @@ by member descriptors:
|
|||
|
||||
.. testcode::
|
||||
|
||||
null = object()
|
||||
null = sentinel('null')
|
||||
|
||||
class Member:
|
||||
|
||||
def __init__(self, name, clsname, offset):
|
||||
'Emulate PyMemberDef in Include/structmember.h'
|
||||
'Emulate PyMemberDef in Include/descrobject.h'
|
||||
# Also see descr_new() in Objects/descrobject.c
|
||||
self.name = name
|
||||
self.clsname = clsname
|
||||
|
|
|
|||
|
|
@ -217,8 +217,9 @@ Example, using the :mod:`sys` APIs in file :file:`example.py`:
|
|||
How to obtain the best results
|
||||
------------------------------
|
||||
|
||||
For best results, Python should be compiled with
|
||||
``CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"`` as this allows
|
||||
For best results, keep frame pointers enabled. On supported GCC-compatible
|
||||
toolchains, CPython builds itself with ``-fno-omit-frame-pointer`` and, when
|
||||
available, ``-mno-omit-leaf-frame-pointer`` by default. These flags allow
|
||||
profilers to unwind using only the frame pointer and not on DWARF debug
|
||||
information. This is because as the code that is interposed to allow ``perf``
|
||||
support is dynamically generated it doesn't have any DWARF debugging information
|
||||
|
|
|
|||
|
|
@ -510,6 +510,81 @@ annotations from the class and puts them in a separate attribute:
|
|||
return typ
|
||||
|
||||
|
||||
Creating a custom callable annotate function
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Custom :term:`annotate functions <annotate function>` may be literal functions like those
|
||||
automatically generated for functions, classes, and modules. Or, they may wish to utilise
|
||||
the encapsulation provided by classes, in which case any :term:`callable` can be used as
|
||||
an :term:`annotate function`.
|
||||
|
||||
To provide the :attr:`~Format.VALUE`, :attr:`~Format.STRING`, or
|
||||
:attr:`~Format.FORWARDREF` formats directly, an :term:`annotate function` must provide
|
||||
the following attribute:
|
||||
|
||||
* A callable ``__call__`` with signature ``__call__(format, /) -> dict``, that does not
|
||||
raise a :exc:`NotImplementedError` when called with a supported format.
|
||||
|
||||
To provide the :attr:`~Format.VALUE_WITH_FAKE_GLOBALS` format, which is used to
|
||||
automatically generate :attr:`~Format.STRING` or :attr:`~Format.FORWARDREF` if they are
|
||||
not supported directly, :term:`annotate functions <annotate function>` must provide the
|
||||
following attributes:
|
||||
|
||||
* A callable ``__call__`` with signature ``__call__(format, /) -> dict``, that does not
|
||||
raise a :exc:`NotImplementedError` when called with
|
||||
:attr:`~Format.VALUE_WITH_FAKE_GLOBALS`.
|
||||
* A :ref:`code object <code-objects>` ``__code__`` containing the compiled code for the
|
||||
annotate function.
|
||||
* Optional: A tuple of the function's positional defaults ``__kwdefaults__``, if the
|
||||
function represented by ``__code__`` uses any positional defaults.
|
||||
* Optional: A dict of the function's keyword defaults ``__defaults__``, if the function
|
||||
represented by ``__code__`` uses any keyword defaults.
|
||||
* Optional: All other :ref:`function attributes <inspect-types>`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Annotate:
|
||||
called_formats = []
|
||||
|
||||
def __call__(self, format=None, /, *, _self=None):
|
||||
# When called with fake globals, `_self` will be the
|
||||
# actual self value, and `self` will be the format.
|
||||
if _self is not None:
|
||||
self, format = _self, self
|
||||
|
||||
self.called_formats.append(format)
|
||||
if format <= 2: # VALUE or VALUE_WITH_FAKE_GLOBALS
|
||||
return {"x": MyType}
|
||||
raise NotImplementedError
|
||||
|
||||
__code__ = __call__.__code__
|
||||
__defaults__ = (None,)
|
||||
__kwdefaults__ = property(lambda self: dict(_self=self))
|
||||
|
||||
__globals__ = {}
|
||||
__builtins__ = {}
|
||||
__closure__ = None
|
||||
|
||||
This can then be called with:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from annotationlib import call_annotate_function, Format
|
||||
>>> call_annotate_function(Annotate(), format=Format.STRING)
|
||||
{'x': 'MyType'}
|
||||
|
||||
Or used as the annotate function for an object:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from annotationlib import get_annotations, Format
|
||||
>>> class C:
|
||||
... pass
|
||||
>>> C.__annotate__ = Annotate()
|
||||
>>> get_annotations(Annotate(), format=Format.STRING)
|
||||
{'x': 'MyType'}
|
||||
|
||||
|
||||
Limitations of the ``STRING`` format
|
||||
------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ Node classes
|
|||
|
||||
.. class:: AST
|
||||
|
||||
This is the base of all AST node classes. The actual node classes are
|
||||
This is the abstract base of all AST node classes. The actual node classes are
|
||||
derived from the :file:`Parser/Python.asdl` file, which is reproduced
|
||||
:ref:`above <abstract-grammar>`. They are defined in the :mod:`!_ast` C
|
||||
module and re-exported in :mod:`!ast`.
|
||||
|
|
@ -168,6 +168,15 @@ Node classes
|
|||
arguments that were set as attributes of the AST node, even if they did not
|
||||
match any of the fields of the AST node. These cases now raise a :exc:`TypeError`.
|
||||
|
||||
.. deprecated-removed:: next 3.20
|
||||
|
||||
In the :ref:`grammar above <abstract-grammar>`, the AST node classes that
|
||||
correspond to production rules with variants (aka "sums") are abstract
|
||||
classes. Previous versions of Python allowed for the creation of direct
|
||||
instances of these abstract node classes. This behavior is deprecated and
|
||||
will be removed in Python 3.20.
|
||||
|
||||
|
||||
.. note::
|
||||
The descriptions of the specific node classes displayed here
|
||||
were initially adapted from the fantastic `Green Tree
|
||||
|
|
@ -271,18 +280,25 @@ Root nodes
|
|||
Literals
|
||||
^^^^^^^^
|
||||
|
||||
.. class:: Constant(value)
|
||||
.. class:: Constant(value, kind)
|
||||
|
||||
A constant value. The ``value`` attribute of the ``Constant`` literal contains the
|
||||
Python object it represents. The values represented can be instances of :class:`str`,
|
||||
:class:`bytes`, :class:`int`, :class:`float`, :class:`complex`, and :class:`bool`,
|
||||
and the constants :data:`None` and :data:`Ellipsis`.
|
||||
|
||||
The ``kind`` attribute is an optional string. For string literals with a
|
||||
``u`` prefix, ``kind`` is set to ``'u'``. For all other
|
||||
constants, ``kind`` is ``None``.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse('123', mode='eval'), indent=4))
|
||||
Expression(
|
||||
body=Constant(value=123))
|
||||
>>> print(ast.dump(ast.parse("u'hello'", mode='eval'), indent=4))
|
||||
Expression(
|
||||
body=Constant(value='hello', kind='u'))
|
||||
|
||||
|
||||
.. class:: FormattedValue(value, conversion, format_spec)
|
||||
|
|
@ -2536,6 +2552,20 @@ and classes for traversing abstract syntax trees:
|
|||
Added the *color* parameter.
|
||||
|
||||
|
||||
.. function:: compare(a, b, /, *, compare_attributes=False)
|
||||
|
||||
Recursively compares two ASTs.
|
||||
|
||||
*compare_attributes* affects whether AST attributes are considered
|
||||
in the comparison. If *compare_attributes* is ``False`` (default), then
|
||||
attributes are ignored. Otherwise they must all be equal. This
|
||||
option is useful to check whether the ASTs are structurally equal but
|
||||
differ in whitespace or similar details. Attributes include line numbers
|
||||
and column offsets.
|
||||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
|
||||
.. _ast-compiler-flags:
|
||||
|
||||
Compiler flags
|
||||
|
|
@ -2571,20 +2601,6 @@ effects on the compilation of a program:
|
|||
.. versionadded:: 3.8
|
||||
|
||||
|
||||
.. function:: compare(a, b, /, *, compare_attributes=False)
|
||||
|
||||
Recursively compares two ASTs.
|
||||
|
||||
*compare_attributes* affects whether AST attributes are considered
|
||||
in the comparison. If *compare_attributes* is ``False`` (default), then
|
||||
attributes are ignored. Otherwise they must all be equal. This
|
||||
option is useful to check whether the ASTs are structurally equal but
|
||||
differ in whitespace or similar details. Attributes include line numbers
|
||||
and column offsets.
|
||||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
|
||||
.. _ast-cli:
|
||||
|
||||
Command-line usage
|
||||
|
|
|
|||
|
|
@ -331,10 +331,14 @@ Compressing and decompressing data in memory
|
|||
|
||||
If *max_length* is non-negative, the method returns at most *max_length*
|
||||
bytes of decompressed data. If this limit is reached and further
|
||||
output can be produced, the :attr:`~.needs_input` attribute will
|
||||
be set to ``False``. In this case, the next call to
|
||||
output can be produced (or EOF is reached), the :attr:`~.needs_input`
|
||||
attribute will be set to ``False``. In this case, the next call to
|
||||
:meth:`~.decompress` may provide *data* as ``b''`` to obtain
|
||||
more of the output.
|
||||
more of the output. The full content can thus be read like::
|
||||
|
||||
process_output(d.decompress(data, max_length))
|
||||
while not d.eof and not d.needs_input:
|
||||
process_output(d.decompress(b"", max_length))
|
||||
|
||||
If all of the input data was decompressed and returned (either
|
||||
because this was less than *max_length* bytes, or because
|
||||
|
|
|
|||
|
|
@ -403,11 +403,26 @@ added matters. To illustrate::
|
|||
.. attribute:: utf8
|
||||
|
||||
If ``False``, follow :rfc:`5322`, supporting non-ASCII characters in
|
||||
headers by encoding them as "encoded words". If ``True``, follow
|
||||
:rfc:`6532` and use ``utf-8`` encoding for headers. Messages
|
||||
headers by encoding them as :rfc:`2047` "encoded words". If ``True``,
|
||||
follow :rfc:`6532` and use ``utf-8`` encoding for headers. Messages
|
||||
formatted in this way may be passed to SMTP servers that support
|
||||
the ``SMTPUTF8`` extension (:rfc:`6531`).
|
||||
|
||||
When ``False``, the generator will raise
|
||||
:exc:`~email.errors.HeaderWriteError` if any header includes non-ASCII
|
||||
characters in a context where :rfc:`2047` does not permit encoded words.
|
||||
This particularly applies to mailboxes ("addr-spec") with non-ASCII
|
||||
characters, which can be created via
|
||||
:class:`~email.headerregistry.Address`. To use a mailbox with a non-ASCII
|
||||
domain name with ``utf8=False``, first encode the domain using the
|
||||
third-party :pypi:`idna` or :pypi:`uts46` module or with
|
||||
:mod:`encodings.idna`. It is not possible to use a non-ASCII username
|
||||
("local-part") in a mailbox when ``utf8=False``.
|
||||
|
||||
.. versionchanged:: 3.15
|
||||
Can trigger the raising of :exc:`~email.errors.HeaderWriteError`.
|
||||
(Earlier versions incorrectly applied :rfc:`2047` in certain contexts,
|
||||
mostly notably in addr-specs.)
|
||||
|
||||
.. attribute:: refold_source
|
||||
|
||||
|
|
|
|||
|
|
@ -713,7 +713,7 @@ However, for reading convenience, most of the examples show sorted sequences.
|
|||
|
||||
.. function:: covariance(x, y, /)
|
||||
|
||||
Return the sample covariance of two inputs *x* and *y*. Covariance
|
||||
Return the sample covariance of two sequence inputs *x* and *y*. Covariance
|
||||
is a measure of the joint variability of two inputs.
|
||||
|
||||
Both inputs must be of the same length (no less than two), otherwise
|
||||
|
|
@ -739,7 +739,7 @@ However, for reading convenience, most of the examples show sorted sequences.
|
|||
|
||||
Return the `Pearson's correlation coefficient
|
||||
<https://en.wikipedia.org/wiki/Pearson_correlation_coefficient>`_
|
||||
for two inputs. Pearson's correlation coefficient *r* takes values
|
||||
for two sequence inputs. Pearson's correlation coefficient *r* takes values
|
||||
between -1 and +1. It measures the strength and direction of a linear
|
||||
relationship.
|
||||
|
||||
|
|
@ -802,7 +802,7 @@ However, for reading convenience, most of the examples show sorted sequences.
|
|||
(it is equal to the difference between predicted and actual values
|
||||
of the dependent variable).
|
||||
|
||||
Both inputs must be of the same length (no less than two), and
|
||||
Both inputs must be sequences of the same length (no less than two), and
|
||||
the independent variable *x* cannot be constant;
|
||||
otherwise a :exc:`StatisticsError` is raised.
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,10 @@ Some facts and figures:
|
|||
a Zstandard dictionary used to improve compression of smaller amounts of
|
||||
data.
|
||||
|
||||
For modes ``'w:gz'`` and ``'w|gz'``, :func:`tarfile.open` accepts the
|
||||
keyword argument *mtime* to create a gzip archive header with that mtime. By
|
||||
default, the mtime is set to the time of creation of the archive.
|
||||
|
||||
For special purposes, there is a second format for *mode*:
|
||||
``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile`
|
||||
object that processes its data as a stream of blocks. No random seeking will
|
||||
|
|
|
|||
|
|
@ -1436,3 +1436,159 @@ is equivalent to::
|
|||
Currently, :class:`Lock`, :class:`RLock`, :class:`Condition`,
|
||||
:class:`Semaphore`, and :class:`BoundedSemaphore` objects may be used as
|
||||
:keyword:`with` statement context managers.
|
||||
|
||||
|
||||
Iterator synchronization
|
||||
------------------------
|
||||
|
||||
By default, Python iterators do not support concurrent access. Most iterators make
|
||||
no guarantees when accessed simultaneously from multiple threads. Generator
|
||||
iterators, for example, raise :exc:`ValueError` if one of their iterator methods
|
||||
is called while the generator is already executing. The tools in this section
|
||||
allow reliable concurrency support to be added to ordinary iterators and
|
||||
iterator-producing callables.
|
||||
|
||||
The :class:`serialize_iterator` wrapper lets multiple threads share a single iterator and
|
||||
take turns consuming from it. While one thread is running ``__next__()``, the
|
||||
others block until the iterator becomes available. Each value produced by the
|
||||
underlying iterator is delivered to exactly one caller.
|
||||
|
||||
The :func:`concurrent_tee` function lets multiple threads each receive the full
|
||||
stream of values from one underlying iterator. It creates independent iterators
|
||||
that all draw from the same source. Values are buffered until consumed by all
|
||||
of the derived iterators.
|
||||
|
||||
.. class:: serialize_iterator(iterable)
|
||||
|
||||
Return an iterator wrapper that serializes concurrent calls to
|
||||
:meth:`~iterator.__next__` using a lock.
|
||||
|
||||
If the wrapped iterator also defines :meth:`~generator.send`,
|
||||
:meth:`~generator.throw`, or :meth:`~generator.close`, those calls
|
||||
are serialized as well.
|
||||
|
||||
This makes it possible to share a single iterator, including a generator
|
||||
iterator, between multiple threads. A lock ensures that calls are handled
|
||||
one at a time. No values are duplicated or skipped by the wrapper itself.
|
||||
Each item from the underlying iterator is given to exactly one caller.
|
||||
|
||||
This wrapper does not copy or buffer values. Threads that call
|
||||
:func:`next` while another thread is already advancing the iterator will
|
||||
block until the active call completes.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import threading
|
||||
|
||||
def squares(n):
|
||||
for x in range(n):
|
||||
yield x * x
|
||||
|
||||
def consume(name, iterable):
|
||||
for item in iterable:
|
||||
print(name, item)
|
||||
|
||||
source = threading.serialize_iterator(squares(5))
|
||||
|
||||
t1 = threading.Thread(target=consume, args=("left", source))
|
||||
t2 = threading.Thread(target=consume, args=("right", source))
|
||||
t1.start()
|
||||
t2.start()
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
In this example, each number is printed exactly once, but the work is shared
|
||||
between the two threads.
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: synchronized_iterator(func)
|
||||
|
||||
Wrap an iterator-producing callable so that each iterator it returns is
|
||||
automatically passed through :class:`serialize_iterator`.
|
||||
|
||||
This is especially useful as a :term:`decorator` for generator functions,
|
||||
allowing their generator-iterators to be consumed from multiple threads.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import threading
|
||||
|
||||
@threading.synchronized_iterator
|
||||
def squares(n):
|
||||
for x in range(n):
|
||||
yield x * x
|
||||
|
||||
def consume(name, iterable):
|
||||
for item in iterable:
|
||||
print(name, item)
|
||||
|
||||
source = squares(5)
|
||||
|
||||
t1 = threading.Thread(target=consume, args=("left", source))
|
||||
t2 = threading.Thread(target=consume, args=("right", source))
|
||||
t1.start()
|
||||
t2.start()
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
The returned wrapper preserves the metadata of *func*, such as its name and
|
||||
wrapped function reference.
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. function:: concurrent_tee(iterable, n=2)
|
||||
|
||||
Return *n* independent iterators from a single input *iterable*, with
|
||||
guaranteed behavior when the derived iterators are consumed concurrently.
|
||||
|
||||
This function is similar to :func:`itertools.tee`, but is intended for cases
|
||||
where the source iterator may feed consumers running in different threads.
|
||||
Each returned iterator yields every value from the underlying iterable, in
|
||||
the same order.
|
||||
|
||||
Internally, values are buffered until every derived iterator has consumed
|
||||
them.
|
||||
|
||||
The returned iterators share the same underlying synchronization lock. Each
|
||||
individual derived iterator is intended to be consumed by one thread at a
|
||||
time. If a single derived iterator must itself be shared by multiple
|
||||
threads, wrap it with :class:`serialize_iterator`.
|
||||
|
||||
If *n* is ``0``, return an empty tuple. If *n* is negative, raise
|
||||
:exc:`ValueError`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import threading
|
||||
|
||||
def squares(n):
|
||||
for x in range(n):
|
||||
yield x * x
|
||||
|
||||
def consume(name, iterable):
|
||||
for item in iterable:
|
||||
print(name, item)
|
||||
|
||||
source = squares(5)
|
||||
left, right = threading.concurrent_tee(source)
|
||||
|
||||
t1 = threading.Thread(target=consume, args=("left", left))
|
||||
t2 = threading.Thread(target=consume, args=("right", right))
|
||||
t1.start()
|
||||
t2.start()
|
||||
t1.join()
|
||||
t2.join()
|
||||
|
||||
In this example, both consumer threads see the full sequence of squares
|
||||
from a single generator expression.
|
||||
|
||||
.. versionadded:: next
|
||||
|
|
|
|||
|
|
@ -2361,6 +2361,12 @@ without the dedicated syntax, as documented below.
|
|||
>>> Alias.__module__
|
||||
'__main__'
|
||||
|
||||
This attribute is writable.
|
||||
|
||||
.. versionchanged:: 3.15
|
||||
|
||||
The attribute is now writable.
|
||||
|
||||
.. attribute:: __type_params__
|
||||
|
||||
The type parameters of the type alias, or an empty tuple if the alias is
|
||||
|
|
|
|||
|
|
@ -308,6 +308,11 @@ Decompression objects support the following methods and attributes:
|
|||
:attr:`unconsumed_tail`. This bytestring must be passed to a subsequent call to
|
||||
:meth:`decompress` if decompression is to continue. If *max_length* is zero
|
||||
then the whole input is decompressed, and :attr:`unconsumed_tail` is empty.
|
||||
For example, the full content could be read like::
|
||||
|
||||
process_output(d.decompress(data, max_length))
|
||||
while chunk := d.decompress(d.unconsumed_tail, max_length):
|
||||
process_output(chunk)
|
||||
|
||||
.. versionchanged:: 3.6
|
||||
*max_length* can be used as a keyword argument.
|
||||
|
|
|
|||
|
|
@ -780,6 +780,24 @@ also be used to improve performance.
|
|||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
.. option:: --without-frame-pointers
|
||||
|
||||
Disable frame pointers, which are enabled by default (see :pep:`831`).
|
||||
|
||||
By default, the build appends ``-fno-omit-frame-pointer`` (and
|
||||
``-mno-omit-leaf-frame-pointer`` when the compiler supports it) to
|
||||
``BASECFLAGS`` so profilers, debuggers, and system tracing tools
|
||||
(``perf``, ``eBPF``, ``dtrace``, ``gdb``) can walk the C call stack
|
||||
without DWARF metadata. The flags propagate to third-party C
|
||||
extensions through :mod:`sysconfig`. On compilers that do not
|
||||
understand them, the build silently skips them.
|
||||
|
||||
Downstream packagers and authors of native libraries built with
|
||||
custom build systems should set the same flags so the unwind chain
|
||||
stays unbroken across all native frames.
|
||||
|
||||
.. versionadded:: 3.15
|
||||
|
||||
.. option:: --without-mimalloc
|
||||
|
||||
Disable the fast :ref:`mimalloc <mimalloc>` allocator
|
||||
|
|
|
|||
|
|
@ -953,10 +953,24 @@ when a module is imported) will still emit the syntax warning.
|
|||
(Contributed by Irit Katriel in :gh:`130080`.)
|
||||
|
||||
|
||||
.. _incremental-garbage-collection:
|
||||
.. _whatsnew314-incremental-gc:
|
||||
|
||||
Incremental garbage collection
|
||||
------------------------------
|
||||
Garbage collection
|
||||
------------------
|
||||
|
||||
**From Python 3.14.5 onwards:**
|
||||
|
||||
The garbage collector (GC) has changed in Python 3.14.5.
|
||||
|
||||
Python 3.14.0-3.14.4 shipped with a new incremental GC.
|
||||
However, due to a number of `reports
|
||||
<https://github.com/python/cpython/issues/142516>`__
|
||||
of significant memory pressure in production environments,
|
||||
it has been reverted back to the generational GC from 3.13.
|
||||
This is the GC now used in Python 3.14.5 and later.
|
||||
|
||||
**Previously in Python 3.14.0-3.14.4:**
|
||||
|
||||
The cycle garbage collector is now incremental.
|
||||
This means that maximum pause times are reduced
|
||||
|
|
@ -2203,7 +2217,18 @@ difflib
|
|||
gc
|
||||
--
|
||||
|
||||
* The new :ref:`incremental garbage collector <whatsnew314-incremental-gc>`
|
||||
* **From Python 3.14.5 onwards:**
|
||||
|
||||
Python 3.14.0-3.14.4 shipped with a new incremental garbage collector.
|
||||
However, due to a number of `reports
|
||||
<https://github.com/python/cpython/issues/142516>`__
|
||||
of significant memory pressure in production environments,
|
||||
it has been reverted back to the generational GC from 3.13.
|
||||
This is the GC now used in Python 3.14.5 and later.
|
||||
|
||||
* **Previously in Python 3.14.0-3.14.4:**
|
||||
|
||||
The new :ref:`incremental garbage collector <whatsnew314-incremental-gc>`
|
||||
means that maximum pause times are reduced
|
||||
by an order of magnitude or more for larger heaps.
|
||||
|
||||
|
|
@ -3447,3 +3472,17 @@ Changes in the C API
|
|||
functions on Python 3.13 and older.
|
||||
|
||||
.. _pythoncapi-compat project: https://github.com/python/pythoncapi-compat/
|
||||
|
||||
|
||||
Notable changes in 3.14.5
|
||||
=========================
|
||||
|
||||
gc
|
||||
--
|
||||
|
||||
* The incremental garbage collector shipped in Python 3.14.0-3.14.4 has been
|
||||
reverted back to the generational garbage collector from 3.13,
|
||||
due to a number of `reports
|
||||
<https://github.com/python/cpython/issues/142516>`__
|
||||
of significant memory pressure in production environments.
|
||||
See :ref:`whatsnew314-incremental-gc` for details.
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ Summary -- Release highlights
|
|||
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
|
||||
<whatsnew315-pybyteswriter>`
|
||||
* :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
|
||||
* :pep:`831`: :ref:`Frame pointers everywhere <whatsnew315-frame-pointers>`
|
||||
* :ref:`The JIT compiler has been significantly upgraded <whatsnew315-jit>`
|
||||
* :ref:`Improved error messages <whatsnew315-improved-error-messages>`
|
||||
* :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter
|
||||
|
|
@ -914,6 +915,16 @@ faulthandler
|
|||
(Contributed by Eric Froemling in :gh:`149085`.)
|
||||
|
||||
|
||||
email
|
||||
-----
|
||||
|
||||
* Email generators now raise an error when an :class:`.EmailMessage` cannot be
|
||||
accurately flattened due to a non-ASCII email address (mailbox) in an address
|
||||
header. Options for supporting Email Address Internationalization (EAI) are
|
||||
discussed in :attr:`.EmailPolicy.utf8`.
|
||||
(Contributed by R David Murray and Mike Edmunds in :gh:`122540`.)
|
||||
|
||||
|
||||
functools
|
||||
---------
|
||||
|
||||
|
|
@ -1268,6 +1279,16 @@ tarfile
|
|||
(Contributed by Christoph Walcher in :gh:`57911`.)
|
||||
|
||||
|
||||
threading
|
||||
---------
|
||||
|
||||
* Added :class:`~threading.serialize_iterator`,
|
||||
:func:`~threading.synchronized_iterator`,
|
||||
and :func:`~threading.concurrent_tee` to support concurrent access to
|
||||
generators and iterators.
|
||||
(Contributed by Raymond Hettinger in :gh:`124397`.)
|
||||
|
||||
|
||||
timeit
|
||||
------
|
||||
|
||||
|
|
@ -1579,11 +1600,11 @@ Upgraded JIT compiler
|
|||
|
||||
Results from the `pyperformance <https://github.com/python/pyperformance>`__
|
||||
benchmark suite report
|
||||
`6-7% <https://www.doesjitgobrrr.com/run/2026-04-01>`__
|
||||
`8-9% <https://www.doesjitgobrrr.com/run/2026-04-29>`__
|
||||
geometric mean performance improvement for the JIT over the standard CPython
|
||||
interpreter built with all optimizations enabled on x86-64 Linux. On AArch64
|
||||
macOS, the JIT has a
|
||||
`12-13% <https://www.doesjitgobrrr.com/run/2026-04-01>`__
|
||||
`12-13% <https://www.doesjitgobrrr.com/run/2026-04-29>`__
|
||||
speedup over the :ref:`tail calling interpreter <whatsnew314-tail-call-interpreter>`
|
||||
with all optimizations enabled. The speedups for JIT
|
||||
builds versus no JIT builds range from roughly 15% slowdown to over
|
||||
|
|
@ -1825,6 +1846,13 @@ Deprecated
|
|||
New deprecations
|
||||
----------------
|
||||
|
||||
* :mod:`ast`
|
||||
|
||||
* Creating instances of abstract AST nodes (such as :class:`ast.AST`
|
||||
or :class:`!ast.expr`) is deprecated and will raise an error in Python 3.20.
|
||||
|
||||
(Contributed by Brian Schubert in :gh:`116021`.)
|
||||
|
||||
* :mod:`base64`:
|
||||
|
||||
* Accepting the ``+`` and ``/`` characters with an alternative alphabet in
|
||||
|
|
@ -2252,6 +2280,16 @@ Build changes
|
|||
and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode <debug-build>`.
|
||||
(Contributed by Donghee Na in :gh:`141770`.)
|
||||
|
||||
.. _whatsnew315-frame-pointers:
|
||||
|
||||
* CPython is now built with frame pointers enabled by default
|
||||
(:pep:`831`). Pass :option:`--without-frame-pointers` to opt out.
|
||||
Authors of C extensions and native libraries built with custom build
|
||||
systems should add ``-fno-omit-frame-pointer`` and
|
||||
``-mno-omit-leaf-frame-pointer`` to their own ``CFLAGS`` to keep the
|
||||
unwind chain intact.
|
||||
(Contributed by Pablo Galindo Salgado and Savannah Ostrowski in :gh:`149201`.)
|
||||
|
||||
.. _whatsnew315-windows-tail-calling-interpreter:
|
||||
|
||||
* 64-bit builds using Visual Studio 2026 (MSVC 18) may now use the new
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ typedef struct {
|
|||
PyAPI_FUNC(int) PyUnstable_PerfMapState_Init(void);
|
||||
PyAPI_FUNC(int) PyUnstable_WritePerfMapEntry(
|
||||
const void *code_addr,
|
||||
unsigned int code_size,
|
||||
size_t code_size,
|
||||
const char *entry_name);
|
||||
PyAPI_FUNC(void) PyUnstable_PerfMapState_Fini(void);
|
||||
PyAPI_FUNC(int) PyUnstable_CopyPerfMapFile(const char* parent_filename);
|
||||
|
|
|
|||
1
Include/internal/pycore_ast_state.h
generated
1
Include/internal/pycore_ast_state.h
generated
|
|
@ -161,6 +161,7 @@ struct ast_state {
|
|||
PyObject *__module__;
|
||||
PyObject *_attributes;
|
||||
PyObject *_fields;
|
||||
PyObject *abstract_types;
|
||||
PyObject *alias_type;
|
||||
PyObject *annotation;
|
||||
PyObject *arg;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ typedef struct {
|
|||
void* (*init_state)(void);
|
||||
// Callback to register every trampoline being created
|
||||
void (*write_state)(void* state, const void *code_addr,
|
||||
unsigned int code_size, PyCodeObject* code);
|
||||
size_t code_size, PyCodeObject* code);
|
||||
// Callback to free the trampoline state
|
||||
int (*free_state)(void* state);
|
||||
} _PyPerf_Callbacks;
|
||||
|
|
@ -108,6 +108,10 @@ extern PyStatus _PyPerfTrampoline_AfterFork_Child(void);
|
|||
#ifdef PY_HAVE_PERF_TRAMPOLINE
|
||||
extern _PyPerf_Callbacks _Py_perfmap_callbacks;
|
||||
extern _PyPerf_Callbacks _Py_perfmap_jit_callbacks;
|
||||
extern void _PyPerfJit_WriteNamedCode(const void *code_addr,
|
||||
size_t code_size,
|
||||
const char *entry,
|
||||
const char *filename);
|
||||
#endif
|
||||
|
||||
static inline PyObject*
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ typedef struct _Py_DebugOffsets {
|
|||
uint64_t state;
|
||||
uint64_t length;
|
||||
uint64_t asciiobject_size;
|
||||
uint64_t compactunicodeobject_size;
|
||||
} unicode_object;
|
||||
|
||||
// GC runtime state offset;
|
||||
|
|
@ -370,6 +371,7 @@ typedef struct _Py_DebugOffsets {
|
|||
.state = offsetof(PyUnicodeObject, _base._base.state), \
|
||||
.length = offsetof(PyUnicodeObject, _base._base.length), \
|
||||
.asciiobject_size = sizeof(PyASCIIObject), \
|
||||
.compactunicodeobject_size = sizeof(PyCompactUnicodeObject), \
|
||||
}, \
|
||||
.gc = { \
|
||||
.size = sizeof(struct _gc_runtime_state), \
|
||||
|
|
|
|||
|
|
@ -1582,6 +1582,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
|
|||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(align));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all_interpreters));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(all_threads));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code));
|
||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alphabet));
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@ struct _Py_global_strings {
|
|||
STRUCT_FOR_ID(alias)
|
||||
STRUCT_FOR_ID(align)
|
||||
STRUCT_FOR_ID(all)
|
||||
STRUCT_FOR_ID(all_interpreters)
|
||||
STRUCT_FOR_ID(all_threads)
|
||||
STRUCT_FOR_ID(allow_code)
|
||||
STRUCT_FOR_ID(alphabet)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ struct code_arena_st;
|
|||
struct trampoline_api_st {
|
||||
void* (*init_state)(void);
|
||||
void (*write_state)(void* state, const void *code_addr,
|
||||
unsigned int code_size, PyCodeObject* code);
|
||||
size_t code_size, PyCodeObject* code);
|
||||
int (*free_state)(void* state);
|
||||
void *state;
|
||||
Py_ssize_t code_padding;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ typedef _Py_CODEUNIT *(*jit_func)(
|
|||
_PyStackRef _tos_cache0, _PyStackRef _tos_cache1, _PyStackRef _tos_cache2
|
||||
);
|
||||
|
||||
_Py_CODEUNIT *_PyJIT(
|
||||
_Py_CODEUNIT *_PyJIT_Entry(
|
||||
_PyExecutorObject *executor, _PyInterpreterFrame *frame,
|
||||
_PyStackRef *stack_pointer, PyThreadState *tstate
|
||||
);
|
||||
|
|
|
|||
68
Include/internal/pycore_jit_unwind.h
Normal file
68
Include/internal/pycore_jit_unwind.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef Py_INTERNAL_JIT_UNWIND_H
|
||||
#define Py_INTERNAL_JIT_UNWIND_H
|
||||
|
||||
#ifndef Py_BUILD_CORE
|
||||
# error "this header requires Py_BUILD_CORE define"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_Py_JIT) && defined(__linux__) && defined(__ELF__)
|
||||
# define PY_HAVE_JIT_GDB_UNWIND
|
||||
#endif
|
||||
|
||||
#if defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND)
|
||||
|
||||
#if defined(PY_HAVE_JIT_GDB_UNWIND)
|
||||
extern PyMutex _Py_jit_debug_mutex;
|
||||
#endif
|
||||
|
||||
/* DWARF exception-handling pointer encodings shared by JIT unwind users. */
|
||||
enum {
|
||||
DWRF_EH_PE_absptr = 0x00,
|
||||
DWRF_EH_PE_omit = 0xff,
|
||||
|
||||
/* Data type encodings */
|
||||
DWRF_EH_PE_uleb128 = 0x01,
|
||||
DWRF_EH_PE_udata2 = 0x02,
|
||||
DWRF_EH_PE_udata4 = 0x03,
|
||||
DWRF_EH_PE_udata8 = 0x04,
|
||||
DWRF_EH_PE_sleb128 = 0x09,
|
||||
DWRF_EH_PE_sdata2 = 0x0a,
|
||||
DWRF_EH_PE_sdata4 = 0x0b,
|
||||
DWRF_EH_PE_sdata8 = 0x0c,
|
||||
DWRF_EH_PE_signed = 0x08,
|
||||
|
||||
/* Reference type encodings */
|
||||
DWRF_EH_PE_pcrel = 0x10,
|
||||
DWRF_EH_PE_textrel = 0x20,
|
||||
DWRF_EH_PE_datarel = 0x30,
|
||||
DWRF_EH_PE_funcrel = 0x40,
|
||||
DWRF_EH_PE_aligned = 0x50,
|
||||
DWRF_EH_PE_indirect = 0x80
|
||||
};
|
||||
|
||||
/* Return the size of the generated .eh_frame data for the given encoding. */
|
||||
size_t _PyJitUnwind_EhFrameSize(int absolute_addr);
|
||||
|
||||
/*
|
||||
* Build DWARF .eh_frame data for JIT code; returns size written or 0 on error.
|
||||
* absolute_addr selects the FDE address encoding:
|
||||
* - 0: PC-relative offsets (perf jitdump synthesized DSO).
|
||||
* - nonzero: absolute addresses (GDB JIT in-memory ELF).
|
||||
*/
|
||||
size_t _PyJitUnwind_BuildEhFrame(uint8_t *buffer, size_t buffer_size,
|
||||
const void *code_addr, size_t code_size,
|
||||
int absolute_addr);
|
||||
|
||||
void *_PyJitUnwind_GdbRegisterCode(const void *code_addr,
|
||||
size_t code_size,
|
||||
const char *entry,
|
||||
const char *filename);
|
||||
|
||||
void _PyJitUnwind_GdbUnregisterCode(void *handle);
|
||||
|
||||
#endif // defined(PY_HAVE_PERF_TRAMPOLINE) || defined(PY_HAVE_JIT_GDB_UNWIND)
|
||||
|
||||
#endif // Py_INTERNAL_JIT_UNWIND_H
|
||||
6
Include/internal/pycore_opcode_metadata.h
generated
6
Include/internal/pycore_opcode_metadata.h
generated
|
|
@ -1236,7 +1236,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = {
|
|||
[LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG },
|
||||
[LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
|
||||
[LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
|
||||
[LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG },
|
||||
[LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
[LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
[LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
[LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG },
|
||||
|
|
@ -1460,8 +1460,8 @@ _PyOpcode_macro_expansion[256] = {
|
|||
[LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, OPARG_SIMPLE, 0 } } },
|
||||
[LIST_EXTEND] = { .nuops = 2, .uops = { { _LIST_EXTEND, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } },
|
||||
[LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, OPARG_SIMPLE, 8 } } },
|
||||
[LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_CLASS] = { .nuops = 4, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 1 }, { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { .nuops = 7, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_PEP_523, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME, 2, 3 }, { _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME, OPERAND1_4, 5 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 9 }, { _PUSH_FRAME, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } },
|
||||
[LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 4, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 1, 3 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } },
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ extern "C" {
|
|||
#define CONSTANT_BUILTIN_ANY 4
|
||||
#define CONSTANT_BUILTIN_LIST 5
|
||||
#define CONSTANT_BUILTIN_SET 6
|
||||
#define NUM_COMMON_CONSTANTS 7
|
||||
#define CONSTANT_NONE 7
|
||||
#define CONSTANT_EMPTY_STR 8
|
||||
#define CONSTANT_TRUE 9
|
||||
#define CONSTANT_FALSE 10
|
||||
#define CONSTANT_MINUS_ONE 11
|
||||
#define NUM_COMMON_CONSTANTS 12
|
||||
|
||||
/* Values used in the oparg for RESUME */
|
||||
#define RESUME_AT_FUNC_START 0
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ typedef struct _PyExecutorObject {
|
|||
uint32_t code_size;
|
||||
size_t jit_size;
|
||||
void *jit_code;
|
||||
void *jit_gdb_handle;
|
||||
_PyExitData exits[1];
|
||||
} _PyExecutorObject;
|
||||
|
||||
|
|
|
|||
1
Include/internal/pycore_runtime_init_generated.h
generated
1
Include/internal/pycore_runtime_init_generated.h
generated
|
|
@ -1580,6 +1580,7 @@ extern "C" {
|
|||
INIT_ID(alias), \
|
||||
INIT_ID(align), \
|
||||
INIT_ID(all), \
|
||||
INIT_ID(all_interpreters), \
|
||||
INIT_ID(all_threads), \
|
||||
INIT_ID(allow_code), \
|
||||
INIT_ID(alphabet), \
|
||||
|
|
|
|||
|
|
@ -1000,6 +1000,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
|
|||
_PyUnicode_InternStatic(interp, &string);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
assert(PyUnicode_GET_LENGTH(string) != 1);
|
||||
string = &_Py_ID(all_interpreters);
|
||||
_PyUnicode_InternStatic(interp, &string);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
assert(PyUnicode_GET_LENGTH(string) != 1);
|
||||
string = &_Py_ID(all_threads);
|
||||
_PyUnicode_InternStatic(interp, &string);
|
||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||
|
|
|
|||
2049
Include/internal/pycore_uop_ids.h
generated
2049
Include/internal/pycore_uop_ids.h
generated
File diff suppressed because it is too large
Load diff
21
Include/internal/pycore_uop_metadata.h
generated
21
Include/internal/pycore_uop_metadata.h
generated
|
|
@ -397,6 +397,7 @@ 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,
|
||||
[_RROT_3] = HAS_PURE_FLAG,
|
||||
[_START_EXECUTOR] = HAS_DEOPT_FLAG,
|
||||
[_MAKE_WARM] = 0,
|
||||
[_FATAL_ERROR] = 0,
|
||||
|
|
@ -3699,6 +3700,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = {
|
|||
{ -1, -1, -1 },
|
||||
},
|
||||
},
|
||||
[_RROT_3] = {
|
||||
.best = { 0, 1, 2, 3 },
|
||||
.entries = {
|
||||
{ 3, 0, _RROT_3_r03 },
|
||||
{ 3, 1, _RROT_3_r13 },
|
||||
{ 3, 2, _RROT_3_r23 },
|
||||
{ 3, 3, _RROT_3_r33 },
|
||||
},
|
||||
},
|
||||
[_START_EXECUTOR] = {
|
||||
.best = { 0, 0, 0, 0 },
|
||||
.entries = {
|
||||
|
|
@ -4695,6 +4705,10 @@ 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,
|
||||
[_RROT_3_r03] = _RROT_3,
|
||||
[_RROT_3_r13] = _RROT_3,
|
||||
[_RROT_3_r23] = _RROT_3,
|
||||
[_RROT_3_r33] = _RROT_3,
|
||||
[_START_EXECUTOR_r00] = _START_EXECUTOR,
|
||||
[_MAKE_WARM_r00] = _MAKE_WARM,
|
||||
[_MAKE_WARM_r11] = _MAKE_WARM,
|
||||
|
|
@ -5896,6 +5910,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = {
|
|||
[_RETURN_GENERATOR_r01] = "_RETURN_GENERATOR_r01",
|
||||
[_RETURN_VALUE] = "_RETURN_VALUE",
|
||||
[_RETURN_VALUE_r11] = "_RETURN_VALUE_r11",
|
||||
[_RROT_3] = "_RROT_3",
|
||||
[_RROT_3_r03] = "_RROT_3_r03",
|
||||
[_RROT_3_r13] = "_RROT_3_r13",
|
||||
[_RROT_3_r23] = "_RROT_3_r23",
|
||||
[_RROT_3_r33] = "_RROT_3_r33",
|
||||
[_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET",
|
||||
[_SAVE_RETURN_OFFSET_r00] = "_SAVE_RETURN_OFFSET_r00",
|
||||
[_SAVE_RETURN_OFFSET_r11] = "_SAVE_RETURN_OFFSET_r11",
|
||||
|
|
@ -6808,6 +6827,8 @@ int _PyUop_num_popped(int opcode, int oparg)
|
|||
return 0;
|
||||
case _LOAD_CONST_INLINE_BORROW:
|
||||
return 0;
|
||||
case _RROT_3:
|
||||
return 0;
|
||||
case _START_EXECUTOR:
|
||||
return 0;
|
||||
case _MAKE_WARM:
|
||||
|
|
|
|||
|
|
@ -553,6 +553,7 @@ extern "C" {
|
|||
# if !defined(_Py_MEMORY_SANITIZER)
|
||||
# define _Py_MEMORY_SANITIZER
|
||||
# define _Py_NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory))
|
||||
# define _Py_MSAN_UNPOISON(PTR, SIZE) (__msan_unpoison(PTR, SIZE))
|
||||
# endif
|
||||
# endif
|
||||
# if __has_feature(address_sanitizer)
|
||||
|
|
@ -591,6 +592,9 @@ extern "C" {
|
|||
#ifndef _Py_NO_SANITIZE_MEMORY
|
||||
# define _Py_NO_SANITIZE_MEMORY
|
||||
#endif
|
||||
#ifndef _Py_MSAN_UNPOISON
|
||||
# define _Py_MSAN_UNPOISON(PTR, SIZE)
|
||||
#endif
|
||||
|
||||
/* AIX has __bool__ redefined in it's system header file. */
|
||||
#if defined(_AIX) && defined(__bool__)
|
||||
|
|
|
|||
|
|
@ -38,12 +38,13 @@
|
|||
)
|
||||
from .layout import LayoutMap, LayoutResult, LayoutRow, WrappedRow, layout_content_lines
|
||||
from .render import RenderCell, RenderLine, RenderedScreen, ScreenOverlay
|
||||
from .utils import ANSI_ESCAPE_SEQUENCE, THEME, StyleRef, wlen, gen_colors
|
||||
from .utils import ANSI_ESCAPE_SEQUENCE, ColorSpan, THEME, StyleRef, wlen, gen_colors
|
||||
from .trace import trace
|
||||
|
||||
|
||||
# types
|
||||
Command = commands.Command
|
||||
from collections.abc import Callable, Iterator
|
||||
from .types import (
|
||||
Callback,
|
||||
CommandName,
|
||||
|
|
@ -304,6 +305,7 @@ class Reader:
|
|||
lxy: CursorXY = field(init=False)
|
||||
scheduled_commands: list[CommandName] = field(default_factory=list)
|
||||
can_colorize: bool = False
|
||||
gen_colors: Callable[[str], Iterator[ColorSpan]] = gen_colors
|
||||
threading_hook: Callback | None = None
|
||||
invalidation: RefreshInvalidation = field(init=False)
|
||||
|
||||
|
|
@ -534,7 +536,7 @@ def _build_content_lines(
|
|||
prompt_from_cache: bool,
|
||||
) -> tuple[ContentLine, ...]:
|
||||
if self.can_colorize:
|
||||
colors = list(gen_colors(self.get_unicode()))
|
||||
colors = list(self.gen_colors(self.get_unicode()))
|
||||
else:
|
||||
colors = None
|
||||
trace("colors = {colors}", colors=colors)
|
||||
|
|
|
|||
13
Lib/dis.py
13
Lib/dis.py
|
|
@ -643,6 +643,7 @@ def get_argval_argrepr(self, op, arg, offset):
|
|||
argrepr = _intrinsic_2_descs[arg]
|
||||
elif deop == LOAD_COMMON_CONSTANT:
|
||||
obj = _common_constants[arg]
|
||||
argval = obj
|
||||
if isinstance(obj, type):
|
||||
argrepr = obj.__name__
|
||||
else:
|
||||
|
|
@ -692,10 +693,15 @@ def _get_const_value(op, arg, co_consts):
|
|||
Otherwise (if it is a LOAD_CONST and co_consts is not
|
||||
provided) returns the dis.UNKNOWN sentinel.
|
||||
"""
|
||||
assert op in hasconst or op == LOAD_SMALL_INT
|
||||
assert op in hasconst or op == LOAD_SMALL_INT or op == LOAD_COMMON_CONSTANT
|
||||
|
||||
if op == LOAD_SMALL_INT:
|
||||
return arg
|
||||
if op == LOAD_COMMON_CONSTANT:
|
||||
# Opargs 0-6 are callables; 7-11 are literal values.
|
||||
if 7 <= arg <= 11:
|
||||
return _common_constants[arg]
|
||||
return UNKNOWN
|
||||
argval = UNKNOWN
|
||||
if co_consts is not None:
|
||||
argval = co_consts[arg]
|
||||
|
|
@ -1015,8 +1021,9 @@ def _find_imports(co):
|
|||
if op == IMPORT_NAME and i >= 2:
|
||||
from_op = opargs[i-1]
|
||||
level_op = opargs[i-2]
|
||||
if (from_op[0] in hasconst and
|
||||
(level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)):
|
||||
if ((from_op[0] in hasconst or from_op[0] == LOAD_COMMON_CONSTANT) and
|
||||
(level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT or
|
||||
level_op[0] == LOAD_COMMON_CONSTANT)):
|
||||
level = _get_const_value(level_op[0], level_op[1], consts)
|
||||
fromlist = _get_const_value(from_op[0], from_op[1], consts)
|
||||
# IMPORT_NAME encodes lazy/eager flags in bits 0-1,
|
||||
|
|
|
|||
|
|
@ -157,10 +157,7 @@ def all_defects(self):
|
|||
def startswith_fws(self):
|
||||
return self[0].startswith_fws()
|
||||
|
||||
@property
|
||||
def as_ew_allowed(self):
|
||||
"""True if all top level tokens of this part may be RFC2047 encoded."""
|
||||
return all(part.as_ew_allowed for part in self)
|
||||
as_ew_allowed = True
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
|
|
@ -429,6 +426,7 @@ def addr_spec(self):
|
|||
class AngleAddr(TokenList):
|
||||
|
||||
token_type = 'angle-addr'
|
||||
as_ew_allowed = False
|
||||
|
||||
@property
|
||||
def local_part(self):
|
||||
|
|
@ -847,26 +845,22 @@ def params(self):
|
|||
|
||||
class ContentType(ParameterizedHeaderValue):
|
||||
token_type = 'content-type'
|
||||
as_ew_allowed = False
|
||||
maintype = 'text'
|
||||
subtype = 'plain'
|
||||
|
||||
|
||||
class ContentDisposition(ParameterizedHeaderValue):
|
||||
token_type = 'content-disposition'
|
||||
as_ew_allowed = False
|
||||
content_disposition = None
|
||||
|
||||
|
||||
class ContentTransferEncoding(TokenList):
|
||||
token_type = 'content-transfer-encoding'
|
||||
as_ew_allowed = False
|
||||
cte = '7bit'
|
||||
|
||||
|
||||
class HeaderLabel(TokenList):
|
||||
token_type = 'header-label'
|
||||
as_ew_allowed = False
|
||||
|
||||
|
||||
class MsgID(TokenList):
|
||||
|
|
@ -1509,11 +1503,6 @@ def get_local_part(value):
|
|||
local_part.defects.append(errors.ObsoleteHeaderDefect(
|
||||
"local-part is not a dot-atom (contains CFWS)"))
|
||||
local_part[0] = obs_local_part
|
||||
try:
|
||||
local_part.value.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
local_part.defects.append(errors.NonASCIILocalPartDefect(
|
||||
"local-part contains non-ASCII characters)"))
|
||||
return local_part, value
|
||||
|
||||
def get_obs_local_part(value):
|
||||
|
|
@ -2835,13 +2824,68 @@ def _steal_trailing_WSP_if_exists(lines):
|
|||
|
||||
|
||||
def _refold_parse_tree(parse_tree, *, policy):
|
||||
"""Return string of contents of parse_tree folded according to RFC rules.
|
||||
|
||||
"""
|
||||
# max_line_length 0/None means no limit, ie: infinitely long.
|
||||
maxlen = policy.max_line_length or sys.maxsize
|
||||
encoding = 'utf-8' if policy.utf8 else 'us-ascii'
|
||||
lines = [''] # Folded lines to be output
|
||||
if parse_tree.as_ew_allowed:
|
||||
_refold_with_ew(parse_tree, lines, maxlen, encoding, policy=policy)
|
||||
else:
|
||||
_refold_without_ew(parse_tree, lines, maxlen, encoding, policy=policy)
|
||||
return policy.linesep.join(lines) + policy.linesep
|
||||
|
||||
def _refold_without_ew(parse_tree, lines, maxlen, encoding, *, policy):
|
||||
parts = list(parse_tree)
|
||||
while parts:
|
||||
part = parts.pop(0)
|
||||
tstr = str(part)
|
||||
try:
|
||||
tstr.encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
if any(isinstance(x, errors.UndecodableBytesDefect)
|
||||
for x in part.all_defects):
|
||||
# There is garbage data from parsing a message in binary mode,
|
||||
# just pass it through. Not good, but the best we can do.
|
||||
pass
|
||||
elif policy.utf8:
|
||||
# If this happens, it's a programmer error.
|
||||
raise
|
||||
else:
|
||||
raise errors.HeaderWriteError(
|
||||
f"Non-ASCII {part.token_type} '{part}' is invalid"
|
||||
" under current policy setting (utf8=False)"
|
||||
)
|
||||
if len(tstr) <= maxlen - len(lines[-1]):
|
||||
lines[-1] += tstr
|
||||
continue
|
||||
# This part is too long to fit. The RFC wants us to break at
|
||||
# "major syntactic breaks", so unless we don't consider this
|
||||
# to be one, check if it will fit on the next line by itself.
|
||||
if (part.syntactic_break and
|
||||
len(tstr) + 1 <= maxlen):
|
||||
newline = _steal_trailing_WSP_if_exists(lines)
|
||||
if newline or part.startswith_fws():
|
||||
lines.append(newline + tstr)
|
||||
continue
|
||||
if not hasattr(part, 'encode'):
|
||||
# It's not a terminal, try folding the subparts.
|
||||
newparts = list(part)
|
||||
parts = newparts + parts
|
||||
continue
|
||||
# We can't figure out how to wrap, it, so give up.
|
||||
newline = _steal_trailing_WSP_if_exists(lines)
|
||||
if newline or part.startswith_fws():
|
||||
lines.append(newline + tstr)
|
||||
else:
|
||||
# We can't fold it onto the next line either...
|
||||
lines[-1] += tstr
|
||||
return
|
||||
|
||||
|
||||
def _refold_with_ew(parse_tree, lines, maxlen, encoding, *, policy):
|
||||
"""Return string of contents of parse_tree folded according to RFC rules.
|
||||
|
||||
"""
|
||||
last_word_is_ew = False
|
||||
last_ew = None # if there is an encoded word in the last line of lines,
|
||||
# points to the encoded word's first character
|
||||
|
|
@ -2855,6 +2899,11 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||
if part is end_ew_not_allowed:
|
||||
wrap_as_ew_blocked -= 1
|
||||
continue
|
||||
if part.token_type == 'mime-parameters':
|
||||
# Mime parameter folding (using RFC2231) is extra special.
|
||||
_fold_mime_parameters(part, lines, maxlen, encoding)
|
||||
last_word_is_ew = False
|
||||
continue
|
||||
tstr = str(part)
|
||||
if not want_encoding:
|
||||
if part.token_type in ('ptext', 'vtext'):
|
||||
|
|
@ -2876,14 +2925,11 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||
charset = 'utf-8'
|
||||
want_encoding = True
|
||||
|
||||
if part.token_type == 'mime-parameters':
|
||||
# Mime parameter folding (using RFC2231) is extra special.
|
||||
_fold_mime_parameters(part, lines, maxlen, encoding)
|
||||
last_word_is_ew = False
|
||||
continue
|
||||
|
||||
if want_encoding and not wrap_as_ew_blocked:
|
||||
if not part.as_ew_allowed:
|
||||
if any(
|
||||
not x.as_ew_allowed for x in part
|
||||
if hasattr(x, 'as_ew_allowed')
|
||||
):
|
||||
want_encoding = False
|
||||
last_ew = None
|
||||
if part.syntactic_break:
|
||||
|
|
@ -2964,6 +3010,8 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||
[ValueTerminal(make_quoted_pairs(p), 'ptext')
|
||||
for p in newparts] +
|
||||
[ValueTerminal('"', 'ptext')])
|
||||
_refold_without_ew(newparts, lines, maxlen, encoding, policy=policy)
|
||||
continue
|
||||
if part.token_type == 'comment':
|
||||
newparts = (
|
||||
[ValueTerminal('(', 'ptext')] +
|
||||
|
|
@ -2991,7 +3039,7 @@ def _refold_parse_tree(parse_tree, *, policy):
|
|||
lines[-1] += tstr
|
||||
last_word_is_ew = last_word_is_ew and not bool(tstr.strip(_WSP))
|
||||
|
||||
return policy.linesep.join(lines) + policy.linesep
|
||||
return
|
||||
|
||||
def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset, last_word_is_ew):
|
||||
"""Fold string to_encode into lines as encoded word, combining if allowed.
|
||||
|
|
|
|||
|
|
@ -109,9 +109,9 @@ class ObsoleteHeaderDefect(HeaderDefect):
|
|||
"""Header uses syntax declared obsolete by RFC 5322"""
|
||||
|
||||
class NonASCIILocalPartDefect(HeaderDefect):
|
||||
"""local_part contains non-ASCII characters"""
|
||||
# This defect only occurs during unicode parsing, not when
|
||||
# parsing messages decoded from binary.
|
||||
"""Unused. Note: this error is deprecated and may be removed in the future."""
|
||||
# RFC 6532 permits a non-ASCII local-part. _header_value_parser previously
|
||||
# treated this as a parse-time defect (when parsing Unicode, but not bytes).
|
||||
|
||||
class InvalidDateDefect(HeaderDefect):
|
||||
"""Header has unparsable or invalid date"""
|
||||
|
|
|
|||
|
|
@ -41,7 +41,10 @@
|
|||
_special_method_names = _opcode.get_special_method_names()
|
||||
_common_constants = [builtins.AssertionError, builtins.NotImplementedError,
|
||||
builtins.tuple, builtins.all, builtins.any, builtins.list,
|
||||
builtins.set]
|
||||
builtins.set,
|
||||
# Append-only — must match CONSTANT_* in
|
||||
# Include/internal/pycore_opcode_utils.h.
|
||||
None, "", True, False, -1]
|
||||
_nb_ops = _opcode.get_nb_ops()
|
||||
|
||||
hascompare = [opmap["COMPARE_OP"]]
|
||||
|
|
|
|||
|
|
@ -920,17 +920,11 @@ def save_picklebuffer(self, obj):
|
|||
# Write data in-band
|
||||
# XXX The C implementation avoids a copy here
|
||||
buf = m.tobytes()
|
||||
in_memo = id(buf) in self.memo
|
||||
if m.readonly:
|
||||
if in_memo:
|
||||
self._save_bytes_no_memo(buf)
|
||||
else:
|
||||
self.save_bytes(buf)
|
||||
self._save_bytes_no_memo(buf)
|
||||
else:
|
||||
if in_memo:
|
||||
self._save_bytearray_no_memo(buf)
|
||||
else:
|
||||
self.save_bytearray(buf)
|
||||
self._save_bytearray_no_memo(buf)
|
||||
self.memoize(obj)
|
||||
else:
|
||||
# Write data out-of-band
|
||||
self.write(NEXT_BUFFER)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD
|
|||
try:
|
||||
self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
|
||||
except RuntimeError as err:
|
||||
if os.name == "nt" and sys.executable.endswith("python.exe"):
|
||||
raise SystemExit(
|
||||
"Running profiling.sampling from virtualenv on Windows platform is not supported"
|
||||
) from err
|
||||
raise SystemExit(err) from err
|
||||
# Track sample intervals and total sample count
|
||||
self.sample_intervals = deque(maxlen=100)
|
||||
|
|
|
|||
|
|
@ -836,7 +836,11 @@ def binomialvariate(self, n=1, p=0.5):
|
|||
if not c:
|
||||
return x
|
||||
while True:
|
||||
y += _floor(_log2(random()) / c) + 1
|
||||
try:
|
||||
y += _floor(_log2(random()) / c) + 1
|
||||
except ValueError:
|
||||
# Reject case where random() returned 0.0
|
||||
continue
|
||||
if y > n:
|
||||
return x
|
||||
x += 1
|
||||
|
|
|
|||
25
Lib/runpy.py
25
Lib/runpy.py
|
|
@ -102,8 +102,10 @@ def _run_module_code(code, init_globals=None,
|
|||
|
||||
# Helper to get the full name, spec and code for a module
|
||||
def _get_module_details(mod_name, error=ImportError):
|
||||
# name= is only accepted by ImportError and its subclasses.
|
||||
kwargs = {"name": mod_name} if issubclass(error, ImportError) else {}
|
||||
if mod_name.startswith("."):
|
||||
raise error("Relative module names not supported")
|
||||
raise error("Relative module names not supported", **kwargs)
|
||||
pkg_name, _, _ = mod_name.rpartition(".")
|
||||
if pkg_name:
|
||||
# Try importing the parent to avoid catching initialization errors
|
||||
|
|
@ -136,12 +138,13 @@ def _get_module_details(mod_name, error=ImportError):
|
|||
if mod_name.endswith(".py"):
|
||||
msg += (f". Try using '{mod_name[:-3]}' instead of "
|
||||
f"'{mod_name}' as the module name.")
|
||||
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
|
||||
raise error(msg.format(mod_name, type(ex).__name__, ex),
|
||||
**kwargs) from ex
|
||||
if spec is None:
|
||||
raise error("No module named %s" % mod_name)
|
||||
raise error("No module named %s" % mod_name, **kwargs)
|
||||
if spec.submodule_search_locations is not None:
|
||||
if mod_name == "__main__" or mod_name.endswith(".__main__"):
|
||||
raise error("Cannot use package as __main__ module")
|
||||
raise error("Cannot use package as __main__ module", **kwargs)
|
||||
try:
|
||||
pkg_main_name = mod_name + ".__main__"
|
||||
return _get_module_details(pkg_main_name, error)
|
||||
|
|
@ -149,17 +152,19 @@ def _get_module_details(mod_name, error=ImportError):
|
|||
if mod_name not in sys.modules:
|
||||
raise # No module loaded; being a package is irrelevant
|
||||
raise error(("%s; %r is a package and cannot " +
|
||||
"be directly executed") %(e, mod_name))
|
||||
"be directly executed") %(e, mod_name),
|
||||
**kwargs)
|
||||
loader = spec.loader
|
||||
if loader is None:
|
||||
raise error("%r is a namespace package and cannot be executed"
|
||||
% mod_name)
|
||||
% mod_name,
|
||||
**kwargs)
|
||||
try:
|
||||
code = loader.get_code(mod_name)
|
||||
except ImportError as e:
|
||||
raise error(format(e)) from e
|
||||
raise error(format(e), **kwargs) from e
|
||||
if code is None:
|
||||
raise error("No code object available for %s" % mod_name)
|
||||
raise error("No code object available for %s" % mod_name, **kwargs)
|
||||
return mod_name, spec, code
|
||||
|
||||
class _Error(Exception):
|
||||
|
|
@ -232,6 +237,7 @@ def _get_main_module_details(error=ImportError):
|
|||
# Also moves the standard __main__ out of the way so that the
|
||||
# preexisting __loader__ entry doesn't cause issues
|
||||
main_name = "__main__"
|
||||
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
|
||||
saved_main = sys.modules[main_name]
|
||||
del sys.modules[main_name]
|
||||
try:
|
||||
|
|
@ -239,7 +245,8 @@ def _get_main_module_details(error=ImportError):
|
|||
except ImportError as exc:
|
||||
if main_name in str(exc):
|
||||
raise error("can't find %r module in %r" %
|
||||
(main_name, sys.path[0])) from exc
|
||||
(main_name, sys.path[0]),
|
||||
**kwargs) from exc
|
||||
raise
|
||||
finally:
|
||||
sys.modules[main_name] = saved_main
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ def count_positive(iterable):
|
|||
elif x == 0.0:
|
||||
found_zero = True
|
||||
else:
|
||||
raise StatisticsError('No negative inputs allowed', x)
|
||||
raise StatisticsError(f'No negative inputs allowed: {x!r}')
|
||||
|
||||
total = fsum(map(log, count_positive(data)))
|
||||
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ class _Stream:
|
|||
"""
|
||||
|
||||
def __init__(self, name, mode, comptype, fileobj, bufsize,
|
||||
compresslevel, preset):
|
||||
compresslevel, preset, mtime):
|
||||
"""Construct a _Stream object.
|
||||
"""
|
||||
self._extfileobj = True
|
||||
|
|
@ -372,7 +372,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize,
|
|||
self.exception = zlib.error
|
||||
self._init_read_gz()
|
||||
else:
|
||||
self._init_write_gz(compresslevel)
|
||||
self._init_write_gz(compresslevel, mtime)
|
||||
|
||||
elif comptype == "bz2":
|
||||
try:
|
||||
|
|
@ -421,7 +421,7 @@ def __del__(self):
|
|||
if hasattr(self, "closed") and not self.closed:
|
||||
self.close()
|
||||
|
||||
def _init_write_gz(self, compresslevel):
|
||||
def _init_write_gz(self, compresslevel, mtime):
|
||||
"""Initialize for writing with gzip compression.
|
||||
"""
|
||||
self.cmp = self.zlib.compressobj(compresslevel,
|
||||
|
|
@ -429,7 +429,9 @@ def _init_write_gz(self, compresslevel):
|
|||
-self.zlib.MAX_WBITS,
|
||||
self.zlib.DEF_MEM_LEVEL,
|
||||
0)
|
||||
timestamp = struct.pack("<L", int(time.time()))
|
||||
if mtime is None:
|
||||
mtime = int(time.time())
|
||||
timestamp = struct.pack("<L", mtime)
|
||||
self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
|
||||
if self.name.endswith(".gz"):
|
||||
self.name = self.name[:-3]
|
||||
|
|
@ -1745,7 +1747,7 @@ class TarFile(object):
|
|||
def __init__(self, name=None, mode="r", fileobj=None, format=None,
|
||||
tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
|
||||
errors="surrogateescape", pax_headers=None, debug=None,
|
||||
errorlevel=None, copybufsize=None, stream=False):
|
||||
errorlevel=None, copybufsize=None, stream=False, mtime=None):
|
||||
"""Open an (uncompressed) tar archive 'name'. 'mode' is either 'r' to
|
||||
read from an existing archive, 'a' to append data to an existing
|
||||
file or 'w' to create a new file overwriting an existing one. 'mode'
|
||||
|
|
@ -1951,8 +1953,9 @@ def not_compressed(comptype):
|
|||
|
||||
compresslevel = kwargs.pop("compresslevel", 6)
|
||||
preset = kwargs.pop("preset", None)
|
||||
mtime = kwargs.pop("mtime", None)
|
||||
stream = _Stream(name, filemode, comptype, fileobj, bufsize,
|
||||
compresslevel, preset)
|
||||
compresslevel, preset, mtime)
|
||||
try:
|
||||
t = cls(name, filemode, stream, **kwargs)
|
||||
except:
|
||||
|
|
@ -1988,7 +1991,8 @@ def gzopen(cls, name, mode="r", fileobj=None, compresslevel=6, **kwargs):
|
|||
raise CompressionError("gzip module is not available") from None
|
||||
|
||||
try:
|
||||
fileobj = GzipFile(name, mode + "b", compresslevel, fileobj)
|
||||
mtime = kwargs.pop("mtime", None)
|
||||
fileobj = GzipFile(name, mode + "b", compresslevel, fileobj, mtime=mtime)
|
||||
except OSError as e:
|
||||
if fileobj is not None and mode == 'r':
|
||||
raise ReadError("not a gzip file") from e
|
||||
|
|
|
|||
|
|
@ -3100,6 +3100,51 @@ def test_bytearray_memoization(self):
|
|||
self.assertIsNot(b2a, b2b)
|
||||
self.assert_is_copy(b2a, b2b)
|
||||
|
||||
def test_picklebuffer_memoization(self):
|
||||
if self.py_version < (3, 8):
|
||||
self.skipTest('not supported in Python < 3.8')
|
||||
array_types = [bytes, bytearray]
|
||||
for proto in range(5, pickle.HIGHEST_PROTOCOL + 1):
|
||||
for array_type in array_types:
|
||||
for s in b'', b'xyz', b'xyz'*100:
|
||||
with self.subTest(proto=proto, array_type=array_type, s=s, independent=False):
|
||||
b = pickle.PickleBuffer(array_type(s))
|
||||
p = self.dumps((b, b), proto)
|
||||
b1, b2 = self.loads(p)
|
||||
self.assertIs(b1, b2)
|
||||
|
||||
with self.subTest(proto=proto, array_type=array_type, s=s, independent=True):
|
||||
b = array_type(s)
|
||||
b1a = pickle.PickleBuffer(b)
|
||||
b2a = pickle.PickleBuffer(b)
|
||||
p = self.dumps((b1a, b2a), proto)
|
||||
b1b, b2b = self.loads(p)
|
||||
if array_type is not bytes:
|
||||
self.assertIsNot(b1b, b2b)
|
||||
self.assert_is_copy(b1b, b)
|
||||
self.assert_is_copy(b2b, b)
|
||||
|
||||
def test_empty_picklebuffer_memoization(self):
|
||||
# gh-148914: Empty writable PickleBuffer memoized an empty bytearray
|
||||
# with the id of b'' (a singleton in CPython).
|
||||
if self.py_version < (3, 8):
|
||||
self.skipTest('not supported in Python < 3.8')
|
||||
for proto in range(5, pickle.HIGHEST_PROTOCOL + 1):
|
||||
for readonly in False, True:
|
||||
with self.subTest(proto=proto, readonly=readonly):
|
||||
b = b''
|
||||
ba = bytearray()
|
||||
buf = pickle.PickleBuffer(b if readonly else ba)
|
||||
p = self.dumps((buf, b, ba), proto)
|
||||
buf, b, ba = self.loads(p)
|
||||
array_type = bytes if readonly else bytearray
|
||||
self.assertIsInstance(buf, array_type)
|
||||
self.assertIsInstance(b, bytes)
|
||||
self.assertIsInstance(ba, bytearray)
|
||||
self.assertEqual(buf, b'')
|
||||
self.assertEqual(b, b'')
|
||||
self.assertEqual(ba, b'')
|
||||
|
||||
def test_ints(self):
|
||||
for proto in protocols:
|
||||
n = sys.maxsize
|
||||
|
|
|
|||
|
|
@ -1619,6 +1619,84 @@ def annotate(format, /):
|
|||
# Some non-Format value
|
||||
annotationlib.call_annotate_function(annotate, 7)
|
||||
|
||||
def test_basic_non_function_annotate(self):
|
||||
class Annotate:
|
||||
def __call__(self, format, /, __Format=Format,
|
||||
__NotImplementedError=NotImplementedError):
|
||||
if format == __Format.VALUE:
|
||||
return {'x': str}
|
||||
elif format == __Format.VALUE_WITH_FAKE_GLOBALS:
|
||||
return {'x': int}
|
||||
elif format == __Format.STRING:
|
||||
return {'x': "float"}
|
||||
else:
|
||||
raise __NotImplementedError(format)
|
||||
|
||||
annotations = annotationlib.call_annotate_function(Annotate(), Format.VALUE)
|
||||
self.assertEqual(annotations, {"x": str})
|
||||
|
||||
annotations = annotationlib.call_annotate_function(Annotate(), Format.STRING)
|
||||
self.assertEqual(annotations, {"x": "float"})
|
||||
|
||||
with self.assertRaises(AttributeError) as cm:
|
||||
annotations = annotationlib.call_annotate_function(
|
||||
Annotate(), Format.FORWARDREF
|
||||
)
|
||||
|
||||
self.assertEqual(cm.exception.name, "__builtins__")
|
||||
self.assertIsInstance(cm.exception.obj, Annotate)
|
||||
|
||||
def test_full_non_function_annotate(self):
|
||||
def outer():
|
||||
local = str
|
||||
|
||||
class Annotate:
|
||||
called_formats = []
|
||||
|
||||
def __call__(self, format=None, *, _self=None):
|
||||
nonlocal local
|
||||
if _self is not None:
|
||||
self, format = _self, self
|
||||
|
||||
self.called_formats.append(format)
|
||||
if format == 1: # VALUE
|
||||
return {"x": MyClass, "y": int, "z": local}
|
||||
if format == 2: # VALUE_WITH_FAKE_GLOBALS
|
||||
return {"w": unknown, "x": MyClass, "y": int, "z": local}
|
||||
raise NotImplementedError
|
||||
|
||||
__globals__ = {"MyClass": MyClass}
|
||||
__builtins__ = {"int": int}
|
||||
__closure__ = (types.CellType(str),)
|
||||
__defaults__ = (None,)
|
||||
|
||||
__kwdefaults__ = property(lambda self: dict(_self=self))
|
||||
__code__ = property(lambda self: self.__call__.__code__)
|
||||
|
||||
return Annotate()
|
||||
|
||||
annotate = outer()
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.call_annotate_function(annotate, Format.VALUE),
|
||||
{"x": MyClass, "y": int, "z": str}
|
||||
)
|
||||
self.assertEqual(annotate.called_formats[-1], Format.VALUE)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.call_annotate_function(annotate, Format.STRING),
|
||||
{"w": "unknown", "x": "MyClass", "y": "int", "z": "local"}
|
||||
)
|
||||
self.assertIn(Format.STRING, annotate.called_formats)
|
||||
self.assertEqual(annotate.called_formats[-1], Format.VALUE_WITH_FAKE_GLOBALS)
|
||||
|
||||
self.assertEqual(
|
||||
annotationlib.call_annotate_function(annotate, Format.FORWARDREF),
|
||||
{"w": support.EqualToForwardRef("unknown"), "x": MyClass, "y": int, "z": str}
|
||||
)
|
||||
self.assertIn(Format.FORWARDREF, annotate.called_formats)
|
||||
self.assertEqual(annotate.called_formats[-1], Format.VALUE_WITH_FAKE_GLOBALS)
|
||||
|
||||
def test_error_from_value_raised(self):
|
||||
# Test that the error from format.VALUE is raised
|
||||
# if all formats fail
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import _ast_unparse
|
||||
import _ast
|
||||
import ast
|
||||
import builtins
|
||||
import contextlib
|
||||
|
|
@ -85,7 +86,9 @@ def _assertTrueorder(self, ast_node, parent_pos):
|
|||
self.assertEqual(ast_node._fields, ast_node.__match_args__)
|
||||
|
||||
def test_AST_objects(self):
|
||||
x = ast.AST()
|
||||
# Directly instantiating abstract node class AST is allowed (but deprecated)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
x = ast.AST()
|
||||
self.assertEqual(x._fields, ())
|
||||
x.foobar = 42
|
||||
self.assertEqual(x.foobar, 42)
|
||||
|
|
@ -94,7 +97,7 @@ def test_AST_objects(self):
|
|||
with self.assertRaises(AttributeError):
|
||||
x.vararg
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
with self.assertRaises(TypeError), self.assertWarns(DeprecationWarning):
|
||||
# "ast.AST constructor takes 0 positional arguments"
|
||||
ast.AST(2)
|
||||
|
||||
|
|
@ -110,15 +113,21 @@ def cleanup():
|
|||
|
||||
msg = "type object 'ast.AST' has no attribute '_fields'"
|
||||
# Both examples used to crash:
|
||||
with self.assertRaisesRegex(AttributeError, msg):
|
||||
with (
|
||||
self.assertRaisesRegex(AttributeError, msg),
|
||||
self.assertWarns(DeprecationWarning),
|
||||
):
|
||||
ast.AST(arg1=123)
|
||||
with self.assertRaisesRegex(AttributeError, msg):
|
||||
with (
|
||||
self.assertRaisesRegex(AttributeError, msg),
|
||||
self.assertWarns(DeprecationWarning),
|
||||
):
|
||||
ast.AST()
|
||||
|
||||
def test_AST_garbage_collection(self):
|
||||
def test_node_garbage_collection(self):
|
||||
class X:
|
||||
pass
|
||||
a = ast.AST()
|
||||
a = ast.Module()
|
||||
a.x = X()
|
||||
a.x.a = a
|
||||
ref = weakref.ref(a.x)
|
||||
|
|
@ -439,7 +448,15 @@ def _construct_ast_class(self, cls):
|
|||
elif typ is object:
|
||||
kwargs[name] = b'capybara'
|
||||
elif isinstance(typ, type) and issubclass(typ, ast.AST):
|
||||
kwargs[name] = self._construct_ast_class(typ)
|
||||
if _ast._is_abstract(typ):
|
||||
# Use an arbitrary concrete subclass
|
||||
concrete = next((sub for sub in typ.__subclasses__()
|
||||
if not _ast._is_abstract(sub)), None)
|
||||
msg = f"abstract node class {typ} has no concrete subclasses"
|
||||
self.assertIsNotNone(concrete, msg)
|
||||
else:
|
||||
concrete = typ
|
||||
kwargs[name] = self._construct_ast_class(concrete)
|
||||
return cls(**kwargs)
|
||||
|
||||
def test_arguments(self):
|
||||
|
|
@ -578,6 +595,10 @@ def test_nodeclasses(self):
|
|||
with self.assertRaisesRegex(TypeError, re.escape(msg)):
|
||||
ast.BinOp(1, 2, 3, foobarbaz=42)
|
||||
|
||||
# Directly instantiating abstract node types is allowed (but deprecated)
|
||||
self.assertWarns(DeprecationWarning, ast.stmt)
|
||||
self.assertWarns(DeprecationWarning, ast.expr_context)
|
||||
|
||||
def test_no_fields(self):
|
||||
# this used to fail because Sub._fields was None
|
||||
x = ast.Sub()
|
||||
|
|
@ -585,7 +606,10 @@ def test_no_fields(self):
|
|||
|
||||
def test_invalid_sum(self):
|
||||
pos = dict(lineno=2, col_offset=3)
|
||||
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], [])
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
# Creating instances of ast.expr is deprecated
|
||||
e = ast.expr(**pos)
|
||||
m = ast.Module([ast.Expr(e, **pos)], [])
|
||||
with self.assertRaises(TypeError) as cm:
|
||||
compile(m, "<test>", "exec")
|
||||
self.assertIn("but got expr()", str(cm.exception))
|
||||
|
|
@ -1107,14 +1131,19 @@ class CopyTests(unittest.TestCase):
|
|||
def iter_ast_classes():
|
||||
"""Iterate over the (native) subclasses of ast.AST recursively.
|
||||
|
||||
This excludes the special class ast.Index since its constructor
|
||||
returns an integer.
|
||||
This excludes:
|
||||
* abstract AST nodes
|
||||
* the special class ast.Index, since its constructor returns
|
||||
an integer.
|
||||
"""
|
||||
def do(cls):
|
||||
if cls.__module__ != 'ast':
|
||||
return
|
||||
if cls is ast.Index:
|
||||
return
|
||||
# Don't attempt to create instances of abstract AST nodes
|
||||
if _ast._is_abstract(cls):
|
||||
return
|
||||
|
||||
yield cls
|
||||
for sub in cls.__subclasses__():
|
||||
|
|
@ -2656,11 +2685,12 @@ def test_get_docstring(self):
|
|||
|
||||
def get_load_const(self, tree):
|
||||
# Compile to bytecode, disassemble and get parameter of LOAD_CONST
|
||||
# instructions
|
||||
# and LOAD_COMMON_CONSTANT instructions
|
||||
co = compile(tree, '<string>', 'exec')
|
||||
consts = []
|
||||
for instr in dis.get_instructions(co):
|
||||
if instr.opcode in dis.hasconst:
|
||||
if instr.opcode in dis.hasconst or \
|
||||
instr.opname == 'LOAD_COMMON_CONSTANT':
|
||||
consts.append(instr.argval)
|
||||
return consts
|
||||
|
||||
|
|
|
|||
|
|
@ -3446,6 +3446,27 @@ def testfunc(n):
|
|||
self.assertIn("_BUILD_LIST", uops)
|
||||
self.assertNotIn("_LOAD_COMMON_CONSTANT", uops)
|
||||
|
||||
def test_load_common_constant_new_literals(self):
|
||||
def testfunc(n):
|
||||
x = None
|
||||
s = ""
|
||||
t = True
|
||||
f = False
|
||||
m = -1
|
||||
for _ in range(n):
|
||||
x = None
|
||||
s = ""
|
||||
t = True
|
||||
f = False
|
||||
m = -1
|
||||
return x, s, t, f, m
|
||||
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
|
||||
self.assertEqual(res, (None, "", True, False, -1))
|
||||
self.assertIsNotNone(ex)
|
||||
uops = get_opnames(ex)
|
||||
self.assertNotIn("_LOAD_COMMON_CONSTANT", uops)
|
||||
self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)
|
||||
|
||||
def test_load_small_int(self):
|
||||
def testfunc(n):
|
||||
x = 0
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
freevars: ()
|
||||
nlocals: 0
|
||||
flags: 67108867
|
||||
consts: ("'doc string'", 'None')
|
||||
consts: ("'doc string'",)
|
||||
|
||||
>>> def keywordonly_args(a,b,*,k1):
|
||||
... return a,b,k1
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
freevars: ()
|
||||
nlocals: 3
|
||||
flags: 67108995
|
||||
consts: ("'This is a docstring from async function'", 'None')
|
||||
consts: ("'This is a docstring from async function'",)
|
||||
|
||||
>>> def no_docstring(x, y, z):
|
||||
... return x + "hello" + y + z + "world"
|
||||
|
|
@ -532,7 +532,7 @@ def test_co_positions_artificial_instructions(self):
|
|||
],
|
||||
[
|
||||
("PUSH_EXC_INFO", None),
|
||||
("LOAD_CONST", None), # artificial 'None'
|
||||
("LOAD_COMMON_CONSTANT", None), # artificial 'None'
|
||||
("STORE_NAME", "e"), # XX: we know the location for this
|
||||
("DELETE_NAME", "e"),
|
||||
("RERAISE", 1),
|
||||
|
|
|
|||
|
|
@ -2485,12 +2485,13 @@ def f():
|
|||
start_line, end_line, _, _ = instr.positions
|
||||
self.assertEqual(start_line, end_line)
|
||||
|
||||
# Expect four `LOAD_CONST None` instructions:
|
||||
# Expect four `None`-loading instructions:
|
||||
# three for the no-exception __exit__ call, and one for the return.
|
||||
# They should all have the locations of the context manager ('xyz').
|
||||
|
||||
load_none = [instr for instr in dis.get_instructions(f) if
|
||||
instr.opname == 'LOAD_CONST' and instr.argval is None]
|
||||
instr.opname in ('LOAD_CONST', 'LOAD_COMMON_CONSTANT')
|
||||
and instr.argval is None]
|
||||
return_value = [instr for instr in dis.get_instructions(f) if
|
||||
instr.opname == 'RETURN_VALUE']
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ def cm(cls, x):
|
|||
COMPARE_OP 72 (==)
|
||||
LOAD_FAST_BORROW 0 (self)
|
||||
STORE_ATTR 0 (x)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,)
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ def cm(cls, x):
|
|||
COMPARE_OP 72 (==)
|
||||
LOAD_FAST_BORROW 0
|
||||
STORE_ATTR 0
|
||||
LOAD_CONST 1
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ def cm(cls, x):
|
|||
COMPARE_OP 72 (==)
|
||||
LOAD_FAST_BORROW 0 (cls)
|
||||
STORE_ATTR 0 (x)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,)
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ def cm(cls, x):
|
|||
LOAD_SMALL_INT 1
|
||||
COMPARE_OP 72 (==)
|
||||
STORE_FAST 0 (x)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,)
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ def bug708901():
|
|||
|
||||
%3d L2: END_FOR
|
||||
POP_ITER
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (bug708901.__code__.co_firstlineno,
|
||||
bug708901.__code__.co_firstlineno + 1,
|
||||
|
|
@ -229,7 +229,7 @@ def bug42562():
|
|||
|
||||
dis_bug42562 = """\
|
||||
RESUME 0
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ def wrap_func_w_kwargs():
|
|||
LOAD_CONST 1 (('c',))
|
||||
CALL_KW 3
|
||||
POP_TOP
|
||||
LOAD_CONST 2 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (wrap_func_w_kwargs.__code__.co_firstlineno,
|
||||
wrap_func_w_kwargs.__code__.co_firstlineno + 1)
|
||||
|
|
@ -295,7 +295,7 @@ def wrap_func_w_kwargs():
|
|||
IMPORT_NAME 2 (math + eager)
|
||||
CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR)
|
||||
POP_TOP
|
||||
LOAD_CONST 2 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ def wrap_func_w_kwargs():
|
|||
|
||||
%3d LOAD_GLOBAL 0 (spam)
|
||||
POP_TOP
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -331,19 +331,19 @@ def wrap_func_w_kwargs():
|
|||
|
||||
%4d LOAD_GLOBAL 0 (spam)
|
||||
POP_TOP
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
dis_module_expected_results = """\
|
||||
Disassembly of f:
|
||||
4 RESUME 0
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
Disassembly of g:
|
||||
5 RESUME 0
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
"""
|
||||
|
|
@ -368,7 +368,7 @@ def wrap_func_w_kwargs():
|
|||
LOAD_SMALL_INT 1
|
||||
BINARY_OP 0 (+)
|
||||
STORE_NAME 0 (x)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ def wrap_func_w_kwargs():
|
|||
LOAD_SMALL_INT 0
|
||||
CALL 1
|
||||
STORE_SUBSCR
|
||||
LOAD_CONST 2 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -427,7 +427,7 @@ def foo(a: int, b: str) -> str:
|
|||
MAKE_FUNCTION
|
||||
SET_FUNCTION_ATTRIBUTE 16 (annotate)
|
||||
STORE_NAME 0 (foo)
|
||||
LOAD_CONST 2 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""
|
||||
|
||||
|
|
@ -477,14 +477,14 @@ def foo(a: int, b: str) -> str:
|
|||
LOAD_ATTR 2 (__traceback__)
|
||||
STORE_FAST 1 (tb)
|
||||
L7: POP_EXCEPT
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
STORE_FAST 0 (e)
|
||||
DELETE_FAST 0 (e)
|
||||
|
||||
%4d LOAD_FAST 1 (tb)
|
||||
RETURN_VALUE
|
||||
|
||||
-- L8: LOAD_CONST 1 (None)
|
||||
-- L8: LOAD_COMMON_CONSTANT 7 (None)
|
||||
STORE_FAST 0 (e)
|
||||
DELETE_FAST 0 (e)
|
||||
RERAISE 1
|
||||
|
|
@ -554,15 +554,15 @@ def _with(c):
|
|||
%4d LOAD_SMALL_INT 1
|
||||
STORE_FAST 1 (x)
|
||||
|
||||
%4d L2: LOAD_CONST 1 (None)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_CONST 1 (None)
|
||||
%4d L2: LOAD_COMMON_CONSTANT 7 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
CALL 3
|
||||
POP_TOP
|
||||
|
||||
%4d LOAD_SMALL_INT 2
|
||||
STORE_FAST 2 (y)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
%4d L3: PUSH_EXC_INFO
|
||||
|
|
@ -579,7 +579,7 @@ def _with(c):
|
|||
|
||||
%4d LOAD_SMALL_INT 2
|
||||
STORE_FAST 2 (y)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
-- L8: COPY 3
|
||||
|
|
@ -617,7 +617,7 @@ async def _asyncwith(c):
|
|||
CALL 0
|
||||
GET_AWAITABLE 1
|
||||
PUSH_NULL
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
L2: SEND 4 (to L5)
|
||||
L3: YIELD_VALUE 1
|
||||
L4: RESUME 3
|
||||
|
|
@ -628,13 +628,13 @@ async def _asyncwith(c):
|
|||
%4d LOAD_SMALL_INT 1
|
||||
STORE_FAST 1 (x)
|
||||
|
||||
%4d L7: LOAD_CONST 0 (None)
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_CONST 0 (None)
|
||||
%4d L7: LOAD_COMMON_CONSTANT 7 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
CALL 3
|
||||
GET_AWAITABLE 2
|
||||
PUSH_NULL
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
L8: SEND 4 (to L11)
|
||||
L9: YIELD_VALUE 1
|
||||
L10: RESUME 3
|
||||
|
|
@ -644,7 +644,7 @@ async def _asyncwith(c):
|
|||
|
||||
%4d LOAD_SMALL_INT 2
|
||||
STORE_FAST 2 (y)
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
%4d L12: CLEANUP_THROW
|
||||
|
|
@ -655,7 +655,7 @@ async def _asyncwith(c):
|
|||
WITH_EXCEPT_START
|
||||
GET_AWAITABLE 2
|
||||
PUSH_NULL
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
L17: SEND 5 (to L21)
|
||||
L18: YIELD_VALUE 1
|
||||
L19: RESUME 3
|
||||
|
|
@ -674,7 +674,7 @@ async def _asyncwith(c):
|
|||
|
||||
%4d LOAD_SMALL_INT 2
|
||||
STORE_FAST 2 (y)
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
-- L26: COPY 3
|
||||
|
|
@ -895,7 +895,7 @@ def foo(x):
|
|||
JUMP_BACKWARD 17 (to L2)
|
||||
L3: END_FOR
|
||||
POP_ITER
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
|
||||
-- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
|
||||
|
|
@ -933,7 +933,7 @@ def loop_test():
|
|||
%3d RESUME_CHECK{: <6} 0
|
||||
|
||||
%3d BUILD_LIST 0
|
||||
LOAD_CONST 2 ((1, 2, 3))
|
||||
LOAD_CONST 1 ((1, 2, 3))
|
||||
LIST_EXTEND 1
|
||||
LOAD_SMALL_INT 3
|
||||
BINARY_OP 5 (*)
|
||||
|
|
@ -949,7 +949,7 @@ def loop_test():
|
|||
|
||||
%3d L2: END_FOR
|
||||
POP_ITER
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
""" % (loop_test.__code__.co_firstlineno,
|
||||
loop_test.__code__.co_firstlineno + 1,
|
||||
|
|
@ -967,7 +967,7 @@ def extended_arg_quick():
|
|||
UNPACK_EX 256
|
||||
POP_TOP
|
||||
STORE_FAST 0 (_)
|
||||
LOAD_CONST 1 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
"""% (extended_arg_quick.__code__.co_firstlineno,
|
||||
extended_arg_quick.__code__.co_firstlineno + 1,)
|
||||
|
|
@ -1084,7 +1084,7 @@ def test_dis_with_some_positions(self):
|
|||
'',
|
||||
'2:3-3:15 NOP',
|
||||
'',
|
||||
'3:11-3:15 LOAD_CONST 0 (None)',
|
||||
'3:11-3:15 LOAD_COMMON_CONSTANT 7 (None)',
|
||||
'3:11-3:15 RETURN_VALUE',
|
||||
'',
|
||||
' -- L1: PUSH_EXC_INFO',
|
||||
|
|
@ -1115,7 +1115,7 @@ def test_dis_with_linenos_but_no_columns(self):
|
|||
'',
|
||||
'2:5-2:6 LOAD_SMALL_INT 1',
|
||||
'2:?-2:? STORE_FAST 0 (x)',
|
||||
'2:?-2:? LOAD_CONST 1 (None)',
|
||||
'2:?-2:? LOAD_COMMON_CONSTANT 7 (None)',
|
||||
'2:?-2:? RETURN_VALUE',
|
||||
'',
|
||||
])
|
||||
|
|
@ -1128,7 +1128,7 @@ def f():
|
|||
f.__code__ = f.__code__.replace(co_linetable=b'')
|
||||
expect = '\n'.join([
|
||||
' RESUME 0',
|
||||
' LOAD_CONST 0 (None)',
|
||||
' LOAD_COMMON_CONSTANT 7 (None)',
|
||||
' RETURN_VALUE',
|
||||
'',
|
||||
])
|
||||
|
|
@ -1533,7 +1533,6 @@ def f(c=c):
|
|||
Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR
|
||||
Constants:
|
||||
0: <code object f at (.*), file "(.*)", line (.*)>
|
||||
1: None
|
||||
Variable names:
|
||||
0: a
|
||||
1: b
|
||||
|
|
@ -1605,7 +1604,6 @@ def f(c=c):
|
|||
Flags: 0x0
|
||||
Constants:
|
||||
0: 1
|
||||
1: None
|
||||
Names:
|
||||
0: x"""
|
||||
|
||||
|
|
@ -1640,7 +1638,6 @@ async def async_def():
|
|||
Flags: OPTIMIZED, NEWLOCALS, COROUTINE
|
||||
Constants:
|
||||
0: 1
|
||||
1: None
|
||||
Names:
|
||||
0: b
|
||||
1: c
|
||||
|
|
@ -1790,7 +1787,7 @@ def _prepare_test_cases():
|
|||
make_inst(opname='MAKE_CELL', arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None),
|
||||
make_inst(opname='MAKE_CELL', arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None),
|
||||
make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, cache_info=[('counter', 1, b'\x00\x00')]),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval=(3, 4), argrepr='(3, 4)', offset=8, start_offset=8, starts_line=True, line_number=2),
|
||||
make_inst(opname='LOAD_CONST', arg=3, argval=(3, 4), argrepr='(3, 4)', offset=8, start_offset=8, starts_line=True, line_number=2),
|
||||
make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=2),
|
||||
make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=2),
|
||||
make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=2),
|
||||
|
|
@ -1802,11 +1799,11 @@ def _prepare_test_cases():
|
|||
make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=26, start_offset=26, starts_line=True, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]),
|
||||
make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=36, start_offset=36, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=38, start_offset=38, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_CONST', arg=2, argval='', argrepr="''", offset=40, start_offset=40, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=8, argval='', argrepr="''", offset=40, start_offset=40, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7),
|
||||
make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7),
|
||||
make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=46, start_offset=46, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_CONST', arg=3, argval='Hello world!', argrepr="'Hello world!'", offset=48, start_offset=48, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_CONST', arg=2, argval='Hello world!', argrepr="'Hello world!'", offset=48, start_offset=48, starts_line=False, line_number=7),
|
||||
make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, start_offset=58, starts_line=False, line_number=7),
|
||||
make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', offset=60, start_offset=60, starts_line=True, line_number=8),
|
||||
|
|
@ -1851,7 +1848,7 @@ def _prepare_test_cases():
|
|||
make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('e', 'f'), argrepr='e, f', offset=24, start_offset=24, starts_line=False, line_number=4),
|
||||
make_inst(opname='CALL', arg=6, argval=6, argrepr='', offset=26, start_offset=26, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=34, start_offset=34, starts_line=False, line_number=4),
|
||||
make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=36, start_offset=36, starts_line=False, line_number=4),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=36, start_offset=36, starts_line=False, line_number=4),
|
||||
make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=38, start_offset=38, starts_line=False, line_number=4),
|
||||
]
|
||||
|
||||
|
|
@ -1934,16 +1931,16 @@ def _prepare_test_cases():
|
|||
make_inst(opname='LOAD_CONST', arg=3, argval='Never reach this', argrepr="'Never reach this'", offset=292, start_offset=292, starts_line=False, line_number=26),
|
||||
make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=294, start_offset=294, starts_line=False, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=302, start_offset=302, starts_line=False, line_number=26),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=True, line_number=25),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=25),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=25),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=True, line_number=25),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=25),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=25),
|
||||
make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=310, start_offset=310, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=318, start_offset=318, starts_line=False, line_number=25),
|
||||
make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=320, start_offset=320, starts_line=True, line_number=28, label=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]),
|
||||
make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=330, start_offset=330, starts_line=False, line_number=28),
|
||||
make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=330, start_offset=330, starts_line=False, line_number=28),
|
||||
make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=28),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=342, start_offset=342, starts_line=False, line_number=28),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=342, start_offset=342, starts_line=False, line_number=28),
|
||||
make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=28),
|
||||
make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=346, start_offset=346, starts_line=True, line_number=25),
|
||||
make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=348, start_offset=348, starts_line=False, line_number=25),
|
||||
|
|
@ -1967,7 +1964,7 @@ def _prepare_test_cases():
|
|||
make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=402, start_offset=402, starts_line=False, line_number=22),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=404, start_offset=404, starts_line=False, line_number=22),
|
||||
make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=406, start_offset=406, starts_line=True, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]),
|
||||
make_inst(opname='LOAD_CONST', arg=5, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=416, start_offset=416, starts_line=False, line_number=23),
|
||||
make_inst(opname='LOAD_CONST', arg=4, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=416, start_offset=416, starts_line=False, line_number=23),
|
||||
make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=418, start_offset=418, starts_line=False, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=23),
|
||||
make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=23),
|
||||
|
|
@ -1978,7 +1975,7 @@ def _prepare_test_cases():
|
|||
make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=438, start_offset=438, starts_line=False, line_number=None),
|
||||
make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=440, start_offset=440, starts_line=False, line_number=None),
|
||||
make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=442, start_offset=442, starts_line=True, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]),
|
||||
make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=452, start_offset=452, starts_line=False, line_number=28),
|
||||
make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=452, start_offset=452, starts_line=False, line_number=28),
|
||||
make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=454, start_offset=454, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
|
||||
make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=462, start_offset=462, starts_line=False, line_number=28),
|
||||
make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=28),
|
||||
|
|
@ -1991,7 +1988,7 @@ def _prepare_test_cases():
|
|||
def simple(): pass
|
||||
expected_opinfo_simple = [
|
||||
make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno),
|
||||
make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno),
|
||||
make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno),
|
||||
make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=6, start_offset=6, starts_line=False, line_number=simple.__code__.co_firstlineno),
|
||||
]
|
||||
|
||||
|
|
@ -2600,7 +2597,7 @@ def test_show_cache(self):
|
|||
CACHE 0 (func_version: 0)
|
||||
CACHE 0
|
||||
POP_TOP
|
||||
LOAD_CONST 0 (None)
|
||||
LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
'''
|
||||
for flag in ['-C', '--show-caches']:
|
||||
|
|
@ -2612,7 +2609,7 @@ def test_show_offsets(self):
|
|||
expect = '''
|
||||
0 0 RESUME 0
|
||||
|
||||
1 4 LOAD_CONST 0 (None)
|
||||
1 4 LOAD_COMMON_CONSTANT 7 (None)
|
||||
6 RETURN_VALUE
|
||||
'''
|
||||
for flag in ['-O', '--show-offsets']:
|
||||
|
|
@ -2624,7 +2621,7 @@ def test_show_positions(self):
|
|||
expect = '''
|
||||
0:0-1:0 RESUME 0
|
||||
|
||||
1:0-1:4 LOAD_CONST 0 (None)
|
||||
1:0-1:4 LOAD_COMMON_CONSTANT 7 (None)
|
||||
1:0-1:4 RETURN_VALUE
|
||||
'''
|
||||
for flag in ['-P', '--show-positions']:
|
||||
|
|
@ -2636,7 +2633,7 @@ def test_specialized_code(self):
|
|||
expect = '''
|
||||
0 RESUME 0
|
||||
|
||||
1 LOAD_CONST 0 (None)
|
||||
1 LOAD_COMMON_CONSTANT 7 (None)
|
||||
RETURN_VALUE
|
||||
'''
|
||||
for flag in ['-S', '--specialized']:
|
||||
|
|
|
|||
|
|
@ -1235,17 +1235,6 @@ def test_get_local_part_valid_and_invalid_qp_in_atom_list(self):
|
|||
'@example.com')
|
||||
self.assertEqual(local_part.local_part, r'\example\\ example')
|
||||
|
||||
def test_get_local_part_unicode_defect(self):
|
||||
# Currently this only happens when parsing unicode, not when parsing
|
||||
# stuff that was originally binary.
|
||||
local_part = self._test_get_x(parser.get_local_part,
|
||||
'exámple@example.com',
|
||||
'exámple',
|
||||
'exámple',
|
||||
[errors.NonASCIILocalPartDefect],
|
||||
'@example.com')
|
||||
self.assertEqual(local_part.local_part, 'exámple')
|
||||
|
||||
# get_dtext
|
||||
|
||||
def test_get_dtext_only(self):
|
||||
|
|
@ -3374,10 +3363,12 @@ def test_fold_unfoldable_element_stealing_whitespace(self):
|
|||
self._test(token, expected, policy=policy)
|
||||
|
||||
def test_encoded_word_with_undecodable_bytes(self):
|
||||
self._test(parser.get_address_list(
|
||||
' =?utf-8?Q?=E5=AE=A2=E6=88=B6=E6=AD=A3=E8=A6=8F=E4=BA=A4=E7?='
|
||||
self._test(
|
||||
parser.get_address_list(
|
||||
' =?utf-8?Q?=E5=AE=A2=E6=88=B6=E6=AD=A3=E8=A6=8F=E4=BA=A4=E7?='
|
||||
' <xyz@abc.com>'
|
||||
)[0],
|
||||
' =?unknown-8bit?b?5a6i5oi25q2j6KaP5Lqk5w==?=\n',
|
||||
' =?unknown-8bit?b?5a6i5oi25q2j6KaP5Lqk5w==?= <xyz@abc.com>\n',
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import io
|
||||
import re
|
||||
import textwrap
|
||||
import unittest
|
||||
import random
|
||||
|
|
@ -295,6 +296,69 @@ def test_keep_long_encoded_newlines(self):
|
|||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
|
||||
def test_non_ascii_addr_spec_raises(self):
|
||||
# non-ascii is not permitted in any part of an addr-spec. If the
|
||||
# programmer generated it, it's an error. (See also
|
||||
# test_non_ascii_addr_spec_preserved below.)
|
||||
p = self.policy.clone(utf8=False, max_line_length=20)
|
||||
g = self.genclass(self.ioclass(), policy=p)
|
||||
# XXX The particular part detected here isn't part of a behavioral
|
||||
# spec and may change in the future.
|
||||
cases = [
|
||||
('wők@example.com', 'wők', 'local-part'),
|
||||
('wok@exàmple.com', 'exàmple.com', 'domain'),
|
||||
('wők@exàmple.com', 'wők', 'local-part'),
|
||||
(
|
||||
'"Name, for display" <wők@example.com>',
|
||||
'wők@example.com',
|
||||
'addr-spec',
|
||||
),
|
||||
(
|
||||
'Näyttönimi <wők@example.com>',
|
||||
'wők@example.com',
|
||||
'addr-spec',
|
||||
),
|
||||
(
|
||||
'"a lőng quoted string as the local part"@example.com',
|
||||
'a lőng quoted string as the local part',
|
||||
'local-part',
|
||||
),
|
||||
|
||||
]
|
||||
for address, badtoken, partname in cases:
|
||||
with self.subTest(address=address):
|
||||
msg = EmailMessage()
|
||||
msg['To'] = address
|
||||
expected_error = (
|
||||
fr"(?i)(?=.*non-ascii)"
|
||||
fr"(?=.*{re.escape(badtoken)})"
|
||||
fr"(?=.*{partname})"
|
||||
fr"(?=.*policy.*utf8)"
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
email.errors.HeaderWriteError, expected_error
|
||||
):
|
||||
g.flatten(msg)
|
||||
|
||||
def test_local_part_quoted_string_wrapped_correctly(self):
|
||||
msg = self.msgmaker(self.typ(textwrap.dedent("""\
|
||||
To: <"a long local part in a quoted string"@example.com>
|
||||
Subject: test
|
||||
|
||||
None
|
||||
""")), policy=self.policy.clone(max_line_length=20))
|
||||
expected = textwrap.dedent("""\
|
||||
To: <"a long local part in a
|
||||
quoted string"@example.com>
|
||||
Subject: test
|
||||
|
||||
None
|
||||
""")
|
||||
s = self.ioclass()
|
||||
g = self.genclass(s, policy=self.policy.clone(max_line_length=30))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
|
||||
def _test_boundary_detection(self, linesep):
|
||||
# Generate a boundary token in the same way as _make_boundary
|
||||
token = random.randrange(sys.maxsize)
|
||||
|
|
@ -515,12 +579,12 @@ def test_cte_type_7bit_transforms_8bit_cte(self):
|
|||
|
||||
def test_smtputf8_policy(self):
|
||||
msg = EmailMessage()
|
||||
msg['From'] = "Páolo <főo@bar.com>"
|
||||
msg['From'] = "Páolo <főo@bàr.com>"
|
||||
msg['To'] = 'Dinsdale'
|
||||
msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'
|
||||
msg.set_content("oh là là, know what I mean, know what I mean?")
|
||||
expected = textwrap.dedent("""\
|
||||
From: Páolo <főo@bar.com>
|
||||
From: Páolo <főo@bàr.com>
|
||||
To: Dinsdale
|
||||
Subject: Nudge nudge, wink, wink \u1F609
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
|
|
@ -555,6 +619,37 @@ def test_smtp_policy(self):
|
|||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), expected)
|
||||
|
||||
def test_non_ascii_addr_spec_preserved(self):
|
||||
# A defective non-ASCII addr-spec parsed from the original
|
||||
# message is left unchanged when flattening.
|
||||
# (See also test_non_ascii_addr_spec_raises above.)
|
||||
source = (
|
||||
'To: jörg@example.com, "But a long name still works with refold_source" <jörg@example.com>'
|
||||
).encode()
|
||||
expected = (
|
||||
b'To: j\xc3\xb6rg@example.com,\n'
|
||||
b' "But a long name still works with refold_source" <j\xc3\xb6rg@example.com>\n'
|
||||
b'\n'
|
||||
)
|
||||
msg = message_from_bytes(source, policy=policy.default)
|
||||
s = io.BytesIO()
|
||||
g = BytesGenerator(s, policy=policy.default)
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), expected)
|
||||
|
||||
def test_idna_encoding_preserved(self):
|
||||
# Nothing tries to decode a pre-encoded IDNA domain.
|
||||
msg = EmailMessage()
|
||||
msg["To"] = Address(
|
||||
username='jörg',
|
||||
domain='☕.example'.encode('idna').decode() # IDNA 2003
|
||||
)
|
||||
expected = 'To: jörg@xn--53h.example\n\n'.encode()
|
||||
s = io.BytesIO()
|
||||
g = BytesGenerator(s, policy=policy.default.clone(utf8=True))
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -1543,17 +1543,19 @@ def test_quoting(self):
|
|||
self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>')
|
||||
|
||||
def test_il8n(self):
|
||||
a = Address('Éric', 'wok', 'exàmple.com')
|
||||
a = Address('Éric', 'wők', 'exàmple.com')
|
||||
self.assertEqual(a.display_name, 'Éric')
|
||||
self.assertEqual(a.username, 'wok')
|
||||
self.assertEqual(a.username, 'wők')
|
||||
self.assertEqual(a.domain, 'exàmple.com')
|
||||
self.assertEqual(a.addr_spec, 'wok@exàmple.com')
|
||||
self.assertEqual(str(a), 'Éric <wok@exàmple.com>')
|
||||
self.assertEqual(a.addr_spec, 'wők@exàmple.com')
|
||||
self.assertEqual(str(a), 'Éric <wők@exàmple.com>')
|
||||
|
||||
# XXX: there is an API design issue that needs to be solved here.
|
||||
#def test_non_ascii_username_raises(self):
|
||||
# with self.assertRaises(ValueError):
|
||||
# Address('foo', 'wők', 'example.com')
|
||||
def test_i18n_in_addr_spec(self):
|
||||
a = Address(addr_spec='wők@exàmple.com')
|
||||
self.assertEqual(a.username, 'wők')
|
||||
self.assertEqual(a.domain, 'exàmple.com')
|
||||
self.assertEqual(a.addr_spec, 'wők@exàmple.com')
|
||||
self.assertEqual(str(a), 'wők@exàmple.com')
|
||||
|
||||
def test_crlf_in_constructor_args_raises(self):
|
||||
cases = (
|
||||
|
|
@ -1574,10 +1576,6 @@ def test_crlf_in_constructor_args_raises(self):
|
|||
with self.subTest(kwargs=kwargs), self.assertRaisesRegex(ValueError, "invalid arguments"):
|
||||
Address(**kwargs)
|
||||
|
||||
def test_non_ascii_username_in_addr_spec_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Address('foo', addr_spec='wők@example.com')
|
||||
|
||||
def test_address_addr_spec_and_username_raises(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Address('foo', username='bing', addr_spec='bar@baz')
|
||||
|
|
|
|||
|
|
@ -559,6 +559,75 @@ def test_self_trace_after_ctypes_import(self):
|
|||
f"stdout: {result.stdout}\nstderr: {result.stderr}"
|
||||
)
|
||||
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(
|
||||
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support",
|
||||
)
|
||||
def test_remote_stack_trace_non_ascii_names(self):
|
||||
# Exercise each PyUnicode kind (1-byte non-ASCII, 2-byte BMP,
|
||||
# 4-byte non-BMP) for both the filename and the function name
|
||||
# reported in the stack trace.
|
||||
latin1 = "zażółć" # 1-byte non-ASCII (forces non-ASCII path)
|
||||
bmp = "λάμβδα" # 2-byte BMP
|
||||
astral = "𐌀𐌁𐌂𐌃" # 4-byte non-BMP (Old Italic; XID, no NFKC fold)
|
||||
func_name = f"{latin1}_{bmp}_{astral}"
|
||||
script_basename = f"mod_{latin1}_{bmp}_{astral}"
|
||||
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(
|
||||
f"""\
|
||||
import socket
|
||||
import time
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
def {func_name}():
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(10_000)
|
||||
|
||||
{func_name}()
|
||||
"""
|
||||
)
|
||||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
script_name = _make_test_script(script_dir, script_basename, script)
|
||||
|
||||
server_socket = _create_server_socket(port)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
_wait_for_signal(client_socket, b"ready")
|
||||
|
||||
stack_trace = get_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest("Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
||||
frames = [
|
||||
frame
|
||||
for interp in stack_trace
|
||||
for thread in interp.threads
|
||||
for frame in thread.frame_info
|
||||
]
|
||||
target = next(
|
||||
(f for f in frames if f.funcname == func_name), None
|
||||
)
|
||||
self.assertIsNotNone(
|
||||
target,
|
||||
f"Frame for {func_name!r} missing; got "
|
||||
f"{[(f.filename, f.funcname) for f in frames]}",
|
||||
)
|
||||
self.assertEqual(target.filename, script_name)
|
||||
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(
|
||||
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
|
|
|
|||
|
|
@ -27,9 +27,8 @@ def _frame_pointers_expected(machine):
|
|||
)
|
||||
|
||||
if "no-omit-frame-pointer" in cflags:
|
||||
# For example, configure adds -fno-omit-frame-pointer if Python
|
||||
# has perf trampoline (PY_HAVE_PERF_TRAMPOLINE) and Python is built
|
||||
# in debug mode.
|
||||
# For example, configure adds -fno-omit-frame-pointer by default on
|
||||
# supported GCC-compatible builds.
|
||||
return True
|
||||
if "omit-frame-pointer" in cflags:
|
||||
return False
|
||||
|
|
|
|||
350
Lib/test/test_gc_stats.py
Normal file
350
Lib/test/test_gc_stats.py
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
import gc
|
||||
import os
|
||||
import textwrap
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from test.support import (
|
||||
Py_GIL_DISABLED,
|
||||
import_helper,
|
||||
requires_gil_enabled,
|
||||
requires_remote_subprocess_debugging,
|
||||
)
|
||||
from test.test_profiling.test_sampling_profiler.helpers import test_subprocess
|
||||
|
||||
try:
|
||||
import _remote_debugging # noqa: F401
|
||||
except ImportError:
|
||||
raise unittest.SkipTest(
|
||||
"Test only runs when _remote_debugging is available"
|
||||
)
|
||||
|
||||
|
||||
GC_STATS_FIELDS = (
|
||||
"gen", "iid", "ts_start", "ts_stop", "collections", "collected",
|
||||
"uncollectable", "candidates", "duration")
|
||||
|
||||
|
||||
def get_interpreter_identifiers(gc_stats) -> tuple[int,...]:
|
||||
return tuple(sorted({s.iid for s in gc_stats}))
|
||||
|
||||
|
||||
def get_generations(gc_stats) -> tuple[int,int,int]:
|
||||
generations = set()
|
||||
for s in gc_stats:
|
||||
generations.add(s.gen)
|
||||
|
||||
return tuple(sorted(generations))
|
||||
|
||||
|
||||
def get_last_item(gc_stats, generation: int, iid: int):
|
||||
item = None
|
||||
for s in gc_stats:
|
||||
if s.gen == generation and s.iid == iid:
|
||||
if item is None or item.ts_start < s.ts_start:
|
||||
item = s
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def has_local_process_debugging():
|
||||
try:
|
||||
return _remote_debugging.is_python_process(os.getpid())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def check_gc_stats_fields(testcase, stats):
|
||||
testcase.assertIsInstance(stats, list)
|
||||
testcase.assertGreater(len(stats), 0)
|
||||
for item in stats:
|
||||
testcase.assertIsInstance(item, _remote_debugging.GCStatsInfo)
|
||||
testcase.assertEqual(type(item).__match_args__, GC_STATS_FIELDS)
|
||||
testcase.assertEqual(len(item), len(GC_STATS_FIELDS))
|
||||
|
||||
|
||||
def gc_stats_counters_advanced(before_stats, after_stats, generations, iid):
|
||||
for generation in generations:
|
||||
before = get_last_item(before_stats, generation, iid)
|
||||
after = get_last_item(after_stats, generation, iid)
|
||||
if after is None or before is None:
|
||||
return False
|
||||
if after.duration <= before.duration:
|
||||
return False
|
||||
if after.candidates <= before.candidates:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@unittest.skipUnless(
|
||||
has_local_process_debugging(), "requires local process debugging")
|
||||
class TestLocalGCStats(unittest.TestCase):
|
||||
|
||||
_main_iid = 0 # main interpreter ID
|
||||
|
||||
def test_gc_stats_fields(self):
|
||||
monitor = _remote_debugging.GCMonitor(os.getpid(), debug=True)
|
||||
stats = monitor.get_gc_stats(all_interpreters=False)
|
||||
check_gc_stats_fields(self, stats)
|
||||
|
||||
def test_module_get_gc_stats_fields(self):
|
||||
stats = _remote_debugging.get_gc_stats(
|
||||
os.getpid(), all_interpreters=False)
|
||||
check_gc_stats_fields(self, stats)
|
||||
|
||||
def test_all_interpreters_filter_for_local_process(self):
|
||||
interpreters = import_helper.import_module("concurrent.interpreters")
|
||||
source = """
|
||||
import gc
|
||||
objects = []
|
||||
obj = []
|
||||
obj.append(obj)
|
||||
objects.append(obj)
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
"""
|
||||
interp = interpreters.create()
|
||||
try:
|
||||
interp.exec(textwrap.dedent(source))
|
||||
for generation in range(3):
|
||||
gc.collect(generation)
|
||||
|
||||
main_stats = _remote_debugging.get_gc_stats(
|
||||
os.getpid(), all_interpreters=False)
|
||||
all_stats = _remote_debugging.get_gc_stats(
|
||||
os.getpid(), all_interpreters=True)
|
||||
finally:
|
||||
interp.close()
|
||||
|
||||
self.assertEqual(get_interpreter_identifiers(main_stats), (0,))
|
||||
self.assertIn(0, get_interpreter_identifiers(all_stats))
|
||||
self.assertGreater(len(get_interpreter_identifiers(all_stats)), 1)
|
||||
self.assertEqual(get_generations(main_stats), (0, 1, 2))
|
||||
self.assertEqual(get_generations(all_stats), (0, 1, 2))
|
||||
for iid in get_interpreter_identifiers(all_stats):
|
||||
for generation in range(3):
|
||||
self.assertIsNotNone(get_last_item(all_stats, generation, iid))
|
||||
|
||||
@unittest.skipUnless(Py_GIL_DISABLED, "requires free-threaded GC")
|
||||
def test_gc_stats_counters_for_main_interpreter_free_threaded(self):
|
||||
generations = (0, 1, 2)
|
||||
before_stats = _remote_debugging.get_gc_stats(
|
||||
os.getpid(), all_interpreters=False)
|
||||
for generation in generations:
|
||||
self.assertIsNotNone(
|
||||
get_last_item(before_stats, generation, self._main_iid))
|
||||
|
||||
objects = []
|
||||
for _ in range(1000):
|
||||
obj = []
|
||||
obj.append(obj)
|
||||
objects.append(obj)
|
||||
for generation in generations:
|
||||
gc.collect(generation)
|
||||
|
||||
after_stats = _remote_debugging.get_gc_stats(
|
||||
os.getpid(), all_interpreters=False)
|
||||
self.assertTrue(
|
||||
gc_stats_counters_advanced(
|
||||
before_stats, after_stats, generations, self._main_iid),
|
||||
(before_stats, after_stats)
|
||||
)
|
||||
|
||||
|
||||
@requires_remote_subprocess_debugging()
|
||||
class TestGCStats(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls._main_iid = 0 # main interpreter ID
|
||||
cls._main_interpreter_script = '''
|
||||
import gc
|
||||
import time
|
||||
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
|
||||
_test_sock.sendall(b"working")
|
||||
objects = []
|
||||
while True:
|
||||
if len(objects) > 100:
|
||||
objects = []
|
||||
|
||||
obj = []
|
||||
obj.append(obj)
|
||||
objects.append(obj)
|
||||
|
||||
time.sleep(0.1)
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
'''
|
||||
cls._script = '''
|
||||
import concurrent.interpreters as interpreters
|
||||
import gc
|
||||
import time
|
||||
|
||||
source = """if True:
|
||||
import gc
|
||||
|
||||
if "objects" not in globals():
|
||||
objects = []
|
||||
if len(objects) > 100:
|
||||
objects = []
|
||||
|
||||
obj = []
|
||||
obj.append(obj)
|
||||
objects.append(obj)
|
||||
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
"""
|
||||
|
||||
if {0}:
|
||||
interp = interpreters.create()
|
||||
interp.exec(source)
|
||||
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
|
||||
_test_sock.sendall(b"working")
|
||||
objects = []
|
||||
while True:
|
||||
if len(objects) > 100:
|
||||
objects = []
|
||||
|
||||
obj = []
|
||||
obj.append(obj)
|
||||
objects.append(obj)
|
||||
|
||||
time.sleep(0.1)
|
||||
if {0}:
|
||||
interp.exec(source)
|
||||
gc.collect(0)
|
||||
gc.collect(1)
|
||||
gc.collect(2)
|
||||
'''
|
||||
|
||||
def _gc_stats_advanced(self, before_stats, after_stats, generations):
|
||||
for generation in generations:
|
||||
before = get_last_item(before_stats, generation, self._main_iid)
|
||||
after = get_last_item(after_stats, generation, self._main_iid)
|
||||
if after is None or before is None:
|
||||
return False
|
||||
if after.ts_stop <= before.ts_stop:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _collect_gc_stats(self, script: str, all_interpreters: bool,
|
||||
generations=(2,)):
|
||||
with (test_subprocess(script, wait_for_working=True) as subproc):
|
||||
monitor = _remote_debugging.GCMonitor(subproc.process.pid, debug=True)
|
||||
before_stats = monitor.get_gc_stats(all_interpreters=all_interpreters)
|
||||
for generation in generations:
|
||||
before = get_last_item(before_stats, generation, self._main_iid)
|
||||
self.assertIsNotNone(before)
|
||||
|
||||
after_stats = before_stats
|
||||
for _ in range(10):
|
||||
time.sleep(0.5)
|
||||
after_stats = monitor.get_gc_stats(all_interpreters=all_interpreters)
|
||||
if self._gc_stats_advanced(before_stats, after_stats, generations):
|
||||
break
|
||||
else:
|
||||
self.fail(
|
||||
f"GC stats for generations {generations!r} did not "
|
||||
f"advance: {before_stats!r} -> {after_stats!r}"
|
||||
)
|
||||
|
||||
return before_stats, after_stats
|
||||
|
||||
def _check_gc_stats(self, before, after):
|
||||
self.assertIsNotNone(before)
|
||||
self.assertIsNotNone(after)
|
||||
|
||||
self.assertGreater(after.collections, before.collections, (before, after))
|
||||
self.assertGreater(after.ts_start, before.ts_start, (before, after))
|
||||
self.assertGreater(after.ts_stop, before.ts_stop, (before, after))
|
||||
self.assertGreater(after.duration, before.duration, (before, after))
|
||||
|
||||
self.assertGreater(after.candidates, before.candidates, (before, after))
|
||||
|
||||
# may not grow
|
||||
self.assertGreaterEqual(after.collected, before.collected, (before, after))
|
||||
self.assertGreaterEqual(after.uncollectable, before.uncollectable, (before, after))
|
||||
|
||||
def _check_interpreter_gc_stats(self, before_stats, after_stats):
|
||||
before_iids = get_interpreter_identifiers(before_stats)
|
||||
after_iids = get_interpreter_identifiers(after_stats)
|
||||
|
||||
self.assertEqual(before_iids, after_iids)
|
||||
|
||||
self.assertEqual(get_generations(before_stats), (0, 1, 2))
|
||||
self.assertEqual(get_generations(after_stats), (0, 1, 2))
|
||||
|
||||
for iid in after_iids:
|
||||
with self.subTest(f"interpreter id={iid}"):
|
||||
before_last_items = (get_last_item(before_stats, 0, iid),
|
||||
get_last_item(before_stats, 1, iid),
|
||||
get_last_item(before_stats, 2, iid))
|
||||
|
||||
after_last_items = (get_last_item(after_stats, 0, iid),
|
||||
get_last_item(after_stats, 1, iid),
|
||||
get_last_item(after_stats, 2, iid))
|
||||
|
||||
for before, after in zip(before_last_items, after_last_items):
|
||||
self._check_gc_stats(before, after)
|
||||
|
||||
def test_gc_stats_timestamps_for_main_interpreter(self):
|
||||
script = textwrap.dedent(self._main_interpreter_script)
|
||||
before_stats, after_stats = self._collect_gc_stats(
|
||||
script, False, generations=(0, 1, 2))
|
||||
|
||||
for generation in range(3):
|
||||
with self.subTest(generation=generation):
|
||||
before = get_last_item(before_stats, generation, self._main_iid)
|
||||
after = get_last_item(after_stats, generation, self._main_iid)
|
||||
|
||||
self.assertIsNotNone(before)
|
||||
self.assertIsNotNone(after)
|
||||
self.assertGreater(
|
||||
after.collections, before.collections,
|
||||
(before, after))
|
||||
self.assertGreater(
|
||||
after.ts_start, before.ts_start,
|
||||
(before, after))
|
||||
self.assertGreater(
|
||||
after.ts_stop, before.ts_stop,
|
||||
(before, after))
|
||||
|
||||
@requires_gil_enabled()
|
||||
def test_gc_stats_for_main_interpreter(self):
|
||||
script = textwrap.dedent(self._script.format(False))
|
||||
before_stats, after_stats = self._collect_gc_stats(script, False)
|
||||
|
||||
self._check_interpreter_gc_stats(before_stats, after_stats)
|
||||
|
||||
@requires_gil_enabled()
|
||||
def test_gc_stats_for_main_interpreter_if_subinterpreter_exists(self):
|
||||
script = textwrap.dedent(self._script.format(True))
|
||||
before_stats, after_stats = self._collect_gc_stats(script, False)
|
||||
|
||||
self._check_interpreter_gc_stats(before_stats, after_stats)
|
||||
|
||||
@requires_gil_enabled()
|
||||
def test_gc_stats_for_all_interpreters(self):
|
||||
script = textwrap.dedent(self._script.format(True))
|
||||
before_stats, after_stats = self._collect_gc_stats(script, True)
|
||||
|
||||
before_iids = get_interpreter_identifiers(before_stats)
|
||||
after_iids = get_interpreter_identifiers(after_stats)
|
||||
|
||||
self.assertGreater(len(before_iids), 1)
|
||||
self.assertGreater(len(after_iids), 1)
|
||||
self.assertEqual(before_iids, after_iids)
|
||||
|
||||
self._check_interpreter_gc_stats(before_stats, after_stats)
|
||||
27
Lib/test/test_gdb/gdb_jit_sample.py
Normal file
27
Lib/test/test_gdb/gdb_jit_sample.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Sample script for use by test_gdb.test_jit
|
||||
|
||||
import _testinternalcapi
|
||||
import operator
|
||||
|
||||
|
||||
WARMUP_ITERATIONS = _testinternalcapi.TIER2_THRESHOLD + 10
|
||||
|
||||
|
||||
def jit_bt_hot(depth, warming_up_caller=False):
|
||||
if depth == 0:
|
||||
if not warming_up_caller:
|
||||
id(42)
|
||||
return
|
||||
|
||||
for iteration in range(WARMUP_ITERATIONS):
|
||||
operator.call(
|
||||
jit_bt_hot,
|
||||
depth - 1,
|
||||
warming_up_caller or iteration + 1 != WARMUP_ITERATIONS,
|
||||
)
|
||||
|
||||
|
||||
# Warm the shared shim once without hitting builtin_id so the real run uses
|
||||
# the steady-state shim path when GDB breaks inside id(42).
|
||||
jit_bt_hot(1, warming_up_caller=True)
|
||||
jit_bt_hot(1)
|
||||
201
Lib/test/test_gdb/test_jit.py
Normal file
201
Lib/test/test_gdb/test_jit.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from .util import setup_module, DebuggerTests
|
||||
|
||||
|
||||
JIT_SAMPLE_SCRIPT = os.path.join(os.path.dirname(__file__), "gdb_jit_sample.py")
|
||||
# In batch GDB, break in builtin_id() while it is running under JIT,
|
||||
# then repeatedly "finish" until the selected frame is the JIT executor.
|
||||
# That gives a deterministic backtrace starting with py::jit:executor.
|
||||
#
|
||||
# builtin_id() sits only a few helper frames above the JIT entry on this path.
|
||||
# This bound is just a generous upper limit so the test fails clearly if the
|
||||
# expected stack shape changes.
|
||||
MAX_FINISH_STEPS = 20
|
||||
# After landing on the JIT entry frame, single-step a bounded number of
|
||||
# instructions further into the blob so the backtrace is taken from JIT code
|
||||
# itself rather than the immediate helper-return site. The exact number of
|
||||
# steps is not significant: each step is cross-checked against the selected
|
||||
# frame's symbol so the test fails loudly if stepping escapes the registered
|
||||
# JIT region, instead of asserting against a misleading backtrace.
|
||||
MAX_JIT_ENTRY_STEPS = 4
|
||||
EVAL_FRAME_RE = r"(_PyEval_EvalFrameDefault|_PyEval_Vector)"
|
||||
JIT_EXECUTOR_FRAME = "py::jit:executor"
|
||||
JIT_ENTRY_SYMBOL = "_PyJIT_Entry"
|
||||
BACKTRACE_FRAME_RE = re.compile(r"^#\d+\s+.*$", re.MULTILINE)
|
||||
|
||||
FINISH_TO_JIT_EXECUTOR = (
|
||||
"python exec(\"import gdb\\n"
|
||||
f"target = {JIT_EXECUTOR_FRAME!r}\\n"
|
||||
f"for _ in range({MAX_FINISH_STEPS}):\\n"
|
||||
" frame = gdb.selected_frame()\\n"
|
||||
" if frame is not None and frame.name() == target:\\n"
|
||||
" break\\n"
|
||||
" gdb.execute('finish')\\n"
|
||||
"else:\\n"
|
||||
" raise RuntimeError('did not reach %s' % target)\\n\")"
|
||||
)
|
||||
STEP_INSIDE_JIT_EXECUTOR = (
|
||||
"python exec(\"import gdb\\n"
|
||||
f"target = {JIT_EXECUTOR_FRAME!r}\\n"
|
||||
f"for _ in range({MAX_JIT_ENTRY_STEPS}):\\n"
|
||||
" frame = gdb.selected_frame()\\n"
|
||||
" if frame is None or frame.name() != target:\\n"
|
||||
" raise RuntimeError('left JIT region during stepping: '\\n"
|
||||
" + repr(frame and frame.name()))\\n"
|
||||
" gdb.execute('si')\\n"
|
||||
"frame = gdb.selected_frame()\\n"
|
||||
"if frame is None or frame.name() != target:\\n"
|
||||
" raise RuntimeError('stepped out of JIT region after si')\\n\")"
|
||||
)
|
||||
|
||||
|
||||
def setUpModule():
|
||||
setup_module()
|
||||
|
||||
|
||||
# The GDB JIT interface registration is gated on __linux__ && __ELF__ in
|
||||
# Python/jit_unwind.c, and the synthetic EH-frame is only implemented for
|
||||
# x86_64 and AArch64 (a #error fires otherwise). Skip cleanly on other
|
||||
# platforms or architectures instead of producing timeouts / empty backtraces.
|
||||
# is_enabled() implies is_available() and also implies that the runtime has
|
||||
# JIT execution active; interpreter-only tier 2 builds don't hit this path.
|
||||
@unittest.skipUnless(sys.platform == "linux",
|
||||
"GDB JIT interface is only implemented for Linux + ELF")
|
||||
@unittest.skipUnless(platform.machine() in ("x86_64", "aarch64"),
|
||||
"GDB JIT CFI emitter only supports x86_64 and AArch64")
|
||||
@unittest.skipUnless(hasattr(sys, "_jit") and sys._jit.is_enabled(),
|
||||
"requires a JIT-enabled build with JIT execution active")
|
||||
class JitBacktraceTests(DebuggerTests):
|
||||
def get_stack_trace(self, **kwargs):
|
||||
# These tests validate the JIT-relevant part of the backtrace via
|
||||
# _assert_jit_backtrace_shape, so an unrelated "?? ()" frame below
|
||||
# the JIT/eval segment (e.g. libc without debug info) is tolerable.
|
||||
kwargs.setdefault("skip_on_truncation", False)
|
||||
return super().get_stack_trace(**kwargs)
|
||||
|
||||
def _extract_backtrace_frames(self, gdb_output):
|
||||
frames = BACKTRACE_FRAME_RE.findall(gdb_output)
|
||||
self.assertGreater(
|
||||
len(frames), 0,
|
||||
f"expected at least one GDB backtrace frame in output:\n{gdb_output}",
|
||||
)
|
||||
return frames
|
||||
|
||||
def _assert_jit_backtrace_shape(self, gdb_output, *, anchor_at_top):
|
||||
# Shape assertions applied to every JIT backtrace we produce:
|
||||
# 1. The synthetic JIT symbol appears exactly once. A second
|
||||
# py::jit:executor frame would mean the unwinder is
|
||||
# materializing two native frames for a single logical JIT
|
||||
# region, or failing to unwind out of the region entirely.
|
||||
# 2. The unwinder must climb directly back out of the JIT region
|
||||
# into the eval loop. _PyJIT_Entry only exists to establish the
|
||||
# physical frame; the synthetic executor FDE collapses it away.
|
||||
# 3. For tests that assert a specific entry PC, the JIT frame
|
||||
# is also at #0.
|
||||
frames = self._extract_backtrace_frames(gdb_output)
|
||||
backtrace = "\n".join(frames)
|
||||
|
||||
jit_frames = [frame for frame in frames if JIT_EXECUTOR_FRAME in frame]
|
||||
jit_count = len(jit_frames)
|
||||
self.assertEqual(
|
||||
jit_count, 1,
|
||||
f"expected exactly 1 {JIT_EXECUTOR_FRAME} frame, got {jit_count}\n"
|
||||
f"backtrace:\n{backtrace}",
|
||||
)
|
||||
eval_frames = [frame for frame in frames if re.search(EVAL_FRAME_RE, frame)]
|
||||
eval_count = len(eval_frames)
|
||||
self.assertGreaterEqual(
|
||||
eval_count, 1,
|
||||
f"expected at least one _PyEval_* frame, got {eval_count}\n"
|
||||
f"backtrace:\n{backtrace}",
|
||||
)
|
||||
jit_frame_index = next(
|
||||
i for i, frame in enumerate(frames) if JIT_EXECUTOR_FRAME in frame
|
||||
)
|
||||
frames_after_jit = frames[jit_frame_index + 1:]
|
||||
first_eval_offset = next(
|
||||
(
|
||||
i for i, frame in enumerate(frames_after_jit)
|
||||
if re.search(EVAL_FRAME_RE, frame)
|
||||
),
|
||||
None,
|
||||
)
|
||||
self.assertIsNotNone(
|
||||
first_eval_offset,
|
||||
f"expected an eval frame after the JIT frame\n"
|
||||
f"backtrace:\n{backtrace}",
|
||||
)
|
||||
unexpected_between = frames_after_jit[:first_eval_offset]
|
||||
self.assertFalse(
|
||||
unexpected_between,
|
||||
"expected the executor frame to unwind directly into eval\n"
|
||||
f"backtrace:\n{backtrace}",
|
||||
)
|
||||
relevant_end = max(
|
||||
i
|
||||
for i, frame in enumerate(frames)
|
||||
if (
|
||||
JIT_EXECUTOR_FRAME in frame
|
||||
or re.search(EVAL_FRAME_RE, frame)
|
||||
)
|
||||
)
|
||||
truncated_frames = [
|
||||
frame for frame in frames[: relevant_end + 1]
|
||||
if " ?? ()" in frame
|
||||
]
|
||||
self.assertFalse(
|
||||
truncated_frames,
|
||||
"unexpected truncated frame before the validated JIT/eval segment\n"
|
||||
f"backtrace:\n{backtrace}",
|
||||
)
|
||||
if anchor_at_top:
|
||||
self.assertRegex(
|
||||
frames[0],
|
||||
re.compile(rf"^#0\s+{re.escape(JIT_EXECUTOR_FRAME)}"),
|
||||
)
|
||||
|
||||
def test_bt_unwinds_through_jit_frames(self):
|
||||
gdb_output = self.get_stack_trace(
|
||||
script=JIT_SAMPLE_SCRIPT,
|
||||
cmds_after_breakpoint=["bt"],
|
||||
PYTHON_JIT="1",
|
||||
)
|
||||
# The executor should appear as a named JIT frame and unwind back into
|
||||
# the eval loop.
|
||||
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=False)
|
||||
|
||||
def test_bt_handoff_from_jit_entry_to_executor(self):
|
||||
gdb_output = self.get_stack_trace(
|
||||
script=JIT_SAMPLE_SCRIPT,
|
||||
breakpoint=JIT_ENTRY_SYMBOL,
|
||||
cmds_after_breakpoint=[
|
||||
"delete 1",
|
||||
"tbreak builtin_id",
|
||||
"continue",
|
||||
"bt",
|
||||
],
|
||||
PYTHON_JIT="1",
|
||||
)
|
||||
# If we stop first in the shim and then continue into the real JIT
|
||||
# workload, the final backtrace should match the architecture's
|
||||
# executor unwind contract.
|
||||
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=False)
|
||||
|
||||
def test_bt_unwinds_from_inside_jit_executor(self):
|
||||
gdb_output = self.get_stack_trace(
|
||||
script=JIT_SAMPLE_SCRIPT,
|
||||
cmds_after_breakpoint=[
|
||||
FINISH_TO_JIT_EXECUTOR,
|
||||
STEP_INSIDE_JIT_EXECUTOR,
|
||||
"bt",
|
||||
],
|
||||
PYTHON_JIT="1",
|
||||
)
|
||||
# Once the selected PC is inside the JIT executor, we require that GDB
|
||||
# identifies the JIT frame at #0 and keeps unwinding into _PyEval_*.
|
||||
self._assert_jit_backtrace_shape(gdb_output, anchor_at_top=True)
|
||||
|
|
@ -20,6 +20,27 @@
|
|||
|
||||
PYTHONHASHSEED = '123'
|
||||
|
||||
# gh-91960, bpo-40019: gdb reports these when the optimizer has dropped
|
||||
# python-frame debug info; the test can't read what's not there.
|
||||
_OPTIMIZED_OUT_PATTERNS = (
|
||||
'(frame information optimized out)',
|
||||
'Unable to read information on python frame',
|
||||
'(unable to read python frame information)',
|
||||
)
|
||||
# gdb prints this when the unwinder genuinely failed to walk a frame —
|
||||
# i.e. the CFI (ours or a library's) is wrong. Treat as a hard failure,
|
||||
# not a skip, so regressions in our own unwind info don't hide.
|
||||
_UNWIND_FAILURE_PATTERNS = (
|
||||
'Backtrace stopped: frame did not save the PC',
|
||||
)
|
||||
# gh-104736: " ?? ()" in the bt usually means the unwinder bailed early,
|
||||
# but can also be unrelated frames without debug info (e.g. libc). Tests
|
||||
# that validate the JIT-relevant part of the backtrace themselves can
|
||||
# opt out via skip_on_truncation=False.
|
||||
_TRUNCATED_BACKTRACE_PATTERNS = (
|
||||
' ?? ()',
|
||||
)
|
||||
|
||||
|
||||
def clean_environment():
|
||||
# Remove PYTHON* environment variables such as PYTHONHOME
|
||||
|
|
@ -160,7 +181,9 @@ def get_stack_trace(self, source=None, script=None,
|
|||
breakpoint=BREAKPOINT_FN,
|
||||
cmds_after_breakpoint=None,
|
||||
import_site=False,
|
||||
ignore_stderr=False):
|
||||
ignore_stderr=False,
|
||||
skip_on_truncation=True,
|
||||
**env_vars):
|
||||
'''
|
||||
Run 'python -c SOURCE' under gdb with a breakpoint.
|
||||
|
||||
|
|
@ -239,7 +262,7 @@ def get_stack_trace(self, source=None, script=None,
|
|||
args += [script]
|
||||
|
||||
# Use "args" to invoke gdb, capturing stdout, stderr:
|
||||
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
|
||||
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED, **env_vars)
|
||||
|
||||
if not ignore_stderr:
|
||||
for line in err.splitlines():
|
||||
|
|
@ -255,26 +278,20 @@ def get_stack_trace(self, source=None, script=None,
|
|||
" because the Program Counter is"
|
||||
" not present")
|
||||
|
||||
for pattern in _UNWIND_FAILURE_PATTERNS:
|
||||
if pattern in out:
|
||||
raise AssertionError(
|
||||
f"gdb unwinder failed ({pattern!r}) — CFI bug in our "
|
||||
f"generated code or in a linked library.\n"
|
||||
f"Full gdb output:\n{out}"
|
||||
)
|
||||
|
||||
# bpo-40019: Skip the test if gdb failed to read debug information
|
||||
# because the Python binary is optimized.
|
||||
for pattern in (
|
||||
'(frame information optimized out)',
|
||||
'Unable to read information on python frame',
|
||||
|
||||
# gh-91960: On Python built with "clang -Og", gdb gets
|
||||
# "frame=<optimized out>" for _PyEval_EvalFrameDefault() parameter
|
||||
'(unable to read python frame information)',
|
||||
|
||||
# gh-104736: On Python built with "clang -Og" on ppc64le,
|
||||
# "py-bt" displays a truncated or not traceback, but "where"
|
||||
# logs this error message:
|
||||
'Backtrace stopped: frame did not save the PC',
|
||||
|
||||
# gh-104736: When "bt" command displays something like:
|
||||
# "#1 0x0000000000000000 in ?? ()", the traceback is likely
|
||||
# truncated or wrong.
|
||||
' ?? ()',
|
||||
):
|
||||
patterns = _OPTIMIZED_OUT_PATTERNS
|
||||
if skip_on_truncation:
|
||||
patterns = patterns + _TRUNCATED_BACKTRACE_PATTERNS
|
||||
for pattern in patterns:
|
||||
if pattern in out:
|
||||
raise unittest.SkipTest(f"{pattern!r} found in gdb output")
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ def test_elim_inversion_of_is_or_in(self):
|
|||
self.check_lnotab(code)
|
||||
|
||||
def test_global_as_constant(self):
|
||||
# LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False
|
||||
# LOAD_GLOBAL None/True/False --> LOAD_COMMON_CONSTANT None/True/False
|
||||
def f():
|
||||
x = None
|
||||
x = None
|
||||
|
|
@ -121,7 +121,7 @@ def h():
|
|||
for func, elem in ((f, None), (g, True), (h, False)):
|
||||
with self.subTest(func=func):
|
||||
self.assertNotInBytecode(func, 'LOAD_GLOBAL')
|
||||
self.assertInBytecode(func, 'LOAD_CONST', elem)
|
||||
self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem)
|
||||
self.check_lnotab(func)
|
||||
|
||||
def f():
|
||||
|
|
@ -129,7 +129,7 @@ def f():
|
|||
return None
|
||||
|
||||
self.assertNotInBytecode(f, 'LOAD_GLOBAL')
|
||||
self.assertInBytecode(f, 'LOAD_CONST', None)
|
||||
self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None)
|
||||
self.check_lnotab(f)
|
||||
|
||||
def test_while_one(self):
|
||||
|
|
@ -146,13 +146,14 @@ def f():
|
|||
|
||||
def test_pack_unpack(self):
|
||||
for line, elem in (
|
||||
('a, = a,', 'LOAD_CONST',),
|
||||
('a, = a,', None),
|
||||
('a, b = a, b', 'SWAP',),
|
||||
('a, b, c = a, b, c', 'SWAP',),
|
||||
):
|
||||
with self.subTest(line=line):
|
||||
code = compile(line,'','single')
|
||||
self.assertInBytecode(code, elem)
|
||||
if elem is not None:
|
||||
self.assertInBytecode(code, elem)
|
||||
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
||||
self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
|
||||
self.check_lnotab(code)
|
||||
|
|
@ -174,10 +175,10 @@ def test_constant_folding_tuples_of_constants(self):
|
|||
# Long tuples should be folded too.
|
||||
code = compile(repr(tuple(range(10000))),'','single')
|
||||
self.assertNotInBytecode(code, 'BUILD_TUPLE')
|
||||
# One LOAD_CONST for the tuple, one for the None return value
|
||||
# One LOAD_CONST for the tuple; None return value uses LOAD_COMMON_CONSTANT
|
||||
load_consts = [instr for instr in dis.get_instructions(code)
|
||||
if instr.opname == 'LOAD_CONST']
|
||||
self.assertEqual(len(load_consts), 2)
|
||||
self.assertEqual(len(load_consts), 1)
|
||||
self.check_lnotab(code)
|
||||
|
||||
# Bug 1053819: Tuple of constants misidentified when presented with:
|
||||
|
|
@ -283,11 +284,11 @@ def test_constant_folding_unaryop(self):
|
|||
('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
||||
('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0),
|
||||
('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5),
|
||||
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -1),
|
||||
('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_COMMON_CONSTANT', -1),
|
||||
('---""', 'UNARY_NEGATIVE', None, False, None, None),
|
||||
('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2),
|
||||
('~~~""', 'UNARY_INVERT', None, False, None, None),
|
||||
('not not True', 'UNARY_NOT', None, True, 'LOAD_CONST', True),
|
||||
('not not True', 'UNARY_NOT', None, True, 'LOAD_COMMON_CONSTANT', True),
|
||||
('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not
|
||||
('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1),
|
||||
('---x', 'UNARY_NEGATIVE', None, False, None, None),
|
||||
|
|
@ -326,7 +327,7 @@ def test_constant_folding_binop(self):
|
|||
('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3),
|
||||
('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6),
|
||||
('1 + ""', 'NB_ADD', False, None, None),
|
||||
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_CONST', -1),
|
||||
('1 - 2', 'NB_SUBTRACT', True, 'LOAD_COMMON_CONSTANT', -1),
|
||||
('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4),
|
||||
('1 - ""', 'NB_SUBTRACT', False, None, None),
|
||||
('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4),
|
||||
|
|
@ -1539,7 +1540,7 @@ def test_optimize_literal_list_for_iter(self):
|
|||
end,
|
||||
('END_FOR', None, 0),
|
||||
('POP_ITER', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
||||
|
|
@ -1572,7 +1573,7 @@ def test_optimize_literal_list_for_iter(self):
|
|||
end,
|
||||
('END_FOR', None, 0),
|
||||
('POP_ITER', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
||||
|
|
@ -1604,7 +1605,7 @@ def test_optimize_literal_set_for_iter(self):
|
|||
end,
|
||||
('END_FOR', None, 0),
|
||||
('POP_ITER', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
||||
|
|
@ -1626,7 +1627,22 @@ def test_optimize_literal_set_for_iter(self):
|
|||
('LOAD_CONST', 0, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
|
||||
expected = [
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_NAME', 0, 0),
|
||||
('BUILD_SET', 2, 0),
|
||||
('GET_ITER', 0, 0),
|
||||
start := self.Label(),
|
||||
('FOR_ITER', end := self.Label(), 0),
|
||||
('STORE_FAST', 0, 0),
|
||||
('JUMP', start, 0),
|
||||
end,
|
||||
('END_FOR', None, 0),
|
||||
('POP_ITER', None, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
|
||||
|
||||
def test_optimize_literal_list_contains(self):
|
||||
# x in [1, 2] ==> x in (1, 2)
|
||||
|
|
@ -1645,7 +1661,7 @@ def test_optimize_literal_list_contains(self):
|
|||
('LOAD_CONST', 1, 0),
|
||||
('CONTAINS_OP', 0, 0),
|
||||
('POP_TOP', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)])
|
||||
|
|
@ -1668,7 +1684,7 @@ def test_optimize_literal_list_contains(self):
|
|||
('BUILD_TUPLE', 2, 0),
|
||||
('CONTAINS_OP', 0, 0),
|
||||
('POP_TOP', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None])
|
||||
|
|
@ -1690,7 +1706,7 @@ def test_optimize_literal_set_contains(self):
|
|||
('LOAD_CONST', 1, 0),
|
||||
('CONTAINS_OP', 0, 0),
|
||||
('POP_TOP', None, 0),
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})])
|
||||
|
|
@ -1707,7 +1723,17 @@ def test_optimize_literal_set_contains(self):
|
|||
('LOAD_CONST', 0, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None])
|
||||
expected = [
|
||||
('LOAD_NAME', 0, 0),
|
||||
('LOAD_SMALL_INT', 1, 0),
|
||||
('LOAD_NAME', 1, 0),
|
||||
('BUILD_SET', 2, 0),
|
||||
('CONTAINS_OP', 0, 0),
|
||||
('POP_TOP', None, 0),
|
||||
('LOAD_COMMON_CONSTANT', 7, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None])
|
||||
|
||||
def test_optimize_unary_not(self):
|
||||
# test folding
|
||||
|
|
@ -1718,10 +1744,10 @@ def test_optimize_unary_not(self):
|
|||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 1, 0),
|
||||
('LOAD_COMMON_CONSTANT', 10, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||
|
||||
# test cancel out
|
||||
before = [
|
||||
|
|
@ -1769,7 +1795,7 @@ def test_optimize_unary_not(self):
|
|||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 0, 0),
|
||||
('LOAD_COMMON_CONSTANT', 9, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||
|
|
@ -1785,10 +1811,10 @@ def test_optimize_unary_not(self):
|
|||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
after = [
|
||||
('LOAD_CONST', 1, 0),
|
||||
('LOAD_COMMON_CONSTANT', 10, 0),
|
||||
('RETURN_VALUE', None, 0),
|
||||
]
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False])
|
||||
self.cfg_optimization_test(before, after, consts=[], expected_consts=[True])
|
||||
|
||||
# test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point)
|
||||
before = [
|
||||
|
|
@ -2430,7 +2456,7 @@ def test_list_to_tuple_get_iter(self):
|
|||
end,
|
||||
("END_FOR", None, 11),
|
||||
("POP_TOP", None, 12),
|
||||
("LOAD_CONST", 0, 13),
|
||||
("LOAD_COMMON_CONSTANT", 7, 13),
|
||||
("RETURN_VALUE", None, 14),
|
||||
]
|
||||
self.cfg_optimization_test(insts, expected_insts, consts=[None])
|
||||
|
|
@ -2454,7 +2480,7 @@ def make_bb(self, insts):
|
|||
maxconst = max(maxconst, arg)
|
||||
consts = [None for _ in range(maxconst + 1)]
|
||||
return insts + [
|
||||
("LOAD_CONST", 0, last_loc + 1),
|
||||
("LOAD_COMMON_CONSTANT", 7, last_loc + 1),
|
||||
("RETURN_VALUE", None, last_loc + 2),
|
||||
], consts
|
||||
|
||||
|
|
@ -2485,7 +2511,7 @@ def test_optimized(self):
|
|||
]
|
||||
expected = [
|
||||
("LOAD_FAST_BORROW", 0, 1),
|
||||
("LOAD_CONST", 1, 2),
|
||||
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||
("SWAP", 2, 3),
|
||||
("POP_TOP", None, 4),
|
||||
]
|
||||
|
|
@ -2523,7 +2549,13 @@ def test_unoptimized_if_support_killed(self):
|
|||
("STORE_FAST", 0, 3),
|
||||
("POP_TOP", None, 4),
|
||||
]
|
||||
self.check(insts, insts)
|
||||
expected = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||
("STORE_FAST", 0, 3),
|
||||
("POP_TOP", None, 4),
|
||||
]
|
||||
self.check(insts, expected)
|
||||
|
||||
insts = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
|
|
@ -2532,7 +2564,14 @@ def test_unoptimized_if_support_killed(self):
|
|||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||
("POP_TOP", None, 5),
|
||||
]
|
||||
self.check(insts, insts)
|
||||
expected = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
("LOAD_COMMON_CONSTANT", 7, 2),
|
||||
("LOAD_COMMON_CONSTANT", 7, 3),
|
||||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||
("POP_TOP", None, 5),
|
||||
]
|
||||
self.check(insts, expected)
|
||||
|
||||
insts = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
|
|
@ -2553,7 +2592,12 @@ def test_unoptimized_if_aliased(self):
|
|||
("LOAD_CONST", 0, 3),
|
||||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||
]
|
||||
self.check(insts, insts)
|
||||
expected = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
("LOAD_COMMON_CONSTANT", 7, 3),
|
||||
("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4),
|
||||
]
|
||||
self.check(insts, expected)
|
||||
|
||||
def test_consume_no_inputs(self):
|
||||
insts = [
|
||||
|
|
@ -2598,7 +2642,19 @@ def test_for_iter(self):
|
|||
("LOAD_CONST", 0, 7),
|
||||
("RETURN_VALUE", None, 8),
|
||||
]
|
||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
||||
expected = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
top := self.Label(),
|
||||
("FOR_ITER", end := self.Label(), 2),
|
||||
("STORE_FAST", 2, 3),
|
||||
("JUMP", top, 4),
|
||||
end,
|
||||
("END_FOR", None, 5),
|
||||
("POP_TOP", None, 6),
|
||||
("LOAD_COMMON_CONSTANT", 7, 7),
|
||||
("RETURN_VALUE", None, 8),
|
||||
]
|
||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||
|
||||
def test_load_attr(self):
|
||||
insts = [
|
||||
|
|
@ -2667,10 +2723,10 @@ def test_send(self):
|
|||
("LOAD_FAST", 0, 1),
|
||||
("LOAD_FAST_BORROW", 1, 2),
|
||||
("SEND", end := self.Label(), 3),
|
||||
("LOAD_CONST", 0, 4),
|
||||
("LOAD_COMMON_CONSTANT", 7, 4),
|
||||
("RETURN_VALUE", None, 5),
|
||||
end,
|
||||
("LOAD_CONST", 0, 6),
|
||||
("LOAD_COMMON_CONSTANT", 7, 6),
|
||||
("RETURN_VALUE", None, 7)
|
||||
]
|
||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||
|
|
@ -2708,7 +2764,15 @@ def test_set_function_attribute(self):
|
|||
("LOAD_CONST", 0, 5),
|
||||
("RETURN_VALUE", None, 6)
|
||||
]
|
||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
||||
expected = [
|
||||
("LOAD_COMMON_CONSTANT", 7, 1),
|
||||
("LOAD_FAST", 0, 2),
|
||||
("SET_FUNCTION_ATTRIBUTE", 2, 3),
|
||||
("STORE_FAST", 1, 4),
|
||||
("LOAD_COMMON_CONSTANT", 7, 5),
|
||||
("RETURN_VALUE", None, 6)
|
||||
]
|
||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||
|
||||
insts = [
|
||||
("LOAD_CONST", 0, 1),
|
||||
|
|
@ -2717,7 +2781,7 @@ def test_set_function_attribute(self):
|
|||
("RETURN_VALUE", None, 4)
|
||||
]
|
||||
expected = [
|
||||
("LOAD_CONST", 0, 1),
|
||||
("LOAD_COMMON_CONSTANT", 7, 1),
|
||||
("LOAD_FAST_BORROW", 0, 2),
|
||||
("SET_FUNCTION_ATTRIBUTE", 2, 3),
|
||||
("RETURN_VALUE", None, 4)
|
||||
|
|
@ -2740,7 +2804,22 @@ def test_get_yield_from_iter(self):
|
|||
("LOAD_CONST", 0, 11),
|
||||
("RETURN_VALUE", None, 12),
|
||||
]
|
||||
self.cfg_optimization_test(insts, insts, consts=[None])
|
||||
expected = [
|
||||
("LOAD_FAST", 0, 1),
|
||||
("GET_ITER", 1, 2),
|
||||
("PUSH_NULL", None, 3),
|
||||
("LOAD_COMMON_CONSTANT", 7, 4),
|
||||
send := self.Label(),
|
||||
("SEND", end := self.Label(), 6),
|
||||
("YIELD_VALUE", 1, 7),
|
||||
("RESUME", 2, 8),
|
||||
("JUMP", send, 9),
|
||||
end,
|
||||
("END_SEND", None, 10),
|
||||
("LOAD_COMMON_CONSTANT", 7, 11),
|
||||
("RETURN_VALUE", None, 12),
|
||||
]
|
||||
self.cfg_optimization_test(insts, expected, consts=[None])
|
||||
|
||||
def test_push_exc_info(self):
|
||||
insts = [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
import unittest
|
||||
|
||||
|
|
@ -17,6 +18,9 @@ def supports_trampoline_profiling():
|
|||
raise unittest.SkipTest("perf trampoline profiling not supported")
|
||||
|
||||
class TestPerfMapWriting(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
perf_map_state_teardown()
|
||||
|
||||
def test_write_perf_map_entry(self):
|
||||
self.assertEqual(write_perf_map_entry(0x1234, 5678, "entry1"), 0)
|
||||
self.assertEqual(write_perf_map_entry(0x2345, 6789, "entry2"), 0)
|
||||
|
|
@ -24,4 +28,15 @@ def test_write_perf_map_entry(self):
|
|||
perf_file_contents = f.read()
|
||||
self.assertIn("1234 162e entry1", perf_file_contents)
|
||||
self.assertIn("2345 1a85 entry2", perf_file_contents)
|
||||
perf_map_state_teardown()
|
||||
|
||||
@unittest.skipIf(sys.maxsize <= 2**32, "requires size_t wider than unsigned int")
|
||||
def test_write_perf_map_entry_large_size(self):
|
||||
code_addr = 0x3456
|
||||
code_size = 1 << 33
|
||||
entry_name = "entry_big"
|
||||
|
||||
self.assertEqual(write_perf_map_entry(code_addr, code_size, entry_name), 0)
|
||||
with open(f"/tmp/perf-{os.getpid()}.map") as f:
|
||||
perf_file_contents = f.read()
|
||||
self.assertIn(f"{code_addr:x} {code_size:x} {entry_name}",
|
||||
perf_file_contents)
|
||||
|
|
|
|||
|
|
@ -1075,6 +1075,12 @@ def test_avg_std(self):
|
|||
msg='%s%r' % (variate.__name__, args))
|
||||
self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2,
|
||||
msg='%s%r' % (variate.__name__, args))
|
||||
def test_binomialvariate_log_zero(self):
|
||||
# gh-149222: Variety random() return 0.0 no input Error
|
||||
with unittest.mock.patch.object(random.Random, 'random', side_effect= [0.0] + [0.5] * 20):
|
||||
result = random.binomialvariate(10, 0.5)
|
||||
self.assertIsInstance(result, int)
|
||||
self.assertIn(result, range(11))
|
||||
|
||||
def test_constant(self):
|
||||
g = random.Random()
|
||||
|
|
|
|||
|
|
@ -217,6 +217,25 @@ def test_invalid_names(self):
|
|||
# Package without __main__.py
|
||||
self.expect_import_error("multiprocessing")
|
||||
|
||||
def test_invalid_names_set_name_attribute(self):
|
||||
cases = [
|
||||
# (mod_name, expected_name) -- comment indicates raise site
|
||||
("nonexistent_runpy_test_module",
|
||||
"nonexistent_runpy_test_module"), # spec is None
|
||||
("sys.imp.eric", "sys.imp.eric"), # find_spec error
|
||||
(".relative_name", ".relative_name"), # relative name rejected
|
||||
("sys", "sys"), # builtin: no code object
|
||||
("multiprocessing", "multiprocessing"), # package without __main__
|
||||
]
|
||||
for mod_name, expected_name in cases:
|
||||
with self.subTest(mod_name=mod_name):
|
||||
try:
|
||||
run_module(mod_name)
|
||||
except ImportError as exc:
|
||||
self.assertEqual(exc.name, expected_name)
|
||||
else:
|
||||
self.fail("Expected ImportError for %r" % mod_name)
|
||||
|
||||
def test_library_module(self):
|
||||
self.assertEqual(run_module("runpy")["__name__"], "runpy")
|
||||
|
||||
|
|
@ -714,6 +733,17 @@ def test_directory_error(self):
|
|||
msg = "can't find '__main__' module in %r" % script_dir
|
||||
self._check_import_error(script_dir, msg)
|
||||
|
||||
def test_directory_error_sets_name_attribute(self):
|
||||
with temp_dir() as script_dir:
|
||||
self._make_test_script(script_dir, 'not_main')
|
||||
try:
|
||||
run_path(script_dir)
|
||||
except ImportError as exc:
|
||||
self.assertEqual(exc.name, '__main__')
|
||||
else:
|
||||
self.fail("Expected ImportError for directory without "
|
||||
"__main__.py")
|
||||
|
||||
def test_zipfile(self):
|
||||
with temp_dir() as script_dir:
|
||||
mod_name = '__main__'
|
||||
|
|
|
|||
|
|
@ -1908,7 +1908,7 @@ def test_pythontypes(self):
|
|||
check = self.check_sizeof
|
||||
# _ast.AST
|
||||
import _ast
|
||||
check(_ast.AST(), size('P'))
|
||||
check(_ast.Module(), size('3P'))
|
||||
try:
|
||||
raise TypeError
|
||||
except TypeError as e:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import re
|
||||
import warnings
|
||||
import stat
|
||||
import time
|
||||
|
||||
import unittest
|
||||
import unittest.mock
|
||||
|
|
@ -1828,6 +1829,19 @@ def test_source_directory_not_leaked(self):
|
|||
payload = pathlib.Path(tmpname).read_text(encoding='latin-1')
|
||||
assert os.path.dirname(tmpname) not in payload
|
||||
|
||||
def test_create_with_mtime(self):
|
||||
tarfile.open(tmpname, self.mode, mtime=0).close()
|
||||
with self.open(tmpname, 'r') as fobj:
|
||||
fobj.read()
|
||||
self.assertEqual(fobj.mtime, 0)
|
||||
|
||||
def test_create_without_mtime(self):
|
||||
before = int(time.time())
|
||||
tarfile.open(tmpname, self.mode).close()
|
||||
after = int(time.time())
|
||||
with self.open(tmpname, 'r') as fobj:
|
||||
fobj.read()
|
||||
self.assertTrue(before <= fobj.mtime <= after)
|
||||
|
||||
class Bz2StreamWriteTest(Bz2Test, StreamWriteTest):
|
||||
decompressor = bz2.BZ2Decompressor if bz2 else None
|
||||
|
|
@ -2134,6 +2148,19 @@ def test_create_with_compresslevel(self):
|
|||
with tarfile.open(tmpname, 'r:gz', compresslevel=1) as tobj:
|
||||
pass
|
||||
|
||||
def test_create_with_mtime(self):
|
||||
tarfile.open(tmpname, self.mode, mtime=0).close()
|
||||
with self.open(tmpname, 'rb') as fobj:
|
||||
fobj.read()
|
||||
self.assertEqual(fobj.mtime, 0)
|
||||
|
||||
def test_create_without_mtime(self):
|
||||
before = int(time.time())
|
||||
tarfile.open(tmpname, self.mode).close()
|
||||
after = int(time.time())
|
||||
with self.open(tmpname, 'r') as fobj:
|
||||
fobj.read()
|
||||
self.assertTrue(before <= fobj.mtime <= after)
|
||||
|
||||
class Bz2CreateTest(Bz2Test, CreateTest):
|
||||
|
||||
|
|
|
|||
|
|
@ -2368,6 +2368,231 @@ class BarrierTests(lock_tests.BarrierTests):
|
|||
barriertype = staticmethod(threading.Barrier)
|
||||
|
||||
|
||||
## Test Synchronization tools for iterators ################
|
||||
|
||||
class ThreadingIteratorToolsTests(BaseTestCase):
|
||||
def test_serialize_serializes_concurrent_iteration(self):
|
||||
limit = 10_000
|
||||
workers_count = 10
|
||||
result = 0
|
||||
result_lock = threading.Lock()
|
||||
start = threading.Event()
|
||||
|
||||
def producer(limit):
|
||||
for x in range(limit):
|
||||
yield x
|
||||
|
||||
def consumer(iterator):
|
||||
nonlocal result
|
||||
start.wait()
|
||||
total = 0
|
||||
for x in iterator:
|
||||
total += x
|
||||
with result_lock:
|
||||
result += total
|
||||
|
||||
iterator = threading.serialize_iterator(producer(limit))
|
||||
workers = [
|
||||
threading.Thread(target=consumer, args=(iterator,))
|
||||
for _ in range(workers_count)
|
||||
]
|
||||
with threading_helper.wait_threads_exit():
|
||||
for worker in workers:
|
||||
worker.start()
|
||||
for worker in workers:
|
||||
# Wait for the worker thread to actually start.
|
||||
while worker.ident is None:
|
||||
time.sleep(0.1)
|
||||
start.set()
|
||||
for worker in workers:
|
||||
worker.join()
|
||||
|
||||
self.assertEqual(result, limit * (limit - 1) // 2)
|
||||
|
||||
def test_serialize_generator_methods(self):
|
||||
# A generator that yields and receives
|
||||
def echo():
|
||||
try:
|
||||
while True:
|
||||
val = yield "ready"
|
||||
yield f"received {val}"
|
||||
except ValueError:
|
||||
yield "caught"
|
||||
|
||||
it = threading.serialize_iterator(echo())
|
||||
|
||||
# Test __next__
|
||||
self.assertEqual(next(it), "ready")
|
||||
|
||||
# Test send()
|
||||
self.assertEqual(it.send("hello"), "received hello")
|
||||
self.assertEqual(next(it), "ready")
|
||||
|
||||
# Test throw()
|
||||
self.assertEqual(it.throw(ValueError), "caught")
|
||||
|
||||
# Test close()
|
||||
it.close()
|
||||
with self.assertRaises(StopIteration):
|
||||
next(it)
|
||||
|
||||
def test_serialize_methods_attribute_error(self):
|
||||
# A standard iterator that does not have send/throw/close
|
||||
# should raise AttributeError when called.
|
||||
standard_it = threading.serialize_iterator([1, 2, 3])
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
standard_it.send("foo")
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
standard_it.throw(ValueError)
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
standard_it.close()
|
||||
|
||||
def test_serialize_generator_methods_locking(self):
|
||||
# Verifies that generator methods also acquire the lock.
|
||||
# We can test this by checking if the lock is held during the call.
|
||||
|
||||
class LockCheckingGenerator:
|
||||
def __init__(self, lock):
|
||||
self.lock = lock
|
||||
def __iter__(self):
|
||||
return self
|
||||
def send(self, value):
|
||||
if not self.lock.locked():
|
||||
raise RuntimeError("Lock not held during send()")
|
||||
return value
|
||||
def throw(self, *args):
|
||||
if not self.lock.locked():
|
||||
raise RuntimeError("Lock not held during throw()")
|
||||
def close(self):
|
||||
if not self.lock.locked():
|
||||
raise RuntimeError("Lock not held during close()")
|
||||
|
||||
# Manually create the serialize object to inspect the lock
|
||||
it = threading.serialize_iterator([])
|
||||
mock_gen = LockCheckingGenerator(it._lock)
|
||||
it._iterator = mock_gen
|
||||
|
||||
# These should not raise RuntimeError
|
||||
it.send(1)
|
||||
it.throw(ValueError)
|
||||
it.close()
|
||||
|
||||
def test_serialize_next_exception(self):
|
||||
# Verify exception pass through for calls to next()
|
||||
|
||||
def f():
|
||||
raise RuntimeError
|
||||
yield None
|
||||
|
||||
g = threading.serialize_iterator(f())
|
||||
with self.assertRaises(RuntimeError):
|
||||
next(g)
|
||||
|
||||
def test_synchronized_serializes_generator_instances(self):
|
||||
unique = 10
|
||||
repetitions = 5
|
||||
limit = 100
|
||||
start = threading.Event()
|
||||
|
||||
@threading.synchronized_iterator
|
||||
def atomic_counter():
|
||||
# The sleep widens the race window that would exist without
|
||||
# synchronization between yielding a value and advancing state.
|
||||
i = 0
|
||||
while True:
|
||||
yield i
|
||||
time.sleep(0.0005)
|
||||
i += 1
|
||||
|
||||
def consumer(counter):
|
||||
start.wait()
|
||||
for _ in range(limit):
|
||||
next(counter)
|
||||
|
||||
unique_counters = [atomic_counter() for _ in range(unique)]
|
||||
counters = unique_counters * repetitions
|
||||
workers = [
|
||||
threading.Thread(target=consumer, args=(counter,))
|
||||
for counter in counters
|
||||
]
|
||||
with threading_helper.wait_threads_exit():
|
||||
for worker in workers:
|
||||
worker.start()
|
||||
start.set()
|
||||
for worker in workers:
|
||||
worker.join()
|
||||
|
||||
self.assertEqual(
|
||||
{next(counter) for counter in unique_counters},
|
||||
{limit * repetitions},
|
||||
)
|
||||
|
||||
def test_synchronized_preserves_wrapped_metadata(self):
|
||||
def gen():
|
||||
yield 1
|
||||
|
||||
wrapped = threading.synchronized_iterator(gen)
|
||||
|
||||
self.assertEqual(wrapped.__name__, gen.__name__)
|
||||
self.assertIs(wrapped.__wrapped__, gen)
|
||||
self.assertEqual(list(wrapped()), [1])
|
||||
|
||||
def test_concurrent_tee_supports_concurrent_consumers(self):
|
||||
limit = 5_000
|
||||
num_threads = 25
|
||||
successes = 0
|
||||
failures = []
|
||||
result_lock = threading.Lock()
|
||||
start = threading.Event()
|
||||
expected = list(range(limit))
|
||||
|
||||
def producer(limit):
|
||||
for x in range(limit):
|
||||
yield x
|
||||
|
||||
def consumer(iterator):
|
||||
nonlocal successes
|
||||
start.wait()
|
||||
items = list(iterator)
|
||||
with result_lock:
|
||||
if items == expected:
|
||||
successes += 1
|
||||
else:
|
||||
failures.append(items[:20])
|
||||
|
||||
tees = threading.concurrent_tee(producer(limit), n=num_threads)
|
||||
workers = [
|
||||
threading.Thread(target=consumer, args=(iterator,))
|
||||
for iterator in tees
|
||||
]
|
||||
with threading_helper.wait_threads_exit():
|
||||
for worker in workers:
|
||||
worker.start()
|
||||
start.set()
|
||||
for worker in workers:
|
||||
worker.join()
|
||||
|
||||
self.assertEqual(failures, [])
|
||||
self.assertEqual(successes, len(tees))
|
||||
|
||||
# Verify that locks are shared
|
||||
self.assertEqual(len({id(t_obj.lock) for t_obj in tees}), 1)
|
||||
|
||||
def test_concurrent_tee_zero_iterators(self):
|
||||
self.assertEqual(threading.concurrent_tee(range(10), n=0), ())
|
||||
|
||||
def test_concurrent_tee_negative_n(self):
|
||||
with self.assertRaises(ValueError):
|
||||
threading.concurrent_tee(range(10), n=-1)
|
||||
|
||||
|
||||
#################
|
||||
|
||||
|
||||
|
||||
class MiscTestCase(unittest.TestCase):
|
||||
def test__all__(self):
|
||||
restore_default_excepthook(self)
|
||||
|
|
|
|||
|
|
@ -372,6 +372,8 @@ def test_module(self):
|
|||
mod_generics_cache.__name__)
|
||||
self.assertEqual(mod_generics_cache.OldStyle.__module__,
|
||||
mod_generics_cache.__name__)
|
||||
Alias.__module__ = "ham.spam.eggs"
|
||||
self.assertEqual(Alias.__module__, "ham.spam.eggs")
|
||||
|
||||
def test_unpack(self):
|
||||
type Alias = tuple[int, int]
|
||||
|
|
|
|||
|
|
@ -3197,7 +3197,7 @@ def test_deeply_nested_deepcopy(self):
|
|||
# This should raise a RecursionError and not crash.
|
||||
# See https://github.com/python/cpython/issues/148801.
|
||||
root = cur = ET.Element('s')
|
||||
for _ in range(150_000):
|
||||
for _ in range(500_000):
|
||||
cur = ET.SubElement(cur, 'u')
|
||||
with support.infinite_recursion():
|
||||
with self.assertRaises(RecursionError):
|
||||
|
|
|
|||
143
Lib/threading.py
143
Lib/threading.py
|
|
@ -29,6 +29,7 @@
|
|||
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError',
|
||||
'setprofile', 'settrace', 'local', 'stack_size',
|
||||
'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile',
|
||||
'serialize_iterator', 'synchronized_iterator', 'concurrent_tee',
|
||||
'setprofile_all_threads','settrace_all_threads']
|
||||
|
||||
# Rename some stuff so "from threading import *" is safe
|
||||
|
|
@ -842,6 +843,148 @@ class BrokenBarrierError(RuntimeError):
|
|||
pass
|
||||
|
||||
|
||||
## Synchronization tools for iterators #####################
|
||||
|
||||
class serialize_iterator:
|
||||
"""Wrap a non-concurrent iterator with a lock to enforce sequential access.
|
||||
|
||||
Applies a non-reentrant lock around calls to __next__. If the
|
||||
wrapped iterator also defines send(), throw(), or close(), those
|
||||
calls are serialized as well.
|
||||
|
||||
Allows iterator and generator instances to be shared by multiple consumer
|
||||
threads.
|
||||
|
||||
For example, itertools.count does not make thread-safe instances,
|
||||
but that is easily fixed with:
|
||||
|
||||
atomic_counter = serialize_iterator(itertools.count())
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ('_iterator', '_lock')
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._iterator = iter(iterable)
|
||||
self._lock = Lock()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
with self._lock:
|
||||
return next(self._iterator)
|
||||
|
||||
def send(self, value, /):
|
||||
"""Send a value to a generator.
|
||||
|
||||
Raises AttributeError if not a generator.
|
||||
"""
|
||||
with self._lock:
|
||||
return self._iterator.send(value)
|
||||
|
||||
def throw(self, *args):
|
||||
"""Call throw() on a generator.
|
||||
|
||||
Raises AttributeError if not a generator.
|
||||
"""
|
||||
with self._lock:
|
||||
return self._iterator.throw(*args)
|
||||
|
||||
def close(self):
|
||||
"""Call close() on a generator.
|
||||
|
||||
Raises AttributeError if not a generator.
|
||||
"""
|
||||
with self._lock:
|
||||
return self._iterator.close()
|
||||
|
||||
|
||||
def synchronized_iterator(func):
|
||||
"""Wrap an iterator-returning callable to make its iterators thread-safe.
|
||||
|
||||
Existing itertools and more-itertools can be wrapped so that their
|
||||
iterator instances are serialized.
|
||||
|
||||
For example, itertools.count does not make thread-safe instances,
|
||||
but that is easily fixed with:
|
||||
|
||||
atomic_counter = synchronized_iterator(itertools.count)
|
||||
|
||||
Can also be used as a decorator for generator function definitions
|
||||
so that the generator instances are serialized::
|
||||
|
||||
import time
|
||||
|
||||
@synchronized_iterator
|
||||
def enumerate_and_timestamp(iterable):
|
||||
for count, value in enumerate(iterable):
|
||||
yield count, time.time_ns(), value
|
||||
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
iterator = func(*args, **kwargs)
|
||||
return serialize_iterator(iterator)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def concurrent_tee(iterable, n=2):
|
||||
"""Variant of itertools.tee() but with guaranteed threading semantics.
|
||||
|
||||
Takes a non-threadsafe iterator as an input and creates concurrent
|
||||
tee objects for other threads to have reliable independent copies of
|
||||
the data stream.
|
||||
|
||||
The new iterators are only thread-safe if consumed within a single thread.
|
||||
To share just one of the new iterators across multiple threads, wrap it
|
||||
with threading.serialize_iterator().
|
||||
"""
|
||||
|
||||
if n < 0:
|
||||
raise ValueError("n must be a non-negative integer")
|
||||
if n == 0:
|
||||
return ()
|
||||
iterator = _concurrent_tee(iterable)
|
||||
result = [iterator]
|
||||
for _ in range(n - 1):
|
||||
result.append(_concurrent_tee(iterator))
|
||||
return tuple(result)
|
||||
|
||||
|
||||
class _concurrent_tee:
|
||||
__slots__ = ('iterator', 'link', 'lock')
|
||||
|
||||
def __init__(self, iterable):
|
||||
if isinstance(iterable, _concurrent_tee):
|
||||
self.iterator = iterable.iterator
|
||||
self.link = iterable.link
|
||||
self.lock = iterable.lock
|
||||
else:
|
||||
self.iterator = iter(iterable)
|
||||
self.link = [None, None]
|
||||
self.lock = Lock()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
link = self.link
|
||||
if link[1] is None:
|
||||
with self.lock:
|
||||
if link[1] is None:
|
||||
link[0] = next(self.iterator)
|
||||
link[1] = [None, None]
|
||||
value, self.link = link
|
||||
return value
|
||||
|
||||
############################################################
|
||||
|
||||
|
||||
# Helper to generate new thread names
|
||||
_counter = _count(1).__next__
|
||||
def _newname(name_template):
|
||||
|
|
|
|||
|
|
@ -246,9 +246,9 @@ def library_recipes():
|
|||
|
||||
result.extend([
|
||||
dict(
|
||||
name="OpenSSL 3.5.5",
|
||||
url="https://github.com/openssl/openssl/releases/download/openssl-3.5.5/openssl-3.5.5.tar.gz",
|
||||
checksum="b28c91532a8b65a1f983b4c28b7488174e4a01008e29ce8e69bd789f28bc2a89",
|
||||
name="OpenSSL 3.5.6",
|
||||
url="https://github.com/openssl/openssl/releases/download/openssl-3.5.6/openssl-3.5.6.tar.gz",
|
||||
checksum="deae7c80cba99c4b4f940ecadb3c3338b13cb77418409238e57d7f31f2a3b736",
|
||||
buildrecipe=build_universal_openssl,
|
||||
configure=None,
|
||||
install=None,
|
||||
|
|
|
|||
|
|
@ -514,6 +514,7 @@ PYTHON_OBJS= \
|
|||
Python/suggestions.o \
|
||||
Python/perf_trampoline.o \
|
||||
Python/perf_jit_trampoline.o \
|
||||
Python/jit_unwind.o \
|
||||
Python/remote_debugging.o \
|
||||
Python/$(DYNLOADFILE) \
|
||||
$(LIBOBJS) \
|
||||
|
|
@ -3209,7 +3210,8 @@ Python/emscripten_trampoline_wasm.c: Python/emscripten_trampoline_inner.wasm
|
|||
$(PYTHON_FOR_REGEN) $(srcdir)/Platforms/emscripten/prepare_external_wasm.py $< $@ getWasmTrampolineModule
|
||||
|
||||
JIT_SHIM_BUILD_OBJS= @JIT_SHIM_BUILD_O@
|
||||
JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_SHIM_BUILD_OBJS)
|
||||
JIT_UNWIND_INFO_H= $(if $(JIT_OBJS),jit_unwind_info.h $(patsubst jit_stencils-%.h,jit_unwind_info-%.h,@JIT_STENCILS_H@))
|
||||
JIT_BUILD_TARGETS= jit_stencils.h @JIT_STENCILS_H@ $(JIT_UNWIND_INFO_H) $(JIT_SHIM_BUILD_OBJS)
|
||||
JIT_TARGETS= $(JIT_BUILD_TARGETS) $(filter-out $(JIT_SHIM_BUILD_OBJS),$(JIT_OBJS))
|
||||
JIT_GENERATED_STAMP= .jit-stamp
|
||||
|
||||
|
|
@ -3237,6 +3239,9 @@ jit_shim-universal2-apple-darwin.o: jit_shim-aarch64-apple-darwin.o jit_shim-x86
|
|||
Python/jit.o: $(srcdir)/Python/jit.c @JIT_STENCILS_H@
|
||||
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
|
||||
|
||||
Python/jit_unwind.o: $(srcdir)/Python/jit_unwind.c $(JIT_UNWIND_INFO_H)
|
||||
$(CC) -c $(PY_CORE_CFLAGS) -o $@ $<
|
||||
|
||||
.PHONY: regen-jit
|
||||
regen-jit: $(JIT_TARGETS)
|
||||
|
||||
|
|
@ -3362,7 +3367,7 @@ clean-profile: clean-retain-profile clean-bolt
|
|||
# gh-141808: The JIT stencils are deliberately kept in clean-profile
|
||||
.PHONY: clean-jit-stencils
|
||||
clean-jit-stencils:
|
||||
-rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_shim*.o
|
||||
-rm -f $(JIT_TARGETS) $(JIT_GENERATED_STAMP) jit_stencils*.h jit_unwind_info*.h jit_shim*.o
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-profile clean-jit-stencils
|
||||
|
|
@ -3491,7 +3496,7 @@ MODULE__DECIMAL_DEPS=@LIBMPDEC_INTERNAL@
|
|||
MODULE__ELEMENTTREE_DEPS=$(srcdir)/Modules/pyexpat.c @LIBEXPAT_INTERNAL@
|
||||
MODULE__HASHLIB_DEPS=$(srcdir)/Modules/hashlib.h
|
||||
MODULE__IO_DEPS=$(srcdir)/Modules/_io/_iomodule.h
|
||||
MODULE__REMOTE_DEBUGGING_DEPS=$(srcdir)/Modules/_remote_debugging/_remote_debugging.h
|
||||
MODULE__REMOTE_DEBUGGING_DEPS=$(srcdir)/Modules/_remote_debugging/_remote_debugging.h $(srcdir)/Modules/_remote_debugging/gc_stats.h
|
||||
|
||||
# HACL*-based cryptographic primitives
|
||||
MODULE__MD5_DEPS=$(srcdir)/Modules/hashlib.h $(LIBHACL_MD5_HEADERS) $(LIBHACL_MD5_LIB_@LIBHACL_LDEPS_LIBTYPE@)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Update to WASI SDK 33.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Support for creating instances of abstract AST nodes from the :mod:`ast` module
|
||||
is deprecated and scheduled for removal in Python 3.20. Patch by Brian Schubert.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Add support for unwinding JIT frames using GDB. Patch by Diego Russo and Pablo Galindo.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Enable frame pointers by default for GCC-compatible CPython builds, including
|
||||
``-mno-omit-leaf-frame-pointer`` when the compiler supports it, so profilers
|
||||
and debuggers can unwind native interpreter frames more reliably. Users can pass
|
||||
``--without-frame-pointers`` to opt out.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Repaired undercount of bytes in type-specific free lists reported by sys._debugmallocstats(). For types that participate in cyclic garbage collection, it was missing two pointers used by GC.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
Add a ``GCMonitor`` class with a ``get_gc_stats`` method to the
|
||||
:mod:`!_remote_debugging` module to allow reading GC statistics from an
|
||||
external Python process without requiring the full ``RemoteUnwinder``
|
||||
functionality.
|
||||
Patch by Sergey Miryanov and Pablo Galindo.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Fix the memory sanitizer false positive in :func:`os.getrandom`.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Allow assignment to the ``__module__`` attribute of
|
||||
:class:`typing.TypeAliasType` instances.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
The :mod:`email` module no longer treats email addresses with non-ASCII
|
||||
characters as defects when parsing a Unicode string or in the ``addr_spec``
|
||||
parameter to :class:`email.headerregistry.Address`. :rfc:`5322` permits such
|
||||
addresses, and they were already supported when parsing bytes and in the Address
|
||||
``username`` parameter.
|
||||
|
||||
The (undocumented) :exc:`!email.errors.NonASCIILocalPartDefect` is no longer
|
||||
used and should be considered deprecated.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
The :mod:`email` module no longer incorrectly uses :rfc:`2047` encoding for
|
||||
a mailbox with non-ASCII characters in its domain. Under a policy with
|
||||
:attr:`~email.policy.EmailPolicy.utf8` set ``False``, attempting to serialize
|
||||
such a message will now raise an :exc:`~email.errors.HeaderWriteError`.
|
||||
Either apply an appropriate IDNA encoding to convert the domain to ASCII before
|
||||
serialization, or use :data:`email.policy.SMTPUTF8` (or another policy with
|
||||
``utf8=True``) to correctly pass through the internationalized domain name
|
||||
as Unicode characters.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
The :mod:`email` module no longer incorrectly uses :rfc:`2047` encoding for
|
||||
a mailbox with non-ASCII characters in its local-part. Under a policy with
|
||||
:attr:`~email.policy.EmailPolicy.utf8` set ``False``, attempting to serialize
|
||||
such a message will now raise an :exc:`~email.errors.HeaderWriteError`.
|
||||
There is no valid 7-bit encoding for an internationalized local-part. Use
|
||||
:data:`email.policy.SMTPUTF8` (or another policy with ``utf8=True``) to
|
||||
correctly pass through the local-part as Unicode characters.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Add optional ``mtime`` argument to :func:`tarfile.open`, for setting the ``mtime`` header field in ``.tar.gz`` archives.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Improve tests and documentation for non-function callables as
|
||||
:term:`annotate functions <annotate function>`.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
The threading module added tooling to support concurrent iterator access:
|
||||
:class:`threading.serialize_iterator`, :func:`threading.synchronized_iterator`,
|
||||
and :func:`threading.concurrent_tee`.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
Fix memoization of in-band :class:`~pickle.PickleBuffer` in the Python
|
||||
implementation of :mod:`pickle`. Previously, identical
|
||||
:class:`!PickleBuffer`\ s did not preserve identity, and empty writable
|
||||
:class:`!PickleBuffer` memoized an empty bytearray object in place of
|
||||
``b''``, so the following references to ``b''`` were unpickled as an empty
|
||||
bytearray object.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Fix :func:`runpy.run_module` and :func:`runpy.run_path` to set the
|
||||
:attr:`~ImportError.name` attribute on the :exc:`ImportError` they
|
||||
raise.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Fix :mod:`!_remote_debugging` misreading non-ASCII Unicode strings (Latin-1,
|
||||
BMP and non-BMP) from a remote process. Filenames and function names that
|
||||
contain non-ASCII characters are now reported correctly in stack traces, the
|
||||
sampling profiler, and :mod:`asyncio` task introspection.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Catch rare math domain error for :func:`random.binomialvariate`.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Update Android and iOS installer to use OpenSSL 3.5.6.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Update macOS installer to use OpenSSL 3.5.6.
|
||||
|
|
@ -285,7 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH)
|
|||
|
||||
#*shared*
|
||||
#_ctypes_test _ctypes/_ctypes_test.c
|
||||
#_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/threads.c _remote_debugging/asyncio.c
|
||||
#_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/interpreters.c
|
||||
#_testcapi _testcapimodule.c
|
||||
#_testimportmultiple _testimportmultiple.c
|
||||
#_testmultiphase _testmultiphase.c
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
@MODULE__PICKLE_TRUE@_pickle _pickle.c
|
||||
@MODULE__QUEUE_TRUE@_queue _queuemodule.c
|
||||
@MODULE__RANDOM_TRUE@_random _randommodule.c
|
||||
@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c
|
||||
@MODULE__REMOTE_DEBUGGING_TRUE@_remote_debugging _remote_debugging/module.c _remote_debugging/gc_stats.c _remote_debugging/object_reading.c _remote_debugging/code_objects.c _remote_debugging/frames.c _remote_debugging/frame_cache.c _remote_debugging/threads.c _remote_debugging/asyncio.c _remote_debugging/binary_io_writer.c _remote_debugging/binary_io_reader.c _remote_debugging/subprocess.c _remote_debugging/interpreters.c
|
||||
@MODULE__STRUCT_TRUE@_struct _struct.c
|
||||
|
||||
# build supports subinterpreters
|
||||
|
|
|
|||
|
|
@ -2225,6 +2225,7 @@ c_void_p_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value)
|
|||
static int
|
||||
set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt)
|
||||
{
|
||||
#if defined(_Py_FFI_SUPPORT_C_COMPLEX)
|
||||
if (!fmt->pffi_type->elements) {
|
||||
stginfo->ffi_type_pointer = *fmt->pffi_type;
|
||||
}
|
||||
|
|
@ -2244,6 +2245,10 @@ set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt)
|
|||
memcpy(stginfo->ffi_type_pointer.elements,
|
||||
fmt->pffi_type->elements, els_size);
|
||||
}
|
||||
#else
|
||||
assert(!fmt->pffi_type->elements);
|
||||
stginfo->ffi_type_pointer = *fmt->pffi_type;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -260,8 +260,10 @@ typedef struct {
|
|||
PyTypeObject *ThreadInfo_Type;
|
||||
PyTypeObject *InterpreterInfo_Type;
|
||||
PyTypeObject *AwaitedInfo_Type;
|
||||
PyTypeObject *GCStatsInfo_Type;
|
||||
PyTypeObject *BinaryWriter_Type;
|
||||
PyTypeObject *BinaryReader_Type;
|
||||
PyTypeObject *GCMonitor_Type;
|
||||
} RemoteDebuggingState;
|
||||
|
||||
enum _ThreadState {
|
||||
|
|
@ -346,6 +348,13 @@ typedef struct {
|
|||
size_t count;
|
||||
} StackChunkList;
|
||||
|
||||
typedef struct {
|
||||
proc_handle_t handle;
|
||||
uintptr_t runtime_start_address;
|
||||
struct _Py_DebugOffsets debug_offsets;
|
||||
int debug;
|
||||
} RuntimeOffsets;
|
||||
|
||||
/*
|
||||
* Context for frame chain traversal operations.
|
||||
*/
|
||||
|
|
@ -376,6 +385,13 @@ typedef struct {
|
|||
int32_t tlbc_index; // Thread-local bytecode index (free-threading)
|
||||
} CodeObjectContext;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
RuntimeOffsets offsets;
|
||||
} GCMonitorObject;
|
||||
|
||||
#define GCMonitor_CAST(op) ((GCMonitorObject *)(op))
|
||||
|
||||
/* Function pointer types for iteration callbacks */
|
||||
typedef int (*thread_processor_func)(
|
||||
RemoteUnwinderObject *unwinder,
|
||||
|
|
@ -390,6 +406,14 @@ typedef int (*set_entry_processor_func)(
|
|||
void *context
|
||||
);
|
||||
|
||||
typedef int (*interpreter_processor_func)(
|
||||
RuntimeOffsets *offsets,
|
||||
uintptr_t interpreter_state_addr,
|
||||
int64_t iid,
|
||||
void *context
|
||||
);
|
||||
|
||||
|
||||
/* ============================================================================
|
||||
* STRUCTSEQ DESCRIPTORS (extern declarations)
|
||||
* ============================================================================ */
|
||||
|
|
@ -401,6 +425,7 @@ extern PyStructSequence_Desc CoroInfo_desc;
|
|||
extern PyStructSequence_Desc ThreadInfo_desc;
|
||||
extern PyStructSequence_Desc InterpreterInfo_desc;
|
||||
extern PyStructSequence_Desc AwaitedInfo_desc;
|
||||
extern PyStructSequence_Desc GCStatsInfo_desc;
|
||||
|
||||
/* ============================================================================
|
||||
* UTILITY FUNCTION DECLARATIONS
|
||||
|
|
@ -588,6 +613,17 @@ extern void _Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py
|
|||
extern int _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st);
|
||||
extern void _Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st);
|
||||
|
||||
/* ============================================================================
|
||||
* INTERPRETER FUNCTION DECLARATIONS
|
||||
* ============================================================================ */
|
||||
|
||||
extern int
|
||||
iterate_interpreters(
|
||||
RuntimeOffsets *offsets,
|
||||
interpreter_processor_func processor,
|
||||
void *context
|
||||
);
|
||||
|
||||
/* ============================================================================
|
||||
* ASYNCIO FUNCTION DECLARATIONS
|
||||
* ============================================================================ */
|
||||
|
|
|
|||
269
Modules/_remote_debugging/clinic/module.c.h
generated
269
Modules/_remote_debugging/clinic/module.c.h
generated
|
|
@ -495,6 +495,181 @@ _remote_debugging_RemoteUnwinder_resume_threads(PyObject *self, PyObject *Py_UNU
|
|||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(_remote_debugging_GCMonitor___init____doc__,
|
||||
"GCMonitor(pid, *, debug=False)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Initialize a new GCMonitor object for monitoring GC events from remote process.\n"
|
||||
"\n"
|
||||
"Args:\n"
|
||||
" pid: Process ID of the target Python process to monitor\n"
|
||||
" debug: If True, chain exceptions to explain the sequence of events that\n"
|
||||
" lead to the exception.\n"
|
||||
"\n"
|
||||
"The GCMonitor provides functionality to read GC statistics from a running\n"
|
||||
"Python process.\n"
|
||||
"\n"
|
||||
"Raises:\n"
|
||||
" PermissionError: If access to the target process is denied\n"
|
||||
" OSError: If unable to attach to the target process or access its memory\n"
|
||||
" RuntimeError: If unable to read debug information from the target process");
|
||||
|
||||
static int
|
||||
_remote_debugging_GCMonitor___init___impl(GCMonitorObject *self, int pid,
|
||||
int debug);
|
||||
|
||||
static int
|
||||
_remote_debugging_GCMonitor___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
int return_value = -1;
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
|
||||
#define NUM_KEYWORDS 2
|
||||
static struct {
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
Py_hash_t ob_hash;
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
} _kwtuple = {
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_hash = -1,
|
||||
.ob_item = { &_Py_ID(pid), &_Py_ID(debug), },
|
||||
};
|
||||
#undef NUM_KEYWORDS
|
||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||
|
||||
#else // !Py_BUILD_CORE
|
||||
# define KWTUPLE NULL
|
||||
#endif // !Py_BUILD_CORE
|
||||
|
||||
static const char * const _keywords[] = {"pid", "debug", NULL};
|
||||
static _PyArg_Parser _parser = {
|
||||
.keywords = _keywords,
|
||||
.fname = "GCMonitor",
|
||||
.kwtuple = KWTUPLE,
|
||||
};
|
||||
#undef KWTUPLE
|
||||
PyObject *argsbuf[2];
|
||||
PyObject * const *fastargs;
|
||||
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
||||
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1;
|
||||
int pid;
|
||||
int debug = 0;
|
||||
|
||||
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser,
|
||||
/*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
|
||||
if (!fastargs) {
|
||||
goto exit;
|
||||
}
|
||||
pid = PyLong_AsInt(fastargs[0]);
|
||||
if (pid == -1 && PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
if (!noptargs) {
|
||||
goto skip_optional_kwonly;
|
||||
}
|
||||
debug = PyObject_IsTrue(fastargs[1]);
|
||||
if (debug < 0) {
|
||||
goto exit;
|
||||
}
|
||||
skip_optional_kwonly:
|
||||
return_value = _remote_debugging_GCMonitor___init___impl((GCMonitorObject *)self, pid, debug);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(_remote_debugging_GCMonitor_get_gc_stats__doc__,
|
||||
"get_gc_stats($self, /, all_interpreters=False)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Get garbage collector statistics from external Python process.\n"
|
||||
"\n"
|
||||
" all_interpreters\n"
|
||||
" If True, return GC statistics from all interpreters.\n"
|
||||
" If False, return only from main interpreter.\n"
|
||||
"\n"
|
||||
"Returns a list of GCStatsInfo objects with GC statistics data.\n"
|
||||
"\n"
|
||||
"Returns:\n"
|
||||
" list of GCStatsInfo: A list of stats samples containing:\n"
|
||||
" - gen: GC generation number.\n"
|
||||
" - iid: Interpreter ID.\n"
|
||||
" - ts_start: Raw timestamp at collection start.\n"
|
||||
" - ts_stop: Raw timestamp at collection stop.\n"
|
||||
" - collections: Total number of collections.\n"
|
||||
" - collected: Total number of collected objects.\n"
|
||||
" - uncollectable: Total number of uncollectable objects.\n"
|
||||
" - candidates: Total objects considered and traversed.\n"
|
||||
" - duration: Total collection time, in seconds.\n"
|
||||
"\n"
|
||||
"Raises:\n"
|
||||
" RuntimeError: If the target process cannot be inspected or if its\n"
|
||||
" debug offsets or GC stats layout are incompatible.");
|
||||
|
||||
#define _REMOTE_DEBUGGING_GCMONITOR_GET_GC_STATS_METHODDEF \
|
||||
{"get_gc_stats", _PyCFunction_CAST(_remote_debugging_GCMonitor_get_gc_stats), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_GCMonitor_get_gc_stats__doc__},
|
||||
|
||||
static PyObject *
|
||||
_remote_debugging_GCMonitor_get_gc_stats_impl(GCMonitorObject *self,
|
||||
int all_interpreters);
|
||||
|
||||
static PyObject *
|
||||
_remote_debugging_GCMonitor_get_gc_stats(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
|
||||
#define NUM_KEYWORDS 1
|
||||
static struct {
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
Py_hash_t ob_hash;
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
} _kwtuple = {
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_hash = -1,
|
||||
.ob_item = { &_Py_ID(all_interpreters), },
|
||||
};
|
||||
#undef NUM_KEYWORDS
|
||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||
|
||||
#else // !Py_BUILD_CORE
|
||||
# define KWTUPLE NULL
|
||||
#endif // !Py_BUILD_CORE
|
||||
|
||||
static const char * const _keywords[] = {"all_interpreters", NULL};
|
||||
static _PyArg_Parser _parser = {
|
||||
.keywords = _keywords,
|
||||
.fname = "get_gc_stats",
|
||||
.kwtuple = KWTUPLE,
|
||||
};
|
||||
#undef KWTUPLE
|
||||
PyObject *argsbuf[1];
|
||||
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
|
||||
int all_interpreters = 0;
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
|
||||
/*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
if (!noptargs) {
|
||||
goto skip_optional_pos;
|
||||
}
|
||||
all_interpreters = PyObject_IsTrue(args[0]);
|
||||
if (all_interpreters < 0) {
|
||||
goto exit;
|
||||
}
|
||||
skip_optional_pos:
|
||||
Py_BEGIN_CRITICAL_SECTION(self);
|
||||
return_value = _remote_debugging_GCMonitor_get_gc_stats_impl((GCMonitorObject *)self, all_interpreters);
|
||||
Py_END_CRITICAL_SECTION();
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(_remote_debugging_BinaryWriter___init____doc__,
|
||||
"BinaryWriter(filename, sample_interval_us, start_time_us, *,\n"
|
||||
" compression=0)\n"
|
||||
|
|
@ -1296,4 +1471,96 @@ _remote_debugging_is_python_process(PyObject *module, PyObject *const *args, Py_
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=34f50b18f317b9b6 input=a9049054013a1b77]*/
|
||||
|
||||
PyDoc_STRVAR(_remote_debugging_get_gc_stats__doc__,
|
||||
"get_gc_stats($module, /, pid, *, all_interpreters=False)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Get garbage collector statistics from external Python process.\n"
|
||||
"\n"
|
||||
" all_interpreters\n"
|
||||
" If True, return GC statistics from all interpreters.\n"
|
||||
" If False, return only from main interpreter.\n"
|
||||
"\n"
|
||||
"Returns:\n"
|
||||
" list of GCStatsInfo: A list of stats samples containing:\n"
|
||||
" - gen: GC generation number.\n"
|
||||
" - iid: Interpreter ID.\n"
|
||||
" - ts_start: Raw timestamp at collection start.\n"
|
||||
" - ts_stop: Raw timestamp at collection stop.\n"
|
||||
" - collections: Total number of collections.\n"
|
||||
" - collected: Total number of collected objects.\n"
|
||||
" - uncollectable: Total number of uncollectable objects.\n"
|
||||
" - candidates: Total objects considered and traversed.\n"
|
||||
" - duration: Total collection time, in seconds.\n"
|
||||
"\n"
|
||||
"Raises:\n"
|
||||
" RuntimeError: If the target process cannot be inspected or if its\n"
|
||||
" debug offsets or GC stats layout are incompatible.");
|
||||
|
||||
#define _REMOTE_DEBUGGING_GET_GC_STATS_METHODDEF \
|
||||
{"get_gc_stats", _PyCFunction_CAST(_remote_debugging_get_gc_stats), METH_FASTCALL|METH_KEYWORDS, _remote_debugging_get_gc_stats__doc__},
|
||||
|
||||
static PyObject *
|
||||
_remote_debugging_get_gc_stats_impl(PyObject *module, int pid,
|
||||
int all_interpreters);
|
||||
|
||||
static PyObject *
|
||||
_remote_debugging_get_gc_stats(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
|
||||
#define NUM_KEYWORDS 2
|
||||
static struct {
|
||||
PyGC_Head _this_is_not_used;
|
||||
PyObject_VAR_HEAD
|
||||
Py_hash_t ob_hash;
|
||||
PyObject *ob_item[NUM_KEYWORDS];
|
||||
} _kwtuple = {
|
||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||
.ob_hash = -1,
|
||||
.ob_item = { &_Py_ID(pid), &_Py_ID(all_interpreters), },
|
||||
};
|
||||
#undef NUM_KEYWORDS
|
||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||
|
||||
#else // !Py_BUILD_CORE
|
||||
# define KWTUPLE NULL
|
||||
#endif // !Py_BUILD_CORE
|
||||
|
||||
static const char * const _keywords[] = {"pid", "all_interpreters", NULL};
|
||||
static _PyArg_Parser _parser = {
|
||||
.keywords = _keywords,
|
||||
.fname = "get_gc_stats",
|
||||
.kwtuple = KWTUPLE,
|
||||
};
|
||||
#undef KWTUPLE
|
||||
PyObject *argsbuf[2];
|
||||
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
|
||||
int pid;
|
||||
int all_interpreters = 0;
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
|
||||
/*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
pid = PyLong_AsInt(args[0]);
|
||||
if (pid == -1 && PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
if (!noptargs) {
|
||||
goto skip_optional_kwonly;
|
||||
}
|
||||
all_interpreters = PyObject_IsTrue(args[1]);
|
||||
if (all_interpreters < 0) {
|
||||
goto exit;
|
||||
}
|
||||
skip_optional_kwonly:
|
||||
return_value = _remote_debugging_get_gc_stats_impl(module, pid, all_interpreters);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=1151e58683dab9f4 input=a9049054013a1b77]*/
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue